Courses / System Design Interview / Chapter 12
Module 2 · Building Blocks · Chapter 12 of 39
Monitoring: Metrics, Logs, Traces and Alerts
Builds on Availability, Reliability, Scalability and Latency, DNS: How a Name Becomes an Address, Load Balancers and Content Delivery Networks. Next up: Distributed Caches.
Part 1 · Why Watch a System
You cannot fix what you cannot see
10:00 am, board results day. Two million students (20 lakh) open the results site at once. Pages load, the servers look busy but fine, and the team relaxes. Meanwhile one of thirty servers has a full disk, and every request it serves fails: about 3% of students get a blank screen. Nobody on the team knows. The first sign is an angry post at 10:40, shared ten thousand times. With monitoring, a graph turns red at 10:01, an alert pages the on-call engineer at 10:05, and the bad server is out of rotation before most students notice. This chapter builds that second morning.
Monitoring answers three questions, in this order:
- Is it working for users right now? Not "are the servers up", but "can students see their results, quickly".
- If not, where is it broken? Which service, which server, which dependency.
- What changed? A deploy, a traffic spike, a full disk, a slow database.
You will also hear observability: how well you can work out what is happening inside a system from what it emits. Monitoring tells you that something is wrong; good observability lets you ask new questions about why, without shipping new code.
The first question is the hardest to answer well. Which numbers tell you users are fine?
The four golden signals
If you can watch only four numbers for a service, watch these. Together they catch most of the problems users feel, and the table shows each one on the results site.
| Signal | Question | On the results site | Watch out |
|---|---|---|---|
| Latency | How long do requests take? | p99 time to show a result | Track percentiles, not averages, and time failed requests separately: fast errors make latency look good |
| Traffic | How much demand is there? | Result lookups per second, by region | A sudden drop is as alarming as a spike; it can mean users cannot reach you |
| Errors | How many requests fail? | Share of lookups ending in an error or timeout | Count wrong answers too, such as "result not found" for a valid roll number, sent with status 200 |
| Saturation | How full is the system? | Database connection pool 90% used; request queue growing | The fullest resource decides, and trouble starts before it reaches 100% |
QuestionHow long do requests take?
On the results sitep99 time to show a result
Watch outTrack percentiles, not averages, and time failed requests separately: fast errors make latency look good
QuestionHow much demand is there?
On the results siteResult lookups per second, by region
Watch outA sudden drop is as alarming as a spike; it can mean users cannot reach you
QuestionHow many requests fail?
On the results siteShare of lookups ending in an error or timeout
Watch outCount wrong answers too, such as "result not found" for a valid roll number, sent with status 200
QuestionHow full is the system?
On the results siteDatabase connection pool 90% used; request queue growing
Watch outThe fullest resource decides, and trouble starts before it reaches 100%
These are the ideas from Availability, Reliability, Scalability and Latency, turned into things you can graph. Saturation is the early warning, because systems usually slow down before they fail.
Two shorter checklists cover the same ground. RED (rate, errors, duration) is for anything that serves requests, such as an API or a queue consumer. USE (utilisation, saturation, errors) is for resources: CPU, disks, network links, connection pools. Apply RED to every service and USE to every machine and pool it depends on.
An average latency of 120 ms can hide that 2 lookups in every 100 take 3 seconds. The p99 shows them at once. You also cannot average percentiles across servers: merge the servers’ latency histograms first, then read the percentile from the merged one.
Part 2 · Metrics, Logs and Traces
Three kinds of telemetry
The golden signals are numbers, but numbers alone cannot say why one student’s lookup failed. Systems emit three kinds of data about themselves, together called telemetry, and each answers a different question.
| Metrics | Logs | Traces | |
|---|---|---|---|
| What it is | Numbers measured over time | Records of single events, as text or JSON | The path of one request through many services, with timings |
| Answers | How many? How fast? Getting worse? | What exactly happened to this request? | Where did the time go? Which service failed? |
| Cost | Cheap: one number per series every few seconds | Expensive at volume: one record per event | In between, because traces are usually sampled |
| Typical use | Dashboards and alerts | Debugging one specific failure | Finding the slow hop in a chain of calls |
MetricsNumbers measured over time
LogsRecords of single events, as text or JSON
TracesThe path of one request through many services, with timings
MetricsHow many? How fast? Getting worse?
LogsWhat exactly happened to this request?
TracesWhere did the time go? Which service failed?
MetricsCheap: one number per series every few seconds
LogsExpensive at volume: one record per event
TracesIn between, because traces are usually sampled
MetricsDashboards and alerts
LogsDebugging one specific failure
TracesFinding the slow hop in a chain of calls
A good setup uses all three, because each is cheap where the others are expensive. Here is one failed lookup seen three ways:
metric http_requests_total{service="results", status="500"} 1423
log {"ts":"2026-10-16T10:00:03Z", "level":"error", "request_id":"r-81f2", "msg":"marksheet timeout"}
trace r-81f2: gateway 3,120 ms > results 3,090 ms > marksheet 3,010 ms > document store 3,000 ms (timeout)A counter that went up, a log line with details, and a trace showing where the time went.
Every unique combination of label values is a separate time series. A region label with 30 values is fine. A roll_number label with 2 million values creates 2 million series and can bring the metrics system down. Keep per-student detail in logs and traces.
Following one request with a trace
The trace line above packs a whole story into one row; this page unpacks it. A results page can involve several calls. A trace records each call as a span: which service, when it started, how long it took, and which span called it. All spans of one request share a trace id, which every service passes along in a request header; the standard header is called traceparent.
You rarely write this by hand. OpenTelemetry, an open standard with SDKs for most languages and a collector, creates the spans, passes the header and also emits metrics and logs, so you can change monitoring vendors without changing your code. The failed lookup’s spans look like this:
| Span | Called by | Starts at | Duration |
|---|---|---|---|
| API gateway | the student’s browser | 0 ms | 3,120 ms |
| Results service | API gateway | 10 ms | 3,090 ms |
| Student profile | Results service | 20 ms | 18 ms |
| Marksheet service | Results service | 45 ms | 3,010 ms |
| Document store | Marksheet service | 50 ms | 3,000 ms, timed out |
Called bythe student’s browser
Starts at0 ms
Duration3,120 ms
Called byAPI gateway
Starts at10 ms
Duration3,090 ms
Called byResults service
Starts at20 ms
Duration18 ms
Called byResults service
Starts at45 ms
Duration3,010 ms
Called byMarksheet service
Starts at50 ms
Duration3,000 ms, timed out
The trace answers in seconds what logs alone would take an hour to piece together: the document store timed out after 3 seconds, and every span above it was waiting. It even suggests a fix, because a 3-second timeout on a call the student is waiting for is too long.
Tracing every request is expensive, so systems sample: keep perhaps 1% of normal requests, but keep every trace that ended in an error or ran slowly. Deciding after a request finishes is called tail-based sampling; it keeps the interesting traces and drops the dull ones, at the cost of buffering every trace for a few seconds until it ends.
Collecting the numbers: pull or push
Traces and metrics are useless while they sit on thousands of servers; they must travel to one place. Usually a small agent runs on every server, gathering numbers from the machine (CPU, memory, disk) and from the services on it. There are two ways to move them.
| Pull (scrape) | Push | |
|---|---|---|
| How | The monitoring server fetches each target’s /metrics page every 15 to 60 seconds | Agents send their numbers to a collector every few seconds |
| Spotting a dead target | Easy: the scrape fails | Harder: silence could mean dead or just idle, so agents send heartbeats |
| Short-lived jobs | Can finish before the next scrape | Push their numbers before they exit |
| Firewalls and private networks | The server must be able to reach every target | Only outgoing connections are needed |
| Load control | The server sets the pace | A burst of agents can flood the collector, so it needs buffering or a queue |
Both work at scale, and many setups mix them: pull inside one network, push from short jobs and from places the server cannot reach. Whichever you choose, decide how a dead server shows up, because a silent server must never look like a healthy one.
Pick Prometheus for pull-based metrics and alert rules you run yourself; Grafana for dashboards over several stores; the OpenTelemetry Collector when services push metrics, logs and traces and you want to switch backends freely; Jaeger or Zipkin for traces; Elasticsearch, OpenSearch or Loki for logs; PagerDuty or a similar on-call tool for paging and rotations. A hosted observability service bundles these when you would rather not run them.
Storing time series
Once the numbers arrive, they need a store built for their shape. A time series is a metric name plus a set of labels, holding a list of (timestamp, value) points. Points arrive in time order, are almost never updated, and are read as ranges. Neighbouring points look alike, so they compress very well, often to a couple of bytes a point.
The results site’s fleet shows the scale. 500 servers with 200 series each, sampled every 15 seconds, make 100,000 series and 576 million points a day. At about 2 bytes a point that is roughly 1.2 GB a day, so 15 days of raw points fit in under 20 GB. Part 5 scales the same sum to a whole company.
Raw data is cheap for days, not for years, so older data is kept at lower resolution, a process called downsampling:
| Age | Resolution | Used for |
|---|---|---|
| Last 15 days | Every 15 s (raw) | Debugging today’s incident |
| Up to 6 months | 5-minute summaries | Comparing with last month |
| Up to 2 years | 1-hour summaries | Comparing with last year’s results day |
Each step down cuts the points twentyfold or more, so two years of history costs less than the raw fortnight.
A 5-minute average can flatten a 30-second outage into a small bump. When downsampling, keep the maximum and a latency histogram alongside the average, so spikes and percentiles survive.
Part 3 · Alerts and the People Behind Them
Alert on symptoms, not causes
Stored numbers help only if someone looks at the right moment, and nobody watches graphs all night. An alert is a rule that watches a metric and notifies a person when it crosses a line. The key decision is what to alert on. A symptom is something users feel: errors, slowness, failed lookups. A cause is something that might lead to a symptom: high CPU, one dead server, a disk at 80%.
Each kind gets a different response:
| Alert on | Example rule | Why |
|---|---|---|
| Symptom: page someone | Result lookup errors above 2% for 5 minutes | Students are hurting now, whatever the cause |
| Symptom: page someone | p99 lookup latency above 1.5 s for 10 minutes | Slow pages drive refreshes, which add load |
| Cause: open a ticket | Disk 80% full and filling for 3 days | Needs action this week, not at 3 am |
| Cause: dashboard only | One server’s CPU at 95% | If users are fine, nothing is urgent; if the server stops answering, the health checks from Load Balancers take it out |
Symptoms page, causes wait. The reason is arithmetic: a fleet has dozens of causes going slightly wrong every day, and paging on each one buries the page that matters. A symptom alert also catches causes nobody thought to watch, such as the full disk on results morning.
The cost is that a symptom alert fires only once users feel something; canaries and error budgets, later in this part and the next, catch trouble while it is small. First, watch one symptom alert end to end.
From an error spike to a page
Here is a symptom alert end to end on results morning. Agents report error rates, a rule watches the overall rate, and only a sustained spike pages the on-call engineer.
Predict before you watch: the rule pages after three checks above 2%. If one server in three starts failing 18% of its lookups, will it page? What if it were one server in thirty?
- One in three: yes, the overall rate is about (0.2 + 18 + 0.3) ÷ 3 ≈ 6.2%. One in thirty: no, it rises only to about 0.8%.
Every 10 seconds, an agent on each server pushes its error rate to the collector
Read it as steps
Watch for the rule’s badge: a bad hotfix on one server pushes the overall error rate past 2%, and only a sustained spike pages.
The rule needed three bad checks in a row before it paged; real rules usually ask for a few minutes. That short wait filters out one-off blips, so the page that does arrive is worth waking up for. But one bad server in thirty would never have crossed the line at all, which is the problem the next page solves.
Catch bad deploys with a canary
In the scene, a person rolled back the hotfix. Most teams automate that step with a canary: the new version goes to a small slice of servers first, and a deploy tool compares that slice with the rest of the fleet before going wider.
- 1Deploy to 1 server in 30about 3% of traffic gets the new version; the other 29 servers are the baseline
- 2Compare for 10 minutescanary error rate and p99 latency against the baseline, not against a fixed line
- 3Canary clearly worsethe tool rolls it back on its own and posts in the team’s channel; nobody is woken
- 4Canary as good as the restwiden in stages, such as 10%, 50%, then everyone, comparing again at each step
- 5Fleet-wide alerts stay onif something slips through, the symptom alert pages as before
The comparison is the point. One bad server in thirty lifts the fleet’s error rate only from 0.2% to about 0.8%, under the 2% page line, but its own 18% stands out at once against its 29 neighbours. A canary turns a fleet-wide blip into an obvious outlier, while only about 3% of students ever see the bad version.
When a deploy and a symptom start together, roll back before you investigate. Keep releases small so rollbacks are quick, and keep database changes compatible with the previous version, so a rollback is always safe. Many teams simply freeze deploys on days like results day.
Dashboards and on-call
When a page does arrive, the engineer opens a dashboard. It is read by a tired person looking for a problem, often at 3 am, so it should answer "is this service healthy?" in five seconds. Four habits make that possible:
- Golden signals at the top: traffic, errors, latency percentiles and saturation, in that order.
- Dependencies next: the database, cache and services this one calls, with their own errors and latency.
- Deploys and config changes as markers on every graph. The most common answer to "what changed?" is "we did".
- Fewer panels. Eight graphs everyone understands beat forty that nobody does.
On-call means carrying a phone that alerts can ring, usually for a week at a time in a rotation across the team. It works only if pages are rare and meaningful. A team that gets dozens of alerts a day starts ignoring them, and the one real alert gets lost. This is alert fatigue, and every page should pass four tests:
If the agent on a server dies, that server’s error rate does not go up; it disappears. Add rules that fire when expected data stops arriving. After any real incident, write a blameless postmortem: what happened, how it was found, why it spread, and what will stop it happening again.
Part 4 · Seeing What Users See
What the server cannot see
Everything so far measures requests that reach your servers. Many failures happen before that: the page’s script crashes on one phone model, DNS fails in one city, a certificate expires, or a CDN edge misbehaves. Your servers see nothing, so their graphs look perfect while students suffer. Four tools together cover the whole path:
| Tool | How it works | Catches |
|---|---|---|
| Server-side metrics | Measure every request your servers handle | Errors and slowness inside your system |
| Client reporting (real-user monitoring) | The page or app sends crashes, script errors and timings to a collection endpoint, sampled | A release broken on one phone model, slow pages on weak mobile networks |
| Synthetic checks | Robots in several cities run key flows every minute: open the site, enter a test roll number, see a result | DNS, TLS and CDN failures, and outages in quiet hours when real traffic is low |
| Traffic-drop alerts | Compare traffic with the same minute last week | Users who cannot reach you at all |
The last row is subtle. If students in one region cannot connect, your error rate does not rise; your traffic falls. A drop of 30% against the same minute last week is often the first sign of a problem outside your servers, such as the resolver outages from DNS: How a Name Becomes an Address or a misbehaving edge from Content Delivery Networks. On results day, compare with last year’s results day, not last week.
A synthetic check that only calls /health on one server proves very little. Send it through DNS, the CDN and the load balancer, and have it complete a real user action.
SLIs, SLOs and error budgets
Seeing what users see raises the next question: how good is good enough? "As reliable as possible" is not a useful target, because every extra nine costs more and slows feature work. Teams agree on numbers instead, using four terms:
| Term | Meaning | Example |
|---|---|---|
| SLI (service level indicator) | A measured ratio of good events to all events | Share of result lookups that succeed within 1 s |
| SLO (service level objective) | The target for an SLI over a time window | 99.9% of lookups succeed within 1 s, over 30 days |
| Error budget | How much the SLO allows to fail | 0.1%: with 10 million lookups a month, 10,000 may fail |
| SLA (service level agreement) | A contract with customers, with penalties | Set looser than the SLO, so you miss the SLO well before the SLA |
The error budget turns reliability into a shared decision. While budget remains, the team ships features and takes risks. When a bad month uses it up, risky launches pause and the team works on reliability until it recovers.
In time terms, 99.9% over 30 days allows about 43 minutes of complete outage: 0.1% of the month’s 43,200 minutes. But a budget counts requests, not minutes. If most of the month’s lookups arrive on results morning, ten minutes of failures that morning can spend more budget than a quiet week of trouble. That is why the budget, not a clock, is what alerts should watch, as the next page shows.
Alerting on the error budget
A fixed line like "errors above 2%" ignores how much budget is left. A burn rate alert asks a better question: how fast are we spending the budget? Burn rate is the current error rate divided by the rate the SLO allows. With a 0.1% budget, 0.1% errors is a burn rate of 1, which uses exactly the whole budget in 30 days, or 720 hours. An error rate of 1.44% is a burn rate of 14.4.
Multiply burn rate by time to get the share of the budget spent. The table shows the usual tiers:
| Burn rate | Sustained for | Budget spent | Action |
|---|---|---|---|
| 14.4 (1.44% errors) | 1 hour | 14.4 ÷ 720 = 2% | Page |
| 6 (0.6% errors) | 6 hours | 36 ÷ 720 = 5% | Page |
| 2 (0.2% errors) | 1 day | 48 ÷ 720 ≈ 7% | Ticket |
Sustained for1 hour
Budget spent14.4 ÷ 720 = 2%
ActionPage
Sustained for6 hours
Budget spent36 ÷ 720 = 5%
ActionPage
Sustained for1 day
Budget spent48 ÷ 720 ≈ 7%
ActionTicket
Fast burns page because the budget will be gone in days; slow burns open a ticket because there is time to fix them in working hours.
One window alone misbehaves. A 1-hour window is slow to notice a sudden outage and keeps firing for up to an hour after it ends. So each tier also checks a short confirming window, about one twelfth as long: page only when the burn rate exceeds 14.4 over both the last hour and the last 5 minutes. A real outage trips both quickly, and once errors stop, the 5-minute window drops at once, so the page clears in minutes instead of an hour.
Part 5 · Designing a Metrics System
A metrics system for a whole company
The results site is one of hundreds of services a large company runs, and all of them send metrics to one shared system. Designing that system tests everything in this chapter: the writes never stop, the volume is huge, and it must keep working while everything it watches is failing. It needs four things:
- Ingest metrics from every host and service at 10-second resolution, without ever blocking the senders.
- Answer dashboard queries over the last hour in about a second.
- Fire alerts within a minute and reach the right team once.
- Keep raw points for 15 days and summaries for a year or more.
The estimate that decides the shape assumes 100,000 hosts, each exposing 1,000 series, sampled every 10 seconds.
Stored per day: ≈ 1.7 TB (15 days of raw points ≈ 26 TB before replicas)
Read it as steps
Watch for the number that decides the design: points per second, not bytes.
Ten million writes a second is the hard part, not the bytes. 1.7 TB a day compressed is modest, but 10 million inserts a second and 100 million active series are far more than one storage node can hold. So the design needs a sharded write path with a buffer in front, which the next page lays out.
The write path and the read path
The estimate says to shard the writes and protect the stores from bursts. Points flow through six stages:
- 1Agents on every hostscrape local services every 10 s, batch and compress, then push; buffer on local disk if the next tier is slow
- 2Stateless ingestersbehind a load balancer: check the tenant’s limits, then append to the log; add more as traffic grows
- 3A durable log, partitioned by series hashabsorbs bursts and slow storage; each series always lands in the same partition, in order
- 4Time-series shardseach reads its partitions, keeps recent hours in memory, and writes compressed blocks to disk, then to object storage
- 5Query layerfans each query out to the shards that hold matching series and merges the partial results
- 6Rule evaluators and alert routerrun recording and alert rules every 30 to 60 s; the router deduplicates, groups and routes notifications
Sizing follows from the estimate. If one storage shard comfortably takes about 500,000 points a second, 10 million needs 20 shards, or 40 to 60 nodes with two or three replicas each.
The log carries points before storage compression. Assume about 20 bytes a point on the wire: an 8-byte value, a timestamp sent as a small delta and a short series reference, with full labels sent once per batch, not per point. That is 200 MB a second, or 20 partitions at about 10 MB a second each, so plan 30 to 40 for headroom.
Why a log in front, instead of writing straight to the shards? A shard that pauses to compact or restart would otherwise push back on 100,000 agents at once. With the log, the shard falls a few seconds behind and catches up, and no agent ever blocks.
Inside the metrics system
The boxes are simple; the hard parts are inside them, and the table names each one.
| Hard part | What you do | Why |
|---|---|---|
| Cardinality | Count active series per tenant at the ingesters; reject new series past a limit, such as 1 million, with a clear error | One roll_number label can add millions of series and slow a shard for everyone |
| Compression | Store timestamps as differences of differences and values as XOR with the previous value, in immutable 2-hour blocks | About 1–2 bytes a point instead of 16, and old blocks move to cheap object storage |
| Recording rules | Every minute, pre-compute common aggregates, such as errors per service, and store them as small new series | Dashboards and alerts read thousands of series instead of millions |
| Query fan-out | Send each query only to shards with matching series; cap the series and time range per query | One heavy dashboard cannot starve everyone else |
| Downsampling | A background compactor rewrites old blocks at 5-minute and 1-hour resolution, keeping min, max, sum, count and histograms | Years of history stay cheap, and spikes survive |
| Alert routing | Deduplicate (two rule replicas fire once), group (200 hosts, one page), route by team and severity, allow silences | One problem wakes one person, once |
| Monitoring the monitoring | A small, separate watcher in another region checks the pipeline end to end, plus a dead man’s switch: an alert that always fires, so its silence pages someone | If the metrics system is down, every other alert is silent too |
Which row matters most depends on the pressure. For cost, it is cardinality limits, downsampling and object storage at about $0.02 per GB-month (check current prices). For reliability, it is the log as a buffer, replicated shards and the separate watcher. For speed, it is recording rules and query fan-out.
Part 6 · Review
In the interview
Monitoring closes almost every design, and "design a metrics system" is also asked on its own; these lines carry the chapter into either.
- Say
- "Each service exports the four golden signals, and I page on symptoms, such as lookup errors above 2% for 5 minutes, not on CPU."
- Say
- "The SLO is 99.9% within 1 s over 30 days; I page when the budget burns 14.4 times too fast for an hour, which spends 2%."
- Say
- "Ten million points a second is a write problem, not a storage one: about 1.7 TB a day compressed. I shard by series hash behind a durable log."
- Avoid
- Paging on every cause: high CPU, one dead server, a busy disk. Dozens of pages a day teach the team to ignore the one that matters.
- Avoid
- One ordinary database table with a row per point. At 10 million inserts a second, that table becomes the outage.
Wrap-up
| Term | Meaning |
|---|---|
| Golden signals | Latency, traffic, errors and saturation: the four numbers to watch first |
| Span, trace id | One timed call in a request; the id that ties all of its spans together |
| Cardinality | How many distinct series a metric’s label values create |
| Downsampling | Keeping old data at a coarser resolution, such as 5-minute summaries |
| Symptom alert | A page on something users feel, such as errors or slowness |
| Canary | A new version on a small slice of servers, compared with the rest |
| SLO, error budget | A reliability target, and the failures it allows before launches slow down |
| Burn rate | Error rate divided by the budget rate; 1 spends the budget in exactly 30 days |
Next: Distributed Caches. Many of the alerts in this chapter, slow pages and a database at full CPU, trace back to a cache that missed, expired or died.
Part 7 · Check yourself
Quick check
Q1Why does a canary compare its error rate with the rest of the fleet instead of relying on the fleet-wide alert?
Think it through, then check.
- A badly broken canary barely moves the fleet: 18% errors on 1 server in 30 lift 0.2% to about 0.8%, under a 2% page line.
- Against its 29 neighbours the canary’s 18% stands out at once, and the live baseline cancels normal traffic swings, so the verdict comes in minutes.
Q2At 10:05 am on results day the dashboard shows traffic, errors (0.1%) and p50 (80 ms) flat, p99 up from 300 ms to 2.5 s, and the database connection pool at 100%. What is happening, and what next?
Think it through, then check.
- Saturation: every pool connection is busy, so some lookups queue for one. Most still finish fast, so p50 stays flat while p99 climbs.
- Find what holds connections longer, such as a slow query from a recent deploy or a lock; deploy markers and slow traces show it.
- Act before errors start: roll back or kill the long queries. Raise the pool size only if the database itself has room.
Q3The SLO is 99.9% of lookups succeeding over 30 days. An incident fails 5% of lookups for 2 hours. How much of the month’s error budget did it spend?
Think it through, then check.
- The burn rate is 5% ÷ 0.1% = 50.
- Two hours at 50 times equals 100 hours of normal spending, out of 720: about 14% of the budget, assuming steady traffic.
Q4The board wants latency per student, for 2 million students. Add a roll_number label to the latency histogram, or something else? What does each cost?
Think it through, then check.
- A label multiplies series: 2 million students × 10 histogram buckets × 50 routes = 1 billion series, far past any per-tenant limit.
- Keep per-student detail in logs or traces, which handle many distinct values, and label metrics by region (about 30 values) instead.
Q5From Load Balancers: every health check is green, yet 5% of result lookups fail. How can both be true, and what would catch it?
Think it through, then check.
- A health check asks one simple endpoint whether the process is up. It does not look up a result, so a broken marksheet client still passes.
- Per-server error rates from an L7 load balancer show which servers fail; a synthetic check that completes a test lookup every minute catches it too.