skip to content

Atomicity & Concurrency

How callers sharing one keyspace avoid corrupting each other's work: what the store makes atomic for you, what it leaves racing, and the remedies available with no transactions.

on this pageshow

questions

21

An in-memory store applies four submitted operations as one uninterleaved group; what does that promise, and what does it not?

level: juniorimportance: must knowfreq 68%

answer

  1. no one else gets in between
  2. queue and apply, not commit
  3. no isolation level, no snapshot
  4. no rollback; earlier steps stay applied

basics

~10 s

It promises only that no other caller's operation is applied between the four. It is not a database transaction: there is no rollback, so when the third step fails, the first two stay applied.

solid answer

~40 s

The construct assembles several operations and applies them back to back as one unit, so no other caller's operation lands in the middle. That non-interleaving is the entire guarantee. There is no isolation level to pick, no snapshot for the reads inside the group, no save point and, above all, no rollback: if a step fails while the group is being applied, the steps before it stay applied, and on most stores that offer this construct the steps after it are applied too. The caller gets one result per step and owns whatever repair the half-applied state needs. Think `queue and apply`, not `commit and undo` — and check the store you are on, because some stores in this class offer no grouping construct at all.

go deeper

for a junior

Remember the one promise — no other caller's operation runs between the grouped steps — and the one absence: nothing is undone when a step fails.

for a middle

Explain why this is not a database transaction: no rollback, no isolation level, no snapshot for the reads inside it. Then say exactly what is stored after a step fails mid-apply.

for a senior

Show that you read the per-step results and design the repair, because a half-applied group is a normal outcome rather than an exception. Say what happens when the caller that owed the repair has died.

for a principal

Decide which invariants may rest on this tier at all, given that it offers non-interleaving and nothing else, and state where the authority for the rest of them lives.

## The one thing a group buys Several stores in this class let a caller hand the server more than one operation to be applied as a single unit: the operations are assembled, the caller marks the boundary, and the store applies them back to back. While that unit is being applied, **no other caller's operation is applied in between**. That non-interleaving property is the whole of what the construct promises, and it is worth saying out loud, because nearly everything else people expect from it is absent. Where the guarantee comes from is the store's own business rather than yours. Some stores run one operation to completion before starting the next, so applying a group is simply a longer run to completion. Others run several worker threads and lock the entry being operated on for the duration of an operation, reaching the same per-operation guarantee by a different route and extending it across the group. A caller on either kind of store is handed the same promise. ## What it is not The word that attaches itself to this construct in conversation is *transaction*, and that word imports expectations from relational databases that simply do not hold at this tier. | Expected from a database transaction | What an operation group actually gives | |---|---| | **All-or-nothing** — a rollback undoes the statements already run | Nothing is undone; a failing step leaves its predecessors applied | | **A chosen isolation level** | Nothing to choose; non-interleaving is all the isolation there is | | **A snapshot** — every read inside sees one point in time | Each step reads the entry as it stands when that step is applied | | **A save point** to unwind to | No markers, no partial undo, no nesting | | **One verdict** for the whole unit | One result per step, and the caller reads the list | The honest summary is **queue and apply, not commit and undo**. There is no commit point at which the store decides whether the work counts. By the time you learn that the third step failed, the first two are already visible to every other caller. ## The two moments something can go wrong A group has two distinct failure moments, and they leave very different states behind. It can be **refused while it is being assembled** — on stores that check the shape of each operation as it is queued, one malformed step can cause the whole group to be refused, in which case nothing was applied and the keyspace is untouched. Or it can be **accepted and then error while it is being applied**, when a step turns out not to fit the value that is actually stored under its key. In that case the earlier steps stay applied, and on most stores offering this construct the later steps are applied as well rather than skipped. Those two outcomes look alike in a diagram and nothing alike in code. ## What varies across stores - **Whether the construct exists at all.** Some stores in this class offer no grouping construct whatsoever; their only multi-step atomicity comes from a submitted program, from a version token presented on write, or from restructuring the data so the change is a single operation. A design that assumes a group is not portable across this class. - **How much is checked at assembly time.** Some stores validate each operation as it is queued; others discover the problem only while applying. - **Whether the steps after a failing one still run.** Treat *they do* as the default and confirm it for the store you are on. - **Whether the entries must share a node.** Where the keyspace is split across nodes, a unit touching several entries is only possible when those entries are co-located; the placement rules themselves are a separate subject. - **What "applied" means for survival.** Applying changed memory on one node. Whether the effect survives a restart, or had reached a replica before the node was lost, is a property of the tier's durability and replication posture, not of the group. One further distinction is worth nailing down because it is confused constantly: sending many operations without waiting for each reply is a **round-trip optimisation**. It makes a loop of calls fast; it promises nothing at all about whether another caller's operation lands between two of them. Grouping and not-waiting-for-replies are different mechanisms that happen to look similar from the client's side. ## Designing with it 1. Ask first what would actually break if another caller's operation landed between your steps. If the answer is *nothing*, you do not need the construct. 2. Order the steps so that any prefix of them leaves a state the rest of the system can live with — the step that makes the work visible to readers goes last. 3. Read the per-step results. One acknowledgement for the group is not evidence that every step applied. 4. Write the repair for a half-applied group before you ship the group, make it idempotent, and make sure something other than the submitting caller can run it. 5. If what you genuinely need is all-or-nothing across several entries, this tier is not the place that invariant can be enforced; enforce it where rollback exists and let this tier hold the fast copy.

  • Do the reads inside a group see a consistent snapshot of the keyspace?
    No. Each read inside the group returns the entry as it stands at the moment that step is applied; nothing was frozen when the group was assembled, and a value the caller read before submitting may already be stale. If the group's correctness depends on an entry not having changed, you need a mechanism that declares the entries the group depends on and abandons the group when one of them changes — a separate construct, and not something the group gives you by itself.
  • Once a group has been applied, is its effect durable?
    Applying is not persisting. Whether the effect survives a restart depends on the tier's durability posture, and on a replicated tier an effect applied on the primary may not have reached a replica before the primary is lost. The unit is about what other callers see while it is applied, not about what survives the node.

Four errands run back to back by one assistant: nobody else gets their attention in between, but if the third shop is closed, the first two errands are still done and nothing un-buys them.

saying these in an interview costs you the question

  • Calls the group a transaction and expects a rollback on failure.
  • Thinks a failing step undoes the steps already applied.
  • Believes the group selects an isolation level for its reads.
  • Assumes every in-memory store offers a grouping construct.
  • Thinks sending operations without waiting for replies stops other callers interleaving.
open as a page

Two callers read one entry holding a quota count, each adds ten, and both write back — what is stored, and what is reported?

level: juniorimportance: must knowfreq 80%

basics

~20 s

The second write lands whole and the first caller's addition is gone. An entry that held 100 holds 110, not 120 — and nothing is reported: both callers were told their write succeeded. That default outcome is last-writer-wins.

open as a page

A write presented with the version token read alongside the value is refused - what happened, and what must the caller do next?

level: juniorimportance: must knowfreq 64%

basics

~20 s

Another caller changed that entry between the read and the write, so the store refused it instead of overwriting. The caller must re-read the entry, recompute the change from the new value, and write with the new token.

open as a page

If a store makes every operation atomic, why can two callers that read one entry, change it and write it back still lose a change?

level: juniorimportance: must knowfreq 78%

basics

~20 s

Per-operation atomicity covers one operation, not a caller's sequence. A read and a write are two operations, and other callers' operations run in the gap between them, so the later write lands whole and the earlier change is gone.

open as a page

Two stores both guarantee that one operation on one entry is atomic - how does run-to-completion execution deliver that, and how does per-entry locking?

level: middleimportance: must knowfreq 60%

basics

~10 s

Run-to-completion executes one operation fully before starting the next, so nothing interleaves. Per-entry locking lets worker threads run concurrently, each holding the entry it operates on for that operation's duration. Same promise, different consequences.

open as a page

On an in-memory store that applies a submitted program as one uninterleaved unit, what does folding a read, a decision and a write into one program change?

level: middleimportance: must knowfreq 58%

basics

~20 s

Folding the three steps into one submitted program makes them a single unit the store applies without interleaving, so no other caller's write can land between the read and the write, and three round trips become one.

open as a page

Forty callers a second read one shared entry and rewrite it under a version token, and adding callers lowers the rate of accepted writes - why, and what would you change?

level: seniorimportance: must knowfreq 57%

basics

~20 s

Only one caller per generation of the entry wins; every other in-flight attempt holds a token that the accepted write invalidates, so its work is discarded and repeated. More callers, more collisions, fewer accepted writes.

open as a page

Your design depends on a grouping construct, but the in-memory store you are moving to offers none — what replaces it?

level: middleimportance: should knowfreq 42%

basics

~20 s

With no grouping construct, multi-step atomicity comes from a single operation the server applies as a unit, a conditional create, or a version token that refuses a stale write — or from making what changes together one value.

open as a page

An operation group can fail at two different moments on an in-memory store — which two, and what does each leave stored?

level: middleimportance: should knowfreq 54%

basics

~20 s

Either the store refuses the group as it is assembled — nothing applied, resubmit a corrected group — or it accepts the group and a step errors while applying, leaving the other steps applied and the repair to you.

open as a page

A store hands back only the bytes it was given, and two services each rewrite one profile document: which fixes for an overwritten change remain?

level: middleimportance: should knowfreq 58%

basics

~20 s

Moving the change onto the server is unavailable, because the server cannot interpret bytes it only stores and returns. What remains is a version token checked on write, a conditional create, or an expiring claim held around the whole read-modify-write cycle.

open as a page

How does a declared read set that abandons an operation group differ from a version token presented on one write?

level: middleimportance: should knowfreq 48%

basics

~20 s

A version token guards one entry as one write is applied. A declared read set guards every entry the caller named, written or not, abandoning a queued operation group before any step applies. Same bet, wider scope.

open as a page

What does a store's per-operation atomicity guarantee actually cover for a caller, and what does it leave uncovered?

level: middleimportance: should knowfreq 50%

basics

~20 s

It covers one operation on one node: applied whole, never observed partway. It does not cover a caller's second operation, entries on another node, or the effect's survival - and multi-entry atomicity depends on the execution model.

open as a page

A group writes a job record and adds its key to a pending set, and the second step errors while applying — how do you repair the state it left?

level: seniorimportance: should knowfreq 47%

basics

~20 s

The record stays, the set entry is missing, and nothing will undo either. Repair with an idempotent compensating write driven from the per-step results, and design so the partial state is detectable and harmless to whoever finds it.

open as a page

An order document on the store keeps losing recently written fields under load, and nothing errors: how do you confirm overwritten changes?

level: seniorimportance: should knowfreq 40%

basics

~20 s

You will not find it in the store's errors, because none are raised. Confirm it from outside: compare the entry against an independent record, log the value each caller read beside the value it wrote, and reproduce the loss with concurrent writers.

open as a page

One entry takes thousands of read-modify-write attempts per second: how do a server-side edit, declare-and-retry, and an expiring claim each degrade?

level: seniorimportance: should knowfreq 52%

basics

~10 s

They spend different currencies. A server-side in-place update stays one round trip and never conflicts. Declare-and-retry turns most attempts into wasted work. An expiring claim serialises callers, so hold time becomes the throughput ceiling.

open as a page

Why is a store's unit of atomicity also the unit other callers wait on, and which callers wait under each execution model?

level: seniorimportance: should knowfreq 44%

basics

~20 s

Atomicity is bought by excluding others for the operation's duration, so that duration is their wait. Under run-to-completion every caller waits, whatever entry they wanted. Under per-entry locking only callers of that entry or its lock stripe wait.

open as a page

Why must a program submitted to an in-memory store decide only from its arguments and what it read, never from the clock or a random draw?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Because the same program may run again elsewhere — on a replica, or replayed from a write log after a restart — and a decision taken from ambient inputs writes something different each run, so the copies stop agreeing.

open as a page

A submitted program loops over a large collection and runs for two seconds; on a store that runs one operation to completion, what happens to every other caller?

level: seniorimportance: should knowfreq 45%

basics

~20 s

They all wait. The program is applied as one uninterleaved unit, so on a store that runs one operation to completion every other caller's work queues for the full two seconds, and their timeouts fire even though nothing has crashed.

open as a page

You bound optimistic retries at five attempts on one contended entry - what contract does that give callers, and what happens to the one that exhausts the bound?

level: principalimportance: should knowfreq 38%

basics

~20 s

The bound trades an open-ended attempt for bounded latency and an explicit failure, turning a correctness mechanism into a best-effort one. The exhausted caller needs a real answer: fail outward, enforce downstream, serialise, or accept last-writer-wins.

open as a page

A conditional write under a version token keeps being refused although no caller touched the field you changed - why, and what do you change?

level: middleimportance: nice to knowfreq 35%

basics

~20 s

The token marks the whole entry, not the field. Any write to that entry moves it and refuses the next token holder, however unrelated the changes were. Fix the granularity, or move the edit to the server.

open as a page

Which invariants would you let a submitted program enforce at the volatile tier, and which would you keep at a durable system of record?

level: principalimportance: nice to knowfreq 32%

basics

~20 s

Enforce invariants whose violation is survivable and re-derivable: admission decisions, claims, dedupe marks, operational counts. Keep at a durable record anything that must never double or vanish, since the program gives atomicity and nothing else.

open as a page