Monitoring, Logging, and Observability Questions
Understanding running systems through their signals. Covers metrics, logs, and traces, instrumentation, dashboards, alerting design, and log analysis and correlation for debugging production. Emphasizes designing observability so problems are detectable and diagnosable before users are affected.
What's the difference between head-based and tail-based sampling for traces? Give a concrete situation where the extra complexity of tail-based sampling is actually worth it, and one where you'd stick with head-based.
Sample Answer
Direct answer
Head-based sampling decides whether to keep a trace at its very first span, before anything about the request's outcome is known: a cheap rule (usually a hashed trace ID compared against a threshold) picks a fixed percentage of traffic, and every downstream service just honors that one decision. Tail-based sampling holds every span of a trace until the whole thing finishes, then decides based on what actually happened (an error, a slow span, a specific status), which guarantees you keep the traces you'd actually want to debug, at the cost of buffering and routing every trace's spans to one place long enough to make that call. Use head-based when uniform, cheap coverage of "normal" traffic is what you need; use tail-based when missing a specific rare failure is unacceptable and you can afford the extra infrastructure.
Structured elaboration
How the decision differs mechanically
| Head-based | Tail-based | |
|---|---|---|
| Decision point | At the trace's first span, before the request has even resolved | After the trace completes, once its outcome is known |
| What it needs | A shared random value or deterministic hash, propagated in context to every downstream service | Buffering (or storing) every span until the trace is complete, then evaluating a policy against the assembled trace |
| Coordination across services | None beyond honoring the one propagated decision | A trace's spans usually have to reach the same collector instance to be evaluated together |
| What it can guarantee | A uniform, reproducible sample rate of overall traffic | Capture of whatever outcome you define as worth keeping (error, high latency, specific status) |
Where head-based sampling itself has variants
Fixed-rate/deterministic head sampling (hash the trace ID, keep it if the hash falls under a threshold) and reservoir sampling (maintain an unbiased random sample of a bounded size over a rolling window, useful when total trace volume is unpredictable or bursty) are both still head-based in the sense that matters here: the keep/drop decision is made without any knowledge of the outcome. That shared blind spot, not something reservoir sampling fixes, is the actual dividing line versus tail-based sampling.
Concrete situations
- Head-based is enough: a well-behaved internal health-check or low-risk read endpoint with a near-zero error rate, where you mainly want representative latency percentiles across normal traffic and don't need to guarantee capture of any specific rare event. A flat, cheap deterministic sample gives you that with no buffering infrastructure at all.
- Tail-based earns its complexity: a payment-authorization service where a specific downstream provider's rejection happens on well under 1% of requests, but every one of those traces matters for debugging a customer-facing failure. At a low head-sample rate, most of those rare error traces simply never get kept; tail-based sampling, evaluated once the trace's outcome (error status) is known, guarantees you capture all of them regardless of how rare they are.
Very high-throughput production services
At extreme volume, pure tail-based sampling (buffering every span of every trace until it completes) becomes expensive in its own right, since buffering cost scales with total traffic, not with the sampled fraction. The common production pattern, and what the OpenTelemetry Collector's tail-sampling processor implements, is a hybrid: a low fixed head-sample rate for baseline, representative coverage of normal traffic, plus a tail-based always-keep policy layered on top for errors and latency outliers. That keeps the guaranteed-capture property where it matters most while bounding the buffering cost to the traffic you'd realistically want full visibility into.
Worked example
Take a service where a rare downstream error occurs independently on roughly 1 in 2,000 requests, and a head-based sample rate of r=1%. For any single occurrence, the chance a head-based sample happens to keep that specific trace is just r=1%, since the sampling decision is made before the error even happens and has no idea it's about to occur. Over k independent occurrences of this same rare error, the chance that head-based sampling misses every single one of them is:
P(miss all k)=(1−r)kFor k=20 occurrences of the error:
P(miss all 20)=(1−0.01)20=0.9920≈0.818So even after 20 separate occurrences of this specific rare failure, a 1% head-based sample rate still has roughly an 82% chance of having captured none of them, purely because the sampling decision never looked at the outcome. Tail-based sampling with an always-keep-errors policy makes that same probability exactly 0%, by construction, since its decision is made only after the error is already known to have happened.
Trade-offs and pitfalls
- Tail-based sampling's guarantee only holds for whatever you defined the keep policy around (error status, a latency threshold). It won't catch an outcome you didn't think to write a rule for, so the policy needs revisiting as new failure modes show up.
- Buffering spans for a tail-based decision adds memory pressure and latency to the sampling pipeline, and requires routing a trace's spans consistently to one collector, which is real infrastructure a pure head-based setup doesn't need.
- Aggressive head-based sampling (a low fixed rate) degrades any metric computed only from the sampled traces, like latency percentiles built from kept traces alone, since a small sample size increases variance on tail percentiles specifically even though the sample is representative on average; prefer computing percentiles from a metrics pipeline that sees 100% of requests, not from sampled traces, when precision matters.
- Deciding sampling deterministically from the trace ID, rather than independently per span, is what makes a trace's sampling decision "sticky": every span belonging to the same trace ID gets the same keep/drop outcome. Losing that property (say, sampling independently at each service) produces incomplete, unusable traces where some spans of a request are kept and others silently dropped.
What is alert fatigue, and how would you go about preventing it on a team you're leading?
Sample Answer
Direct answer
Alert fatigue is what happens when on-call engineers get so many low-value, noisy, or duplicate pages that they start treating all alerts as probably-not-real, including the ones that matter. It's a trust problem as much as a technical one: once someone has been paged repeatedly in a night for something that turned out to be nothing, the next page, which might be the real incident, gets a slower, more skeptical response.
How I'd prevent it on a team I'm leading
- Deduplication and grouping: alerts that share a root cause (same service, same error type) should collapse into a single incident with a count, not fire a separate page per occurrence. This is usually a config change in the alerting tool (fingerprinting by service and error signature) rather than a code change.
- Severity tuning tied to required response time: not every alert deserves a page. A three-tier split (page now, notify during business hours, dashboard-only) forces every new alert to justify why it needs to interrupt someone's sleep.
- Actionable-by-default policy: no new paging alert ships without a linked runbook and a clear "what to check first." An alert with no next step is a dashboard panel that accidentally has a pager attached.
- Automated remediation for known, safe, repeatable fixes: if the same alert reliably resolves by restarting a stuck worker or clearing a queue, and that action is safe and idempotent, automate it and only page if the automated fix fails.
- A regular noise review: periodically look at which alerts fired most often and whether they led to real action; alerts that never lead to action get tuned or removed, not left running indefinitely out of habit.
Worked example
Suppose a team's on-call rotation is getting paged for "queue depth over 100" on a background job processor, firing several times a week, always self-resolving within a few minutes without anyone doing anything. Applying the framework above: first, check whether these spikes line up with a predictable traffic pattern (a nightly batch job, say) and if so, either raise the threshold above that expected peak or add a time-of-day exception. Second, if the queue really can back up unpredictably but always self-resolves within a known window without intervention, the alert should require a longer sustain window (e.g. "queue depth over 100 for 15 minutes") so it only fires when it isn't going to resolve on its own. Third, if manual intervention when it does page is always the same action (scale up worker count), that's a strong automated-remediation candidate: auto-scale on the same threshold, and only page if depth is still elevated after the auto-scale has had time to take effect.
Trade-offs and pitfalls
- Automated remediation without an audit trail or human confirmation for higher-severity cases can turn a noisy-alert problem into a silent-failure problem: the system "fixes" itself repeatedly while masking a root cause that's getting worse.
- Tuning thresholds purely to reduce page volume, without checking against real past incidents, risks quietly increasing false negatives; the goal is signal-to-noise, not just fewer pages.
- Alert fatigue prevention is not a one-time project. It needs an ongoing review cadence, because new alerts get added faster than old noisy ones get cleaned up if nobody owns the process.
Your organization is comparing commercial APM/observability vendors (for example Datadog or New Relic) against a self-hosted Prometheus, Grafana, and ELK stack for an environment that mixes microservices with a legacy monolith. What criteria would you weigh most heavily, and how would you structure a proof-of-concept to validate the choice before committing?
Sample Answer
Weigh the decision on a small set of criteria that actually differ between the options in a mixed monolith-plus-microservices environment: observability depth (does it handle both the legacy monolith and the microservices equally well, not just the newer stuff), real total cost of ownership including the engineer time to run it, data residency/compliance requirements if they apply, and operational burden (who's paging when the observability stack itself breaks). Then validate the choice with a proof of concept that runs the same realistic scenarios against both options side by side, rather than trusting either a vendor's benchmark or a generic feature checklist.
Framework
Criteria that matter most in this specific environment (mixed legacy + microservices):
- Observability depth and coverage of the legacy monolith specifically. Commercial APM tools are usually strong on auto-instrumented microservices; the open-source stack's coverage of an older monolith (does an agent even exist for its stack, or does it need custom instrumentation) is often the actual differentiator, not raw feature count.
- Total cost of ownership, not just license price: for self-hosted, that includes the engineer time to run and upgrade the cluster, which teams routinely undercount because it doesn't show up as a line item the way a vendor invoice does.
- Operational burden and who owns incidents in the observability stack itself. A managed vendor takes this off your plate; self-hosted means your team is now on-call for the monitoring system too.
- Data residency and compliance, if the organization has any (regulated data, contractual data-location requirements): this can eliminate an otherwise-attractive SaaS option outright, or require a specific deployment region/mode.
- Exit path / lock-in risk: how hard is it to get your data and dashboards out if you switch later, particularly for the vendor option.
Structuring the PoC to actually validate the choice:
- Pick representative workloads from both halves of the environment: at least one path through the legacy monolith and two or three real microservices, not just the easy new-stack case.
- Instrument both candidate stacks in parallel (shadow mode) against the same real or realistic traffic, so the comparison is apples-to-apples rather than each vendor's own demo environment.
- Define success metrics up front, before running it: query latency for a realistic dashboard, cost projected at production retention/volume, and how long it took an engineer unfamiliar with the tool to build one useful dashboard and one alert from scratch.
- Include a simulated incident (inject a real failure, or replay a past one) and measure how each stack performs for actual root-cause work, not just whether data shows up.
- Run it long enough to see real cost and operational patterns, not just a demo-length window.
Worked example
A weighted scorecard makes the trade-off explicit instead of a gut call. Assign weights that sum to 1.0 across the criteria above, then score each option 1-5 per criterion (the scores below are illustrative placeholders showing how the arithmetic works; real numbers have to come from your actual PoC, not be guessed):
| Criterion | Weight | Self-hosted (Prometheus/Grafana/ELK) | Commercial APM |
|---|---|---|---|
| Observability depth | 0.30 | 3 | 5 |
| Legacy/monolith integration | 0.20 | 4 | 3 |
| Operational ease | 0.20 | 2 | 5 |
| TCO | 0.20 | 4 | 2 |
| Compliance/data residency | 0.10 | 5 | 3 |
With these illustrative inputs, the commercial option edges out, driven mainly by observability depth and operational ease outweighing its worse TCO score, given the stated weights. The value of walking through this isn't the specific 3.4 vs. 3.8, it's that the weights and scores are now visible and arguable: someone who thinks TCO should be weighted higher, or that the self-hosted operational-ease score of 2 is too harsh given the team's existing SRE capacity, can say so and change a specific number, rather than re-litigating the whole decision from scratch.
Trade-offs and pitfalls
- A weighted scorecard is only honest if the weights are set before you know how each option scores; it's common (and worth explicitly guarding against) for a team that's already leaning toward one option to unconsciously weight the criteria that favor it.
- A PoC that only tests the easy microservices case and skips the legacy monolith will miss exactly the integration gap that's most likely to bite later, since that's the part of the environment least like either vendor's demo.
- Self-hosted TCO is the number most often undercounted: engineer time spent operating, upgrading, and firefighting the observability stack itself rarely gets billed to the "observability" line item, which makes the self-hosted TCO score look better on paper than it is in practice.
- For the commercial option, data portability (can you actually export your historical data and dashboard definitions if you leave) is worth testing during the PoC, not assumed; discovering the exit path is painful only after you're already locked in defeats the purpose of evaluating lock-in risk up front.
You've inherited an organization where monitoring practices vary wildly team to team, which is fueling alert fatigue and slowing incident response. How would you drive standardization across teams without just imposing a rigid policy top-down?
Sample Answer
Direct answer
Standardize by making the good path the easy path, not by mandating it: define a small, high-leverage set of conventions (severity tiers, a runbook requirement, deduplication rules), bake them into shared tooling and templates so following them is less work than not following them, pilot with a couple of willing teams to generate real evidence, and then use a visible shared dashboard plus a recurring alert-lifecycle review to sustain it, rather than a one-time top-down policy.
Structured elaboration
Diagnose before prescribing
Before proposing any standard, measure what's actually driving the fatigue: pull alert volume, mean-time-to-acknowledge, and percentage of alerts closed with "no action taken" per team. Teams vary in maturity for real reasons (a team running a 10-year-old monolith has different noise sources than one running a new microservice), so the diagnosis has to precede the fix or the "standard" will feel disconnected from actual pain.
Define a minimal, defensible standard
Keep the mandated core small: a severity scale with clear paging behavior per level, a requirement that every paging alert links a runbook, and a deduplication/grouping convention so one root cause doesn't fan out into a dozen pages. Everything beyond that (dashboard layout, naming conventions for non-paging alerts) can stay team-specific; over-standardizing invites resistance without adding much value.
Make adoption the path of least resistance
Ship the standard as a template and a linter, not a document: a starter alert-definition file teams copy, and a CI check that flags a paging alert with no runbook link before merge, rather than after an on-call engineer discovers it missing at 3am. Tooling that does the enforcement is far more durable than asking teams to remember a policy.
Pilot, then earn adoption with evidence
Run the standard with a small number of volunteer teams first. Publish what changed for them (alert volume, MTTA) so other teams opt in because they've seen it work for a peer, not because they were told to. This converts "top-down mandate" into "internally proven practice," which is the actual ask in the question.
Sustain it without becoming the rigid policy you were trying to avoid
Two ongoing mechanisms keep this from decaying back into the original problem:
- A shared, visible alert-health dashboard (volume, MTTA, no-action-taken rate per team) makes non-compliance visible without anyone having to police it manually.
- A recurring alert-lifecycle review (e.g., quarterly): every alert that hasn't fired in a defined window, or that consistently gets acknowledged and dismissed with no action, gets retired or demoted in severity. Alert fatigue reappears over time even after a successful rollout if nothing ever prunes stale alerts, so this review is not optional cleanup, it's the mechanism that keeps the standard alive.
Worked example
As an illustration of what the pilot is meant to demonstrate (a hypothetical designed to show the reasoning, not a claimed real result): suppose a pilot team starts with 200 alerts/week, of which 140 (70%) are closed with no action taken. After adopting the dedup convention and the runbook-required gate, suppose the team retires or merges noisy definitions and ends up with 90 alerts/week, of which 40 (44%) are no-action.
noise ratio before=200140=0.70,noise ratio after=9040≈0.44That's the shape of evidence to collect and publish before asking a second team to adopt anything: total volume down, and a materially lower share of "alert nobody needed" among what remains. Reporting only the volume drop (200 to 90) would be misleading on its own, since it doesn't say whether the reduction removed noise or removed signal; the no-action ratio is what actually shows quality improved, not just quantity dropping.
| Phase | Activity | Success signal |
|---|---|---|
| Diagnose | Pull alert volume, MTTA, no-action rate per team | Baseline established, painful teams identified |
| Define | Minimal severity/runbook/dedup standard + template | Standard fits on one page |
| Pilot | 1-2 volunteer teams adopt via templates + CI lint | Noise ratio and MTTA improve for pilot teams |
| Publish | Share pilot results with the org | Other teams request onboarding unprompted |
| Sustain | Dashboard + quarterly alert-lifecycle review | Stale alerts retired before they cause fatigue again |
Trade-offs & pitfalls
- Introducing a CI gate before any team has bought in reads as exactly the top-down imposition the question is warning against; sequence tooling after the pilot has produced willing early adopters, not before.
- A standard that's too rich (dictates dashboard layout, naming down to every label) will be resisted regardless of how it's rolled out; keep the mandated core to what actually reduces fatigue and causes incident-response friction.
- Publishing a shared dashboard without a clear non-punitive framing can turn into a naming-and-shaming exercise that damages trust; frame it as a shared health metric, not a scoreboard.
- Skipping the recurring lifecycle review is the most common way a successful rollout quietly regresses: alerts accumulate again as services evolve, and without a forcing function to prune them, the fatigue returns within a year.
In a long-lived system, how do you evolve a structured logging or metrics schema over time, for example adding a new field or changing what a field means, without breaking dashboards, alerts, and tooling that depend on the old schema?
Sample Answer
Direct answer
Default to additive-only changes (new optional fields with sane defaults), never silently repurpose an existing field's name or meaning, and when the meaning genuinely has to change, introduce it as a new versioned field and dual-emit both the old and new during a defined deprecation window so every consumer (dashboards, alerts, downstream jobs) has time to migrate before the old one disappears.
Structured elaboration
Additive changes are the default and the cheap case
Adding a brand-new field with a sensible default (or simply absent, if consumers already tolerate unknown fields) is safe: existing dashboards and alerts that don't reference it are unaffected, and new tooling can start using it immediately. Most schema evolution should fit this case; if it doesn't, that's a signal the change is more than "add a field."
Never repurpose a field in place
Changing what an existing field means (e.g., a latency field that used to be measured in milliseconds and is now measured in microseconds, keeping the same name) is the most dangerous kind of change, because it fails silently: old dashboards keep running the same query and now show numbers that are wrong by a constant factor, with no error to alert anyone. A rename or unit change should always get a new field name (latency_ms retired in favor of latency_us, both emitted for a transition period), never an in-place redefinition.
Version the schema explicitly
Tag every emitted record with a schema_version. Consumers that need to branch on shape (a downstream parser, a strict dashboard query) can check the version rather than guessing from field presence. This also gives you a clean place to document exactly which version introduced which change.
Deprecation as a process, not an event
- Announce the field's replacement and the planned sunset date.
- Dual-emit: write both the old and new field for a fixed window.
- Track actual usage of the old field (query logs, dashboard/alert definitions referencing it) to confirm consumers have migrated, not just assume they have.
- Only stop emitting the old field once usage has genuinely dropped to zero (or the sunset date passes and remaining consumers have been explicitly notified they'll break).
Testing the transition
Contract tests (automated checks that a producer's output still satisfies what a known consumer expects) and shadow validation (running the old and new emission side by side and diffing the derived metrics they produce) catch the case where the "safe" additive change turns out to interact badly with an existing aggregation, before it reaches production dashboards.
Worked example
A service currently emits {"latency": 245, ...} where latency is milliseconds, and the team wants to switch to microsecond precision.
Wrong approach (in-place redefinition): change the emitter to write {"latency": 245000, ...} under the same field name. A dashboard panel computing avg(latency) over the last hour now silently reports a number 1000x larger with zero errors or warnings; anyone glancing at the dashboard sees "avg latency: 245000ms" and either panics or, worse, doesn't notice because the panel has no sanity bound configured.
Correct approach: add latency_us alongside the existing latency field, dual-emit both for a stated transition window (e.g., until every dashboard query referencing latency has been rewritten to use latency_us, confirmed by grepping the dashboard/alert config repository for the old field name), then drop latency only after that grep returns zero references.
The key diagnostic in this example: the failure mode is not "the pipeline throws an error," it's "the pipeline keeps running and produces a wrong number that looks plausible." That's why additive-with-a-new-name is the default, not an optional extra step.
| Strategy | Backward compat risk | Consumer effort required | When to use |
|---|---|---|---|
| Additive field, new name | None | None (opt-in) | Default choice for any new signal or unit/meaning change |
| Field deprecation (dual-emit then drop) | Low, if the window is long enough and usage is tracked | Must update queries before sunset | Retiring a field that's being replaced |
| In-place semantic change (same name, new meaning) | High: silent, no error | None until someone notices wrong numbers | Avoid; only defensible for a field with zero known consumers |
Trade-offs & pitfalls
- Dual-emitting indefinitely accumulates cost and confusion; every deprecation needs an explicit sunset date, not an open-ended "eventually."
- Tracking actual field usage (rather than assuming consumers migrated because you announced it) is the step most teams skip, and it's exactly the step that prevents a surprise outage when the old field is finally dropped.
- Additive changes still need CI-enforced schema compatibility checks (backward/forward compatibility validation), because "just add a field" can still break a strict consumer that rejects unknown fields.
- A silent semantic change is strictly worse than a loud break: a query that errors gets noticed and fixed; a query that keeps returning a plausible-looking wrong number can go unnoticed for months.
Unlock Full Question Bank
Get access to all Monitoring, Logging, and Observability interview questions and detailed answers.
Sign in to ContinueJoin thousands of developers preparing for their dream job.