Performance¶
Overhead per request¶
Every idempotent request pays for exactly:
- One fingerprint computation (
sha256over method + path + body — a single-digit-microsecond operation for typical request sizes). - One backend round-trip (
acquire_or_get) — one Redis command, or one databaseINSERT/SELECTinside a transaction. - On completion, one more backend round-trip (
complete) to store the response.
For RedisBackend, this is two Redis round-trips total per non-replayed
request (negligible — typically sub-millisecond each on a local/same-AZ
Redis). For DatabaseBackend, it's two additional queries against your
primary database per request — worth being aware of on a
very-high-throughput write endpoint, though still small relative to the
actual business-logic queries most such endpoints perform.
A replayed request is cheaper than the original: one backend read, no view execution, no additional database writes from your business logic at all.
Redis vs. database backend under load¶
RedisBackend is the better choice once idempotency-record writes become
a meaningful fraction of your primary database's write volume — Redis is
purpose-built for exactly this kind of high-throughput, short-lived
key-value workload, and native TTL means no separate cleanup job
competing for database I/O. See Configuration.
Body size and fingerprinting cost¶
compute_fingerprint() hashes the full raw request body. For typical JSON
API payloads (bytes to low kilobytes) this is irrelevant. If your API
accepts very large request bodies on idempotent endpoints, be aware the
fingerprint cost scales with body size — SHA-256 processes on the order of
gigabytes per second on modern hardware, so this remains negligible up to
quite large payloads.
Lock contention¶
LOCK_TTL_SECONDS bounds how long a request can hold its lock. Under
normal operation, lock hold time equals view execution time — pick a
LOCK_TTL_SECONDS comfortably above your view's p99 latency so legitimate
slow requests aren't preempted by a retry before they finish (which would
otherwise briefly present as a spurious 409 to the retry, not data
corruption).
Benchmarking your own setup¶
import time
from drf_idempotency.core import get_backend
from datetime import timedelta
backend = get_backend()
start = time.perf_counter()
for i in range(1000):
backend.acquire_or_get(f"bench-{i}", "fp", lock_ttl=timedelta(seconds=30))
elapsed = time.perf_counter() - start
print(f"{1000 / elapsed:.0f} acquire_or_get calls/sec")
Run this against your actual configured backend (and, for DatabaseBackend,
your actual production-like database) to get numbers representative of
your deployment rather than generic claims.