Skip to content

Commit 98d152d

Browse files
committed
Add E2E test for record-level delay SLO dimensions
Add testRecordLevelDelaySloDimensions to NearlineE2ELatencyTest that verifies the new OTel dimensions (region locality, partial update status, chunking status) are correctly emitted on the ingestion.replication.record.delay histogram after nearline ingestion.
1 parent 48a5df9 commit 98d152d

1 file changed

Lines changed: 162 additions & 0 deletions

File tree

internal/venice-test-common/src/integrationTest/java/com/linkedin/venice/endToEnd/NearlineE2ELatencyTest.java

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
package com.linkedin.venice.endToEnd;
22

3+
import static com.linkedin.davinci.stats.ingestion.heartbeat.RecordLevelDelayOtelMetricEntity.INGESTION_RECORD_DELAY;
4+
import static com.linkedin.venice.integration.utils.VeniceServerWrapper.SERVICE_METRIC_PREFIX;
5+
import static com.linkedin.venice.stats.dimensions.VeniceMetricsDimensions.VENICE_CHUNKING_STATUS;
6+
import static com.linkedin.venice.stats.dimensions.VeniceMetricsDimensions.VENICE_CLUSTER_NAME;
7+
import static com.linkedin.venice.stats.dimensions.VeniceMetricsDimensions.VENICE_PARTIAL_UPDATE_STATUS;
8+
import static com.linkedin.venice.stats.dimensions.VeniceMetricsDimensions.VENICE_REGION_LOCALITY;
9+
import static com.linkedin.venice.stats.dimensions.VeniceMetricsDimensions.VENICE_REGION_NAME;
10+
import static com.linkedin.venice.stats.dimensions.VeniceMetricsDimensions.VENICE_STORE_NAME;
311
import static com.linkedin.venice.utils.IntegrationTestPushUtils.getSamzaProducer;
412
import static com.linkedin.venice.utils.IntegrationTestPushUtils.sendStreamingRecord;
13+
import static com.linkedin.venice.utils.OpenTelemetryDataTestUtils.validateExponentialHistogramPointDataAtLeast;
514
import static com.linkedin.venice.utils.TestWriteUtils.STRING_SCHEMA;
615

716
import com.linkedin.davinci.stats.IngestionStats;
@@ -17,12 +26,19 @@
1726
import com.linkedin.venice.integration.utils.PubSubBrokerWrapper;
1827
import com.linkedin.venice.integration.utils.VeniceClusterWrapper;
1928
import com.linkedin.venice.integration.utils.VeniceMultiClusterWrapper;
29+
import com.linkedin.venice.integration.utils.VeniceServerWrapper;
2030
import com.linkedin.venice.meta.Version;
2131
import com.linkedin.venice.pubsub.PubSubPositionTypeRegistry;
2232
import com.linkedin.venice.pubsub.PubSubProducerAdapterFactory;
2333
import com.linkedin.venice.server.VeniceServer;
34+
import com.linkedin.venice.stats.VeniceMetricsRepository;
35+
import com.linkedin.venice.stats.dimensions.VeniceChunkingStatus;
36+
import com.linkedin.venice.stats.dimensions.VenicePartialUpdateStatus;
37+
import com.linkedin.venice.stats.dimensions.VeniceRegionLocality;
2438
import com.linkedin.venice.utils.TestUtils;
2539
import com.linkedin.venice.utils.Utils;
40+
import io.opentelemetry.api.common.Attributes;
41+
import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader;
2642
import io.tehuti.metrics.MetricsRepository;
2743
import java.util.AbstractMap;
2844
import java.util.Arrays;
@@ -164,4 +180,150 @@ public void testEndToEndNearlineMetric() {
164180
Assert.assertTrue(producerToLocalBroker.get());
165181
Assert.assertTrue(producerToLocalBrokerLatencies.stream().anyMatch(v -> v > 0));
166182
}
183+
184+
/**
185+
* Validates that the record-level delay OTel metric (ingestion.replication.record.delay) includes
186+
* the SLO classification dimensions: region locality, partial update status, and chunking status.
187+
*
188+
* <p>Uses the same hybrid store setup as {@link #testEndToEndNearlineMetric()}: non-WC, non-chunked,
189+
* local-region ingestion. After streaming records and waiting for ingestion, verifies that at least
190+
* one server emitted the histogram with the expected dimension values.
191+
*/
192+
@Test(timeOut = TEST_TIMEOUT)
193+
public void testRecordLevelDelaySloDimensions() {
194+
String storeName = "test-slo-dims";
195+
String parentControllerUrls = parentController.getControllerUrl();
196+
try (ControllerClient parentControllerCli = new ControllerClient(CLUSTER_NAME, parentControllerUrls);
197+
ControllerClient dc0Client =
198+
new ControllerClient(CLUSTER_NAME, childDatacenters.get(0).getControllerConnectString());
199+
ControllerClient dc1Client =
200+
new ControllerClient(CLUSTER_NAME, childDatacenters.get(1).getControllerConnectString())) {
201+
List<ControllerClient> dcControllerClientList = Arrays.asList(dc0Client, dc1Client);
202+
TestUtils.createAndVerifyStoreInAllRegions(storeName, parentControllerCli, dcControllerClientList);
203+
Assert.assertFalse(
204+
parentControllerCli
205+
.updateStore(
206+
storeName,
207+
new UpdateStoreQueryParams().setHybridRewindSeconds(10)
208+
.setHybridOffsetLagThreshold(5)
209+
.setNativeReplicationEnabled(true)
210+
.setPartitionCount(1))
211+
.isError());
212+
TestUtils.verifyDCConfigNativeAndActiveRepl(storeName, true, false, dc0Client, dc1Client);
213+
VersionCreationResponse versionCreationResponse = parentControllerCli.requestTopicForWrites(
214+
storeName,
215+
1024,
216+
Version.PushType.BATCH,
217+
Version.guidBasedDummyPushId(),
218+
true,
219+
false,
220+
false,
221+
Optional.empty(),
222+
Optional.empty(),
223+
Optional.empty(),
224+
false,
225+
-1);
226+
Assert.assertFalse(versionCreationResponse.isError());
227+
PubSubProducerAdapterFactory pubSubProducerAdapterFactory =
228+
childDatacenters.get(0).getKafkaBrokerWrapper().getPubSubClientsFactory().getProducerAdapterFactory();
229+
List<PubSubBrokerWrapper> pubSubBrokerWrappers =
230+
childDatacenters.stream().map(VeniceMultiClusterWrapper::getKafkaBrokerWrapper).collect(Collectors.toList());
231+
Map<String, String> additionalConfigs = PubSubBrokerWrapper.getBrokerDetailsForClients(pubSubBrokerWrappers);
232+
TestUtils.writeBatchData(
233+
versionCreationResponse,
234+
STRING_SCHEMA.toString(),
235+
STRING_SCHEMA.toString(),
236+
IntStream.range(0, 10).mapToObj(i -> new AbstractMap.SimpleEntry<>(String.valueOf(i), String.valueOf(i))),
237+
HelixReadOnlySchemaRepository.VALUE_SCHEMA_STARTING_ID,
238+
pubSubProducerAdapterFactory,
239+
additionalConfigs,
240+
pubSubPositionTypeRegistry);
241+
TestUtils.waitForNonDeterministicPushCompletion(
242+
versionCreationResponse.getKafkaTopic(),
243+
parentControllerCli,
244+
60,
245+
TimeUnit.SECONDS);
246+
}
247+
248+
VeniceClusterWrapper cluster0 = childDatacenters.get(0).getClusters().get(CLUSTER_NAME);
249+
SystemProducer dc0Producer = getSamzaProducer(cluster0, storeName, Version.PushType.STREAM);
250+
251+
for (int i = 10; i < 20; i++) {
252+
sendStreamingRecord(dc0Producer, storeName, i);
253+
}
254+
255+
// Wait for streaming data to be consumed
256+
try (AvroGenericStoreClient client = ClientFactory.getAndStartGenericAvroClient(
257+
ClientConfig.defaultGenericClientConfig(storeName).setVeniceURL(cluster0.getRandomRouterURL()))) {
258+
TestUtils.waitForNonDeterministicAssertion(30, TimeUnit.SECONDS, true, true, () -> {
259+
try {
260+
Object value = client.get("19").get();
261+
Assert.assertNotNull(value, "Last streaming record not yet available");
262+
} catch (Exception e) {
263+
throw new VeniceException(e);
264+
}
265+
});
266+
}
267+
268+
// Wait for the heartbeat reporter thread to emit record-level delay metrics (reports every 60s,
269+
// but in tests the initial report fires shortly after ingestion starts)
270+
String dc0Region = childDatacenters.get(0).getRegionName();
271+
TestUtils.waitForNonDeterministicAssertion(90, TimeUnit.SECONDS, true, true, () -> {
272+
boolean foundMetric = false;
273+
for (VeniceServerWrapper sw: cluster0.getVeniceServers()) {
274+
InMemoryMetricReader reader = getOtelReader(sw);
275+
if (reader == null) {
276+
continue;
277+
}
278+
279+
// Build expected attributes for: local region, non-WC, non-chunked
280+
Attributes expectedAttributes = Attributes.builder()
281+
.put(VENICE_STORE_NAME.getDimensionNameInDefaultFormat(), storeName)
282+
.put(VENICE_CLUSTER_NAME.getDimensionNameInDefaultFormat(), CLUSTER_NAME)
283+
.put(VENICE_REGION_NAME.getDimensionNameInDefaultFormat(), dc0Region)
284+
.put(
285+
VENICE_REGION_LOCALITY.getDimensionNameInDefaultFormat(),
286+
VeniceRegionLocality.LOCAL.getDimensionValue())
287+
.put(
288+
VENICE_PARTIAL_UPDATE_STATUS.getDimensionNameInDefaultFormat(),
289+
VenicePartialUpdateStatus.PARTIAL_UPDATE_DISABLED.getDimensionValue())
290+
.put(
291+
VENICE_CHUNKING_STATUS.getDimensionNameInDefaultFormat(),
292+
VeniceChunkingStatus.UNCHUNKED.getDimensionValue())
293+
.build();
294+
295+
try {
296+
// Validate at least 1 data point with the expected SLO dimensions
297+
validateExponentialHistogramPointDataAtLeast(
298+
reader,
299+
1,
300+
expectedAttributes,
301+
INGESTION_RECORD_DELAY.getMetricEntity().getMetricName(),
302+
SERVICE_METRIC_PREFIX);
303+
foundMetric = true;
304+
LOGGER.info(
305+
"Server {} emitted record-level delay metric with SLO dimensions for store {}",
306+
sw.getAddressForLogging(),
307+
storeName);
308+
} catch (AssertionError e) {
309+
// This server may not be the leader for this partition — try next server
310+
LOGGER.debug(
311+
"Server {} did not emit record-level delay metric for store {}: {}",
312+
sw.getAddressForLogging(),
313+
storeName,
314+
e.getMessage());
315+
}
316+
}
317+
Assert.assertTrue(foundMetric, "No server emitted record-level delay metric with expected SLO dimensions");
318+
});
319+
}
320+
321+
private static InMemoryMetricReader getOtelReader(VeniceServerWrapper server) {
322+
MetricsRepository metricsRepo = server.getMetricsRepository();
323+
if (metricsRepo instanceof VeniceMetricsRepository) {
324+
return (InMemoryMetricReader) ((VeniceMetricsRepository) metricsRepo).getVeniceMetricsConfig()
325+
.getOtelAdditionalMetricsReader();
326+
}
327+
return null;
328+
}
167329
}

0 commit comments

Comments
 (0)