minor: Add logs and metrics observability to the segment upgrade path that fires while using concurrent locking#19651
Conversation
…ncurrent append and replace ingestion mets checkpoint doc and test tidy up self review
FrankChen021
left a comment
There was a problem hiding this comment.
I have reviewed the code for correctness, edge cases, concurrency/lifecycle, security, data loss, API compatibility, and missing-test risks; no high-confidence issues found.
Reviewed 11 of 11 changed files.
This is an automated review by Codex GPT-5.5
kfaraz
left a comment
There was a problem hiding this comment.
Thanks for adding the metrics/logs, @capistrant ! These would be instrumental in debugging failures in concurrent compaction.
Overall looks good, but I have left some nitpicks.
| public class SegmentUpgradeMetrics | ||
| { | ||
| /** Number of upgraded pending segments a REPLACE commit created and handed to the supervisor. Task-action dims. */ | ||
| public static final String COUNT = "ingest/segmentUpgrade/count"; |
There was a problem hiding this comment.
Please include the prefix realtime in all the metrics to distinguish upgrade of pending segments from upgrade of appended segments.
| public static final String COUNT = "ingest/segmentUpgrade/count"; | |
| public static final String REALTIME_PERSISTED = "ingest/realtime/segmentUpgrade/persisted"; |
| // Values for the DruidMetrics.REASON dimension on SKIPPED. | ||
|
|
||
| /** The task holds no pending segment matching upgradedFromSegmentId (request targeted the wrong task). */ | ||
| public static final String REASON_UNKNOWN_BASE = "unknownBase"; |
There was a problem hiding this comment.
Nit: The reasons need not be camel cased, e.g. unknown base sink instead of unknownBase.
| /** The base sink is gone even though this task once held it. */ | ||
| public static final String REASON_NO_SINK = "noSink"; | ||
| /** The base sink is being dropped (handoff in progress); the durable path re-announces at the new version. */ | ||
| public static final String REASON_DROPPING = "dropping"; |
There was a problem hiding this comment.
| public static final String REASON_DROPPING = "dropping"; | |
| public static final String REASON_DROPPING = "dropping base sink"; |
| /** The task holds no pending segment matching upgradedFromSegmentId (request targeted the wrong task). */ | ||
| public static final String REASON_UNKNOWN_BASE = "unknownBase"; | ||
| /** The base sink is gone even though this task once held it. */ | ||
| public static final String REASON_NO_SINK = "noSink"; |
There was a problem hiding this comment.
| public static final String REASON_NO_SINK = "noSink"; | |
| public static final String REASON_NO_SINK = "base sink already dropped"; |
| public static final String COUNT = "ingest/segmentUpgrade/count"; | ||
|
|
||
| /** A record was delivered to at least one running task. Supervisor dims. */ | ||
| public static final String NOTIFIED = "ingest/segmentUpgrade/notified"; |
There was a problem hiding this comment.
| public static final String NOTIFIED = "ingest/segmentUpgrade/notified"; | |
| public static final String REALTIME_TASK_NOTIFIED = "ingest/realtime/segmentUpgrade/notified"; |
| return; | ||
| } | ||
|
|
||
| log.info("Registering [%d] upgraded pending segments created by task[%s] on supervisor[%s]", |
There was a problem hiding this comment.
Maybe move this log to the end of this method and include a summary map from pending segment ID to number of tasks notified.
|
|
||
| supervisor.addTaskGroupToActivelyReadingTaskGroup( | ||
| supervisor.getTaskGroupIdForPartition("0"), | ||
| ImmutableMap.of("0", "5"), |
There was a problem hiding this comment.
Nit: Use Map.of and Set.of for brevity.
| * Outcome of a {@link #registerUpgradedPendingSegment} call, returned so the caller (which owns the emitter) can | ||
| * emit the corresponding metric. |
There was a problem hiding this comment.
| * Outcome of a {@link #registerUpgradedPendingSegment} call, returned so the caller (which owns the emitter) can | |
| * emit the corresponding metric. | |
| * Result of a {@link #registerUpgradedPendingSegment} call. |
| if (basePendingSegment == null || droppingSinks.contains(basePendingSegment) || !sinks.containsKey(basePendingSegment)) { | ||
| if (basePendingSegment == null) { | ||
| // This task never allocated a segment matching upgradedFromSegmentId, i.e. the request targeted the wrong task. | ||
| log.info( |
| log.info( | ||
| "Not announcing upgraded pending segment[%s] because this task has no base sink matching" | ||
| + " upgradedFromSegmentId[%s]; the upgrade request likely targeted the wrong task[%s].", | ||
| upgradedPendingSegment, pendingSegmentRecord.getUpgradedFromSegmentId(), myId | ||
| ); | ||
| return UpgradeAnnouncementOutcome.SKIPPED_UNKNOWN_BASE; | ||
| } else if (droppingSinks.contains(basePendingSegment)) { | ||
| // Expected during handoff: the base sink is being dropped | ||
| log.debug( | ||
| "Not announcing upgraded pending segment[%s] for base segment[%s] on task[%s] because the base sink is being dropped.", | ||
| upgradedPendingSegment, basePendingSegment, myId | ||
| ); | ||
| return UpgradeAnnouncementOutcome.SKIPPED_DROPPING; | ||
| } else { | ||
| // Unexpected: the base sink is gone even though this task once held it. | ||
| log.info( | ||
| "Not announcing upgraded pending segment[%s] for base segment[%s] on task[%s] because the base sink is no longer present.", | ||
| upgradedPendingSegment, basePendingSegment, myId |
There was a problem hiding this comment.
I feel like these logs can be merged into and can be logged by the caller itself using the result of this method.
Description
Investigating potential issues with concurrent append and replace actions not being properly accepted and reflected on realtime ingestion tasks who should be upgrading their segments to reflect the concurrent append that happened on an interval they are ingesting to. This PR attempts to add some logs and metrics to the related code to help give observability into the process and smoke out if there are indeed issues going on. Biggest risk of these new mets and logs is probably chattiness, since they scale with the volume of upgrade segments that may be existing under a clusters normal operaions.
New Metrics
ingest/segmentUpgrade/countdataSource,taskId,taskType,groupId,tagsingest/segmentUpgrade/notifiedingest/segmentUpgrade/count:countshould equalnotified+unmatchedover the same period anddataSource.supervisorId,dataSource,stream,tagscountin a healthy cluster.ingest/segmentUpgrade/unmatchedsupervisorId,dataSource,stream,tagsingest/segmentUpgrade/sendFailedsupervisorId,dataSource,stream,taskId,tagsingest/segmentUpgrade/announcedingest/segmentUpgrade/count, which is per-segment rather than per-replica.dataSource,taskId,taskType,groupId,tagsingest/segmentUpgrade/skippedreasondimension is one ofunknownBase(the request reached the wrong task),noSink(the base sink is no longer present), ordropping(the base sink is handing off, which is benign and covered by the durable publish path).dataSource,taskId,taskType,groupId,tags,reasonreason=dropping.Release note
TBD
Key changed/added classes in this PR
TBD
This PR has: