|
1 | 1 | package com.linkedin.venice.endToEnd; |
2 | 2 |
|
| 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; |
3 | 11 | import static com.linkedin.venice.utils.IntegrationTestPushUtils.getSamzaProducer; |
4 | 12 | import static com.linkedin.venice.utils.IntegrationTestPushUtils.sendStreamingRecord; |
| 13 | +import static com.linkedin.venice.utils.OpenTelemetryDataTestUtils.validateExponentialHistogramPointDataAtLeast; |
5 | 14 | import static com.linkedin.venice.utils.TestWriteUtils.STRING_SCHEMA; |
6 | 15 |
|
7 | 16 | import com.linkedin.davinci.stats.IngestionStats; |
|
17 | 26 | import com.linkedin.venice.integration.utils.PubSubBrokerWrapper; |
18 | 27 | import com.linkedin.venice.integration.utils.VeniceClusterWrapper; |
19 | 28 | import com.linkedin.venice.integration.utils.VeniceMultiClusterWrapper; |
| 29 | +import com.linkedin.venice.integration.utils.VeniceServerWrapper; |
20 | 30 | import com.linkedin.venice.meta.Version; |
21 | 31 | import com.linkedin.venice.pubsub.PubSubPositionTypeRegistry; |
22 | 32 | import com.linkedin.venice.pubsub.PubSubProducerAdapterFactory; |
23 | 33 | 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; |
24 | 38 | import com.linkedin.venice.utils.TestUtils; |
25 | 39 | import com.linkedin.venice.utils.Utils; |
| 40 | +import io.opentelemetry.api.common.Attributes; |
| 41 | +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; |
26 | 42 | import io.tehuti.metrics.MetricsRepository; |
27 | 43 | import java.util.AbstractMap; |
28 | 44 | import java.util.Arrays; |
@@ -164,4 +180,150 @@ public void testEndToEndNearlineMetric() { |
164 | 180 | Assert.assertTrue(producerToLocalBroker.get()); |
165 | 181 | Assert.assertTrue(producerToLocalBrokerLatencies.stream().anyMatch(v -> v > 0)); |
166 | 182 | } |
| 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 | + } |
167 | 329 | } |
0 commit comments