Skip to content

Commit afe5128

Browse files
authored
Enforce punctuation latency ceiling (#1841)
1 parent d8d7f87 commit afe5128

File tree

1 file changed

+30
-0
lines changed

1 file changed

+30
-0
lines changed

commons-kstreams/src/main/java/org/dependencytrack/kstreams/processor/TombstoneEmittingProcessor.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.time.Duration;
3636
import java.time.Instant;
3737
import java.util.Optional;
38+
import java.util.concurrent.TimeUnit;
3839
import java.util.function.Function;
3940

4041
/**
@@ -51,6 +52,8 @@
5152
public class TombstoneEmittingProcessor<K, V> extends ContextualFixedKeyProcessor<K, V, V> {
5253

5354
private static final Logger LOGGER = LoggerFactory.getLogger(TombstoneEmittingProcessor.class);
55+
private static final int PUNCTUATE_LATENCY_MEASUREMENT_RECORD_THRESHOLD = 1000;
56+
private static final long MAX_PUNCTUATE_LATENCY_MS = TimeUnit.SECONDS.toMillis(5);
5457

5558
private final String storeName;
5659
private final Duration checkInterval;
@@ -94,8 +97,35 @@ public void close() {
9497
private void punctuate(final long timestamp) {
9598
final Instant cutoffTimestamp = Instant.ofEpochMilli(timestamp).minus(maxLifetime);
9699

100+
long recordsProcessed = 0;
101+
final long punctuateStartTimeMillis = System.currentTimeMillis();
102+
long punctuateLatencyMillis;
103+
97104
try (final KeyValueIterator<K, Long> all = store.all()) {
98105
while (all.hasNext()) {
106+
// Only measure punctuation latency every N records to reduce the
107+
// CPU overhead of time comparisons.
108+
recordsProcessed++;
109+
if (recordsProcessed % PUNCTUATE_LATENCY_MEASUREMENT_RECORD_THRESHOLD == 0) {
110+
// For large state stores, it could happen that iterating over, and deserializing,
111+
// all entries takes too long. Punctuation blocks record processing and thus also
112+
// consumer polling. Enforce a ceiling as to how long punctuation can reasonably
113+
// take to prevent the consumer from being considered dead by the broker.
114+
//
115+
// Punctuation may be invoked for every single record so eventually the
116+
// work will get done, although spread over a longer time frame.
117+
punctuateLatencyMillis = System.currentTimeMillis() - punctuateStartTimeMillis;
118+
if (punctuateLatencyMillis >= MAX_PUNCTUATE_LATENCY_MS) {
119+
LOGGER.warn("""
120+
Punctuator took {}ms to iterate over state store records. \
121+
The maximum punctuation latency is {}ms. Aborting iteration \
122+
to not block stream task thread for too long.""",
123+
punctuateLatencyMillis,
124+
MAX_PUNCTUATE_LATENCY_MS);
125+
break;
126+
}
127+
}
128+
99129
final KeyValue<K, Long> record = all.next();
100130
if (record.value != null && Instant.ofEpochMilli(record.value).isBefore(cutoffTimestamp)) {
101131
LOGGER.debug("Sending tombstone for key {}", record.key);

0 commit comments

Comments
 (0)