The tenant's bucket is nearly full; the next write would spill over the line, so the store turns it away — and if you'd already started writing, you're left holding half an object.
Every tenant has a storage quota — a hard cap on how much space their data may occupy. A write that would push usage past that cap is rejected, often with HTTP 507, EDQUOT, or a 403 QuotaExceeded result.
The real danger is timing. If the rejection lands mid-operation — partway through a multi-part upload, or mid-batch — and writes aren't atomic, you can be left with a half-written, inconsistent object that still consumed space. The calm response is to triage (confirm it's quota, find what consumed it, check for partial junk) and then contain (stop the writer, fail cleanly, abort the fragment, free space or raise the cap, then resume).
The fix is to decide before you start writing. Atomically reserve the space, check it against what remains, and reject up front if it won't fit. Then a rejection costs nothing and never leaves a fragment behind. If a write still fails mid-stream, abort the partial object so it doesn't linger as orphaned bytes.
def write(tenant, obj, size):
# Pre-flight: reserve space ATOMICALLY before a single byte lands.
# used + size is computed under a lock (or a conditional update / CAS)
# so two concurrent writers can't both squeak past the same headroom.
with tenant.reservation_lock:
if tenant.used + size > tenant.limit:
reject(QUOTA_EXCEEDED) # 507 / EDQUOT — fail fast, clean
return
tenant.used += size # the reservation is now committed
try:
store.put(tenant, obj) # only now do bytes actually flow
except WriteFailed:
store.abort(tenant, obj) # roll back the partial object...
tenant.used -= size # ...and release the reservation
raise
| What | Reading |
|---|---|
| Write latency at the limit | Pre-flighted: a fast, cheap reject. Discovered mid-write: slow — bytes were moved, then undone. |
| Partial-object cleanup | An aborted fragment must be located and deleted; until then it's orphaned space the tenant paid for. |
| User-facing impact | Writes fail. Uploads stall at the cap; clients see 507 / 403 QuotaExceeded. |
| Remediation options | Raise the quota (move the cap up) or free space (delete expired temp / orphaned data), then resume. |
| Signal to watch | Quota-exceeded error rate, the used / limit ratio nearing 100%, and the orphaned-partial count. |
507 isn't a blip. Blind retry-with-backoff becomes a retry storm that can never succeed until space is freed.85% so there's time to raise quota or free space first.A tenant sits at 9.8 / 10 GB and starts a 0.5 GB upload. Without a pre-flight check, the store happily writes 0.2 GB, hits the cap, and rejects — leaving a 0.2 GB partial fragment behind.
Triage: the error is QuotaExceeded, not a generic failure; the cause is this tenant at the cap; an orphaned 0.2 GB partial is confirmed.
Contain: abort the partial (usage drops back to 9.8), then free 1 GB of expired temp data (down to 8.8). Now 8.8 + 0.5 = 9.3 < 10 — the upload retries and succeeds.
1. A 0.5 GB write is rejected with 507 when the tenant is already at the cap. What's the safest immediate response?
2. What single change keeps a quota rejection from ever leaving a half-written object?