Free preview

The Agent and Collector Design

In one line: this is the design that actually solves the problem. It inverts probing — instead of you sampling the network, the users tell you — which fixes the coverage ceiling by construction.

The two components

ComponentResponsibility
AgentA module embedded in the client application that sends reports when failures occur
CollectorA standalone service that receives error reports. Independent of the primary service, so it remains reachable even when the main service fails. Reports are analyzed to detect spikes in client-side errors

The essential move is in that diagram: the client cannot reach the service, and it reports that fact somewhere else. If the report went to the primary service it would fail for exactly the same reason as the original request — which is why the collector's independence is not a nice-to-have but the entire premise.

Why this beats probing on coverage

Probing samples the network from wherever you happened to place a prober. Agents report from wherever your users actually are — every autonomous system, ISP, mobile network, and corporate proxy that has a real customer behind it.

Coverage stops being something you buy and becomes a property of having users. That inverts the coverage problem: the networks you most need visibility into are, by definition, the ones with users in them.

The two are complementary. Probing works with zero users and no consent; agent reporting works everywhere your users are and reflects real conditions.

Scaling the collector tier

Collectors are organized in a hierarchical aggregation architecture. Two supporting decisions:

DecisionDetail
Deploy close to client networksReduces latency — reports arrive from a nearby collector rather than crossing the world
Stream processingEnables near real-time analysis, so a spike is visible in seconds rather than after a batch job

Sampling

To control costs, the system may sample reports — for example collecting data from 1% of users — instead of capturing every event. A system that ingests and processes every event would require substantially more infrastructure and cost.

Why 1% is enough here, when it wouldn't be elsewhere

Sampling works because of what you are looking for. The goal is detecting a spike in client-side errors, not reconstructing any individual user's session.

A regional outage affects a large fraction of users in that region, so even a 1% sample surfaces it clearly — you are estimating a rate, and rates survive sampling. Contrast that with distributed tracing, where you often need the specific trace and sampling can lose exactly the one you wanted.

Worth saying out loud in an interview: sample when you need a rate, keep everything when you need an instance.

What the collector does with ten million reports

Collecting is half the system. A large client fleet in a bad hour produces millions of error reports, and a list of millions of errors is not more useful than no errors — it is less, because nobody reads it. The processing side is where a client-error system becomes usable, and it is the part interviewers probe once the collection story is done.

Grouping: millions of reports, dozens of problems

Almost every report is a duplicate of another. One broken deploy produces the same failure on every device that hits it, so the raw stream is enormously redundant.

A fingerprint is a stable hash over the parts of a report that identify the fault rather than the occurrence — typically the error type plus the top few frames of the stack, with the volatile parts deliberately excluded.

Choosing what to exclude is the whole difficulty, and it fails in both directions:

Fingerprint too NARROWFingerprint too BROAD
Includes volatile data — a user ID, a timestamp, an interpolated value in the messageIncludes only the error type
Every occurrence is its own issue. Ten million issues of count 1 — you have re-created the raw listUnrelated faults merge. Every TypeError in the application becomes one issue, so a new regression hides inside an old bucket
Nothing is ever recognised as recurringThe count is meaningless and the first-seen date is years old

The signal worth stating: rank issues by distinct users affected, not by report count. One user in a retry loop can generate a hundred thousand reports and outrank a bug hitting ten thousand people once each. Report count measures the loudest client; user count measures the actual problem.

And tag every report with the client release version, because the first question about any new issue is which deploy introduced it. An issue whose first-seen coincides with a release is diagnosed before anyone reads the stack trace — which is also what makes the headline metric worth computing: crash-free sessions per release, rather than a raw error total that grows with traffic.

Symbolication: the stack trace you receive is unreadable

Shipped client code is minified or compiled. What arrives looks like this:

TypeError: undefined is not an object
  at t (main.4f9a2.js:1:48213)
  at r (main.4f9a2.js:1:52907)

Which names no function, no file, and no line anyone can act on — and worse, it defeats fingerprinting too, since a rebuild changes those offsets and every issue looks new after every deploy.

Two consequences worth volunteering. The source map must be uploaded at build time and keyed by the exact build identifier, because it is only valid for the artifact it was generated from — which means the pipeline needs the build ID in the report. And source maps must not be served publicly alongside the bundle: doing so hands anyone your original source. Upload them to the monitoring system and keep them out of the CDN.

Error storms

The last processing concern is protective rather than analytical.

This is the same shape as the logging chapter's incident-time volume problem: error volume spikes precisely when the pipeline is most needed. Two caps handle it — a per-client budget so one looping device cannot dominate, and per-fingerprint sampling once a group is well established, since the ten-thousandth instance of a known issue adds a count and nothing else. Keep counting all of them; stop storing all of them.

The three questions this raises

Embedding a reporter in the client creates three problems the rest of the chapter addresses:

QuestionCovered in
Can a user activate and deactivate client-side reports?Lesson 5
How do agents reach collectors under faulty conditions?Lesson 6
How do we protect user privacy?Lesson 7

Note that two of the three are about the user, not about the system. That ratio is unusual among the building blocks in this course, and it is the defining characteristic of client-side monitoring: you are running code on someone else's device.

Key takeaway

Agent plus independent collector solves the coverage problem by reporting from where users actually are, and the collector's independence from the primary service is the load-bearing constraint. Collection is only half of it: millions of reports must be fingerprinted into dozens of issues, ranked by distinct users affected rather than report count, symbolicated through build-keyed source maps or the traces are unreadable and grouping breaks on every deploy, and capped per client and per fingerprint, because error volume spikes exactly when the pipeline matters most.

Interview signal by level

LevelWhat a strong answer sounds like
L4"The client could report errors back to us."
L5Gets the independence right: "an agent in the client reporting to a separate collector — separate, because if it reported to the main service the report would fail too."
Staff+Adds coverage and cost reasoning: "this fixes probing's coverage ceiling — reports come from every network that has a user in it. I'd aggregate hierarchically with collectors near clients, stream-process for near real-time spike detection, and sample at around 1%, because we're estimating a rate rather than reconstructing sessions."

Next: whether the user agreed to any of this.

Enjoying the preview?

Create a free account to unlock the rest of this course, the in-browser judge, and live AI mock interviews.

Sign up free to continue