8000 fix(inkless:delete): do not sleep inside the topic purger tick [KC-349] by viktorsomogyi · Pull Request #792 · aiven/inkless · GitHub
[go: up one dir, main page]

Skip to content

fix(inkless:delete): do not sleep inside the topic purger tick [KC-349] - #792

Merged
giuseppelillo merged 1 commit into
mainfrom
svv/topic-purger-flaky
Sep 9, 2026
Merged

giuseppelillo merged 1 commit into
mainfrom
svv/topic-purger-flaky

Conversation

@viktorsomogyi
Copy link
Copy Markdown
Contributor

KafkaScheduler uses scheduleAtFixedRate. An idle or error sleep inside run() queues missed ticks and the next work cycle bursts past max.batches.per.cycle / interval. Empty cycles return immediately. Errors set nextEligibleMs and later ticks skip until that time.

InklessDisklessTopicDeleteTest asserts the per-cycle cap from TopicPurger logs instead of a COUNT(*) poll.

@viktorsomogyi
viktorsomogyi marked this pull request as ready for review September 9, 2026 07:58
KafkaScheduler uses scheduleAtFixedRate. An idle or error sleep inside
run() queues missed ticks and the next work cycle bursts past
max.batches.per.cycle / interval. Empty cycles return immediately.
Errors set nextEligibleMs and later ticks skip until that time.

InklessDisklessTopicDeleteTest asserts the per-cycle cap from TopicPurger
logs instead of a COUNT(*) poll.

Co-authored-by: Cursor <cursoragent@cursor.com>
@viktorsomogyi
viktorsomogyi requested a review from jeqo September 9, 2026 08:52
@giuseppelillo giuseppelillo 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.

LGTM. Merging this one, but we have the same issue on FileCleaner, could you also fix that one?

@giuseppelillo
giuseppelillo merged commit 5693b9d into main Sep 9, 2026
5 checks passed
@giuseppelillo
giuseppelillo deleted the svv/topic-purger-flaky branch September 9, 2026 09:49
8000
@jeqo jeqo 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.

The fix looks good. Added a few comments.

Also, a related finding is that FileCleaner may have a similar issue as it's sleeping and it's scheduled thread. We could handle it here or in a separate PR, as you prefer.

LOGGER.error("Error while purging deleted logs, waiting for {}", Duration.ofMillis(backoff), e);
time.sleep(backoff);
nextEligibleMs = time.milliseconds() + backoff;
LOGGER.error("Error while purging deleted logs, retrying after {}", Duration.ofMillis(backoff), e);
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:

Suggested change
LOGGER.error("Error while purging deleted logs, retrying after {}", Duration.ofMillis(backoff), e);
LOGGER.error("Error while purging deleted logs, skipping ticks for the next {}", Duration.ofMillis(backoff), e);

}

attempts.set(0);
nextEligibleMs = 0L;
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.

At this point nextEligibleMs is already in the past, so should be fine to delete this one

Comment on lines +45 to +47
// KafkaScheduler uses scheduleAtFixedRate. Sleeping inside run() queues missed ticks, and
// the next work cycle bursts past topic.purger.max.batches.per.cycle / interval. Skip the
// tick until this time instead.
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: a shorter version

Suggested change
// KafkaScheduler uses scheduleAtFixedRate. Sleeping inside run() queues missed ticks, and
// the next work cycle bursts past topic.purger.max.batches.per.cycle / interval. Skip the
// tick until this time instead.
// Do not sleep in run(): KafkaScheduler uses scheduleAtFixedRate, and a sleep queues missed
// ticks that then fire back to back past topic.purger.max.batches.per.cycle.

assertEquals(0, beforeDelete.deletedLogCount(), "Live logs must not be stamped deleted");

admin.deleteTopics(Collections.singletonList(TOPIC_NAME)).all().get(30, TimeUnit.SECONDS);
try (LogCaptureAppender appender = LogCaptureAppender.createAndRegister(TopicPurger.class)) {
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: pin the log level explicitly

Suggested change
try (LogCaptureAppender appender = LogCaptureAppender.createAndRegister(TopicPurger.class)) {
try (LogCaptureAppender appender = LogCaptureAppender.createAndRegister(TopicPurger.class)) {
appender.setClassLogger(TopicPurger.class, Level.INFO)

Comment on lines +293 to +306
/**
* One cycle cannot finish the drain: each broker deletes at most
* {@code topic.purger.max.batches.per.cycle} batches. Count saturated cycles from TopicPurger
* logs instead of a {@code COUNT(*)} poll, which can miss leftovers between 1s ticks.
*/
private static void assertSaturatedAcrossMultipleCycles(LogCaptureAppender appender, long batchesAtDelete) {
long saturated = appender.getMessages().stream()
.filter(message -> message.contains("per-cycle cap reached"))
.count();
assertTrue(saturated >= batchesAtDelete - NUM_BROKERS,
"TopicPurger must hit the per-cycle cap across multiple cycles (max "
+ MAX_BATCHES_PER_CYCLE + " per cycle); saturated=" + saturated
+ " batchesAtDelete=" + batchesAtDelete
+ " messages=" + appender.getMessages());
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.

Maybe worth adding a doc for batchesAtDelete - NUM_BROKERS as it's not obvious where this is coming from.

We could also consider to move the saturated calculation to a wait if condition, to expect all brokers to do the logging:

final long expected = batchesAtDelete - 1; // every deleting cycle but the last hits the cap
TestUtils.waitForCondition(() -> countSaturated(appender) >= expected, 10_000,
    () -> "TopicPurger must hit the per-cycle cap on every cycle but the last; saturated="
        + countSaturated(appender) + " batchesAtDelete=" + batchesAtDelete
        + " messages=" + appender.getMessages());

private static long countSaturated(LogCaptureAppender appender) {
    return appender.getMessages().stream()
        .filter(message -> message.contains(TopicPurger.CAP_REACHED_LOG_FRAGMENT))
        .count();
}

jeqo pushed a commit that referenced this pull request Sep 9, 2026
…9] (#792)

KafkaScheduler uses scheduleAtFixedRate. An idle or error sleep inside
run() queues missed ticks and the next work cycle bursts past
max.batches.per.cycle / interval. Empty cycles return immediately.
Errors set nextEligibleMs and later ticks skip until that time.

InklessDisklessTopicDeleteTest asserts the per-cycle cap from TopicPurger
logs instead of a COUNT(*) poll.

Co-authored-by: Cursor <cursoragent@cursor.com>
jeqo pushed a commit that referenced this pull request Sep 9, 2026
…9] (#792)

KafkaScheduler uses scheduleAtFixedRate. An idle or error sleep inside
run() queues missed ticks and the next work cycle bursts past
max.batches.per.cycle / interval. Empty cycles return immediately.
Errors set nextEligibleMs and later ticks skip until that time.

InklessDisklessTopicDeleteTest asserts the per-cycle cap from TopicPurger
logs instead of a COUNT(*) poll.

Co-authored-by: Cursor <cursoragent@cursor.com>
jeqo pushed a commit that referenced this pull request Sep 9, 2026
…9] (#792)

KafkaScheduler uses scheduleAtFixedRate. An idle or error sleep inside
run() queues missed ticks and the next work cycle bursts past
max.batches.per.cycle / interval. Empty cycles return immediately.
Errors set nextEligibleMs and later ticks skip until that time.

InklessDisklessTopicDeleteTest asserts the per-cycle cap from TopicPurger
logs instead of a COUNT(*) poll.

Co-authored-by: Cursor <cursoragent@cursor.com>
jeqo added a commit that referenced this pull request Sep 10, 2026
* docs(inkless:sync): archive the 0.48 cherry-pick sessions for 4.1, 4.2, 4.3

Session logs for bringing the three active release branches up to main
(5fa6669, #784) ahead of the 0.48 release. inkless-4.3 took its 8
commits clean. inkless-4.2 and inkless-4.1 took 19 each; the logs record
the conflict resolutions and the sync(compile)/sync(test) adaptations
needed for the 4.2.1 and 4.1.2 APIs (Record package, LogReadResult
constructor, Seq-based DelayedFetch statuses).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

docs(inkless:sync): complete the 0.48 cherry-pick session logs

The three branches took six more main commits (#794, #787, #792, #793,
tables and the verification that covered them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(inkless:release): list inkless-4.3 as an active release branch

inkless-4.3 is on Kafka 4.3.1 (#779) and gets its first Kafka-base tag
with 0.48, so it moves from Planned to Active in RELEASES.md. The release
workflow header names it among the branches the cherry-pick sync has to
cover before a run.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(inkless:release): add 0.48 changelog entry

Generated from inkless-release-0.47..inkless-release-0.48 and curated:
every metric delta traced to its commit, the four config default changes
the configs.rst diff shows added next to the four new keys, and the V28
lock on logs described for operators.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
giuseppelillo pushed a commit that referenced this pull request Sep 18, 2026
* docs(inkless:sync): archive the 0.48 cherry-pick sessions for 4.1, 4.2, 4.3

Session logs for bringing the three active release branches up to main
(5fa6669, #784) ahead of the 0.48 release. inkless-4.3 took its 8
commits clean. inkless-4.2 and inkless-4.1 took 19 each; the logs record
the conflict resolutions and the sync(compile)/sync(test) adaptations
needed for the 4.2.1 and 4.1.2 APIs (Record package, LogReadResult
constructor, Seq-based DelayedFetch statuses).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

docs(inkless:sync): complete the 0.48 cherry-pick session logs

The three branches took six more main commits (#794, #787, #792, #793,
tables and the verification that covered them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(inkless:release): list inkless-4.3 as an active release branch

inkless-4.3 is on Kafka 4.3.1 (#779) and gets its first Kafka-base tag
with 0.48, so it moves from Planned to Active in RELEASES.md. The release
workflow header names it among the branches the cherry-pick sync has to
cover before a run.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(inkless:release): add 0.48 changelog entry

Generated from inkless-release-0.47..inkless-release-0.48 and curated:
every metric delta traced to its commit, the four config default changes
the configs.rst diff shows added next to the four new keys, and the V28
lock on logs described for operators.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
giuseppelillo pushed a commit that referenced this pull request Sep 18, 2026
* docs(inkless:sync): archive the 0.48 cherry-pick sessions for 4.1, 4.2, 4.3

Session logs for bringing the three active release branches up to main
(5fa6669, #784) ahead of the 0.48 release. inkless-4.3 took its 8
commits clean. inkless-4.2 and inkless-4.1 took 19 each; the logs record
the conflict resolutions and the sync(compile)/sync(test) adaptations
needed for the 4.2.1 and 4.1.2 APIs (Record package, LogReadResult
constructor, Seq-based DelayedFetch statuses).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

docs(inkless:sync): complete the 0.48 cherry-pick session logs

The three branches took six more main commits (#794, #787, #792, #793,
tables and the verification that covered them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(inkless:release): list inkless-4.3 as an active release branch

inkless-4.3 is on Kafka 4.3.1 (#779) and gets its first Kafka-base tag
with 0.48, so it moves from Planned to Active in RELEASES.md. The release
workflow header names it among the branches the cherry-pick sync has to
cover before a run.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(inkless:release): add 0.48 changelog entry

Generated from inkless-release-0.47..inkless-release-0.48 and curated:
every metric delta traced to its commit, the four config default changes
the configs.rst diff shows added next to the four new keys, and the V28
lock on logs described for operators.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
giuseppelillo pushed a commit that referenced this pull request Sep 24, 2026
* docs(inkless:sync): archive the 0.48 cherry-pick sessions for 4.1, 4.2, 4.3

Session logs for bringing the three active release branches up to main
(5fa6669, #784) ahead of the 0.48 release. inkless-4.3 took its 8
commits clean. inkless-4.2 and inkless-4.1 took 19 each; the logs record
the conflict resolutions and the sync(compile)/sync(test) adaptations
needed for the 4.2.1 and 4.1.2 APIs (Record package, LogReadResult
constructor, Seq-based DelayedFetch statuses).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

docs(inkless:sync): complete the 0.48 cherry-pick session logs

The three branches took six more main commits (#794, #787, #792, #793,
tables and the verification that covered them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(inkless:release): list inkless-4.3 as an active release branch

inkless-4.3 is on Kafka 4.3.1 (#779) and gets its first Kafka-base tag
with 0.48, so it moves from Planned to Active in RELEASES.md. The release
workflow header names it among the branches the cherry-pick sync has to
cover before a run.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* docs(inkless:release): add 0.48 changelog entry

Generated from inkless-release-0.47..inkless-release-0.48 and curated:
every metric delta traced to its commit, the four config default changes
the configs.rst diff shows added next to the four new keys, and the V28
lock on logs described for operators.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
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.

3 participants

0