fix: Stop kill task if segment IDs that share load spec of killable segment could not be determined#19737
fix: Stop kill task if segment IDs that share load spec of killable segment could not be determined#19737kfaraz wants to merge 9 commits into
Conversation
abhishekrb19
left a comment
There was a problem hiding this comment.
Thanks for the fix @kfaraz! A few comments on test & logs.
| e, | ||
| "Could not retrieve parent segment ids using task action[retrieveUpgradedFromSegmentIds]." | ||
| + " Stopping kill task to avoid data loss in case the segment files" | ||
| + " are shared by other segments." |
There was a problem hiding this comment.
What do you think about adding a unit test in KillUnusedSegmentsTaskTest or UnusedSegmentsKillerTest to verify that segments aren't spuriously purged in this case?
There was a problem hiding this comment.
Thanks for the suggestion! Added some tests.
FrankChen021
left a comment
There was a problem hiding this comment.
| Severity | Findings |
|---|---|
| P0 | 0 |
| P1 | 2 |
| P2 | 0 |
| P3 | 0 |
| Total | 2 |
Reviewed 12 of 12 changed files.
This is an automated review by Codex GPT-5.6-Sol
|
|
||
| toolbox.getDataSegmentKiller().kill(segmentsToBeKilled); | ||
| emitMetric(toolbox.getEmitter(), TaskMetrics.SEGMENTS_DELETED_FROM_DEEPSTORE, segmentsToBeKilled.size()); | ||
| // 3. Nuke all eligible unused segments |
There was a problem hiding this comment.
[P1] Close the sharing race before deleting deep storage
The new relationship lookup is only a preflight before SegmentNukeAction. With concurrent locks enabled this task holds a REPLACE lock, which can coexist with APPEND publication; an append publish or retry can therefore insert a parent or upgraded sibling sharing the load spec after step 2. Step 3 then removes the old metadata and step 4 deletes a file still referenced by the newly published segment. Keep the preflight, but perform a fail-closed sharing recheck after the nuke and before deep-storage deletion (ideally in a transaction/critical section that excludes a new publication window).
There was a problem hiding this comment.
It is very unlikely for this to happen. It had been called out here as well.
#16362 (comment)
Essentially, the kill buffer period protects us against this.
Concurrent APPEND would only perform upgrades on a "used" segment.
And kill task would only nuke segments that have been been "unused" for a while (default is 30 days).
There was a problem hiding this comment.
Adding a comment in javadoc.
| response.getUpgradedToSegmentIds().forEach((parent, children) -> { | ||
| if (!CollectionUtils.isNullOrEmpty(children)) { | ||
| // Do not kill segment if its parent or any of its siblings still exist in metadata store | ||
| if (!segmentIdsBeingKilled.containsAll(children)) { |
There was a problem hiding this comment.
[P1] Fail closed when the relationship response is null
If the action response itself or its upgradedToSegmentIds map is null, this guard skips the only new safety check and treats every candidate as unshared. Both the response DTO and remote conversion permit null, so a malformed or missing result can still proceed through metadata and deep-storage deletion. Throw before SegmentNukeAction unless a non-null map was returned.
|
@abhishekrb19 , thanks a lot for the prompt review! I have updated the tests and added a couple of new ones. |
abhishekrb19
left a comment
There was a problem hiding this comment.
Some minor comments, but lgtm otherwise - thanks!
| final Set<DataSegment> unusedSegments = unusedSegmentsPlus.stream() | ||
| .map(DataSegmentPlus::getDataSegment) | ||
| .collect(Collectors.toSet()); | ||
|
|
||
| if (!TaskLocks.isLockCoversSegments(taskLockMap, unusedSegments)) { |
There was a problem hiding this comment.
nit: perhaps we could introduce a thin wrapper for isLockCoversSegments() that also accepts DataSegmentPlus that delegates to isLockCoversSegments(NavigableMap<DateTime, List>, Collection segments) to avoid another iteration over the set of segments.
There was a problem hiding this comment.
The Set<DataSegment> unusedSegments created before this line is also used later in this method.
| final Set<String> segmentIds = unusedSegments.stream().map( | ||
| s -> s.getDataSegment().getId().toString() | ||
| ).collect(Collectors.toSet()); |
There was a problem hiding this comment.
nit: We could compute this once in the caller and pass it to both this function and getKillableSegments.
Bug
When concurrent append and replace is enabled and
killtasks (or embedded kill tasks on Overlord) are used, there may be potential data loss if the task actionretrieveUpgradedFromSegmentIdsorretrieveUpgradedToSegmentIdsfired by thekilltask fails. This is because the failure of these task actions is currently ignored and we may end up removing segment files from deep store even if the same load specs are shared by other segments.Changes
killtask (and embedded kill tasks) if parent IDs of the killable unused segments could not be determined. There are 2 cases possible:killtask (and embedded kill tasks) if sibling segment IDs or children segment IDs could not be determined. Same cases as above.This PR has: