skip to content

Server-Side Logic

Shipping a small program to the store so a read, a decision and a write happen as one unit: fewer round trips and no race between them, paid for by the callers a long one stalls.

on this pageshow

questions

4

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%

answer

  1. two calls, one unprotected gap
  2. move the decision to the data
  3. one unit, nothing interleaves it
  4. closes the race and drops messages
  5. not a transaction: no rollback

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.

solid answer

~50 s

A read and a later write are two operations, and per-operation atomicity says nothing about the gap between them: another caller can read and write in that window, the second write lands whole, the first change is gone and nothing is reported — `last-writer-wins`. A **submitted program** is a short program the caller sends to the store, which reads, branches and writes there; the store applies it as one unit no other caller interleaves. That buys two things at once — the race between this caller's own read and its own write closes, with no retry loop and no claim to hold, and the messages between the steps disappear because the decision is taken where the data is. It is not a transaction: no isolation level, no rollback. Where the server understands the value, a plain server-side in-place update may already be enough.

go deeper

for a junior

Recall that a read and a write are two separate operations with an unguarded window between them, and that a short program sent to the store does both as one step, so nothing else gets in between.

for a middle

Explain the mechanics: what per-operation atomicity covers, why the client-side gap exists at all, and the two effects of shipping the decision — the race closes and the messages between the steps disappear.

for a senior

Show where it stops. The program is not a transaction, its durability is whatever the tier's posture gives, it is one node's unit, and it only guards the entry against callers that actually go through it.

for a principal

Frame it as a contract: putting a decision in the store makes the store the enforcement point for that invariant, which commits you to its failure domain, its durability posture and a second place where behaviour is deployed.

## Two calls are two operations Nearly every store in this class gives you **per-operation atomicity**: one operation against one entry either happened or did not, and no other caller observes it half-done. Stores reach that guarantee by different routes — some run **one operation at a time to completion**, finishing it before starting the next, while others run several worker threads and lock the entry being operated on for the duration of that operation. Either way, the guarantee stops at the edge of the operation. A read and a later write are two operations. Between them the value sits in the caller's own memory, a network hop away, and the store is free to serve everybody else. If a second caller reads the same entry in that window, both compute from the same starting value and the later write lands whole: the first caller's change is gone, and **nothing is reported to anyone**. That silent outcome is `last-writer-wins`, and any sequence whose new value depends on the old one has this shape — adding to a count, appending a member when the collection is under a limit, flipping one field of a document the service stores whole. ## Folding the steps into one unit A **submitted program** is a short program the caller sends to the store: read these entries, branch on what you read, write the result. The store applies it as a single unit that no other caller interleaves. On a store that runs one operation to completion, the program simply *is* one such unit; on a store whose worker threads lock the entries an operation touches, the same effect is reached by holding those entries for the program's duration. Two things change at once, and doing both is the reason the technique exists: 1. **The gap closes.** This caller's read and this caller's write are no longer separated by a network hop and a scheduler, so there is no window for another caller to write into — and no retry loop, no version token to recheck, no expiring claim to acquire and release. 2. **The messages collapse.** Read, think, write becomes one request and one reply, and the decision is evaluated next to the data instead of a round trip away. Every other remedy in this area buys one of those and not the other: a version token still costs the extra exchange and can still lose the retry, and a claim still costs the acquire and the release. ## What it does not change | You might assume | What is actually true | |---|---| | It is a transaction | There is no isolation level, no save point and no rollback. If the program errors after its third write, those three writes stay and the caller owns the repair. | | Its effect is now durable | Atomic and durable are different properties. What survives a restart or a failover is whatever the tier's durability and replication posture gives, and stores differ — some acknowledge only once a replica holds the write, others acknowledge immediately. | | It covers the whole keyspace | Where the keyspace is split across nodes, the unit is one node's. Entries that are not co-located are not in the same unit. | | The invariant is now enforced | It is enforced against callers that go through the program. A second code path writing the same entry directly is not covered by anything. | ## When a program is the wrong tool Where the server can interpret the value — a count it can add to, a map field it can replace, a collection member it can insert — a **server-side in-place update** already moves the whole read-modify-write to the server in one operation. It is simpler, cheaper and available on more stores, and reaching past it for a program is a common over-engineering tell. A program earns its place when the write depends on a branch the store has no single operation for: - decrement only while the result stays at or above zero, and report which way it went; - write this member only if another entry still names this owner; - replace two co-located entries together, or neither; - read a value, transform it in a way the server cannot express, and write the result back. It also has to exist. Not every store in this class offers a submitted-program facility at all; where none does, multi-step atomicity is whatever the store's other constructs give — a group of operations applied as one uninterleaved unit, a version token rechecked on write, or a conditional create — and the remedy has to come from that list instead. ## The two prices Name them in the same breath as the benefit, because an interviewer is listening for them. First, **determinism**: the program should decide only from what it was given and what it read, since ambient inputs make its effect unreproducible wherever the effect is replicated or replayed. Second, **blast radius**: the program inherits the execution model's guarantee that nothing interleaves it, which is exactly why a long or unbounded one is the worst thing in the system to run — every caller behind it waits, and there is no partial result to salvage. ## Two callers racing on the same entry when each caller's read, decision and write are one unit the store does not interleave: the second caller sees the first caller's result, so the cap holds. Had each caller read over the network, decided in its own process and written back, both would have read 39 and both would have written 40 ``` caller A caller B stored count ----------------------------------------------------------------------------- 39 [program applied as one unit] (its operation waits) 39 reads 39 39 39 is under the cap of 40 39 writes 40 40 [unit ends] 40 [program applied as a unit] 40 reads 40 40 40 is not under the cap 40 writes nothing 40 ```

  • The program errors after writing two of the three entries it was meant to change. What is the state of those two writes?
    They stay applied. The unit means no other caller interleaved the program, not that the store can undo it — there is no rollback and no save point. The caller owns the repair, which is why a program is usually written so that re-running it from the start is harmless: write the whole result rather than adjusting it step by step, and re-derive rather than accumulate.
  • Does folding the decision into a program make the resulting write survive the node's loss?
    No. Atomicity and durability are separate. Whether the effect survives depends on the tier's durability posture and on replication, and stores differ: some acknowledge a write only once a replica holds it, others acknowledge immediately and the effect can be lost on failover. Decide that separately, and never read an atomic effect as a durable one.
  • The store you are using offers no submitted-program facility at all. What is left?
    Whatever the store's other constructs give. Typically a version token read with the value and presented on write, so a changed entry refuses the write and the caller re-reads; a conditional create, which writes only when nothing is there and reports whether it happened; or, where offered, a group of operations applied as one uninterleaved unit. Each closes a narrower case than a program does.

saying these in an interview costs you the question

  • Calls a submitted program a transaction and expects rollback.
  • Thinks per-operation atomicity also covers a read and a later write.
  • Assumes every store in this class can run a submitted program.
  • Reaches for a program where one server-side in-place update already closes the gap.
  • Assumes the writes are durable because they were atomic.
  • Believes one program can span entries held on different nodes.
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

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