Skip to content

Commit 4d1cca9

Browse files
committed
[da-vinci][client-common][test] Add per-metric attribution + per-replica-type
ingestion gauges OTel ingestion gauges: - New entities: ingestion.task.count, ingestion.key.active_count, ingestion.key.unique_ingested_count, dimensioned by version-role and (where applicable) replica-type. Wired through multi-emit gauges via createAsyncByRole / createAsyncByRoleAndReplicaType helpers. - IngestionOtelStats.resolveByRole consolidates version-role lookup; setIngestionTaskPushTimeoutGauge / recordIdleTime gate writes on task registration via computeIfPresent. - HostLevelIngestionStats uses the new no-arg aggregators on StoreIngestionTask to avoid duplicating partition iteration. StoreIngestionTask aggregation: - getActiveKeyCount(ReplicaType) and getEstimatedUniqueIngestedKeyCount (ReplicaType) backed by getKeyCountByReplicaType + matchesReplicaType (LEADER<->LEADER, FOLLOWER<->STANDBY; transitional states excluded from per-replica aggregates). - Consolidated active-key-count invalidation into a recordActiveKeyCountInvalidation helper. recordFailureMetric per-metric attribution: - New VENICE_METRIC_NAME dimension. recordFailureMetric switched to MetricEntityStateGeneric and tags every failure with venice.metric.name. - New MetricEntity.createWithCustomPrefix factory (dimensions + custom prefix); prefix validation centralized in a private helper. Removed unused 5-arg createWithNoDimensions overload. - Defensive re-entry guard in incrementFailureCounter to short-circuit recursion if recording the failure metric itself ever throws. - Extracted INTERNAL_METRIC_PREFIX constant. Tests: - New StoreIngestionTaskAggregationTest covers per-replica-type aggregators (LEADER-only, FOLLOWER-only, IN_TRANSITION/PAUSE_TRANSITION exclusion, untracked, no-arg total, LEADER+FOLLOWER<total invariant). - VeniceOpenTelemetryMetricsRepositoryTest: per-metric attribution, accumulation, null-message exception tolerance, both Exception/String overloads accumulating into the same data point. - MetricEntityTest: createWithCustomPrefix happy path + validation branches (empty prefix, "venice." prefix, empty dimensions, RATIO + long-only metric type rejection). - VeniceMetricsDimensionsTest: VENICE_METRIC_NAME in snake_case, camelCase, and PascalCase switches.
1 parent 18dae4a commit 4d1cca9

27 files changed

Lines changed: 1158 additions & 491 deletions

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
@@ -939,8 +939,7 @@ private PubSubMessageHeaders computeActiveKeyCountSignal(
939939
}
940940
// Underflow detected (count was 0 but got a delete) — count drifted, now invalidated to
941941
// ACTIVE_KEY_COUNT_NOT_TRACKED.
942-
aggVersionedIngestionStats.recordActiveKeyCountInvalidation(storeName, versionNumber);
943-
getHostLevelIngestionStats().recordActiveKeyCountInvalidation();
942+
recordActiveKeyCountInvalidation();
944943
return new PubSubMessageHeaders().add(KEY_COUNT_INVALIDATE_SIGNAL);
945944
}
946945
return EmptyPubSubMessageHeaders.SINGLETON;
@@ -1056,8 +1055,7 @@ private boolean isValuePresentForKey(
10561055
// A transient RocksDB I/O failure must not halt ingestion. Invalidate the count so we stop
10571056
// publishing a wrong number, and return false (assume key absent) to skip the signal.
10581057
partitionConsumptionState.setActiveKeyCount(ACTIVE_KEY_COUNT_NOT_TRACKED);
1059-
aggVersionedIngestionStats.recordActiveKeyCountInvalidation(storeName, versionNumber);
1060-
getHostLevelIngestionStats().recordActiveKeyCountInvalidation();
1058+
recordActiveKeyCountInvalidation();
10611059
String msg =
10621060
"keyExists failed for replica " + partitionConsumptionState.getReplicaId() + "; invalidating activeKeyCount.";
10631061
if (!REDUNDANT_LOGGING_FILTER.isRedundantException(msg)) {

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

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@
124124
import com.linkedin.venice.serializer.FastSerializerDeserializerFactory;
125125
import com.linkedin.venice.serializer.RecordDeserializer;
126126
import com.linkedin.venice.server.VersionRole;
127+
import com.linkedin.venice.stats.dimensions.ReplicaType;
127128
import com.linkedin.venice.stats.dimensions.VeniceIngestionFailureReason;
128129
import com.linkedin.venice.stats.dimensions.VeniceRecordType;
129130
import com.linkedin.venice.storage.protocol.ChunkedValueManifest;
@@ -186,8 +187,10 @@
186187
import java.util.function.BooleanSupplier;
187188
import java.util.function.Consumer;
188189
import java.util.function.Function;
190+
import java.util.function.LongPredicate;
189191
import java.util.function.Predicate;
190192
import java.util.function.Supplier;
193+
import java.util.function.ToLongFunction;
191194
import javax.annotation.Nonnull;
192195
import org.apache.avro.AvroRuntimeException;
193196
import org.apache.avro.Schema;
@@ -4696,8 +4699,8 @@ private void trackActiveKeyCount(
46964699

46974700
// Follower RT: apply "kcs" signal from leader
46984701
if (activeKeyCountForHybridStoreEnabled && isActiveActiveReplicationEnabled
4699-
&& partitionConsumptionState.getActiveKeyCount() >= 0 && partitionConsumptionState.isEndOfPushReceived()
4700-
&& leaderProducedRecordContext == null
4702+
&& partitionConsumptionState.getActiveKeyCount() != ACTIVE_KEY_COUNT_NOT_TRACKED
4703+
&& partitionConsumptionState.isEndOfPushReceived() && leaderProducedRecordContext == null
47014704
&& (messageType == MessageType.PUT ? !isChunkFragment(writerSchemaId) : messageType == MessageType.DELETE)) {
47024705
PubSubMessageHeader signalHeader = consumerRecord.getPubSubMessageHeaders().get(KEY_COUNT_SIGNAL_HEADER);
47034706
if (signalHeader != null && signalHeader.value() != null && signalHeader.value().length == 1) {
@@ -4706,8 +4709,7 @@ private void trackActiveKeyCount(
47064709
partitionConsumptionState.incrementActiveKeyCount();
47074710
} else if (signal == ActiveActiveStoreIngestionTask.KEY_DELETED_SIGNAL_VALUE) {
47084711
if (!partitionConsumptionState.decrementActiveKeyCount()) {
4709-
versionedIngestionStats.recordActiveKeyCountInvalidation(storeName, versionNumber);
4710-
hostLevelIngestionStats.recordActiveKeyCountInvalidation();
4712+
recordActiveKeyCountInvalidation();
47114713
}
47124714
} else if (signal == ActiveActiveStoreIngestionTask.KEY_COUNT_INVALIDATE_SIGNAL_VALUE) {
47134715
invalidateActiveKeyCount(partitionConsumptionState);
@@ -4735,6 +4737,11 @@ private void trackActiveKeyCount(
47354737
/** Invalidates the active key count and records invalidation metrics (both OTel and Tehuti). */
47364738
private void invalidateActiveKeyCount(PartitionConsumptionState partitionConsumptionState) {
47374739
partitionConsumptionState.setActiveKeyCount(ACTIVE_KEY_COUNT_NOT_TRACKED);
4740+
recordActiveKeyCountInvalidation();
4741+
}
4742+
4743+
/** Records active-key-count invalidation on both the OTel (per-version) and Tehuti (host-level) paths. */
4744+
protected final void recordActiveKeyCountInvalidation() {
47384745
versionedIngestionStats.recordActiveKeyCountInvalidation(storeName, versionNumber);
47394746
hostLevelIngestionStats.recordActiveKeyCountInvalidation();
47404747
}
@@ -5784,18 +5791,78 @@ public long getEstimatedUniqueIngestedKeyCount() {
57845791
}
57855792

57865793
/**
5787-
* Returns the estimated count of unique keys ever put or deleted across partitions on this host.
5788-
* If stateFilter is provided, only partitions matching that state are summed.
5789-
* If stateFilter is null, all partitions are summed.
5794+
* HLL estimate of unique keys ever put or deleted, filtered by {@code replicaType}
5795+
* ({@code null} = all partitions). HLL has no "untracked" sentinel — every matching partition
5796+
* contributes (zero if empty), and the no-match fallback is {@code 0}.
5797+
*/
5798+
public long getEstimatedUniqueIngestedKeyCount(ReplicaType replicaType) {
5799+
return getKeyCountByReplicaType(
5800+
replicaType,
5801+
PartitionConsumptionState::getEstimatedUniqueIngestedKeyCount,
5802+
v -> true,
5803+
0L);
5804+
}
5805+
5806+
/** Sums {@link #getActiveKeyCount(ReplicaType)} across all partitions regardless of replica type. */
5807+
public long getActiveKeyCount() {
5808+
return getActiveKeyCount(null);
5809+
}
5810+
5811+
/**
5812+
* Exact count of currently active (alive) keys, filtered by {@code replicaType} ({@code null} =
5813+
* all partitions). Non-monotonic. Returns {@link OffsetRecord#ACTIVE_KEY_COUNT_NOT_TRACKED} when
5814+
* no matching partition has tracking active (no batch baseline); 0 means tracked but empty
5815+
* (e.g., empty push). The {@link OffsetRecord#ACTIVE_KEY_COUNT_NOT_TRACKED} vs 0 distinction is
5816+
* intentional — unlike HLL which has no "untracked" state, the active count uses
5817+
* {@link OffsetRecord#ACTIVE_KEY_COUNT_NOT_TRACKED} to signal that tracking was never initialized.
5818+
*/
5819+
public long getActiveKeyCount(ReplicaType replicaType) {
5820+
return getKeyCountByReplicaType(
5821+
replicaType,
5822+
PartitionConsumptionState::getActiveKeyCount,
5823+
v -> v != ACTIVE_KEY_COUNT_NOT_TRACKED,
5824+
ACTIVE_KEY_COUNT_NOT_TRACKED);
5825+
}
5826+
5827+
/**
5828+
* Iterates partitions filtered by {@code replicaType} (null = all), reads a per-partition
5829+
* count via {@code keyCountFn}, and sums values that pass {@code valueInclusionCheck}. Returns
5830+
* {@code emptyReturnValue} when no value passed (used as a "not tracked" sentinel by callers
5831+
* that distinguish "no contributions" from "zero").
57905832
*/
5791-
public long getEstimatedUniqueIngestedKeyCount(LeaderFollowerStateType stateFilter) {
5833+
private long getKeyCountByReplicaType(
5834+
ReplicaType replicaType,
5835+
ToLongFunction<PartitionConsumptionState> keyCountFn,
5836+
LongPredicate valueInclusionCheck,
5837+
long emptyReturnValue) {
57925838
long total = 0;
5793-
for (PartitionConsumptionState pcs: partitionConsumptionStateMap.values()) {
5794-
if (stateFilter == null || pcs.getLeaderFollowerState() == stateFilter) {
5795-
total += pcs.getEstimatedUniqueIngestedKeyCount();
5839+
boolean hasValidKeyCount = false;
5840+
for (PartitionConsumptionState pcs: getPartitionConsumptionStates()) {
5841+
if (replicaType != null && !matchesReplicaType(pcs, replicaType)) {
5842+
continue;
57965843
}
5844+
long value = keyCountFn.applyAsLong(pcs);
5845+
if (valueInclusionCheck.test(value)) {
5846+
total += value;
5847+
hasValidKeyCount = true;
5848+
}
5849+
}
5850+
return hasValidKeyCount ? total : emptyReturnValue;
5851+
}
5852+
5853+
/**
5854+
* LEADER matches {@link LeaderFollowerStateType#LEADER}; FOLLOWER matches
5855+
* {@link LeaderFollowerStateType#STANDBY}; anything else returns {@code false}.
5856+
*/
5857+
private static boolean matchesReplicaType(PartitionConsumptionState pcs, ReplicaType replicaType) {
5858+
switch (replicaType) {
5859+
case LEADER:
5860+
return pcs.getLeaderFollowerState() == LeaderFollowerStateType.LEADER;
5861+
case FOLLOWER:
5862+
return pcs.getLeaderFollowerState() == LeaderFollowerStateType.STANDBY;
5863+
default:
5864+
return false;
57975865
}
5798-
return total;
57995866
}
58005867

58015868
@VisibleForTesting

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

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import static com.linkedin.davinci.stats.IngestionStats.BATCH_PROCESSING_REQUEST_LATENCY;
66
import static com.linkedin.davinci.stats.IngestionStats.BATCH_PROCESSING_REQUEST_RECORDS;
77
import static com.linkedin.davinci.stats.IngestionStats.BATCH_PROCESSING_REQUEST_SIZE;
8+
import static com.linkedin.venice.offsets.OffsetRecord.ACTIVE_KEY_COUNT_NOT_TRACKED;
89

910
import com.linkedin.davinci.config.VeniceServerConfig;
1011
import com.linkedin.davinci.kafka.consumer.PartitionConsumptionState;
@@ -310,25 +311,25 @@ public HostLevelIngestionStats(
310311
registerSensor(new AsyncGauge((ignored, ignored2) -> ingestionTaskMap.size(), "ingestion_task_count"));
311312
}
312313

313-
// Active key count gauge. -1 = not tracked, 0 = tracked but empty.
314+
// Active key count gauge. ACTIVE_KEY_COUNT_NOT_TRACKED = not tracked, 0 = tracked but empty.
314315
// Cannot use measurable() because its 0-fallback conflates "untracked" with "empty".
315316
if (isTotalStats) {
316317
registerSensor(new AsyncGauge((ignored, ignored2) -> {
317318
long total = 0;
318319
boolean anyTracked = false;
319320
for (StoreIngestionTask task: ingestionTaskMap.values()) {
320-
long storeCount = sumActiveKeyCount(task);
321-
if (storeCount >= 0) {
321+
long storeCount = task.getActiveKeyCount();
322+
if (storeCount != ACTIVE_KEY_COUNT_NOT_TRACKED) {
322323
anyTracked = true;
323324
total += storeCount;
324325
}
325326
}
326-
return anyTracked ? total : -1;
327+
return anyTracked ? total : ACTIVE_KEY_COUNT_NOT_TRACKED;
327328
}, "active_key_count"));
328329
} else {
329330
registerSensor(new AsyncGauge((ignored, ignored2) -> {
330331
StoreIngestionTask sit = ingestionTaskMap.get(storeName);
331-
return sit == null ? -1 : sumActiveKeyCount(sit);
332+
return sit == null ? ACTIVE_KEY_COUNT_NOT_TRACKED : sit.getActiveKeyCount();
332333
}, "active_key_count"));
333334
}
334335

@@ -582,19 +583,6 @@ private Measurable measurable(
582583
};
583584
}
584585

585-
private static long sumActiveKeyCount(StoreIngestionTask task) {
586-
long sum = 0;
587-
boolean anyTracked = false;
588-
for (PartitionConsumptionState pcs: task.getPartitionConsumptionStates()) {
589-
long count = pcs.getActiveKeyCount();
590-
if (count >= 0) {
591-
anyTracked = true;
592-
sum += count;
593-
}
594-
}
595-
return anyTracked ? sum : -1;
596-
}
597-
598586
/** Record a host-level byte consumption rate across all store versions */
599587
public void recordTotalBytesConsumed(long bytes) {
600588
totalBytesConsumedRate.record(bytes);

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

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import static com.linkedin.venice.stats.dimensions.VeniceMetricsDimensions.VENICE_VERSION_ROLE;
1717
import static com.linkedin.venice.utils.Utils.setOf;
1818

19+
import com.linkedin.venice.offsets.OffsetRecord;
1920
import com.linkedin.venice.stats.dimensions.VeniceMetricsDimensions;
2021
import com.linkedin.venice.stats.metrics.MetricEntity;
2122
import com.linkedin.venice.stats.metrics.MetricType;
@@ -323,21 +324,43 @@ public enum IngestionOtelMetricEntity implements ModuleMetricEntityInterface {
323324
setOf(VENICE_STORE_NAME, VENICE_CLUSTER_NAME, VENICE_VERSION_ROLE)
324325
),
325326

327+
/**
328+
* HLL estimate of unique keys ever put or deleted, per replica type, for a store version on this
329+
* host. Monotonically increasing within a version; resets on new version push.
330+
*
331+
* <p>LEADER aggregates partitions in LEADER state; FOLLOWER aggregates partitions in STANDBY
332+
* state. Partitions in any IN_TRANSITION_* state contribute to neither aggregate. No data point
333+
* is emitted when no task exists for the role.
334+
*/
326335
UNIQUE_INGESTED_KEY_COUNT(
327336
"ingestion.key.unique_ingested_count", MetricType.ASYNC_GAUGE, MetricUnit.NUMBER,
328-
"Estimated unique keys ever put or deleted per replica type for a store version on this host (HLL-based, monotonically increasing, resets on new version push)",
337+
"Estimated unique keys ever put or deleted per replica type for a store version on this host "
338+
+ "(HLL-based, monotonically increasing, resets on new version push), for leaders and followers.",
329339
setOf(VENICE_STORE_NAME, VENICE_CLUSTER_NAME, VENICE_VERSION_ROLE, VENICE_REPLICA_TYPE)
330340
),
331341

332342
INGESTION_TASK_COUNT(
333343
"ingestion.task.count", MetricType.ASYNC_GAUGE, MetricUnit.NUMBER,
334-
"Whether an active ingestion task exists for this store version (0 or 1)",
344+
"Emits 1 when an active ingestion task exists for this store version and role; no data point is emitted otherwise.",
335345
setOf(VENICE_STORE_NAME, VENICE_CLUSTER_NAME, VENICE_VERSION_ROLE)
336346
),
337347

348+
/**
349+
* Point-in-time count of unique active keys across partitions of this store version on this
350+
* host. Non-monotonic (tracks creates and deletes).
351+
*
352+
* <p>LEADER aggregates partitions in LEADER state; FOLLOWER aggregates partitions in STANDBY
353+
* state. Partitions in any IN_TRANSITION_* state contribute to neither aggregate.
354+
*
355+
* <p>Sentinels: {@link OffsetRecord#ACTIVE_KEY_COUNT_NOT_TRACKED} = no partition of this replica
356+
* type has tracking active; 0 = tracked but empty (e.g., empty push). No data point is emitted
357+
* when no task exists for the role.
358+
*/
338359
ACTIVE_KEY_COUNT(
339360
"ingestion.key.active_count", MetricType.ASYNC_GAUGE, MetricUnit.NUMBER,
340-
"Point-in-time count of unique active keys across partitions of this store version on this host. Non-monotonic (tracks creates and deletes). -1 = not tracked, 0 = tracked but empty",
361+
"Point-in-time count of unique active keys across partitions of this store version on this host. "
362+
+ "Non-monotonic (tracks creates and deletes). -1 = not tracked, 0 = tracked but empty, "
363+
+ "for leaders and followers.",
341364
setOf(VENICE_STORE_NAME, VENICE_CLUSTER_NAME, VENICE_VERSION_ROLE, VENICE_REPLICA_TYPE)
342365
),
343366

0 commit comments

Comments
 (0)