skip to content

One Slow Operation

One expensive call delays the callers behind it - all of them where the server runs operations one at a time, some of them where threads share the work - and the mean never shows it.

on this pageshow

questions

4

In an in-memory store where most operations cost microseconds of server service time, what makes one call cost hundreds of milliseconds?

level: middleimportance: must knowfreq 62%

answer

  1. small request, large work
  2. cost follows the data
  3. proportional to members or keys
  4. grows without any release
  5. which store, which operations offered

basics

~20 s

A call's server service time scales with how much stored data it must touch, not with how many bytes the caller sent. Returning every member of one entry, or walking the whole keyspace, does work proportional to what is stored.

solid answer

~50 s

Server service time tracks the amount of **stored data** an operation must touch, not the size of its arguments. A single-key read or write is one index lookup plus one copy: microseconds, and roughly flat however much the store holds. The expensive class is the one whose work is proportional to a collection's member count or to the keyspace's key count — returning every member of one entry, combining, sorting or ranking a collection server-side, or a keyspace-wide listing. Each is requested with a few bytes, so the cost is invisible at the call site and grows as the data grows, with no release to blame. Stores differ in which of these they even offer: one that keeps values as opaque bytes and answers only single-key reads and writes has no whole-collection read at all, and its expensive calls are one very large value or an administrative sweep instead.

go deeper

for a junior

Know that a call to this tier is normally microseconds of server work plus a network trip, and that some calls are exceptions because they read a lot of stored data at once. Recognising the exception by name is enough at this stage.

for a middle

Explain the mechanism: service time is proportional to the members or keys touched, not to the arguments, so a tiny request can carry a huge cost. Be able to do the arithmetic for a stated member count.

for a senior

Show that you recognise the class at design time rather than from a graph, and that you ask which store is meant before assuming the expensive operation exists at all. Say what the cost will be at ten times the data.

for a principal

The judgment is about growth, not about one call: a cost decided by stored data is an unbounded liability in a shared tier. Decide where a bound is mandatory and who owns the limit when the data grows past it.

## Two cost curves behind one call site An in-memory store answers a single-key read or write in microseconds, and that number barely moves as the keyspace grows: the server finds the key in its index and copies one value. Call that the **constant class**. A second class exists in every store of this kind and obeys a different rule — the server must touch many stored items before it can reply, so its **server service time** (what the store spends executing, as distinct from the **caller's wall time**, which also contains the network trip and any waiting) is proportional to how much is stored. The defining property of that second class is a mismatch the call site hides: - the **request** is a few bytes and looks exactly like a cheap one; - the **work** is set by data the caller never mentions; - the cost therefore **grows on its own**, with no code change to trigger an investigation. ## What the work is proportional to - **The member count of one entry.** Where a store understands structure — a map, a list, a set, a score-ordered collection — an operation that returns every member, or that combines, sorts or ranks a whole collection, walks every member and serializes each one. Ten members and ten million members are the same line of code at the call site. - **The key count of the whole keyspace.** An operation that must consider every key — a keyspace-wide listing, an administrative sweep — is proportional to the number of keys the store holds, not to anything in the request. - **The bytes written out.** Even when the walk itself is cheap, producing and buffering a reply of many megabytes occupies the server for the duration. What the cost is *not* proportional to is the argument. A request that names one key and asks for all of it is small however large "all of it" turns out to be. | Operation shape | What sets its server service time | What the request reveals about that | |---|---|---| | Single-key read or write | one index lookup and one value copy | the whole cost | | Returning every member of one entry | that entry's member count | nothing | | Combining, sorting or ranking a collection | member count plus the ordering work | nothing | | A keyspace-wide listing or sweep | the number of keys stored | nothing | ## The arithmetic that turns it into a production problem Suppose one request path returns every member of a collection that gains members steadily. At ten thousand members the call costs a few milliseconds and nobody notices; at a million it costs hundreds. Nothing in the deployment changed — no release, no configuration change, no traffic shift — and staging, where the collection is small, will never reproduce it. Inline arithmetic is the strongest evidence you can bring to a design review. If the server spends roughly a microsecond per member walked and serialized, a hundred thousand members is roughly 100 ms of server service time charged to one call. Against a typical operation of ten microseconds, that single call is the work of ten thousand ordinary ones. ## Stores differ in which expensive calls they even have This is the part most often got wrong by generalising from the one store someone has used: - Stores that keep values as **opaque bytes** and answer only single-key reads and writes have **no** whole-collection read, no server-side combination and no ranking — the server never inspects the value. Their expensive class is one very large value being copied out, plus any operation that touches the whole keyspace. - Stores that **understand structure** add the whole family of collection operations, and with them the possibility of a call whose cost is decided entirely by stored data. - Stores differ again in whether a **bounded** or **incremental** alternative exists for those operations — a range instead of everything, a cursor returning a slice per call. "Just use the incremental form" is advice that has to be checked against the store in front of you. Naming which of these the store is should come before any estimate of what a call costs. ## The habit this leaves you with For every call a request path makes to the volatile tier, ask one question: **is this call's cost decided by my request, or by what happens to be stored?** If the answer is "by what is stored", the call is in the expensive class and needs a bound — a range, a slice with a cursor, a limit the store enforces — before it ships, not after the collection grows. A cost that grows silently is worse than a cost that is merely high, because nothing ever prompts anyone to look.

  • Is a call that walks many members the same cost problem as one that returns many bytes?
    They are two components of the same call and can appear separately. Walking members is processing the server does before it can answer; writing bytes out occupies it afterwards and also fills the reply buffer at each end. A ranking over a large collection that returns ten results is heavy on the first and light on the second; a single very large value is the reverse.
  • How do you spot this class in a code review, with no measurements available?
    Look for calls whose result size is decided by stored data rather than by the request — an unbounded read of one entry, or anything that considers every key. For each, ask what its cost is at ten times today's data. If the answer is "ten times as much", it belongs to the expensive class regardless of what it measures today.

saying these in an interview costs you the question

  • Assumes a small request means a small amount of server work.
  • Says the tier is in-memory, so no single call can be slow.
  • Assumes every store of this class offers collection operations.
  • Judges cost by the reply size and never by the members touched.
  • Treats today's measurement as the cost, ignoring that it grows with the data.
open as a page

One call keeps an in-memory store busy for 200 milliseconds: who is delayed if the server executes one operation at a time, and who if it serves requests from a thread pool?

level: seniorimportance: must knowfreq 58%

basics

~20 s

Where the server executes one operation at a time, every other caller waits the full 200 ms and then the backlog drain. Where a thread pool serves requests, one worker is lost and only callers needing the same lock block.

open as a page

An in-memory store stalls for 200 ms a few times a minute, yet its mean server service time and operations-per-second counter look normal; why?

level: seniorimportance: should knowfreq 48%

basics

~20 s

Averaging buries a rare stall: one 200 ms sample among millions of microsecond samples barely moves the mean, and the delayed operations are still counted, only later. The waiting shows in the callers' wall time, at a high percentile.

open as a page

A service makes several calls to a volatile tier whose cost grows with stored data; which bounded forms do you require, and what does each cost?

level: principalimportance: should knowfreq 36%

basics

~20 s

Require that no call's cost be decided by stored data: ask for a bounded range, walk incrementally with a cursor, cap what one request may return, or precompute the answer into its own entry. Each bound costs trips, freshness or a refusal.

open as a page