A write rejected when the quota runs out

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.

The idea

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).

See it work

tenant storage limit incoming writes
A series of writes is queued for this tenant. Press Play, or step through.

How it works

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

Signals

WhatReading
Write latency at the limitPre-flighted: a fast, cheap reject. Discovered mid-write: slow — bytes were moved, then undone.
Partial-object cleanupAn aborted fragment must be located and deleted; until then it's orphaned space the tenant paid for.
User-facing impactWrites fail. Uploads stall at the cap; clients see 507 / 403 QuotaExceeded.
Remediation optionsRaise the quota (move the cap up) or free space (delete expired temp / orphaned data), then resume.
Signal to watchQuota-exceeded error rate, the used / limit ratio nearing 100%, and the orphaned-partial count.

Watch out for

Worked example

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.

Check yourself

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?