[da-vinci][changelog] Keep lag reporter alive when recordStats fails - #3017
[da-vinci][changelog] Keep lag reporter alive when recordStats fails#3017minhmo1620 wants to merge 2 commits into
Conversation
The changelog consumer's background reporter thread is started at most once per consumer and is guarded by a Thread.State.NEW check, so it is never restarted. Its run loop only caught InterruptedException, so any RuntimeException escaping recordStats terminated the thread for the remaining lifetime of the consumer. When that happens, HeartBeatDelay and CurrentConsumingVersion stop being emitted while the poll-path metrics (PollCount, RecordsConsumedCount, VersionSwapCount) keep reporting normally, so the consumer looks healthy. The failure is also easy to miss because an uncaught exception in a thread goes to the default handler on stderr rather than through the application logger. Wrap the recordStats call so a failed cycle is logged and the loop continues on the next interval. Applied to both VeniceChangelogConsumerImpl and VeniceChangelogConsumerDaVinciRecordTransformerImpl, which share this pattern. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Strengthen the existing regression test and add coverage for the transformer reporter thread.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Keeps changelog metric reporter threads alive when recordStats() throws.
Changes:
- Catches and logs per-cycle reporting failures in both reporter threads.
- Adds regression coverage for heartbeat reporter survival.
- Preserves existing interruption and shutdown behavior.
File summaries
| File | Summary |
|---|---|
clients/da-vinci-client/src/test/java/com/linkedin/davinci/consumer/VeniceChangelogConsumerImplTest.java |
Tests reporter survival; review requests stronger synchronization with completed reporting cycles. |
clients/da-vinci-client/src/main/java/com/linkedin/davinci/consumer/VeniceChangelogConsumerImpl.java |
Keeps the heartbeat reporter alive after reporting failures. |
clients/da-vinci-client/src/main/java/com/linkedin/davinci/consumer/VeniceChangelogConsumerDaVinciRecordTransformerImpl.java |
Keeps the background reporter alive; review requests equivalent regression coverage. |
Review details
Suppressed comments (1)
clients/da-vinci-client/src/test/java/com/linkedin/davinci/consumer/VeniceChangelogConsumerImplTest.java:726
atLeastOnce()only waits until Mockito records the call; on the unfixed implementation that invocation is recorded before the thrownNumberFormatExceptionfinishes unwinding, so the followingisAlive()check can observe the thread during that brief window and pass even though it is about to terminate. Use a short reporter interval and require a second assignment call (or otherwise wait for the cycle to complete) so this regression test proves the loop continued.
() -> Mockito.verify(mockPubSubConsumer, atLeastOnce()).getAssignment());
- Files reviewed: 3/3 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…ormer thread The heartbeat reporter test asserted on a single recordStats invocation, which Mockito records before the exception finishes unwinding, so it could observe a thread that was already terminating. Shorten the reporter interval to one second and require two cycles, which only happens if the loop actually resumed. Add the equivalent regression test for BackgroundReporterThread in VeniceChangelogConsumerDaVinciRecordTransformerImplTest, since that thread has the same failure handling but had no coverage. Both tests fail without the corresponding main-source guard and pass with it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Both review points addressed in 9118faa. 1. Agreed — the invocation is recorded before the 2. No coverage for Added the equivalent test in Verification — ran both suites in each direction rather than just asserting the new tests pass:
Full suites green with the guard restored: 22 tests / 0 failures and 28 tests / 0 failures. |
Problem Statement
The changelog consumer's background reporter thread emits two metrics on a fixed interval:
HeartBeatDelayandCurrentConsumingVersion. It is started at most once per consumer, andstartHeartbeatReporterIfNeeded()guards the start with aThread.State.NEWcheck, so once thethread terminates it is never restarted.
The run loop only caught
InterruptedException:Any
RuntimeExceptionescapingrecordStatstherefore propagates out ofrun()and permanentlykills lag reporting for the rest of the consumer's lifetime.
recordStatsis not exception-free —it calls
Version.parseVersionFromKafkaTopicName, which does an unguardedInteger.parseInton thetopic name and throws
NumberFormatExceptionfor any assignment entry that is not a versioned topic.Two things make this failure mode hard to notice:
PollCount,RecordsConsumedCount,VersionSwapCount) are emitted from adifferent code path and keep reporting normally, so the consumer still looks healthy. Only the two
reporter-thread metrics go silent.
does not go through the application logger and can be absent from log aggregation entirely.
Solution
Wrap the
recordStatscall so that a failed reporting cycle is logged and the loop continues on thenext interval, instead of terminating the thread. The
InterruptedExceptionhandling is unchanged,so shutdown behaviour on
close()is preserved.The same loop pattern exists in both
VeniceChangelogConsumerImpl.HeartbeatReporterThreadandVeniceChangelogConsumerDaVinciRecordTransformerImpl.BackgroundReporterThread, so both are updated.Exceptionis caught rather thanThrowable, soErrorconditions such asOutOfMemoryErrorstillpropagate rather than being swallowed and retried every interval.
Code changes
LOGGER.errorcan fire at most once per reporter interval(
backgroundReporterThreadSleepIntervalInSeconds, default 60s) per consumer, so it isinherently rate limited and does not need additional throttling.
Concurrency-Specific Checks
Both reviewer and PR author to verify
synchronized,RWLock) are used where needed.ConcurrentHashMap,CopyOnWriteArrayList).How was this PR tested?
Added
testMetricReportingThreadSurvivesRecordStatsFailure, which drives the failure through theproduction path by making
getTopicAssignment()throw, starts the real reporter thread, and assertsthe thread is still alive after a reporting cycle.
The test was verified to be a genuine regression test: it fails on the unmodified source (the
thread terminates) and passes with the fix applied.
VeniceChangelogConsumerImplTestandVeniceChangelogConsumerDaVinciRecordTransformerImplTestboth pass, and rootspotlessCheckis clean.Does this PR introduce any user-facing or breaking changes?
No. This only changes failure handling inside an internal reporter thread. In the failure case the
thread now survives and keeps emitting metrics on subsequent intervals instead of stopping silently;
the success path is unchanged.
🤖 Generated with GitHub Copilot CLI