Deep Engineering
Intermediate·Published·20 MIN

Health checks: three different questions called by one name

Liveness asks "are you alive", readiness asks "are you ready to take traffic", startup asks "have you finished booting". They get confused, and the price of the confusion is measurable: a probe tied to a shared database turns one database outage into a simultaneous restart of every replica.

Full technical treatment

TL;DR

A health check is not one question but three. Startup asks whether the application has finished booting; liveness asks whether restarting this process would help; readiness asks whether traffic can be sent here. What separates them is not strictness but consequence: a restart, more waiting, or removal from rotation. And the contents of each probe follow from its consequence — check what that consequence can actually fix.

Hence the central mistake: a probe tied to a shared dependency turns one outage into two. A restart makes nothing outside the process more available — a database, a network, a neighbouring service do not revive because you restarted — while the process itself loses its warm caches and connection pools. Model: a database outage costs 1000 failed requests on its own and 1200 if liveness checks the database. The extra 200 are every replica restarting at once.

Beyond that: the numbers and the boundaries. For a degraded replica, liveness alone gives 520 slow answers and liveness plus readiness 80 — 6.5 times fewer; no probes at all gives 1600, because the balancer keeps sending load to the sick replica. It goes equally blind when readiness always answers "ready". But the opposite extreme exists too: readiness is not the sum of every dependency a service has, it is the answer to whether this replica can serve any traffic at all. The numbers here are model numbers: they follow from declared assumptions, and what carries meaning is the ratios between rows rather than the absolute values.

Where to start
Before this lesson it is enough to understand
  • a service does not run as a single copy: several identical replicas serve one stream of requests;
  • a balancer in front of them decides which replica gets the next request, and can stop giving requests to one of them;
  • something watches the replicas themselves and can restart one that has gone wrong.
You do not need to know in advance
  • how liveness differs from readiness and what startup is for — that is what this lesson is about;
  • how an orchestrator configures probes: periods, thresholds, field names;
  • restart cascades, removal from rotation, a falsely negative probe.

What is actually being asked

The ladder usually runs like this:

  1. "Why do you need health checks?" — the warm-up.
  2. "How does liveness differ from readiness?" — where the substance starts.
  3. "What do you check inside a probe?" — the central question.
  4. "Why should liveness not touch the database?" — about cascades.
  5. "Why a separate startup probe?" — about slow boots.
  6. "What happens when readiness lies?" — about the opposite mistake.
model with assumptionsThe numbers here come from the simulation in bench/probes/model.py rather than from measuring a cluster. Assumptions: three replicas, constant traffic, one replica degrading from second 10, a shared database down from second 20 to 30, a probe every second, a threshold of three failures, a five-second restart.

This lesson rests on a model rather than on a measurement: there is no common standard for how probes behave, only the documentation of particular products. The model answers not "how much exactly" but "what is connected to what", and its assumptions are named.

Base: three questions, not one check

A service has several identical replicas, and from outside nothing about the inside of any of them is visible. There is exactly one way to ask: knock and look at the answer. That is where a health check comes from — a short question somebody puts to a replica at regular intervals, plus an action decided in advance for the case where the answer is no.

The whole topic rests on "are you healthy" not being one question. The observer can take several different actions, and different actions fix different things. So there are three checks, each with a question of its own:

ProbeThe question it answersWhat is done on "no"
startuphas the application finished booting?nothing: keep waiting, and do not judge the replica strictly
livenesswould restarting this process help?the process is restarted
readinesscan traffic be sent here?traffic stops, the process is left alone

The middle column for liveness is phrased unusually, and that is the main point of the lesson. The usual phrasing is "is the process alive" — but a probe is not needed for that question: a dead process does not answer at all, and that is visible without any check. The probe exists for the other case: the process is alive, it answers requests, and it has stopped working. What it decides is not "alive or not" but whether the one available cure — a restart — is worth applying.

The right-hand column answers the lesson's main question too: what goes inside each probe? It follows from the consequence — check what that consequence fixes, and nothing else.

That is already enough to answer the basic interview question. Everything below is about the price of getting that choice wrong: what missing readiness costs, why a probe that looks into a shared database adds failures to somebody else's outage, and why an over-diligent readiness is as harmful as a credulous one.

Mechanism 1: the consequence is the definition

There is no normative document for this split: below is a distinction by consequence, which this lesson uses as a definition. The confusion starts wherever all three questions are answered by one handler.

Liveness — "would restarting this process help". A negative answer means: the process is broken badly enough that only a restart can return it to working order. A deadlock, an exhausted thread pool, an infinite loop — internal, cumulative failures, all of them cured by putting the process back into a known state.

Readiness — "can traffic be sent here right now". A negative answer means: do not send me requests, but do not restart me either. A warming cache, a lost connection to a dependency, a temporary overload — states a replica leaves on its own if it is not being loaded.

Startup — "has it finished booting". A separate probe exists so that a slow boot does not look like a death. This lesson assumes liveness does not apply until the startup probe has passed — that is what the separate probe is for.

The difference is in the consequence: liveness restarts, readiness removes from rotation. Hence the rule everything else follows from: check what the consequence can actually fix. For liveness that reads literally: only what a restart of this process can fix belongs inside it.

Mechanism 2: what each probe buys

The model: three replicas, constant traffic, one replica starting to answer slowly — but answering — from the tenth second.

REQUESTS SERVED SLOWLY OR NOT AT ALL
------------------------------------
                  mode       slow     failed      total
                  none       1600       1000       6000
              liveness        520       1000       6000
    liveness+readiness         80       1000       6000
        liveness-on-db        640       1200       6000
model with assumptionsbench/probes/model.py, a model. The absolute numbers follow from the assumptions (10 requests per step, a 60 s horizon); what carries meaning is the ratios between rows.

No probes — 1600 slow answers. The balancer does not know the replica is unwell and keeps sending it a third of the traffic the whole time.

Liveness only — 520. The probe notices the trouble and restarts the replica; the restart takes it out for five seconds, then it comes back — and degrades again. It helps, but badly: the replica shuttles between "unwell" and "restarting".

Liveness plus readiness — 80. The replica is taken out of rotation without a restart, and the load on it stops. 6.5 times fewer slow answers than with liveness alone.

Hence the answer to the second rung: readiness is not "the same thing but gentler". It is a different consequence, and for degradation it is the apt one: a replica that is not being loaded may recover on its own, while a restarted one begins from a cold start.

Mechanism 3: why liveness does not touch the database

The fourth row of the run above is about the mistake that adds failures to somebody else's outage. Its slow answers (640) sit close to the liveness-only mode's 520; the whole difference went into the failed column, and that is what this section is about. Here the probe checks not only itself but a shared dependency: if the database is unavailable, liveness is considered failed.

WHAT THE THREE MODES ACTUALLY CHANGE
------------------------------------
  no probes: slow answers                      1600
  liveness only: slow answers                  520
  liveness + readiness: slow answers           80
  liveness tied to the database: failures      1200
  the same failures without that tie           1000

  failures added by tying liveness to the database 200
model with assumptionsbench/probes/model.py. The database is down for the same length of time in both runs; the difference of 200 failed requests is the price of the restarts.

The database is down for ten seconds in both runs, and those ten seconds cost 1000 failed requests regardless of the probes — a probe cannot fix somebody else's outage.

The extra 200, however, belong entirely to the probe. The mechanism: the database goes down → liveness fails on every replica at once → every replica restarts → while they come up the service answers nothing at all, and that continues after the database is already back.

Hence the rule worth answering with: liveness checks only what a restart of this process can fix. A database, a network and a neighbouring service are not fixed by restarts; their place is readiness, where the consequence is milder — the replica stops being loaded but stays alive and comes back on its own.

And the second half of the same rule: a shared dependency makes probes synchronous. If the check is identical on every replica and depends on one external object, they all fail together — and the redundancy that three replicas were meant to provide disappears exactly when it is needed.

Deeper: the boundaries of the rule, and both readiness mistakes

The opposite mistake works the other way round and costs the most slow answers in the model. A readiness handler that always answers "ready" — because it is a static handler returning 200, say — makes the balancer blind: it keeps sending requests where there is nobody to serve them.

The model has no separate mode for such a readiness; the nearest one is the mode with no probes at all: 1600 slow answers. The balancer is equally blind in both cases, so the number is the same. The replica is alive and formally ready, and the traffic keeps arriving.

Hence a practical check worth naming in an interview: readiness must reflect the ability to serve a request, not the fact that a process is running. The minimal honest implementation checks what a request cannot be completed without: a database connection, room in a pool, the state of a task queue.

And the other side of it: the more checks inside readiness, the higher the chance it turns falsely negative and a replica leaves rotation over a trifle. So what gets checked is not "everything there is" but what this service cannot serve its requests without.

This is where the boundary of the rule belongs — the one that mirrors the boundary for liveness. From "dependencies live in readiness" it is tempting to conclude "so list every dependency in it", and that is a mistake too, just a different one. Readiness does not answer "is everything around me fine", it answers whether this replica can serve any traffic at all. A service that still answers some of its requests without the second database has to stay in rotation while that database is down: taking it out turns a partial degradation into a total outage — by your own hand rather than by the outage.

A readiness that lists every dependency also brings back exactly the disease the previous section cured. A shared dependency makes probes synchronous no matter which probe it is written into: every replica leaves rotation at the same moment because they all look at the same external object. The consequence is milder than with liveness — at least the replicas are not restarting — but the redundancy disappears just the same.

How to answer in an interview

Short answer: there are three probes and they differ by consequence — liveness restarts, readiness removes from rotation, startup postpones liveness for the duration of the boot. So the liveness question is not "is the process alive" but "would restarting this process help": only what a restart fixes goes inside it, and dependencies go into readiness.

That is enough to answer correctly. Beyond it is what you add when the interviewer digs.

If the interviewer digs deeper

Three things separate a good answer. First, you give the cost of the mistake as a number: in the model a liveness probe tied to the database added 200 failed requests on top of the 1000 the outage itself cost, because every replica restarted at once. Second, you explain why readiness is needed when liveness exists: a restart returns the replica to service again and again, while removing it from rotation stops the load — 80 slow answers against 520 in the model. Third, you mention the opposite mistake: a readiness that always answers "ready" makes the balancer blind, and that is the worst case the model gives — 1600.

And one thing that is easy to overdo. "Dependencies go into readiness" does not mean readiness should be the sum of every dependency. The precise phrasing is: readiness answers whether the replica can serve any traffic at all — and a replica that serves some of its requests without one of its dependencies should not leave rotation. Otherwise the probe itself turns a partial degradation into a total outage, and the shared dependency makes every replica synchronous again.

Next they ask

Next they ask

Can readiness and liveness be one handler?

Short answer

Technically yes. But the consequences differ: one restarts, the other stops the load. A shared handler means any reason for "not ready" turns into a restart.

In the model that is precisely the fourth row: a probe that checks the database added 200 failed requests on top of what the outage itself cost. Splitting the handlers is the cheapest measure in this topic.

Next they ask

Why a separate startup probe?

Short answer

So that a slow boot does not look like a death. If an application takes a minute to come up while liveness is tuned in seconds, it will be restarted before it has started — over and over.

The alternative without a separate probe is stretching the liveness thresholds until they cover the boot. But then the same stretched check applies in steady state too, and a real deadlock is noticed after a minute instead of a second. A separate probe splits the two regimes.

Next they ask

What should readiness check?

Short answer

What this service cannot serve a request without: a database connection if every request goes to the database; room in a pool; a filled cache if answers are meaningless without it. Not "everything there is": every extra check raises the chance of a false removal from rotation.

A useful test while writing it: if the probe answers "not ready", can the replica come back on its own? If yes, this is readiness. If only a restart can help, this is liveness.

Next they ask

Why does a restart sometimes help after all?

Short answer

Because some failures are internal and cumulative: leaked descriptors (the lesson on limits), a deadlock, a thread pool whose threads never come back. A restart puts the process back into a known state, and it is the one cure that does not require understanding the cause.

The price is losing everything accumulated: warm caches, established connections, a hot pool. Which is why a restart should not be the reaction to an external outage: it fixes the internal and damages what was fine.

Common misconceptions

Claim

liveness and readiness are the same thing under different names

Actually

Their consequences differ: liveness restarts the process, readiness stops sending it requests. In the model, for a degraded replica that is 520 slow answers against 80 — 6.5 times — because a restarted replica returns to service and degrades again, while one out of rotation simply is not loaded.

Claim

a probe should check everything the service depends on

Actually

In liveness, only what a restart of this process can fix. Model: a probe checking the shared database added 200 failed requests on top of the 1000 the outage itself cost, because every replica restarted at once and was still coming up after the database returned. Nor can readiness be assembled mechanically from every dependency: it answers whether the replica can serve any traffic at all, and one extra check inside it turns a partial degradation into a total outage.

Claim

if a replica answers slowly it should be restarted

Actually

A restart returns the process to a known state at the price of losing warm caches and connections — and a degraded replica will come back and degrade again. The model shows it directly: liveness alone gives 520 slow answers against 80 when the replica is taken out of rotation.

Claim

a readiness handler that always answers 'ready' is harmless

Actually

It is the worst case the model gives: 1600 slow answers against 80. The balancer goes blind and keeps sending load where nobody can serve it. Readiness must reflect the ability to serve a request, not the fact that the process is running.

Claim

a startup probe is an optional nicety

Actually

Without it the liveness thresholds have to be stretched to cover the slowest boot — and the same stretched check then applies in steady state, where a real deadlock is noticed after a minute instead of a second. A separate probe splits booting from working.

Claim

health checks raise availability by themselves

Actually

They change the distribution of failures, not their number. In the model the database outage cost 1000 failed requests under every probe configuration: a probe does not fix somebody else's outage. What it can do is avoid turning one outage into two.

Claim

including a shared dependency in a probe makes the picture more accurate

Actually

It makes the probes synchronous: every replica fails at the same moment because they all look at the same external object. Three replicas stop being redundancy exactly when redundancy is needed.

Practice

Two exercises. Answer first, then check against the real output: in both, the correct answer comes from a run of the model rather than being written by hand.

Practice · predict the output

Three replicas, one degrading from the tenth second, a shared database down from the twentieth to the thirtieth. The model is run in four modes. Three numbers are printed: slow answers with no probes; slow answers with liveness plus readiness; and the difference in failed requests between the mode where liveness checks the database and the mode where it does not. What does this code print?
none = simulate("none")
liveness = simulate("liveness")
both = simulate("liveness+readiness")
on_db = simulate("liveness-on-db")
print(none["slow"])
print(both["slow"])
print(on_db["failed"] - liveness["failed"])

Practice · estimate

One replica out of three has degraded. How many times does readiness reduce the number of slow answers compared with liveness alone?
times

Knowledge check

Question 1 of 6

How does liveness differ from readiness?

Sources & further reading

1 SOURCE

  1. The model behind this lesson: probes and the restart cascadeSource. This lesson has no external primary source: there is no common standard for how probes behave, only the documentation of particular products. So the lesson rests on a model with declared assumptions: three replicas, constant traffic, one replica degrading from the tenth second, a shared database unavailable from the twentieth to the thirtieth, a probe every second, a threshold of three consecutive failures, a five-second restart. Everything the run prints follows from those rules and can be checked by reading the script./en/bench/probes/model.py