Skip to content

Split internalServerErrorCount by wire-visibility - #3297

Merged
SophieGuo410 merged 6 commits into
linkedin:masterfrom
SophieGuo410:sopguo-linkedin-split-internal-server-error-metric
Aug 28, 2026
Merged

SophieGuo410 merged 6 commits into
linkedin:masterfrom
SophieGuo410:sopguo-linkedin-split-internal-server-error-metric

Conversation

@SophieGuo410

Copy link
Copy Markdown
Contributor

Summary

nettyMetrics.internalServerErrorCount previously incremented unconditionally whenever a generic (non-RestServiceException, non-client-termination) exception was handled in
NettyResponseChannel#getErrorResponse, even when the constructed 500 response could never actually be written to the client because response metadata (e.g. a 200) had already been committed for a streamed response.

This conflated two different failure modes under one counter/alert:

  • a 500 that actually reached the client on the wire
  • a post-commit failure where the client instead sees a force-closed connection after a partially delivered 200

Add internalServerErrorAfterResponseCommittedCount to track the second case separately, so internalServerErrorCount now reflects only errors that were actually written to the wire. The sum of the two new counters equals what internalServerErrorCount counted before this change.

Testing Done

unit test

nettyMetrics.internalServerErrorCount previously incremented
unconditionally whenever a generic (non-RestServiceException,
non-client-termination) exception was handled in
NettyResponseChannel#getErrorResponse, even when the constructed 500
response could never actually be written to the client because
response metadata (e.g. a 200) had already been committed for a
streamed response.

This conflated two different failure modes under one counter/alert:
- a 500 that actually reached the client on the wire
- a post-commit failure where the client instead sees a force-closed
  connection after a partially delivered 200

Add internalServerErrorAfterResponseCommittedCount to track the
second case separately, so internalServerErrorCount now reflects only
errors that were actually written to the wire. The sum of the two
new counters equals what internalServerErrorCount counted before this
change.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@SophieGuo410
SophieGuo410 requested review from beijxu and crliao August 26, 2026 06:29
@codecov-commenter

codecov-commenter commented Aug 26, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 48.00000% with 13 lines in your changes missing coverage. Please review.
✅ Project coverage is 50.73%. Comparing base (52ba813) to head (cc2ea5a).
⚠️ Report is 418 commits behind head on master.

Files with missing lines Patch % Lines
...va/com/github/ambry/rest/NettyResponseChannel.java 14.28% 12 Missing ⚠️
...main/java/com/github/ambry/config/NettyConfig.java 80.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##             master    #3297       +/-   ##
=============================================
- Coverage     64.24%   50.73%   -13.52%     
+ Complexity    10398     8707     -1691     
=============================================
  Files           840      939       +99     
  Lines         71755    80757     +9002     
  Branches       8611     9745     +1134     
=============================================
- Hits          46099    40969     -5130     
- Misses        23004    36388    +13384     
- Partials       2652     3400      +748     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sophie Guo and others added 4 commits August 27, 2026 10:06
… instead

Replace the previous internalServerErrorCount /
internalServerErrorAfterResponseCommittedCount split with the original,
simpler behavior: internalServerErrorCount is incremented
unconditionally for generic internal errors again, exactly as before
this PR, so no existing alerting/dashboards keyed on it need to change.

A 500 that could not be delivered to the client because response
metadata (e.g. a 200) was already committed for a streamed GET is
still a case worth tracking - but only distinctly for known offline
(e.g. composite router secondary/parity-check) callers, since those
already-committed-response drops for that traffic are expected and
otherwise indistinguishable from a genuine client-facing incident.

Add a new netty.server.offline.service.ids config listing the
x-ambry-service-id values (as configured for those callers, e.g. in
the composite router config) that identify offline traffic. When a
request from one of those service IDs hits the already-committed-500
case, increment a new, additive NettyMetrics#offlineInternalServerErrorOnlyCount
counter (OfflineInternalServerErrorOnlyCount.Count.rrd) instead of
introducing a second alerting metric.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Read responseMetadataWriteInitiated after the CAS attempt in
maybeSendErrorResponse(), not before. A concurrent writer (e.g. a
router content-write callback) can commit response metadata on
another thread between the earlier snapshot and our own CAS attempt,
which could cause the offline metric to be silently skipped even
though a response was already committed to the client.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
… case

Adds offlineServiceUnavailableOnlyCount, incremented under the same
conditions as offlineInternalServerErrorOnlyCount but for a genuine
ServiceUnavailable (503) that never reached the wire because response
metadata was already committed, for a request from a configured
offline service ID. Host-level-throttled drops are excluded, matching
how the existing serviceUnavailableErrorCount already excludes them
from polluting SLO dashboards.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ed drops

Mirrors offlineInternalServerErrorOnlyCount/offlineServiceUnavailableOnlyCount:
tracks host-level-throttled 503 drops that never reached the wire because
response metadata was already committed, for a request from a configured
offline service ID. Kept as its own counter, matching how hostLevelThrottledCount
is already tracked separately from serviceUnavailableErrorCount.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

@crliao crliao left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment thread ambry-api/src/main/java/com/github/ambry/config/NettyConfig.java Outdated
Comment thread ambry-api/src/main/java/com/github/ambry/config/NettyConfig.java Outdated
….ids

Previously an unconfigured (or trailing-comma) value produced a set
containing a single empty string instead of a truly empty set, so the
isEmpty() fast-path in isOfflineServiceRequest() was never hit by
default. A stray space after a comma also produced an id that could
never match via exact Set.contains(...). Trim each entry and drop
empties so both cases are handled correctly.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@SophieGuo410
SophieGuo410 merged commit 65a2679 into linkedin:master Aug 28, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants