Skip to content

Commit 47532b4

Browse files
committed
[controller] verify bug with superset schema not updating with read/write
compute disabled
1 parent d957110 commit 47532b4

1 file changed

Lines changed: 160 additions & 0 deletions

File tree

internal/venice-test-common/src/integrationTest/java/com/linkedin/venice/controller/VeniceParentHelixAdminTest.java

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1565,4 +1565,164 @@ private Schema generateSuperSetSchemaNewField() {
15651565
return AvroSchemaParseUtils.parseSchemaFromJSONStrictValidation(schemaStr);
15661566
}
15671567

1568+
/**
1569+
* Test that verifies a bug where the superset schema ID does not update when read/write compute
1570+
* is disabled on a store, even if a superset schema was previously set.
1571+
*
1572+
* Bug scenario:
1573+
* 1. Store is created with read computation enabled
1574+
* 2. Multiple schemas are added, causing a superset schema to be generated (e.g., ID 4)
1575+
* 3. Read/write computation is disabled on the store
1576+
* 4. New schemas are added that would normally require a new superset schema
1577+
* 5. Bug: The superset schema ID remains unchanged (stays at 4 instead of updating)
1578+
*
1579+
* The root cause is in VeniceParentHelixAdmin.addValueSchema() which checks:
1580+
* if (store.isReadComputationEnabled() || store.isWriteComputationEnabled())
1581+
* before updating the superset schema. When both are disabled, the superset schema
1582+
* is not updated even if one was previously set.
1583+
*/
1584+
@Test(timeOut = DEFAULT_TEST_TIMEOUT_MS)
1585+
public void testSupersetSchemaNotUpdatedWhenComputeDisabled() throws IOException {
1586+
String storeName = Utils.getUniqueString("test_superset_compute_disabled");
1587+
String owner = "test_owner";
1588+
String keySchemaStr = "\"long\"";
1589+
1590+
// Load schemas from test resources
1591+
// ValueV1: f0, f1
1592+
// ValueV2: f0, f1, f2
1593+
// ValueV3: f1, f2, f3, f4
1594+
Schema valueSchemaV1 =
1595+
AvroCompatibilityHelper.parse(TestWriteUtils.loadFileAsString("valueSchema/supersetschemas/ValueV1.avsc"));
1596+
Schema valueSchemaV2 =
1597+
AvroCompatibilityHelper.parse(TestWriteUtils.loadFileAsString("valueSchema/supersetschemas/ValueV2.avsc"));
1598+
Schema valueSchemaV3 =
1599+
AvroCompatibilityHelper.parse(TestWriteUtils.loadFileAsString("valueSchema/supersetschemas/ValueV3.avsc"));
1600+
1601+
// Schema with a NEW field f5 that would normally trigger superset schema update
1602+
// Contains f2, f3, f5 - f5 is not in the current superset (f0, f1, f2, f3, f4)
1603+
String schemaWithNewFieldStr = "{\n" + " \"type\" : \"record\",\n" + " \"namespace\" : \"example.avro\",\n"
1604+
+ " \"name\" : \"ValueRecordName\",\n" + " \"fields\" : [\n"
1605+
+ " { \"name\" : \"f2\", \"type\" : \"int\", \"default\" : -1 },\n"
1606+
+ " { \"name\" : \"f3\", \"type\" : \"int\", \"default\" : -1 },\n"
1607+
+ " { \"name\" : \"f5\", \"type\" : \"int\", \"default\" : -1 }\n" + " ]\n" + "}";
1608+
Schema schemaWithNewField = AvroCompatibilityHelper.parse(schemaWithNewFieldStr);
1609+
1610+
try (ControllerClient parentControllerClient =
1611+
new ControllerClient(clusterName, multiRegionMultiClusterWrapper.getControllerConnectString())) {
1612+
1613+
// Step 1: Create store with first schema
1614+
NewStoreResponse newStoreResponse =
1615+
parentControllerClient.createNewStore(storeName, owner, keySchemaStr, valueSchemaV1.toString());
1616+
Assert.assertNotNull(newStoreResponse);
1617+
Assert.assertFalse(newStoreResponse.isError(), "error in newStoreResponse: " + newStoreResponse.getError());
1618+
1619+
// Step 2: Enable read computation to trigger superset schema generation
1620+
UpdateStoreQueryParams params = new UpdateStoreQueryParams();
1621+
params.setReadComputationEnabled(true);
1622+
ControllerResponse updateStoreResponse = parentControllerClient.updateStore(storeName, params);
1623+
Assert.assertNotNull(updateStoreResponse);
1624+
Assert.assertFalse(
1625+
updateStoreResponse.isError(),
1626+
"error in updateStoreResponse: " + updateStoreResponse.getError());
1627+
1628+
// Verify initial superset schema ID is 1 (the first and only schema)
1629+
StoreResponse storeResponse = parentControllerClient.getStore(storeName);
1630+
Assert.assertFalse(storeResponse.isError(), "error in storeResponse: " + storeResponse.getError());
1631+
int initialSupersetSchemaId = storeResponse.getStore().getLatestSuperSetValueSchemaId();
1632+
Assert
1633+
.assertEquals(initialSupersetSchemaId, 1, "Initial superset schema ID should be 1 (the first value schema)");
1634+
1635+
// Step 3: Add schema V2 (f0, f1, f2) - this is a superset of V1 (f0, f1)
1636+
SchemaResponse addSchemaResponse = parentControllerClient.addValueSchema(storeName, valueSchemaV2.toString());
1637+
Assert.assertNotNull(addSchemaResponse);
1638+
Assert.assertFalse(addSchemaResponse.isError(), "error in addSchemaResponse: " + addSchemaResponse.getError());
1639+
1640+
// Verify superset schema is now V2 (since V2 is superset of V1)
1641+
storeResponse = parentControllerClient.getStore(storeName);
1642+
Assert.assertFalse(storeResponse.isError(), "error in storeResponse: " + storeResponse.getError());
1643+
int supersetSchemaIdAfterV2 = storeResponse.getStore().getLatestSuperSetValueSchemaId();
1644+
Assert.assertEquals(supersetSchemaIdAfterV2, 2, "Superset schema ID should be 2 after adding V2");
1645+
1646+
// Step 4: Add schema V3 (f1, f2, f3, f4) - this requires generating a new superset schema
1647+
// combining fields from V2 (f0, f1, f2) and V3 (f1, f2, f3, f4) = superset (f0, f1, f2, f3, f4)
1648+
addSchemaResponse = parentControllerClient.addValueSchema(storeName, valueSchemaV3.toString());
1649+
Assert.assertNotNull(addSchemaResponse);
1650+
Assert.assertFalse(addSchemaResponse.isError(), "error in addSchemaResponse: " + addSchemaResponse.getError());
1651+
1652+
// Verify a new superset schema was generated (schema ID 4)
1653+
storeResponse = parentControllerClient.getStore(storeName);
1654+
Assert.assertFalse(storeResponse.isError(), "error in storeResponse: " + storeResponse.getError());
1655+
int supersetSchemaIdBeforeDisable = storeResponse.getStore().getLatestSuperSetValueSchemaId();
1656+
Assert.assertEquals(
1657+
supersetSchemaIdBeforeDisable,
1658+
4,
1659+
"Superset schema ID should be 4 after adding V3 (V1=1, V2=2, V3=3, superset=4)");
1660+
1661+
// Verify the superset schema contains all fields (f0, f1, f2, f3, f4)
1662+
SchemaResponse supersetSchemaResponse =
1663+
parentControllerClient.getValueSchema(storeName, supersetSchemaIdBeforeDisable);
1664+
Assert.assertFalse(
1665+
supersetSchemaResponse.isError(),
1666+
"error in schemaResponse: " + supersetSchemaResponse.getError());
1667+
Schema supersetSchema = AvroCompatibilityHelper.parse(supersetSchemaResponse.getSchemaStr());
1668+
Assert.assertNotNull(supersetSchema.getField("f0"), "Superset schema should contain f0");
1669+
Assert.assertNotNull(supersetSchema.getField("f1"), "Superset schema should contain f1");
1670+
Assert.assertNotNull(supersetSchema.getField("f2"), "Superset schema should contain f2");
1671+
Assert.assertNotNull(supersetSchema.getField("f3"), "Superset schema should contain f3");
1672+
Assert.assertNotNull(supersetSchema.getField("f4"), "Superset schema should contain f4");
1673+
1674+
// Step 5: Disable read computation
1675+
params = new UpdateStoreQueryParams();
1676+
params.setReadComputationEnabled(false);
1677+
updateStoreResponse = parentControllerClient.updateStore(storeName, params);
1678+
Assert.assertNotNull(updateStoreResponse);
1679+
Assert.assertFalse(
1680+
updateStoreResponse.isError(),
1681+
"error in updateStoreResponse: " + updateStoreResponse.getError());
1682+
1683+
// Verify read computation is disabled
1684+
storeResponse = parentControllerClient.getStore(storeName);
1685+
Assert.assertFalse(storeResponse.isError(), "error in storeResponse: " + storeResponse.getError());
1686+
Assert.assertFalse(storeResponse.getStore().isReadComputationEnabled(), "Read computation should be disabled");
1687+
1688+
// Step 6: Add schema with NEW field f5 (f2, f3, f5)
1689+
// This schema introduces f5 which is NOT in the current superset (f0, f1, f2, f3, f4)
1690+
// With compute enabled, this would trigger a new superset schema containing f0-f5
1691+
addSchemaResponse = parentControllerClient.addValueSchema(storeName, schemaWithNewField.toString());
1692+
Assert.assertNotNull(addSchemaResponse);
1693+
Assert.assertFalse(addSchemaResponse.isError(), "error in addSchemaResponse: " + addSchemaResponse.getError());
1694+
1695+
// BUG VERIFICATION: When compute is disabled, the superset schema logic is completely bypassed.
1696+
// Even though we added a schema with a NEW field (f5), the superset schema is NOT updated.
1697+
storeResponse = parentControllerClient.getStore(storeName);
1698+
Assert.assertFalse(storeResponse.isError(), "error in storeResponse: " + storeResponse.getError());
1699+
int supersetSchemaIdAfterDisable = storeResponse.getStore().getLatestSuperSetValueSchemaId();
1700+
1701+
// The superset schema ID remains unchanged at 4 because compute is disabled.
1702+
// This documents the current (buggy) behavior where the superset schema is not updated
1703+
// even when a new field is introduced that SHOULD be added to the superset.
1704+
Assert.assertEquals(
1705+
supersetSchemaIdAfterDisable,
1706+
supersetSchemaIdBeforeDisable,
1707+
"BUG: Superset schema ID should not change when compute is disabled, "
1708+
+ "even though a schema with NEW field f5 was added.");
1709+
1710+
// Verify the superset schema still does NOT contain f5 (proving the bug)
1711+
supersetSchemaResponse = parentControllerClient.getValueSchema(storeName, supersetSchemaIdAfterDisable);
1712+
Assert.assertFalse(
1713+
supersetSchemaResponse.isError(),
1714+
"error in schemaResponse: " + supersetSchemaResponse.getError());
1715+
Schema supersetSchemaAfterDisable = AvroCompatibilityHelper.parse(supersetSchemaResponse.getSchemaStr());
1716+
Assert.assertNull(
1717+
supersetSchemaAfterDisable.getField("f5"),
1718+
"BUG: Superset schema should NOT contain f5 because superset update was bypassed when compute is disabled");
1719+
1720+
// Verify total schema count: V1=1, V2=2, V3=3, superset=4, schemaWithNewField=5
1721+
MultiSchemaResponse schemaResponse = parentControllerClient.getAllValueSchema(storeName);
1722+
Assert.assertNotNull(schemaResponse);
1723+
Assert.assertFalse(schemaResponse.isError(), "error in schemaResponse: " + schemaResponse.getError());
1724+
Assert.assertEquals(schemaResponse.getSchemas().length, 5, "There should be 5 value schemas total");
1725+
}
1726+
}
1727+
15681728
}

0 commit comments

Comments
 (0)