Skip to content

Commit d89a24e

Browse files
committed
add requestbasedmetarepository integration testing
1 parent 70236be commit d89a24e

File tree

14 files changed

+538
-107
lines changed

14 files changed

+538
-107
lines changed

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ public class DaVinciConfig {
4747
*/
4848
private int largeBatchRequestSplitThreshold = AvroGenericDaVinciClient.DEFAULT_CHUNK_SPLIT_THRESHOLD;
4949

50+
/**
51+
* TODO PRANAV correct place to inject this config?
52+
* Determines whether to enable request-based metadata retrieval directly from the Venice Server.
53+
* By default, metadata is retrieved from a system store via a thin client.
54+
*/
55+
private boolean useRequestBasedMetaRepository = false;
56+
5057
public DaVinciConfig() {
5158
}
5259

@@ -147,4 +154,13 @@ public DaVinciConfig setLargeBatchRequestSplitThreshold(int largeBatchRequestSpl
147154
this.largeBatchRequestSplitThreshold = largeBatchRequestSplitThreshold;
148155
return this;
149156
}
157+
158+
public boolean isUseRequestBasedMetaRepository() {
159+
return useRequestBasedMetaRepository;
160+
}
161+
162+
public DaVinciConfig setUseRequestBasedMetaRepository(boolean useRequestBasedMetaRepository) {
163+
this.useRequestBasedMetaRepository = useRequestBasedMetaRepository;
164+
return this;
165+
}
150166
}

clients/da-vinci-client/src/main/java/com/linkedin/davinci/client/factory/CachingDaVinciClientFactory.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,8 @@ protected synchronized DaVinciClient getClient(
383383
ClientConfig clientConfig = new ClientConfig(internalStoreName).setD2Client(d2Client)
384384
.setD2ServiceName(clusterDiscoveryD2ServiceName)
385385
.setMetricsRepository(metricsRepository)
386-
.setSpecificValueClass(valueClass);
386+
.setSpecificValueClass(valueClass)
387+
.setUseRequestBasedMetaRepository(config.isUseRequestBasedMetaRepository());
387388

388389
DaVinciClient client;
389390
if (config.isIsolated()) {

clients/da-vinci-client/src/main/java/com/linkedin/davinci/repository/NativeMetadataRepository.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ public static NativeMetadataRepository getInstance(
117117
ClientConfig clientConfig,
118118
VeniceProperties backendConfig,
119119
ICProvider icProvider) {
120-
121120
NativeMetadataRepository nativeMetadataRepository;
122121
if (clientConfig.isUseRequestBasedMetaRepository()) {
123122
nativeMetadataRepository = new RequestBasedMetaRepository(clientConfig, backendConfig);

clients/da-vinci-client/src/main/java/com/linkedin/davinci/repository/RequestBasedMetaRepository.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,17 +130,15 @@ protected int getMaxValueSchemaId(String storeName) {
130130
}
131131

132132
protected void cacheStoreSchema(String storeName, StorePropertiesResponseRecord record) {
133-
134133
if (!storeSchemaMap.containsKey(storeName)) {
135-
// New schema data
134+
// New store
136135
Map.Entry<CharSequence, CharSequence> keySchemaEntry =
137136
record.getStoreMetaValue().getStoreKeySchemas().getKeySchemaMap().entrySet().iterator().next();
138137
SchemaData schemaData = new SchemaData(
139138
storeName,
140139
new SchemaEntry(Integer.parseInt(keySchemaEntry.getKey().toString()), keySchemaEntry.getValue().toString()));
141140
storeSchemaMap.put(storeName, schemaData);
142141
}
143-
144142
// Store Value Schemas
145143
for (Map.Entry<CharSequence, CharSequence> entry: record.getStoreMetaValue()
146144
.getStoreValueSchemas()

internal/venice-client-common/src/test/java/com/linkedin/venice/schema/TestAvroSupersetSchemaUtils.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import static com.linkedin.venice.utils.TestWriteUtils.NAME_RECORD_V4_SCHEMA;
77
import static com.linkedin.venice.utils.TestWriteUtils.NAME_RECORD_V5_SCHEMA;
88
import static com.linkedin.venice.utils.TestWriteUtils.NAME_RECORD_V6_SCHEMA;
9+
import static com.linkedin.venice.utils.TestWriteUtils.NAME_RECORD_V7_SCHEMA;
910
import static com.linkedin.venice.utils.TestWriteUtils.loadFileAsString;
1011

1112
import com.linkedin.avroutil1.compatibility.AvroCompatibilityHelper;
@@ -502,19 +503,29 @@ public void testGetLatestUpdateSchemaFromSchemaResponse() {
502503
public void testValidateSubsetSchema() {
503504
Assert.assertTrue(
504505
AvroSupersetSchemaUtils.validateSubsetValueSchema(NAME_RECORD_V1_SCHEMA, NAME_RECORD_V2_SCHEMA.toString()));
505-
Assert.assertFalse(
506+
Assert.assertTrue(
506507
AvroSupersetSchemaUtils.validateSubsetValueSchema(NAME_RECORD_V2_SCHEMA, NAME_RECORD_V3_SCHEMA.toString()));
507-
Assert.assertFalse(
508+
Assert.assertTrue(
508509
AvroSupersetSchemaUtils.validateSubsetValueSchema(NAME_RECORD_V3_SCHEMA, NAME_RECORD_V4_SCHEMA.toString()));
510+
Assert.assertTrue(
511+
AvroSupersetSchemaUtils.validateSubsetValueSchema(NAME_RECORD_V4_SCHEMA, NAME_RECORD_V5_SCHEMA.toString()));
512+
Assert.assertTrue(
513+
AvroSupersetSchemaUtils.validateSubsetValueSchema(NAME_RECORD_V5_SCHEMA, NAME_RECORD_V6_SCHEMA.toString()));
514+
Assert.assertFalse(
515+
AvroSupersetSchemaUtils.validateSubsetValueSchema(NAME_RECORD_V6_SCHEMA, NAME_RECORD_V7_SCHEMA.toString()));
509516

510517
// NAME_RECORD_V5_SCHEMA and NAME_RECORD_V6_SCHEMA are different in props for field.
511518
Assert.assertNotEquals(NAME_RECORD_V5_SCHEMA, NAME_RECORD_V6_SCHEMA);
519+
512520
// Test validation skip comparing props when checking for subset schema.
513521
Schema supersetSchemaForV5AndV4 =
514522
AvroSupersetSchemaUtils.generateSupersetSchema(NAME_RECORD_V5_SCHEMA, NAME_RECORD_V4_SCHEMA);
515523
Assert.assertTrue(
516-
AvroSupersetSchemaUtils.validateSubsetValueSchema(NAME_RECORD_V5_SCHEMA, supersetSchemaForV5AndV4.toString()));
524+
AvroSupersetSchemaUtils.validateSubsetValueSchema(NAME_RECORD_V4_SCHEMA, supersetSchemaForV5AndV4.toString()));
517525
Assert.assertTrue(
526+
AvroSupersetSchemaUtils.validateSubsetValueSchema(NAME_RECORD_V5_SCHEMA, supersetSchemaForV5AndV4.toString()));
527+
Assert.assertFalse( // V4 + V5 != V6
518528
AvroSupersetSchemaUtils.validateSubsetValueSchema(NAME_RECORD_V6_SCHEMA, supersetSchemaForV5AndV4.toString()));
529+
519530
}
520531
}

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -290,14 +290,6 @@ public void testThinClientMetaStoreBasedRepository() throws InterruptedException
290290
}
291291

292292
// TODO PRANAV move this test and use the full DVC
293-
// Can we add a new test file where we run a more
294-
// comprehensive integration test with a DVC? i.e
295-
// push some new versions or make some store config
296-
// changes and make sure the DVC pick up those changes.
297-
// You can see examples like the recently added
298-
// testBatchOnlyMaterializedViewDVCConsumer.
299-
// You probably don't need a VeniceTwoLayerMultiRegionMultiClusterWrapper,
300-
// a single region will be sufficient.
301293
@Test(timeOut = 120 * Time.MS_PER_SECOND)
302294
public void testRequestBasedMetaStoreBasedRepository() throws InterruptedException {
303295
String regularVeniceStoreName = Utils.getUniqueString("venice_store");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
package com.linkedin.venice.endToEnd;
2+
3+
import static com.linkedin.venice.ConfigKeys.CLIENT_SYSTEM_STORE_REPOSITORY_REFRESH_INTERVAL_SECONDS;
4+
import static com.linkedin.venice.ConfigKeys.CLIENT_USE_SYSTEM_STORE_REPOSITORY;
5+
import static com.linkedin.venice.ConfigKeys.DATA_BASE_PATH;
6+
import static com.linkedin.venice.ConfigKeys.PERSISTENCE_TYPE;
7+
import static com.linkedin.venice.utils.TestWriteUtils.getTempDataDirectory;
8+
import static com.linkedin.venice.vpj.VenicePushJobConstants.DEFAULT_VALUE_FIELD_PROP;
9+
import static org.testng.Assert.assertNotNull;
10+
11+
import com.linkedin.d2.balancer.D2Client;
12+
import com.linkedin.davinci.client.DaVinciClient;
13+
import com.linkedin.davinci.client.DaVinciConfig;
14+
import com.linkedin.davinci.client.factory.CachingDaVinciClientFactory;
15+
import com.linkedin.venice.D2.D2ClientUtils;
16+
import com.linkedin.venice.controllerapi.ControllerClient;
17+
import com.linkedin.venice.controllerapi.SchemaResponse;
18+
import com.linkedin.venice.integration.utils.D2TestUtils;
19+
import com.linkedin.venice.integration.utils.ServiceFactory;
20+
import com.linkedin.venice.integration.utils.VeniceClusterCreateOptions;
21+
import com.linkedin.venice.integration.utils.VeniceClusterWrapper;
22+
import com.linkedin.venice.integration.utils.VeniceRouterWrapper;
23+
import com.linkedin.venice.meta.PersistenceType;
24+
import com.linkedin.venice.utils.IntegrationTestPushUtils;
25+
import com.linkedin.venice.utils.PropertyBuilder;
26+
import com.linkedin.venice.utils.TestUtils;
27+
import com.linkedin.venice.utils.TestWriteUtils;
28+
import com.linkedin.venice.utils.Time;
29+
import com.linkedin.venice.utils.Utils;
30+
import com.linkedin.venice.utils.VeniceProperties;
31+
import io.tehuti.Metric;
32+
import io.tehuti.metrics.MetricsRepository;
33+
import java.io.File;
34+
import java.io.IOException;
35+
import java.util.HashMap;
36+
import java.util.Map;
37+
import java.util.Properties;
38+
import java.util.concurrent.ExecutionException;
39+
import java.util.concurrent.TimeUnit;
40+
import org.apache.avro.Schema;
41+
import org.testng.Assert;
42+
import org.testng.annotations.AfterClass;
43+
import org.testng.annotations.BeforeClass;
44+
import org.testng.annotations.Test;
45+
46+
47+
public class TestDaVinciRequestBasedMetaRepository {
48+
private static final int TEST_TIMEOUT = 2 * Time.MS_PER_MINUTE;
49+
50+
private static final String CLUSTER_NAME = "venice-cluster";
51+
private VeniceClusterWrapper clusterWrapper;
52+
53+
private static final String storeNameStringToString = "store-name-string-to-string";
54+
private static final String storeNameStringToNameRecord = "store-name-string-to-name-record";
55+
56+
// StoreName -> ControllerClient
57+
// Using map to check which stores are created
58+
private final Map<String, ControllerClient> controllerClients = new HashMap<>();
59+
// StoreName -> Directory
60+
private final Map<String, File> pushJobAvroDataDirs = new HashMap<>();
61+
62+
private DaVinciConfig daVinciConfig;
63+
private MetricsRepository dvcMetricsRepo;
64+
private D2Client daVinciD2RemoteFabric;
65+
private CachingDaVinciClientFactory daVinciClientFactory;
66+
67+
@BeforeClass(alwaysRun = true)
68+
public void setUp() throws IOException {
69+
70+
VeniceClusterCreateOptions.Builder options = new VeniceClusterCreateOptions.Builder().clusterName(CLUSTER_NAME)
71+
.numberOfRouters(1)
72+
.numberOfServers(2)
73+
.numberOfControllers(2)
74+
.replicationFactor(2)
75+
.forkServer(false);
76+
clusterWrapper = ServiceFactory.getVeniceCluster(options.build());
77+
78+
// Create stores
79+
runPushJob( // String to String
80+
storeNameStringToString,
81+
TestWriteUtils
82+
.writeSimpleAvroFileWithStringToStringSchema(getPushJobAvroFileDirectory(storeNameStringToString)));
83+
runPushJob( // String to Name Record
84+
storeNameStringToNameRecord,
85+
TestWriteUtils.writeSimpleAvroFileWithStringToNameRecordV1Schema(
86+
getPushJobAvroFileDirectory(storeNameStringToNameRecord)));
87+
88+
// Set up DVC Client Factory
89+
VeniceProperties backendConfig =
90+
new PropertyBuilder().put(DATA_BASE_PATH, Utils.getTempDataDirectory().getAbsolutePath())
91+
.put(PERSISTENCE_TYPE, PersistenceType.ROCKS_DB)
92+
.put(CLIENT_USE_SYSTEM_STORE_REPOSITORY, true)
93+
.put(CLIENT_SYSTEM_STORE_REPOSITORY_REFRESH_INTERVAL_SECONDS, 1)
94+
.build();
95+
daVinciConfig = new DaVinciConfig();
96+
daVinciConfig.setUseRequestBasedMetaRepository(true);
97+
daVinciD2RemoteFabric = D2TestUtils.getAndStartD2Client(clusterWrapper.getZk().getAddress());
98+
dvcMetricsRepo = new MetricsRepository();
99+
daVinciClientFactory = new CachingDaVinciClientFactory(
100+
daVinciD2RemoteFabric,
101+
VeniceRouterWrapper.CLUSTER_DISCOVERY_D2_SERVICE_NAME,
102+
dvcMetricsRepo,
103+
backendConfig);
104+
}
105+
106+
@AfterClass(alwaysRun = true)
107+
public void cleanUp() {
108+
109+
// Shutdown remote fabric
110+
D2ClientUtils.shutdownClient(daVinciD2RemoteFabric);
111+
112+
// Close client factory
113+
daVinciClientFactory.close();
114+
115+
// Close controller clients
116+
for (Map.Entry<String, ControllerClient> entry: controllerClients.entrySet()) {
117+
entry.getValue().close();
118+
}
119+
120+
// Close cluster wrapper
121+
clusterWrapper.close();
122+
}
123+
124+
@Test(timeOut = TEST_TIMEOUT)
125+
public void testDVCRequestBasedMetaRepositoryStringToString()
126+
throws IOException, ExecutionException, InterruptedException {
127+
128+
try (DaVinciClient<String, Object> storeClient =
129+
daVinciClientFactory.getAndStartGenericAvroClient(storeNameStringToString, daVinciConfig)) {
130+
storeClient.subscribeAll().get();
131+
132+
int recordCount = TestWriteUtils.DEFAULT_USER_DATA_RECORD_COUNT;
133+
for (int i = 1; i <= recordCount; i++) {
134+
Assert.assertEquals(
135+
storeClient.get(Integer.toString(i)).get().toString(),
136+
TestWriteUtils.DEFAULT_USER_DATA_VALUE_PREFIX + i);
137+
}
138+
Assert
139+
.assertEquals(getMetric(dvcMetricsRepo, "current_version_number.Gauge", storeNameStringToString), (double) 1);
140+
141+
// Perform another push with 200 keys to verify future version ingestion
142+
recordCount = 200;
143+
runPushJob(
144+
storeNameStringToString,
145+
TestWriteUtils.writeSimpleAvroFileWithStringToStringSchema(
146+
getPushJobAvroFileDirectory(storeNameStringToString),
147+
recordCount));
148+
149+
// Perform another push with 200 keys to verify future version ingestion and version swap
150+
TestUtils.waitForNonDeterministicAssertion(60, TimeUnit.SECONDS, false, () -> {
151+
Assert.assertEquals(
152+
getMetric(dvcMetricsRepo, "current_version_number.Gauge", storeNameStringToString),
153+
(double) 2);
154+
});
155+
156+
for (int i = 1; i <= recordCount; i++) {
157+
Assert.assertEquals(
158+
storeClient.get(Integer.toString(i)).get().toString(),
159+
TestWriteUtils.DEFAULT_USER_DATA_VALUE_PREFIX + i);
160+
}
161+
}
162+
}
163+
164+
@Test(timeOut = TEST_TIMEOUT)
165+
public void testDVCRequestBasedMetaRepositoryStringToNameRecord() throws ExecutionException, InterruptedException {
166+
167+
try (DaVinciClient<String, Object> storeClient =
168+
daVinciClientFactory.getAndStartGenericAvroClient(storeNameStringToNameRecord, daVinciConfig)) {
169+
storeClient.subscribeAll().get();
170+
171+
int recordCount = TestWriteUtils.DEFAULT_USER_DATA_RECORD_COUNT;
172+
for (int i = 1; i <= recordCount; i++) {
173+
Assert.assertEquals(
174+
storeClient.get(Integer.toString(i)).get().toString(),
175+
TestWriteUtils.renderNameRecord(TestWriteUtils.STRING_TO_NAME_RECORD_V1_SCHEMA, i)
176+
.get(DEFAULT_VALUE_FIELD_PROP)
177+
.toString());
178+
}
179+
Assert.assertEquals(
180+
getMetric(dvcMetricsRepo, "current_version_number.Gauge", storeNameStringToNameRecord),
181+
(double) 1);
182+
}
183+
}
184+
185+
@Test(timeOut = TEST_TIMEOUT)
186+
public void testDVCRequestBasedMetaRepositoryStringToNameRecordVersions()
187+
throws IOException, ExecutionException, InterruptedException {
188+
189+
try (DaVinciClient<String, Object> storeClient =
190+
daVinciClientFactory.getAndStartGenericAvroClient(storeNameStringToNameRecord, daVinciConfig)) {
191+
storeClient.subscribeAll().get();
192+
193+
for (int i = 0; i < TestWriteUtils.countStringToNameRecordSchemas(); i++) {
194+
Schema schema = TestWriteUtils.getStringToNameRecordSchema(i);
195+
int currentValueVersion = i + 2;
196+
197+
int recordCount = currentValueVersion * TestWriteUtils.DEFAULT_USER_DATA_RECORD_COUNT;
198+
runPushJob(
199+
storeNameStringToNameRecord,
200+
TestWriteUtils.writeSimpleAvroFileWithStringToNameRecordSchema(
201+
getPushJobAvroFileDirectory(storeNameStringToNameRecord),
202+
schema,
203+
recordCount));
204+
205+
// Perform another push with 200 keys to verify future version ingestion and version swap
206+
TestUtils.waitForNonDeterministicAssertion(60, TimeUnit.SECONDS, false, () -> {
207+
Assert.assertEquals(
208+
getMetric(dvcMetricsRepo, "current_version_number.Gauge", storeNameStringToNameRecord),
209+
(double) (currentValueVersion));
210+
});
211+
212+
for (int j = 1; j <= recordCount; j++) {
213+
Assert.assertEquals(
214+
storeClient.get(Integer.toString(j)).get().toString(),
215+
TestWriteUtils.renderNameRecord(schema, j).get(DEFAULT_VALUE_FIELD_PROP).toString());
216+
}
217+
}
218+
}
219+
}
220+
221+
private double getMetric(MetricsRepository metricsRepository, String metricName, String storeName) {
222+
Metric metric = metricsRepository.getMetric("." + storeName + "--" + metricName);
223+
assertNotNull(metric, "Expected metric " + metricName + " not found.");
224+
return metric.value();
225+
}
226+
227+
private File getPushJobAvroFileDirectory(String storeName) {
228+
if (!pushJobAvroDataDirs.containsKey(storeName)) {
229+
pushJobAvroDataDirs.put(storeName, getTempDataDirectory());
230+
}
231+
232+
return pushJobAvroDataDirs.get(storeName);
233+
}
234+
235+
private void runPushJob(String storeName, Schema schema) {
236+
237+
ControllerClient controllerClient;
238+
File dataDir = getPushJobAvroFileDirectory(storeName);
239+
String dataDirPath = "file:" + dataDir.getAbsolutePath();
240+
241+
if (!controllerClients.containsKey(storeName)) {
242+
// Init store
243+
controllerClient = IntegrationTestPushUtils.createStoreForJob(
244+
CLUSTER_NAME,
245+
schema,
246+
TestWriteUtils.defaultVPJProps(
247+
clusterWrapper.getVeniceControllers().get(0).getControllerUrl(),
248+
dataDirPath,
249+
storeName));
250+
controllerClients.put(storeName, controllerClient);
251+
} else {
252+
controllerClient = controllerClients.get(storeName);
253+
254+
// Add new schema
255+
Schema valueSchema = schema.getField(DEFAULT_VALUE_FIELD_PROP).schema();
256+
SchemaResponse schemaResponse = controllerClient.addValueSchema(storeName, valueSchema.toString());
257+
Assert.assertFalse(schemaResponse.isError(), schemaResponse.getError());
258+
}
259+
260+
Properties props =
261+
TestWriteUtils.defaultVPJProps(controllerClient.getLeaderControllerUrl(), dataDirPath, storeName);
262+
TestWriteUtils.runPushJob(storeName + "_" + Utils.getUniqueString("push_job"), props);
263+
}
264+
}

0 commit comments

Comments
 (0)