skip to content

The Memory Ceiling

What a stored byte actually costs in an in-memory store, and what the store does as the ceiling nears: refuse, remove, or be killed. Capacity questions start here.

on this pageshow

questions

26

A running in-memory store exhausts the memory available to it: what three outcomes can follow, and how does each appear to the caller?

level: juniorimportance: must knowfreq 76%

answer

  1. one condition, several possible endings
  2. ask what happens to reads
  3. somebody chose this, probably by default
  4. the third one arrives as latency first

basics

~20 s

Memory exhaustion has three configured endings: the store refuses writes while reads still work, it removes entries to make room, or the operating system kills the process. Which one you get was a choice, usually a default nobody made deliberately.

solid answer

~50 s

One condition — the store needs bytes it cannot get — has three quite different endings, and which one happens was configured in advance. **Refusal**: writes fail while reads of existing entries generally keep succeeding, so the caller sees a partial outage on one path and nothing already stored is lost. **Removal (eviction)**: the store drops entries to stay under its memory ceiling and tells nobody, so the caller meets unexplained absence rather than an error, and a later read cannot distinguish a removed entry from one never written. **The kill**: if the store has no ceiling of its own, or one at or above the machine limit, the operating system ends the process and the whole keyspace goes with it. The asymmetry is the point: refusal is loud and recoverable, removal is silent and lossy, the kill is total.

go deeper

for a junior

Remember that running out of memory has more than one ending: writes can start failing, entries can quietly disappear, or the process can be killed outright. Knowing that a disappearing entry is not always an error message is most of the value here.

for a middle

Explain each outcome from the caller's seat and say which of the two limits caused it — the store's own memory ceiling, or the machine limit underneath it. Be precise that removal under pressure is eviction, not expiry.

for a senior

Show that you can read the outcome back from a production symptom: write errors with healthy reads, silent absence with no errors, or climbing latency followed by a vanished process. Note that where paging is unavailable the third gives no warning at all.

for a principal

Frame the posture as a blast-radius decision rather than a setting. Say what varies across implementations — default posture, whether a choice exists, whether the store has a ceiling at all — instead of presenting one behaviour as the model.

Memory is the one resource in an in-memory store whose exhaustion is **configured rather than fated**. A disk fills and writes fail; a network partitions and calls time out. Memory runs out and the store does one of three quite different things — and which one it does was decided in advance, usually by whoever accepted a default without reading it. ## One condition, three endings The condition itself is dull: the store needs bytes it cannot get. What makes it interesting is that two limits are in play, not one. - **The memory ceiling** is the store's own configured limit, enforced by the store's own code against the memory it attributes to its entries. - **The machine limit** is what the operating system or the container will actually let the process hold. A store that has a ceiling below the machine limit reaches its own ceiling first, and its own code chooses the outcome. A store with no ceiling concept at all, or with one set at or above the machine limit, never gets to choose — the operating system chooses for it. That single structural fact is why the third outcome exists. ## Outcome one: writes are refused The store keeps everything it holds and starts failing the calls that would need more memory. From the caller's seat this is a **partial outage**: the write path errors, while reads of entries already stored generally continue to succeed, because serving an existing entry needs no new memory for it. (The honest qualifier: a read that has to assemble a large reply still needs memory, so refusal is not a guarantee that every read survives.) This outcome is **loud and recoverable**. Nothing already stored is lost, the failure arrives at the caller with a return value attached, and somebody's monitoring can see it immediately. What the application should do about a refused write — retry it, drop it, route it elsewhere — is its own subject. ## Outcome two: entries are removed The store makes room by deleting entries it still holds. This is **eviction**: removal triggered by the ceiling. It is not the same event as **expiry**, which is removal because an entry's lifetime elapsed — same disappearance, different trigger, and only the first one is caused by memory pressure. The caller is told nothing. The write that triggered the removal succeeds normally. The loss surfaces later, somewhere else, as an entry that is simply not there — indistinguishable from one that was never written. This outcome is **silent and lossy**, and whether "lossy" matters turns on one question: *does the removed entry have a source of truth?* If it is a copy of something stored durably elsewhere, removal costs a slower read later. If it is the only copy — a session, a lease, a counter, a record that a job already ran — removal is data loss with nothing to re-read. ## Outcome three: the process is killed When the process reaches the machine or container limit, the operating system ends it. Where paging to disk is available, this is preceded by paging: the process's memory moves to disk, and operations that took microseconds start taking milliseconds. **This is why "it just got slow" is usually the third outcome arriving.** Where paging is disabled or unavailable — common in containers — there is no slow phase at all and the kill is abrupt. The kill is **total**: the whole keyspace goes at once, and the store logs nothing about it, because the decision was made outside the process. ## The three side by side | Outcome | What the caller sees | What is lost | How loud | |---|---|---|---| | Writes refused | Errors on writes, reads still served | Nothing already stored | Loud, immediate | | Entries removed | Nothing at the time; absence later | The removed entries | Silent | | Process killed | Rising latency, then no store at all | The entire keyspace | Total, external | ## Reading the outcome back from the symptom 1. Write calls failing while reads keep working for minutes on end → the store is refusing. 2. Entries missing that nobody deleted, with no errors anywhere → the store is removing. 3. Every operation slowing, then the process gone with nothing in its own log → the operating system killed it (abruptly, where paging is unavailable). ## What varies between stores None of this is uniform across the class, and asserting one store's behaviour as the model is the classic mistake: - whether a refusal posture exists at all — some stores in this class only ever remove; - whether the default is to refuse or to remove, which differs between implementations; - whether the store has a ceiling concept of its own, or leaves the limit entirely to the operating system; - whether removal may take any entry or only entries that carry a lifetime; - whether the ceiling is enforced against what the store attributes to its entries rather than against the process's resident size — where those two diverge, the process can exceed the machine limit while the store still believes itself under its ceiling, which turns an expected outcome one or two into an unexpected outcome three.

  • If reads keep succeeding while writes are refused, is the store "up"?
    It is half up, and that is worse than it sounds. Anything read-only carries on looking healthy, so a dashboard built on read success reports green while every write path is failing. Treat refusal as an outage of the write path specifically, and alert on write errors rather than on reachability.
  • Why is a removed entry harder to notice than a refused write?
    A refusal is returned to the caller that caused it, at the moment it happened. A removal is not returned to anyone: the write that forced it succeeds, and the loss appears later as a read that finds nothing, at a different caller, in a different code path, usually with no way to tell removal from an entry that was never written.
  • Can a store configured to remove entries still be killed by the operating system?
    Yes. The ceiling is enforced against the memory the store attributes to its own entries, while the operating system counts everything the process holds — buffers for connections and followers, and memory the allocator has not returned. If that gap is large enough, the process can cross the machine limit while the store still considers itself under its ceiling.

A full car park has three configured endings. The barrier stays down and new drivers are turned away, but every parked car is untouched. Or the attendant tows cars to free spaces, and owners find out only when they come back. Or the whole structure is condemned and every car inside is gone. Which ending is acceptable depends entirely on whether the cars are replaceable.

saying these in an interview costs you the question

  • Assumes reaching the ceiling always means eviction
  • Says the store simply crashes when it runs out of memory
  • Thinks a removed entry is reported to the caller as an error
  • Believes a store refusing writes is entirely unavailable
  • Treats every removed entry as a reloadable copy
  • Calls the slow phase before a kill a separate incident
open as a page

What does one entry in an in-memory store cost beyond the bytes of its key and value?

level: juniorimportance: must knowfreq 72%

basics

~20 s

An entry also costs a slot in the store's lookup structure, a small header of lengths and pointers, a lifetime field or record if it carries one, and whatever the allocator rounds each of its several allocations up to.

open as a page

Only 5 million of a store's 200 million entries, all copies of database rows, are read hourly — which number sizes it?

level: juniorimportance: must knowfreq 62%

basics

~20 s

Size for the working set — the entries actually touched in a window — not total data. A database holds the rows, so an absent entry costs one extra read, and memory bought for untouched entries is never read.

open as a page

An in-memory store's memory steps sharply when its small maps each gain their hundredth field — which two representations explain that step?

level: middleimportance: must knowfreq 58%

basics

~20 s

Many in-memory stores keep two representations of one value: a compact form that packs elements together, and a general form with a lookup structure per element. Crossing the promotion threshold swaps them, so memory steps rather than climbs.

open as a page

Why does a store report 2.4 GB of data size for 10 million entries whose payloads total 800 MB?

level: middleimportance: must knowfreq 64%

basics

~20 s

Because the fixed per-entry cost is charged 10 million times. Data size over entry count is 240 bytes, of which 80 is payload and 160 is lookup slot, header, key bytes and allocator rounding. Overhead scales with count, not bytes.

open as a page

When a store removes entries at its memory ceiling, what does restricting removal to entries that carry a lifetime buy, and how does it fail?

level: middleimportance: must knowfreq 62%

basics

~20 s

Restricting eviction to entries that carry a lifetime protects entries nobody marked as disposable, so state with no other copy survives memory pressure. It fails when nothing eligible remains: the store cannot free memory and writes needing it start failing.

open as a page

A store reports 6 GB held in entries while the operating system shows the process resident at 9 GB — what does each number measure?

level: middleimportance: must knowfreq 62%

basics

~20 s

Data size is what the store attributes to its own entries; resident size is what the operating system says the whole process occupies. The gap between them holds allocator rounding, blocks the process freed but never gave back, and memory held for connections rather than for entries.

open as a page

A store up 40 days shows resident size at twice its entry total, and the ratio has climbed every day this week — how do you tell unreturned memory from a leak?

level: seniorimportance: must knowfreq 58%

basics

~20 s

The snapshot cannot decide it; the trend and the workload history can. Establish which of the two figures is moving, what the workload did before the gap opened, whether the gap is flat or climbing, and whether a restart returns it and the climb then resumes on the same curve.

open as a page

A store's memory ceiling is set to the container's full 16 GB limit — why is that fatal, and what belongs in the gap?

level: seniorimportance: must knowfreq 58%

basics

~20 s

The process holds memory the ceiling does not account for: buffers for followers and for slow connections, and duplication while a whole-keyspace background copy runs. With no gap, the machine limit is reached before the store's own ceiling.

open as a page

A store set to remove only entries carrying a lifetime reaches its memory ceiling while every entry is permanent: what happens to the next write?

level: middleimportance: should knowfreq 48%

basics

~20 s

It is refused. Removal restricted to entries carrying a lifetime has no eligible victim when nothing carries one, so a store configured to remove behaves exactly like one configured to refuse: writes fail while reads keep working.

open as a page

A store places every value in the smallest of a set of fixed-size blocks — where does the wasted memory come from?

level: middleimportance: should knowfreq 44%

basics

~20 s

A store allocating from fixed-size blocks puts each value in the smallest block that fits and wastes the remainder of that block. The waste is set by which boundary the value just crossed, not by how big the value is.

open as a page

What bookkeeping does each removal family - recency, frequency, remaining lifetime, random - charge an in-memory store?

level: middleimportance: should knowfreq 48%

basics

~20 s

Recency needs per-entry metadata written on every read; frequency needs a counter plus an ageing rule; remaining lifetime needs the deadline the entry already stores; random needs nothing. Each family is priced by what it adds to the read path.

open as a page

A store had a third of its entries deleted an hour ago; its data total fell but the process never shrank — why?

level: middleimportance: should knowfreq 55%

basics

~20 s

Freeing an entry returns its bytes to the process's own allocator, not to the operating system. Those blocks wait in free lists for reuse, and the process shrinks only when a whole region happens to empty — which a deletion scattered across the keyspace rarely produces.

open as a page

A store of database copies at its memory ceiling saw its hit ratio slide from 95% to 72% at flat traffic — evidence of what?

level: middleimportance: should knowfreq 50%

basics

~20 s

Evidence that the working set no longer fits under the ceiling: either more distinct entries are touched in the same window, or each entry now costs more memory. It is a size signal, not a policy verdict.

open as a page

Callers of an in-memory store report steadily worse latency, and then the process disappears with no error in its own log: what happened?

level: seniorimportance: should knowfreq 56%

basics

~20 s

The operating system's out-of-memory kill ended the process, and the slow phase before it was paging to disk. Nothing is in the store's own log because the decision was external, and its own ceiling never engaged.

open as a page

A bulk load briefly made every collection in a store large; the collections were trimmed back, yet the store's own data size stayed high — why?

level: seniorimportance: should knowfreq 46%

basics

~20 s

Stores that swap to a general representation at the threshold usually never swap back when the value shrinks: re-checking on every removal costs work and would thrash at the boundary. The value keeps the larger layout until it is written again.

open as a page

How do you measure the real per-entry cost of your own entries in an in-memory store rather than estimating it?

level: seniorimportance: should knowfreq 52%

basics

~20 s

Write a large sample of realistically shaped entries into a store in a known state, read its own accounting of memory attributed to entries before and after, and divide the difference by the number written. Repeat per entry shape.

open as a page

Keeping an exact removal order over millions of entries costs work on every access; what do stores buy instead, and what does it cost?

level: seniorimportance: should knowfreq 55%

basics

~20 s

Exact order needs a per-entry structure maintained on every access, so most stores inspect a small sample at removal time and take the best candidate in it. The removal is usually a near-miss rather than the true worst entry.

open as a page

A store allocating from fixed-size blocks had its small values purged and replaced with much larger ones; far fewer entries now fit in the same total — why?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Free space belongs to the block size it was cut for. Blocks released by the purged small values can only take values that fit them, so the larger values must come from the classes that serve their size — and the memory freed elsewhere is present, free, and unusable.

open as a page

One in-memory tier holds both reconstructible copies and records that exist nowhere else: which ceiling posture do you choose, and what does the choice cost?

level: principalimportance: should knowfreq 44%

basics

~20 s

Neither posture is right for both halves, so the real answer is to split them onto separate tiers. Forced into one, choose by blast radius: silent removal destroys the only-copy records, while refusal stops writes loudly but loses nothing already stored.

open as a page

You must size a store for entries whose sizes sit near both a promotion threshold and a block boundary — how do you plan?

level: principalimportance: should knowfreq 34%

basics

~20 s

Measure the real size distribution against both steps rather than extrapolating one measurement. A promotion threshold and a block boundary each turn a small payload change into a large memory jump, so say which side of each the workload sits on.

open as a page

A team moves its tier to a different in-memory store; which part of its capacity model must be re-derived, and why?

level: principalimportance: should knowfreq 44%

basics

~20 s

The fixed per-entry cost, and everything computed from it. Entry counts, payload distributions and growth rates describe your data and transfer intact; the per-entry constant describes the store's entry representation, lookup structure, allocator and build, and transfers not at all.

open as a page

One store holds recomputable page fragments alongside leases and deduplication records under a single ceiling and removal policy. What is wrong, and what would you do?

level: principalimportance: should knowfreq 30%

basics

~20 s

One removal policy applies to every entry, but the blast radius of a removal differs per population: a fragment costs a refetch, a lease costs mutual exclusion. Separate the populations, or use eligibility so the irreplaceable entries are never candidates.

open as a page

A long-running in-memory store's resident size only ever grows between restarts, and only a restart returns it — how do you plan around that?

level: principalimportance: should knowfreq 35%

basics

~20 s

Treat the gap as a rate rather than a number: measure how fast it grows and how much room is left, then choose between changing the workload that produces it, letting the store consolidate free space where it can, and scheduling the restart as a planned availability event instead of an incident.

open as a page

How do you arrive at the number you set as an in-memory store's memory ceiling on a 32 GB node?

level: principalimportance: should knowfreq 44%

basics

~20 s

Measure what one real entry costs, multiply by the distinct entries touched in a named window, and set the ceiling there — far enough below the machine limit that the named headroom consumers fit in the gap.

open as a page

Why does a frequency-based removal family keep a small decaying counter per entry rather than an exact access count?

level: seniorimportance: nice to knowfreq 38%

basics

~20 s

An exact count is wide and monotone: it never forgets, so an entry popular last week outranks one that is hot now. A small counter that rises sub-linearly and decays with age turns a total into an approximate current rate.

open as a page