Storage Quota System Low-Level Design

What is a Storage Quota System?

A storage quota system tracks and enforces per-user limits on resource consumption: disk storage (Dropbox, Google Drive), API calls per month, emails sent, or any countable resource. The design must handle concurrent writes without race conditions, provide real-time usage visibility, and enforce limits at high throughput without a performance bottleneck.

Requirements

  • Track storage used per user (bytes)
  • Enforce quotas: reject uploads that would exceed the limit
  • Show current usage vs limit in real time
  • Quota tiers: free (5GB), pro (100GB), enterprise (unlimited)
  • Handle concurrent uploads: two simultaneous 3GB uploads for a 5GB-limit user must not both succeed
  • Usage history for billing: monthly storage-byte-hours

Data Model

UserQuota(
    user_id              UUID PRIMARY KEY,
    plan                 VARCHAR,
    storage_limit_bytes  BIGINT,            -- NULL = unlimited
    storage_used_bytes   BIGINT DEFAULT 0,  -- denormalized current usage
    updated_at           TIMESTAMPTZ
)

StorageObject(
    object_id   UUID PRIMARY KEY,
    user_id     UUID NOT NULL,
    key         VARCHAR NOT NULL,
    size_bytes  BIGINT NOT NULL,
    created_at  TIMESTAMPTZ,
    deleted_at  TIMESTAMPTZ
)

UsageDailySnapshot(
    user_id         UUID,
    date            DATE,
    storage_bytes   BIGINT,
    PRIMARY KEY (user_id, date)
)

Atomic Quota Check-and-Reserve

Two concurrent 3GB uploads for a user at 4.9GB (limit 5GB): both read usage=4.9GB, both check 4.9+3 less than 5 = fails, so one should succeed and one should fail. Solution: atomic conditional UPDATE:

def reserve_quota(user_id, bytes_needed):
    result = db.execute('''
        UPDATE UserQuota
        SET storage_used_bytes = storage_used_bytes + :bytes,
            updated_at = NOW()
        WHERE user_id = :uid
          AND (storage_limit_bytes IS NULL
               OR storage_used_bytes + :bytes <= storage_limit_bytes)
        RETURNING storage_used_bytes, storage_limit_bytes
    ''', uid=user_id, bytes=bytes_needed)

    if not result:
        raise QuotaExceeded('Storage quota exceeded')
    return result

def release_quota(user_id, bytes_freed):
    db.execute('''
        UPDATE UserQuota
        SET storage_used_bytes = GREATEST(0, storage_used_bytes - :bytes)
        WHERE user_id = :uid
    ''', uid=user_id, bytes=bytes_freed)

def upload_file(user_id, file_key, file_bytes):
    reserve_quota(user_id, len(file_bytes))
    try:
        s3.put_object(key=file_key, body=file_bytes)
        db.insert(StorageObject(user_id=user_id, key=file_key,
                                size_bytes=len(file_bytes)))
    except Exception:
        release_quota(user_id, len(file_bytes))
        raise

Redis-Based Quota for High Throughput

For API-call-rate quotas at 100K checks/second, Redis atomic operations outperform DB transactions. Use a Redis Lua script for an atomic check-and-increment:

# Lua script: atomically check limit and increment
# Returns -1 if over limit, otherwise returns new total
QUOTA_LUA = """
local current = tonumber(redis.call('GET', KEYS[1]) or 0)
local limit = tonumber(ARGV[2])
local delta = tonumber(ARGV[1])
if limit > 0 and current + delta > limit then
    return -1
end
return redis.call('INCRBY', KEYS[1], delta)
"""

def check_and_increment(user_id, amount, limit):
    key = 'quota:' .. user_id .. ':storage'
    result = redis.run_lua_script(QUOTA_LUA, keys=[key],
                                  args=[amount, limit])
    if result == -1:
        raise QuotaExceeded()
    return result

Usage History for Billing

-- Nightly snapshot job
INSERT INTO UsageDailySnapshot (user_id, date, storage_bytes)
SELECT user_id, CURRENT_DATE, storage_used_bytes FROM UserQuota
ON CONFLICT (user_id, date) DO UPDATE
    SET storage_bytes = EXCLUDED.storage_bytes;

-- Monthly billing: average GB-months
SELECT user_id,
       SUM(storage_bytes) / (1024.0^3) / 30 AS avg_gb_months
FROM UsageDailySnapshot
WHERE date >= date_trunc('month', CURRENT_DATE)
GROUP BY user_id;

Key Design Decisions

  • Conditional UPDATE for atomic reservation — prevents over-quota under concurrency without locks
  • Denormalized storage_used_bytes — never compute with SUM on the hot path; maintain incrementally
  • Redis Lua for high-throughput quotas — atomic, sub-millisecond; sync to DB periodically
  • Daily snapshots for billing — exact billing requires historical usage, not just current state
  • GREATEST(0, …) on decrement — prevents negative quota from race conditions or double-deletes

Storage quota and resource limit system design is discussed in Google system design interview questions.

3233467805.

Storage quota and SaaS resource limit design is covered in Atlassian system design interview preparation.

Storage quota and S3 usage tracking design is discussed in Amazon system design interview guide.

Scroll to Top