Skip to content

Commit 76fe392

Browse files
committed
address review comments
1 parent 8fa5317 commit 76fe392

8 files changed

Lines changed: 274 additions & 168 deletions

File tree

clients/da-vinci-client/src/main/java/com/linkedin/davinci/kafka/consumer/ActiveActiveStoreIngestionTask.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -937,7 +937,7 @@ private PubSubMessageHeaders computeActiveKeyCountSignal(
937937
if (partitionConsumptionState.decrementActiveKeyCount()) {
938938
return new PubSubMessageHeaders().add(KEY_DELETED_SIGNAL);
939939
}
940-
invalidateActiveKeyCount(partitionConsumptionState, "Decrement underflow on leader during DCR");
940+
invalidateActiveKeyCount(partitionConsumptionState, ActiveKeyCountInvalidationReason.LEADER_DCR_UNDERFLOW);
941941
return new PubSubMessageHeaders().add(KEY_COUNT_INVALIDATE_SIGNAL);
942942
}
943943
return EmptyPubSubMessageHeaders.SINGLETON;
@@ -1050,9 +1050,7 @@ private boolean isValuePresentForKey(
10501050
try {
10511051
return storageEngine.keyExists(partitionConsumptionState.getPartition(), storageKey);
10521052
} catch (VeniceException e) {
1053-
// Transient RocksDB I/O failure: invalidate the count and assume key absent so ingestion
1054-
// doesn't halt and we stop publishing a wrong number.
1055-
invalidateActiveKeyCount(partitionConsumptionState, "keyExists failed", e);
1053+
invalidateActiveKeyCount(partitionConsumptionState, ActiveKeyCountInvalidationReason.KEY_EXISTS_FAILURE, e);
10561054
return false;
10571055
}
10581056
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.linkedin.davinci.kafka.consumer;
2+
3+
/**
4+
* Reasons for invalidating a partition's active-key-count tracking. Each value carries a message
5+
* template (some with a {@code %d} placeholder for runtime detail like the offending signal value
6+
* or header length) that becomes the prefix of the operator-visible ERROR log emitted by
7+
* {@link StoreIngestionTask#invalidateActiveKeyCount}.
8+
*/
9+
public enum ActiveKeyCountInvalidationReason {
10+
/** Follower received {@code kcs=-1} but its count was already zero (count drifted). */
11+
FOLLOWER_DECREMENT_UNDERFLOW("Decrement underflow on follower from kcs=-1"),
12+
/** Follower received {@code kcs=0} (leader-propagated invalidation signal). */
13+
LEADER_PROPAGATED_INVALIDATION("Leader propagated invalidation signal"),
14+
/** Follower received a single-byte {@code kcs} value outside the {-1, 0, +1} contract. */
15+
CORRUPT_KCS_SIGNAL_VALUE("Unexpected kcs signal value %d"),
16+
/** Follower received a multi-byte {@code kcs} header (corrupt or future producer). */
17+
CORRUPT_MULTI_BYTE_KCS_SIGNAL("Unexpected multi-byte kcs signal (length=%d)"),
18+
/** Leader detected an underflow during DCR (count was zero but a delete was processed). */
19+
LEADER_DCR_UNDERFLOW("Decrement underflow on leader during DCR"),
20+
/**
21+
* Leader's {@link com.linkedin.davinci.store.StorageEngine#keyExists} call (a RocksDB
22+
* value-column-family lookup used to determine whether a key currently has a live value) threw.
23+
* The transient I/O failure must not stop ingestion or leave the active count in a wrong state —
24+
* invalidate so we stop publishing a stale value.
25+
*/
26+
KEY_EXISTS_FAILURE("RocksDB value column family lookup failed");
27+
28+
private final String messageTemplate;
29+
30+
ActiveKeyCountInvalidationReason(String messageTemplate) {
31+
this.messageTemplate = messageTemplate;
32+
}
33+
34+
String getMessage() {
35+
return messageTemplate;
36+
}
37+
38+
String getMessage(int detail) {
39+
return String.format(messageTemplate, detail);
40+
}
41+
}

clients/da-vinci-client/src/main/java/com/linkedin/davinci/kafka/consumer/StoreIngestionTask.java

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4740,48 +4740,76 @@ private void trackActiveKeyCount(
47404740
partitionConsumptionState.incrementActiveKeyCount();
47414741
} else if (signal == ActiveActiveStoreIngestionTask.KEY_DELETED_SIGNAL_VALUE) {
47424742
if (!partitionConsumptionState.decrementActiveKeyCount()) {
4743-
invalidateActiveKeyCount(partitionConsumptionState, "Decrement underflow on follower from kcs=-1");
4743+
invalidateActiveKeyCount(
4744+
partitionConsumptionState,
4745+
ActiveKeyCountInvalidationReason.FOLLOWER_DECREMENT_UNDERFLOW);
47444746
}
47454747
} else if (signal == ActiveActiveStoreIngestionTask.KEY_COUNT_INVALIDATE_SIGNAL_VALUE) {
4746-
invalidateActiveKeyCount(partitionConsumptionState, "Leader propagated invalidation signal");
4748+
invalidateActiveKeyCount(
4749+
partitionConsumptionState,
4750+
ActiveKeyCountInvalidationReason.LEADER_PROPAGATED_INVALIDATION);
47474751
} else {
4748-
invalidateActiveKeyCount(partitionConsumptionState, "Unexpected kcs signal value " + signal);
4752+
invalidateActiveKeyCount(
4753+
partitionConsumptionState,
4754+
ActiveKeyCountInvalidationReason.CORRUPT_KCS_SIGNAL_VALUE,
4755+
signal);
47494756
}
47504757
} else if (signalHeader != null && signalHeader.value() != null && signalHeader.value().length > 1) {
47514758
invalidateActiveKeyCount(
47524759
partitionConsumptionState,
4753-
"Unexpected multi-byte kcs signal (length=" + signalHeader.value().length + ")");
4760+
ActiveKeyCountInvalidationReason.CORRUPT_MULTI_BYTE_KCS_SIGNAL,
4761+
signalHeader.value().length);
47544762
}
47554763
}
47564764
}
47574765

47584766
/**
47594767
* Sets the active key count to {@link OffsetRecord#ACTIVE_KEY_COUNT_NOT_TRACKED}, records the
4760-
* invalidation metric, and emits a rate-limited ERROR log. The state mutation and metric are
4761-
* wrapped in try/finally so the log fires even if either throws.
4768+
* invalidation metric on both the OTel and Tehuti paths, and emits a rate-limited ERROR log.
4769+
* The state mutation and metric are wrapped in try/finally so the log fires even if either throws.
4770+
*/
4771+
final void invalidateActiveKeyCount(
4772+
PartitionConsumptionState partitionConsumptionState,
4773+
ActiveKeyCountInvalidationReason reason) {
4774+
invalidateActiveKeyCountAndLog(partitionConsumptionState, reason.getMessage(), null);
4775+
}
4776+
4777+
/**
4778+
* @param cause attached to the ERROR log; {@code null} yields a no-stack-trace log
4779+
*/
4780+
final void invalidateActiveKeyCount(
4781+
PartitionConsumptionState partitionConsumptionState,
4782+
ActiveKeyCountInvalidationReason reason,
4783+
Throwable cause) {
4784+
invalidateActiveKeyCountAndLog(partitionConsumptionState, reason.getMessage(), cause);
4785+
}
4786+
4787+
/**
4788+
* @param detail integer rendered into the reason's {@code %d} placeholder
47624789
*/
47634790
final void invalidateActiveKeyCount(
47644791
PartitionConsumptionState partitionConsumptionState,
4765-
String reason,
4792+
ActiveKeyCountInvalidationReason reason,
4793+
int detail) {
4794+
invalidateActiveKeyCountAndLog(partitionConsumptionState, reason.getMessage(detail), null);
4795+
}
4796+
4797+
private void invalidateActiveKeyCountAndLog(
4798+
PartitionConsumptionState partitionConsumptionState,
4799+
String reasonText,
47664800
Throwable cause) {
47674801
try {
47684802
partitionConsumptionState.setActiveKeyCount(ACTIVE_KEY_COUNT_NOT_TRACKED);
47694803
recordActiveKeyCountInvalidation();
47704804
} finally {
47714805
String msg =
4772-
reason + " for replica " + partitionConsumptionState.getReplicaId() + "; invalidating activeKeyCount.";
4806+
reasonText + " for replica " + partitionConsumptionState.getReplicaId() + "; invalidating activeKeyCount.";
47734807
if (!REDUNDANT_LOGGING_FILTER.isRedundantException(msg)) {
4774-
// log4j2's error(String, Throwable) accepts a null throwable, so this single call covers
4775-
// both with-cause and no-cause invocations.
47764808
LOGGER.error(msg, cause);
47774809
}
47784810
}
47794811
}
47804812

4781-
final void invalidateActiveKeyCount(PartitionConsumptionState partitionConsumptionState, String reason) {
4782-
invalidateActiveKeyCount(partitionConsumptionState, reason, null);
4783-
}
4784-
47854813
/** Records active-key-count invalidation on both the OTel (per-version) and Tehuti (host-level) paths. */
47864814
protected final void recordActiveKeyCountInvalidation() {
47874815
versionedIngestionStats.recordActiveKeyCountInvalidation(storeName, versionNumber);

clients/da-vinci-client/src/main/java/com/linkedin/davinci/stats/HostLevelIngestionStats.java

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -314,29 +314,7 @@ public HostLevelIngestionStats(
314314
registerSensor(new AsyncGauge((ignored, ignored2) -> ingestionTaskMap.size(), "ingestion_task_count"));
315315
}
316316

317-
// ACTIVE_KEY_COUNT_NOT_TRACKED = not tracked, 0 = tracked but empty.
318-
// Cannot use measurable() because its 0-fallback conflates "untracked" with "empty".
319-
if (activeKeyCountEnabled) {
320-
if (isTotalStats) {
321-
registerSensor(new AsyncGauge((ignored, ignored2) -> {
322-
long total = 0;
323-
boolean anyTracked = false;
324-
for (StoreIngestionTask task: ingestionTaskMap.values()) {
325-
long storeCount = task.getActiveKeyCount();
326-
if (storeCount != ACTIVE_KEY_COUNT_NOT_TRACKED) {
327-
anyTracked = true;
328-
total += storeCount;
329-
}
330-
}
331-
return anyTracked ? total : ACTIVE_KEY_COUNT_NOT_TRACKED;
332-
}, "active_key_count"));
333-
} else {
334-
registerSensor(new AsyncGauge((ignored, ignored2) -> {
335-
StoreIngestionTask sit = ingestionTaskMap.get(storeName);
336-
return sit == null ? ACTIVE_KEY_COUNT_NOT_TRACKED : sit.getActiveKeyCount();
337-
}, "active_key_count"));
338-
}
339-
}
317+
registerActiveKeyCountGauge(activeKeyCountEnabled, isTotalStats, ingestionTaskMap, storeName);
340318

341319
// Stats which are per-store only:
342320
String keySizeSensorName = "record_key_size_in_bytes";
@@ -760,6 +738,36 @@ public void recordActiveKeyCountInvalidation() {
760738
}
761739
}
762740

741+
/** Uses the ACTIVE_KEY_COUNT_NOT_TRACKED sentinel to distinguish untracked from empty (which {@code measurable()}'s 0-fallback would conflate). */
742+
private void registerActiveKeyCountGauge(
743+
boolean activeKeyCountEnabled,
744+
boolean isTotalStats,
745+
Map<String, StoreIngestionTask> ingestionTaskMap,
746+
String storeName) {
747+
if (!activeKeyCountEnabled) {
748+
return;
749+
}
750+
if (isTotalStats) {
751+
registerSensor(new AsyncGauge((ignored, ignored2) -> {
752+
long total = 0;
753+
boolean anyTracked = false;
754+
for (StoreIngestionTask task: ingestionTaskMap.values()) {
755+
long storeCount = task.getActiveKeyCount();
756+
if (storeCount != ACTIVE_KEY_COUNT_NOT_TRACKED) {
757+
anyTracked = true;
758+
total += storeCount;
759+
}
760+
}
761+
return anyTracked ? total : ACTIVE_KEY_COUNT_NOT_TRACKED;
762+
}, "active_key_count"));
763+
return;
764+
}
765+
registerSensor(new AsyncGauge((ignored, ignored2) -> {
766+
StoreIngestionTask sit = ingestionTaskMap.get(storeName);
767+
return sit == null ? ACTIVE_KEY_COUNT_NOT_TRACKED : sit.getActiveKeyCount();
768+
}, "active_key_count"));
769+
}
770+
763771
public void recordTotalLeaderBytesConsumed(long bytes) {
764772
totalLeaderBytesConsumedRate.record(bytes);
765773
}

clients/da-vinci-client/src/test/java/com/linkedin/davinci/kafka/consumer/ActiveKeyCountScenarioTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ public void testGaugeEmitsNegativeOneAfterFollowerInvalidateSignal() {
338338

339339
/** A single invalidation event must increment both the Tehuti rate and the OTel counter exactly once. */
340340
@Test
341-
public void testInvalidationParity_tehutiAndOtelBothRecord() {
341+
public void testInvalidationParityRecordsToBothTehutiAndOtel() {
342342
InMemoryMetricReader reader = InMemoryMetricReader.create();
343343
VeniceMetricsRepository otelRepo = new VeniceMetricsRepository(
344344
new VeniceMetricsConfig.Builder().setMetricEntities(SERVER_METRIC_ENTITIES)

0 commit comments

Comments
 (0)