Files
rustfs/docs/operations/durable-quota-reservation-v2.md
T
bf47a919ac feat(quota): add sharded durable reservation protocol (#8402)
* feat(quota): add sharded durable reservation protocol

Co-Authored-By: heihutu <heihutu@gmail.com>

Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* fix(quota): preserve typed task error context

Co-Authored-By: heihutu <heihutu@gmail.com>

Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* fix(quota): initialize all durable reservation shards

Co-Authored-By: heihutu <heihutu@gmail.com>

Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* fix(quota): persist bootstrap shard eligibility

Co-Authored-By: heihutu <heihutu@gmail.com>

Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* perf(quota): reuse allocator bootstrap state

Co-Authored-By: heihutu <heihutu@gmail.com>

Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* test(connect): widen inventory startup wait

Co-Authored-By: heihutu <heihutu@gmail.com>

Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* test(quota): reserve protocol v3 for unknown fixtures

Co-Authored-By: heihutu <heihutu@gmail.com>

Co-Authored-By: zhi22915 <qiuzgang@gmail.com>

* fix(quota): reload allocator after concurrent credit refills

---------

Co-authored-by: zhi22915 <qiuzgang@gmail.com>
Co-authored-by: overtrue <anzhengchao@gmail.com>
2026-10-08 16:58:32 +08:00

3.6 KiB

Durable quota reservation protocol v2

Protocol v2 is an opt-in hard-quota protocol for distributed deployments. It keeps the quota limit authoritative in one allocator ledger while moving request reservations into sixteen independently locked shard ledgers.

State and invariants

The allocator ledger contains the bucket incarnation, quota revision, limit, monotonic generation, and durable credit grants. A grant belongs to exactly one shard and is never larger than the remaining quota at the allocator write that issued it. A shard ledger contains the same incarnation/revision, the allocator generation it adopted, its grants, accounted usage, and pending reservations.

The safety invariant is:

sum(shard grant amounts) <= quota_limit
shard.accounted_usage <= sum(shard grant amounts)

accounted_usage includes committed bytes and pending positive reservation growth. An abort removes the pending growth; a commit removes only the pending record because its growth is already accounted. A reservation whose commit marker was persisted is removed only as a precharged reconcile candidate; the shard is then rebuilt from objects belonging to that shard.

Locking and fencing

The v2 lock order is:

caller object/upload -> metadata transaction -> operation lock -> allocator
read lock -> shard write lock

Credit refill releases the operation and shard locks before taking the allocator write lock. The allocator write is fenced before persistence. Shard reservation and settlement fence the shard before persistence. The allocator generation and bucket incarnation/revision are checked on every load, so a late writer from an older epoch fails closed. Allocator and shard paths include the bucket incarnation and quota revision, so a quota change starts a new epoch after an exact bucket bootstrap instead of reusing old grants.

Crash and replay behavior

Allocator grants are durable before a shard can adopt them. If a process exits after the allocator write but before the shard write, the next shard writer adopts the grant by ID. If a process exits after a shard reservation, the operation lock probe can reclaim reservations and marks commit_started entries for exact shard reconciliation, preventing a crash window from releasing credit for an object that may already be durable. Repeated commit/abort operations are keyed by the operation ID and expected reservation fields; mismatches fail closed.

Mixed-version rollout

reservation_protocol: 2 is serialized with reservation_quota. A node that does not understand v2 must reject the configuration rather than treating it as the legacy snapshot protocol. Operators must upgrade the whole fleet before calling BucketQuota::new_sharded. Downgrades require clearing or reconciling all v2 allocator/shard ledgers first; v1 and v2 ledgers are never mixed.

The default BucketQuota::new remains protocol v1. This keeps existing deployments compatible until an operator has completed the rollout and chosen the sharded protocol explicitly.

Performance boundary

Normal v2 requests take a shared allocator read lock and one shard write lock; the bucket-wide allocator write lock is used only to issue a new credit chunk. The per-grant chunk is bounded by roughly one sixteenth of the quota (or the requested growth when larger), so one active shard cannot consume the entire remaining quota through a single speculative grant. It is a conservative upper bound, not a best-effort token bucket. Unused credit is intentionally retained until the quota epoch changes or a future reconciliation operation returns it, trading some capacity utilization for a simple crash-safe upper bound.