Skip to content

fix: Stop kill task if segment IDs that share load spec of killable segment could not be determined#19737

Open
kfaraz wants to merge 9 commits into
apache:masterfrom
kfaraz:improve_embedded_kill
Open

fix: Stop kill task if segment IDs that share load spec of killable segment could not be determined#19737
kfaraz wants to merge 9 commits into
apache:masterfrom
kfaraz:improve_embedded_kill

Conversation

@kfaraz

@kfaraz kfaraz commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Bug

When concurrent append and replace is enabled and kill tasks (or embedded kill tasks on Overlord) are used, there may be potential data loss if the task action retrieveUpgradedFromSegmentIds or retrieveUpgradedToSegmentIds fired by the kill task 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

  • Stop kill task (and embedded kill tasks) if parent IDs of the killable unused segments could not be determined. There are 2 cases possible:
    • Segment does not share load spec: Do not remove segment from metadata store, otherwise deep store files would become orphaned and will never be removed.
    • Segment shares load spec with other segments: Do not remove segment files from deep store as the same files are needed by other segments.
  • Stop kill task (and embedded kill tasks) if sibling segment IDs or children segment IDs could not be determined. Same cases as above.
  • Improve performance of embedded kill tasks by avoiding extra DB call to fetch parent IDs
    • pre-fetch the parent IDs in the previous call which identifies the killable unused segments in the first place.

This PR has:

  • been self-reviewed.
  • added documentation for new or modified features or behaviors.
  • a release note entry in the PR description.
  • added Javadocs for most classes and all non-trivial methods. Linked related entities via Javadoc links.
  • added or updated version, license, or notice information in licenses.yaml
  • added comments explaining the "why" and the intent of the code wherever would not be obvious for an unfamiliar reader.
  • added unit tests or modified existing tests to cover new code paths, ensuring the threshold for code coverage is met.
  • added integration tests.
  • been tested in a test Druid cluster.

@kfaraz kfaraz added the Bug label Jul 23, 2026
@kfaraz kfaraz added this to the 38.0.0 milestone Jul 23, 2026
@kfaraz kfaraz changed the title fix: Stop kill task if parent IDs of segment could not be determined fix: Stop kill task if segment IDs that share load spec of killable segment could not be determined Jul 23, 2026

@abhishekrb19 abhishekrb19 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.

Thanks for the fix @kfaraz! A few comments on test & logs.

Comment on lines +385 to +388
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."

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.

What do you think about adding a unit test in KillUnusedSegmentsTaskTest or UnusedSegmentsKillerTest to verify that segments aren't spuriously purged in this case?

@kfaraz kfaraz Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestion! Added some tests.

@FrankChen021 FrankChen021 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[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).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[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.

@kfaraz

kfaraz commented Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

@abhishekrb19 , thanks a lot for the prompt review! I have updated the tests and added a couple of new ones.

@abhishekrb19 abhishekrb19 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.

Some minor comments, but lgtm otherwise - thanks!

Comment on lines +251 to 255
final Set<DataSegment> unusedSegments = unusedSegmentsPlus.stream()
.map(DataSegmentPlus::getDataSegment)
.collect(Collectors.toSet());

if (!TaskLocks.isLockCoversSegments(taskLockMap, unusedSegments)) {

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.

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.

@kfaraz kfaraz Jul 25, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The Set<DataSegment> unusedSegments created before this line is also used later in this method.

Comment on lines +369 to +371
final Set<String> segmentIds = unusedSegments.stream().map(
s -> s.getDataSegment().getId().toString()
).collect(Collectors.toSet());

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.

nit: We could compute this once in the caller and pass it to both this function and getKillableSegments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants