Handling Optimistic Concurrency in FoundationDB: Retry Patterns and Limits
FoundationDB's optimistic concurrency control lets transactions run locally and validates at commit, requiring application-level retry logic. Learn the transaction cycle, a worked counter example, and how to monitor conflict rates.
08 Feb 2026, 10:44 UTC

When multiple services try to update the same record at the same time, naïve locking can stall throughput and waste CPU cycles. FoundationDB sidesteps this by using optimistic concurrency control: transactions run locally and are only checked for conflicts at commit time.
The takeaway for engineers is that FoundationDB delivers high horizontal scale, but the application must handle commit-conflict errors and retry the transaction.
Transaction lifecycle in FoundationDB
When a transaction begins, the client buffers every read and write in a local read set and write set. These operations are invisible to other nodes until commit. On commit, the client sends the sets to the sequencer, which verifies that none of the read keys have been modified by another transaction since the read set was captured. If the check passes, the sequencer orders the transaction and the storage servers apply the writes atomically. If any read key changed, the sequencer returns a conflict error and the transaction must be retried.
Building a retry loop
Because conflicts are expected, your code cannot assume a single commit will succeed. A typical pattern wraps the transaction logic in a loop that catches the conflict error, re-reads the latest data, and re-applies the business logic. Most language bindings provide a helper that automates this, but understanding the underlying mechanism helps you tune retry limits and diagnose retry storms.
- Start a transaction.
- Perform reads and writes.
- Attempt
commit. - If a conflict error is caught, discard the transaction and retry from step 1.
- Optionally add exponential back-off or a maximum retry count to avoid endless loops.
Worked example: atomic counter increment
The following Python-style snippet shows a counter that many workers can increment safely. The transaction function is written so that the binding's automatic retry wrapper will re-invoke it on conflict. Run this from an application host with network access to the cluster file; no special server-side permissions are needed beyond a valid cluster connection. This is illustrative code, not a tested script — check the exact API against your installed binding version, since method names differ across releases.
import fdb
fdb.api_version(730)
def increment_counter(tr, key):
current = tr[key]
count = int(current.decode()) if current is not None else 0
tr[key] = str(count + 1).encode()
return count + 1
db = fdb.open()
new_value = db.transact(increment_counter, b'/stats/page_increments')
print(new_value)The transact helper starts a transaction, runs your function, and commits. If another transaction modified the same key between the read and the commit, the sequencer rejects the commit, the helper retries the function with a fresh snapshot, and the loop continues until success or the retry limit is hit. The risk to watch: if your function has side effects outside the database (sending an email, calling an API), they will re-execute on every retry — keep transaction functions pure.
Trade-offs and monitoring
Optimistic concurrency works well when conflicts are rare, but two practical limits appear:
- High contention. When many clients target the exact same key, the conflict rate rises, causing wasted work as transactions repeatedly retry. Sharding the counter across multiple keys (for example, per-worker sub-keys that you sum at read time) reduces the hotspot.
- Transaction size and duration. FoundationDB caps the number of mutations and the wall-clock time a transaction may occupy. Long-running transactions have a larger window for a conflicting write, increasing abort probability. Keep transactions short, batch related mutations, and avoid reads that span large key ranges.
To verify that your retry logic is healthy, watch the conflict-related metrics in the FoundationDB status output (for example via fdbcli status commands) or your monitoring pipeline. A rising conflict-to-commit ratio signals that transactions are too long or hitting hot keys. Trace logs can further show whether latency comes from execution or from repeated retries.
Apply these patterns: keep transactions brief, implement a robust retry loop with back-off, and watch conflict metrics. When conflict rates climb, consider redesigning the data model to spread writes across more keys or introduce application-level batching.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.