Skip to content

Commit 9cd7569

Browse files
authored
[server] Add OTel metrics to RocksDBStats (linkedin#2731)
Add 13 OTel metrics alongside the existing 27 Tehuti sensors, consolidating per-component metrics using dimensions: - 4 per-component block cache metrics (miss.count, hit.count, add.count, bytes.inserted) with VENICE_ROCKSDB_BLOCK_CACHE_COMPONENT dimension (INDEX/FILTER/DATA/COMPRESSION_DICT) — consolidates 16 Tehuti sensors into 4 OTel metrics - 7 joint Tehuti+OTel metrics for block cache add failures, bytes read/write, bloom filter, memtable hit/miss, and compaction cancelled - 1 per-level get-hit metric (get.hit.count) with VENICE_ROCKSDB_LEVEL dimension (LEVEL_0/LEVEL_1/LEVEL_2_AND_UP) — consolidates 3 Tehuti sensors into 1 OTel metric - 1 read amplification factor using ASYNC_DOUBLE_GAUGE with split callbacks - Overall block cache miss/hit/add counters remain Tehuti-only: OTel totals are derivable via sum by (component). Block cache hit ratio is also Tehuti-only (derivable from per-component OTel gauges).
1 parent 8f922d1 commit 9cd7569

14 files changed

Lines changed: 1114 additions & 84 deletions

File tree

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package com.linkedin.davinci.stats;
2+
3+
import static com.linkedin.venice.stats.dimensions.VeniceMetricsDimensions.VENICE_CLUSTER_NAME;
4+
import static com.linkedin.venice.stats.dimensions.VeniceMetricsDimensions.VENICE_ROCKSDB_BLOCK_CACHE_COMPONENT;
5+
import static com.linkedin.venice.stats.dimensions.VeniceMetricsDimensions.VENICE_ROCKSDB_LEVEL;
6+
import static com.linkedin.venice.utils.Utils.setOf;
7+
8+
import com.linkedin.venice.stats.dimensions.VeniceMetricsDimensions;
9+
import com.linkedin.venice.stats.metrics.MetricEntity;
10+
import com.linkedin.venice.stats.metrics.MetricType;
11+
import com.linkedin.venice.stats.metrics.MetricUnit;
12+
import com.linkedin.venice.stats.metrics.ModuleMetricEntityInterface;
13+
import java.util.Set;
14+
15+
16+
/**
17+
* OTel metric entity definitions for {@link com.linkedin.venice.stats.RocksDBStats}.
18+
*
19+
* <p>Per-component block cache metrics (index, filter, data, compression_dict) are consolidated
20+
* into single metrics with a {@code VENICE_ROCKSDB_BLOCK_CACHE_COMPONENT} dimension. Overall
21+
* block cache miss/hit/add counters are Tehuti-only — OTel derives totals via
22+
* {@code sum by (component)}. Overall-only counters without per-component equivalents
23+
* (add failures, bytes read/write) are emitted as joint Tehuti+OTel.
24+
*
25+
* <p>Get-hit-by-level metrics are consolidated into {@link #GET_HIT_COUNT} with a
26+
* {@code VENICE_ROCKSDB_LEVEL} dimension.
27+
*
28+
* <p>Block cache hit ratio is Tehuti-only (derivable from existing OTel gauge metrics).
29+
*
30+
* @see com.linkedin.venice.stats.dimensions.VeniceRocksDBBlockCacheComponent
31+
* @see com.linkedin.venice.stats.dimensions.VeniceRocksDBLevel
32+
*/
33+
public enum RocksDBStatsOtelMetricEntity implements ModuleMetricEntityInterface {
34+
// Block Cache — per-component (COMPONENT dimension: INDEX/FILTER/DATA/COMPRESSION_DICT)
35+
BLOCK_CACHE_MISS_COUNT(
36+
"rocksdb.block_cache.miss.count", MetricUnit.NUMBER, "Block cache misses by component",
37+
setOf(VENICE_CLUSTER_NAME, VENICE_ROCKSDB_BLOCK_CACHE_COMPONENT)
38+
),
39+
40+
BLOCK_CACHE_HIT_COUNT(
41+
"rocksdb.block_cache.hit.count", MetricUnit.NUMBER, "Block cache hits by component",
42+
setOf(VENICE_CLUSTER_NAME, VENICE_ROCKSDB_BLOCK_CACHE_COMPONENT)
43+
),
44+
45+
BLOCK_CACHE_ADD_COUNT(
46+
"rocksdb.block_cache.add.count", MetricUnit.NUMBER, "Blocks added to cache by component",
47+
setOf(VENICE_CLUSTER_NAME, VENICE_ROCKSDB_BLOCK_CACHE_COMPONENT)
48+
),
49+
50+
BLOCK_CACHE_BYTES_INSERTED(
51+
"rocksdb.block_cache.bytes.inserted", MetricUnit.BYTES, "Bytes inserted into cache by component",
52+
setOf(VENICE_CLUSTER_NAME, VENICE_ROCKSDB_BLOCK_CACHE_COMPONENT)
53+
),
54+
55+
// Block Cache — overall (no per-component breakdown available)
56+
BLOCK_CACHE_ADD_FAILURE_COUNT(
57+
"rocksdb.block_cache.add.failure.count", MetricUnit.NUMBER, "Failed attempts to add blocks to cache",
58+
setOf(VENICE_CLUSTER_NAME)
59+
),
60+
61+
BLOCK_CACHE_READ_BYTES(
62+
"rocksdb.block_cache.bytes.read", MetricUnit.BYTES, "Bytes served from block cache on hits",
63+
setOf(VENICE_CLUSTER_NAME)
64+
),
65+
66+
BLOCK_CACHE_WRITE_BYTES(
67+
"rocksdb.block_cache.bytes.written", MetricUnit.BYTES, "Bytes written into block cache on misses",
68+
setOf(VENICE_CLUSTER_NAME)
69+
),
70+
71+
// Bloom Filter
72+
BLOOM_FILTER_USEFUL_COUNT(
73+
"rocksdb.bloom_filter.useful_count", MetricUnit.NUMBER, "Bloom filter checks that avoided a disk read",
74+
setOf(VENICE_CLUSTER_NAME)
75+
),
76+
77+
// Memtable
78+
MEMTABLE_HIT_COUNT(
79+
"rocksdb.memtable.hit.count", MetricUnit.NUMBER, "Get requests served from memtable", setOf(VENICE_CLUSTER_NAME)
80+
),
81+
82+
MEMTABLE_MISS_COUNT(
83+
"rocksdb.memtable.miss.count", MetricUnit.NUMBER, "Get requests not found in memtable", setOf(VENICE_CLUSTER_NAME)
84+
),
85+
86+
// Get Hit by Level (LEVEL dimension: LEVEL_0/LEVEL_1/LEVEL_2_AND_UP)
87+
GET_HIT_COUNT(
88+
"rocksdb.get.hit.count", MetricUnit.NUMBER, "Get requests served from SST files by level",
89+
setOf(VENICE_CLUSTER_NAME, VENICE_ROCKSDB_LEVEL)
90+
),
91+
92+
// Compaction
93+
COMPACTION_CANCELLED_COUNT(
94+
"rocksdb.compaction.cancelled_count", MetricUnit.NUMBER, "Compactions cancelled", setOf(VENICE_CLUSTER_NAME)
95+
),
96+
97+
// Computed Ratio
98+
READ_AMPLIFICATION_FACTOR(
99+
"rocksdb.read_amplification_factor", MetricType.ASYNC_DOUBLE_GAUGE, MetricUnit.RATIO,
100+
"Read amplification — ratio of total bytes read to useful bytes. Values > 1.0 indicate amplification",
101+
setOf(VENICE_CLUSTER_NAME)
102+
);
103+
104+
private final MetricEntity metricEntity;
105+
106+
/** Constructor for ASYNC_GAUGE metrics (default type). */
107+
RocksDBStatsOtelMetricEntity(
108+
String metricName,
109+
MetricUnit unit,
110+
String description,
111+
Set<VeniceMetricsDimensions> dimensions) {
112+
this(metricName, MetricType.ASYNC_GAUGE, unit, description, dimensions);
113+
}
114+
115+
/** Constructor with explicit MetricType. */
116+
RocksDBStatsOtelMetricEntity(
117+
String metricName,
118+
MetricType metricType,
119+
MetricUnit unit,
120+
String description,
121+
Set<VeniceMetricsDimensions> dimensions) {
122+
this.metricEntity = new MetricEntity(metricName, metricType, unit, description, dimensions);
123+
}
124+
125+
@Override
126+
public MetricEntity getMetricEntity() {
127+
return metricEntity;
128+
}
129+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ public static List<Class<? extends ModuleMetricEntityInterface>> getMetricEntity
4646
StorageEngineOtelMetricEntity.class,
4747
DiskHealthOtelMetricEntity.class,
4848
NativeMetadataRepositoryOtelMetricEntity.class,
49-
ServerLoadOtelMetricEntity.class);
49+
ServerLoadOtelMetricEntity.class,
50+
RocksDBStatsOtelMetricEntity.class);
5051
}
5152

5253
public static final Collection<MetricEntity> SERVER_METRIC_ENTITIES =
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package com.linkedin.davinci.stats;
2+
3+
import static com.linkedin.venice.stats.dimensions.VeniceMetricsDimensions.VENICE_CLUSTER_NAME;
4+
import static com.linkedin.venice.stats.dimensions.VeniceMetricsDimensions.VENICE_ROCKSDB_BLOCK_CACHE_COMPONENT;
5+
import static com.linkedin.venice.stats.dimensions.VeniceMetricsDimensions.VENICE_ROCKSDB_LEVEL;
6+
import static com.linkedin.venice.utils.Utils.setOf;
7+
8+
import com.linkedin.venice.stats.metrics.MetricType;
9+
import com.linkedin.venice.stats.metrics.MetricUnit;
10+
import com.linkedin.venice.stats.metrics.ModuleMetricEntityTestFixture;
11+
import com.linkedin.venice.stats.metrics.ModuleMetricEntityTestFixture.MetricEntityExpectation;
12+
import java.util.HashMap;
13+
import java.util.Map;
14+
import org.testng.annotations.Test;
15+
16+
17+
public class RocksDBStatsOtelMetricEntityTest {
18+
@Test
19+
public void testMetricEntities() {
20+
new ModuleMetricEntityTestFixture<>(RocksDBStatsOtelMetricEntity.class, expectedDefinitions()).assertAll();
21+
}
22+
23+
private static Map<RocksDBStatsOtelMetricEntity, MetricEntityExpectation> expectedDefinitions() {
24+
Map<RocksDBStatsOtelMetricEntity, MetricEntityExpectation> map = new HashMap<>();
25+
26+
// Block Cache — per-component (COMPONENT dimension)
27+
map.put(
28+
RocksDBStatsOtelMetricEntity.BLOCK_CACHE_MISS_COUNT,
29+
new MetricEntityExpectation(
30+
"rocksdb.block_cache.miss.count",
31+
MetricType.ASYNC_GAUGE,
32+
MetricUnit.NUMBER,
33+
"Block cache misses by component",
34+
setOf(VENICE_CLUSTER_NAME, VENICE_ROCKSDB_BLOCK_CACHE_COMPONENT)));
35+
map.put(
36+
RocksDBStatsOtelMetricEntity.BLOCK_CACHE_HIT_COUNT,
37+
new MetricEntityExpectation(
38+
"rocksdb.block_cache.hit.count",
39+
MetricType.ASYNC_GAUGE,
40+
MetricUnit.NUMBER,
41+
"Block cache hits by component",
42+
setOf(VENICE_CLUSTER_NAME, VENICE_ROCKSDB_BLOCK_CACHE_COMPONENT)));
43+
map.put(
44+
RocksDBStatsOtelMetricEntity.BLOCK_CACHE_ADD_COUNT,
45+
new MetricEntityExpectation(
46+
"rocksdb.block_cache.add.count",
47+
MetricType.ASYNC_GAUGE,
48+
MetricUnit.NUMBER,
49+
"Blocks added to cache by component",
50+
setOf(VENICE_CLUSTER_NAME, VENICE_ROCKSDB_BLOCK_CACHE_COMPONENT)));
51+
map.put(
52+
RocksDBStatsOtelMetricEntity.BLOCK_CACHE_BYTES_INSERTED,
53+
new MetricEntityExpectation(
54+
"rocksdb.block_cache.bytes.inserted",
55+
MetricType.ASYNC_GAUGE,
56+
MetricUnit.BYTES,
57+
"Bytes inserted into cache by component",
58+
setOf(VENICE_CLUSTER_NAME, VENICE_ROCKSDB_BLOCK_CACHE_COMPONENT)));
59+
60+
// Block Cache — overall (no per-component breakdown)
61+
map.put(
62+
RocksDBStatsOtelMetricEntity.BLOCK_CACHE_ADD_FAILURE_COUNT,
63+
new MetricEntityExpectation(
64+
"rocksdb.block_cache.add.failure.count",
65+
MetricType.ASYNC_GAUGE,
66+
MetricUnit.NUMBER,
67+
"Failed attempts to add blocks to cache",
68+
setOf(VENICE_CLUSTER_NAME)));
69+
map.put(
70+
RocksDBStatsOtelMetricEntity.BLOCK_CACHE_READ_BYTES,
71+
new MetricEntityExpectation(
72+
"rocksdb.block_cache.bytes.read",
73+
MetricType.ASYNC_GAUGE,
74+
MetricUnit.BYTES,
75+
"Bytes served from block cache on hits",
76+
setOf(VENICE_CLUSTER_NAME)));
77+
map.put(
78+
RocksDBStatsOtelMetricEntity.BLOCK_CACHE_WRITE_BYTES,
79+
new MetricEntityExpectation(
80+
"rocksdb.block_cache.bytes.written",
81+
MetricType.ASYNC_GAUGE,
82+
MetricUnit.BYTES,
83+
"Bytes written into block cache on misses",
84+
setOf(VENICE_CLUSTER_NAME)));
85+
86+
// Bloom Filter, Memtable, Compaction
87+
map.put(
88+
RocksDBStatsOtelMetricEntity.BLOOM_FILTER_USEFUL_COUNT,
89+
new MetricEntityExpectation(
90+
"rocksdb.bloom_filter.useful_count",
91+
MetricType.ASYNC_GAUGE,
92+
MetricUnit.NUMBER,
93+
"Bloom filter checks that avoided a disk read",
94+
setOf(VENICE_CLUSTER_NAME)));
95+
map.put(
96+
RocksDBStatsOtelMetricEntity.MEMTABLE_HIT_COUNT,
97+
new MetricEntityExpectation(
98+
"rocksdb.memtable.hit.count",
99+
MetricType.ASYNC_GAUGE,
100+
MetricUnit.NUMBER,
101+
"Get requests served from memtable",
102+
setOf(VENICE_CLUSTER_NAME)));
103+
map.put(
104+
RocksDBStatsOtelMetricEntity.MEMTABLE_MISS_COUNT,
105+
new MetricEntityExpectation(
106+
"rocksdb.memtable.miss.count",
107+
MetricType.ASYNC_GAUGE,
108+
MetricUnit.NUMBER,
109+
"Get requests not found in memtable",
110+
setOf(VENICE_CLUSTER_NAME)));
111+
map.put(
112+
RocksDBStatsOtelMetricEntity.COMPACTION_CANCELLED_COUNT,
113+
new MetricEntityExpectation(
114+
"rocksdb.compaction.cancelled_count",
115+
MetricType.ASYNC_GAUGE,
116+
MetricUnit.NUMBER,
117+
"Compactions cancelled",
118+
setOf(VENICE_CLUSTER_NAME)));
119+
120+
// Get Hit by Level (SST_LEVEL dimension)
121+
map.put(
122+
RocksDBStatsOtelMetricEntity.GET_HIT_COUNT,
123+
new MetricEntityExpectation(
124+
"rocksdb.get.hit.count",
125+
MetricType.ASYNC_GAUGE,
126+
MetricUnit.NUMBER,
127+
"Get requests served from SST files by level",
128+
setOf(VENICE_CLUSTER_NAME, VENICE_ROCKSDB_LEVEL)));
129+
130+
// Computed Ratio
131+
map.put(
132+
RocksDBStatsOtelMetricEntity.READ_AMPLIFICATION_FACTOR,
133+
new MetricEntityExpectation(
134+
"rocksdb.read_amplification_factor",
135+
MetricType.ASYNC_DOUBLE_GAUGE,
136+
MetricUnit.RATIO,
137+
"Read amplification — ratio of total bytes read to useful bytes. Values > 1.0 indicate amplification",
138+
setOf(VENICE_CLUSTER_NAME)));
139+
140+
return map;
141+
}
142+
}

clients/da-vinci-client/src/test/java/com/linkedin/davinci/stats/ServerMetricEntityTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
public class ServerMetricEntityTest {
2323
@Test
2424
public void testServerMetricEntitiesCount() {
25-
assertEquals(SERVER_METRIC_ENTITIES.size(), 159, "Expected 159 unique metric entities");
25+
assertEquals(SERVER_METRIC_ENTITIES.size(), 172, "Expected 172 unique metric entities");
2626
}
2727

2828
/**

internal/venice-client-common/src/main/java/com/linkedin/venice/stats/dimensions/VeniceMetricsDimensions.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,13 @@ public enum VeniceMetricsDimensions {
162162
VENICE_METRIC_NAME("venice.metric.name"),
163163

164164
/** {@link VeniceServerLoadRequestOutcome} Server load request outcome: accepted or rejected. */
165-
VENICE_SERVER_LOAD_REQUEST_OUTCOME("venice.server.load_controller.request_outcome");
165+
VENICE_SERVER_LOAD_REQUEST_OUTCOME("venice.server.load_controller.request_outcome"),
166+
167+
/** {@link VeniceRocksDBLevel} RocksDB level where a Get was served from. */
168+
VENICE_ROCKSDB_LEVEL("venice.rocksdb.level"),
169+
170+
/** {@link VeniceRocksDBBlockCacheComponent} RocksDB block cache component type. */
171+
VENICE_ROCKSDB_BLOCK_CACHE_COMPONENT("venice.rocksdb.block_cache_component");
166172

167173
private final String[] dimensionName = new String[VeniceOpenTelemetryMetricNamingFormat.SIZE];
168174

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.linkedin.venice.stats.dimensions;
2+
3+
/** RocksDB block cache component type. */
4+
public enum VeniceRocksDBBlockCacheComponent implements VeniceDimensionInterface {
5+
INDEX, FILTER, DATA, COMPRESSION_DICT;
6+
7+
@Override
8+
public VeniceMetricsDimensions getDimensionName() {
9+
return VeniceMetricsDimensions.VENICE_ROCKSDB_BLOCK_CACHE_COMPONENT;
10+
}
11+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.linkedin.venice.stats.dimensions;
2+
3+
/**
4+
* Bucketed RocksDB LSM level where a Get was served from. LEVEL_2_AND_UP covers all
5+
* levels >= 2, matching RocksDB's GET_HIT_L2_AND_UP ticker.
6+
*/
7+
public enum VeniceRocksDBLevel implements VeniceDimensionInterface {
8+
LEVEL_0, LEVEL_1, LEVEL_2_AND_UP;
9+
10+
@Override
11+
public VeniceMetricsDimensions getDimensionName() {
12+
return VeniceMetricsDimensions.VENICE_ROCKSDB_LEVEL;
13+
}
14+
}

internal/venice-client-common/src/test/java/com/linkedin/venice/stats/dimensions/VeniceMetricsDimensionsTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,12 @@ public void testGetDimensionNameInSnakeCase() {
166166
case VENICE_SERVER_LOAD_REQUEST_OUTCOME:
167167
assertEquals(dimension.getDimensionName(format), "venice.server.load_controller.request_outcome");
168168
break;
169+
case VENICE_ROCKSDB_LEVEL:
170+
assertEquals(dimension.getDimensionName(format), "venice.rocksdb.level");
171+
break;
172+
case VENICE_ROCKSDB_BLOCK_CACHE_COMPONENT:
173+
assertEquals(dimension.getDimensionName(format), "venice.rocksdb.block_cache_component");
174+
break;
169175
default:
170176
throw new IllegalArgumentException("Unknown dimension: " + dimension);
171177
}
@@ -330,6 +336,12 @@ public void testGetDimensionNameInCamelCase() {
330336
case VENICE_SERVER_LOAD_REQUEST_OUTCOME:
331337
assertEquals(dimension.getDimensionName(format), "venice.server.loadController.requestOutcome");
332338
break;
339+
case VENICE_ROCKSDB_LEVEL:
340+
assertEquals(dimension.getDimensionName(format), "venice.rocksdb.level");
341+
break;
342+
case VENICE_ROCKSDB_BLOCK_CACHE_COMPONENT:
343+
assertEquals(dimension.getDimensionName(format), "venice.rocksdb.blockCacheComponent");
344+
break;
333345
default:
334346
throw new IllegalArgumentException("Unknown dimension: " + dimension);
335347
}
@@ -494,6 +506,12 @@ public void testGetDimensionNameInPascalCase() {
494506
case VENICE_SERVER_LOAD_REQUEST_OUTCOME:
495507
assertEquals(dimension.getDimensionName(format), "Venice.Server.LoadController.RequestOutcome");
496508
break;
509+
case VENICE_ROCKSDB_LEVEL:
510+
assertEquals(dimension.getDimensionName(format), "Venice.Rocksdb.Level");
511+
break;
512+
case VENICE_ROCKSDB_BLOCK_CACHE_COMPONENT:
513+
assertEquals(dimension.getDimensionName(format), "Venice.Rocksdb.BlockCacheComponent");
514+
break;
497515
default:
498516
throw new IllegalArgumentException("Unknown dimension: " + dimension);
499517
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.linkedin.venice.stats.dimensions;
2+
3+
import com.linkedin.venice.utils.CollectionUtils;
4+
import java.util.Map;
5+
import org.testng.annotations.Test;
6+
7+
8+
public class VeniceRocksDBBlockCacheComponentTest {
9+
@Test
10+
public void testDimensionInterface() {
11+
Map<VeniceRocksDBBlockCacheComponent, String> expectedValues =
12+
CollectionUtils.<VeniceRocksDBBlockCacheComponent, String>mapBuilder()
13+
.put(VeniceRocksDBBlockCacheComponent.INDEX, "index")
14+
.put(VeniceRocksDBBlockCacheComponent.FILTER, "filter")
15+
.put(VeniceRocksDBBlockCacheComponent.DATA, "data")
16+
.put(VeniceRocksDBBlockCacheComponent.COMPRESSION_DICT, "compression_dict")
17+
.build();
18+
new VeniceDimensionTestFixture<>(
19+
VeniceRocksDBBlockCacheComponent.class,
20+
VeniceMetricsDimensions.VENICE_ROCKSDB_BLOCK_CACHE_COMPONENT,
21+
expectedValues).assertAll();
22+
}
23+
}

0 commit comments

Comments
 (0)