Skip to content

Commit 2df785a

Browse files
KaiSernLimclaude
andcommitted
🤖 Route chunked GlobalRtDiv storage entirely through metadata partition
Previously, chunk and manifest messages for GlobalRtDiv were buffered in user-data storage (via prependHeaderAndWriteToStorageEngine) and assembled using GenericChunkingAdapter reading from the user-data partition. This violated the invariant that GlobalRtDiv state must not touch user-data storage. Add putGlobalRtDivChunk / getGlobalRtDivChunk / deleteGlobalRtDivChunk to StorageEngine, AbstractStorageEngine, and DelegatingStorageEngine. These store intermediate chunks in the metadata partition under a GRTD_CK_{partitionId}_{chunkKey} key scheme. Update putGlobalRtDivStateInMetadata in StoreIngestionTask: - CHUNK: build value-with-header, write to metadata via putGlobalRtDivChunk - MANIFEST: read manifest, assemble chunks from metadata, decompress, store via storageMetadataService.putGlobalRtDivState, then delete chunk entries - Non-chunked: unchanged (decompress + store directly) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent e6eb386 commit 2df785a

4 files changed

Lines changed: 114 additions & 33 deletions

File tree

clients/da-vinci-client/src/main/java/com/linkedin/davinci/kafka/consumer/StoreIngestionTask.java

Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
import com.linkedin.davinci.helix.LeaderFollowerPartitionStateModel;
3535
import com.linkedin.davinci.ingestion.LagType;
3636
import com.linkedin.davinci.listener.response.AdminResponse;
37-
import com.linkedin.davinci.listener.response.NoOpReadResponseStats;
3837
import com.linkedin.davinci.notifier.VeniceNotifier;
3938
import com.linkedin.davinci.stats.AggVersionedDIVStats;
4039
import com.linkedin.davinci.stats.AggVersionedDaVinciRecordTransformerStats;
@@ -43,9 +42,7 @@
4342
import com.linkedin.davinci.stats.ingestion.heartbeat.HeartbeatMonitoringService;
4443
import com.linkedin.davinci.storage.StorageMetadataService;
4544
import com.linkedin.davinci.storage.StorageService;
46-
import com.linkedin.davinci.storage.chunking.ChunkedValueManifestContainer;
4745
import com.linkedin.davinci.storage.chunking.ChunkingUtils;
48-
import com.linkedin.davinci.storage.chunking.GenericChunkingAdapter;
4946
import com.linkedin.davinci.store.DelegatingStorageEngine;
5047
import com.linkedin.davinci.store.StorageEngine;
5148
import com.linkedin.davinci.store.StoragePartitionAdjustmentTrigger;
@@ -119,7 +116,6 @@
119116
import com.linkedin.venice.pubsub.manager.TopicManagerRepository;
120117
import com.linkedin.venice.pushmonitor.ExecutionStatus;
121118
import com.linkedin.venice.schema.SchemaEntry;
122-
import com.linkedin.venice.serialization.RawBytesStoreDeserializerCache;
123119
import com.linkedin.venice.serialization.avro.AvroProtocolDefinition;
124120
import com.linkedin.venice.serialization.avro.ChunkedValueManifestSerializer;
125121
import com.linkedin.venice.serialization.avro.InternalAvroSpecificSerializer;
@@ -4111,50 +4107,59 @@ protected void putInStorageEngine(int partition, byte[] keyBytes, Put put) {
41114107

41124108
protected void putGlobalRtDivStateInMetadata(int partition, byte[] keyBytes, Put put) {
41134109
if (put.schemaId == CHUNK_SCHEMA_ID) {
4114-
// Intermediate chunk: store in user-data storage for later assembly when the manifest arrives.
4115-
prependHeaderAndWriteToStorageEngine(partition, keyBytes, put);
4110+
// Intermediate chunk: store in the metadata partition for later assembly when the manifest arrives.
4111+
byte[] chunkPayload = ByteUtils.extractByteArray(put.putValue);
4112+
byte[] valueWithHeader = new byte[ValueRecord.SCHEMA_HEADER_LENGTH + chunkPayload.length];
4113+
ByteUtils.writeInt(valueWithHeader, CHUNK_SCHEMA_ID, 0);
4114+
System.arraycopy(chunkPayload, 0, valueWithHeader, ValueRecord.SCHEMA_HEADER_LENGTH, chunkPayload.length);
4115+
executeStorageEngineRunnable(
4116+
partition,
4117+
() -> storageEngine.putGlobalRtDivChunk(partition, keyBytes, valueWithHeader));
41164118
return;
41174119
}
41184120

41194121
if (put.schemaId == CHUNK_MANIFEST_SCHEMA_ID) {
4120-
// Manifest for a chunked GlobalRtDiv: write manifest to user-data storage, then assemble all chunks
4121-
// and persist the assembled (decompressed) value in the metadata partition.
4122-
prependHeaderAndWriteToStorageEngine(partition, keyBytes, put);
4123-
// Strip the KeyWithChunkingSuffixSerializer suffix to recover the original broker-URL key.
4122+
// Manifest for a chunked GlobalRtDiv: assemble all chunks from the metadata partition,
4123+
// decompress the result, then persist it in the metadata partition.
41244124
byte[] originalKeyBytes = Arrays.copyOf(keyBytes, keyBytes.length - KEY_CHUNKING_SUFFIX_LENGTH);
41254125
String key = new String(originalKeyBytes);
41264126
if (!key.startsWith(GLOBAL_RT_DIV_KEY_PREFIX)) {
41274127
throw new VeniceException("Invalid chunked Global RT DIV manifest key: " + Arrays.toString(keyBytes));
41284128
}
41294129
String brokerUrl = key.substring(GLOBAL_RT_DIV_KEY_PREFIX.length());
4130+
byte[] manifestBytes = ByteUtils.extractByteArray(put.putValue);
4131+
ChunkedValueManifest manifest = manifestSerializer.deserialize(manifestBytes, CHUNK_MANIFEST_SCHEMA_ID);
4132+
ByteBuffer assembled = ByteBuffer.allocate(manifest.size);
4133+
for (ByteBuffer chunkKeyBuf: manifest.keysWithChunkIdSuffix) {
4134+
byte[] chunkKey = ByteUtils.extractByteArray(chunkKeyBuf);
4135+
byte[] chunkBytes = storageEngine.getGlobalRtDivChunk(partition, chunkKey);
4136+
if (chunkBytes == null) {
4137+
throw new VeniceException(
4138+
"Missing GlobalRtDiv chunk in metadata partition for partition: " + partition + ", broker: " + brokerUrl);
4139+
}
4140+
if (ValueRecord.parseSchemaId(chunkBytes) != CHUNK_SCHEMA_ID) {
4141+
throw new VeniceException(
4142+
"Unexpected schema ID in GlobalRtDiv chunk: " + ValueRecord.parseSchemaId(chunkBytes));
4143+
}
4144+
assembled
4145+
.put(chunkBytes, ValueRecord.SCHEMA_HEADER_LENGTH, chunkBytes.length - ValueRecord.SCHEMA_HEADER_LENGTH);
4146+
}
4147+
assembled.flip();
41304148
try {
4131-
// Use the serialized manifest key (keyBytes) directly with isChunked=false so the adapter does not
4132-
// double-serialize it. All chunks were already written to user-data storage above.
4133-
ByteBuffer assembledValue = (ByteBuffer) GenericChunkingAdapter.INSTANCE.get(
4134-
storageEngine,
4149+
byte[] valueBytes =
4150+
ByteUtils.extractByteArray(compressor.get().decompress(assembled.array(), 0, assembled.limit()));
4151+
executeStorageEngineRunnable(
41354152
partition,
4136-
ByteBuffer.wrap(keyBytes),
4137-
false,
4138-
null,
4139-
null,
4140-
NoOpReadResponseStats.SINGLETON,
4141-
AvroProtocolDefinition.GLOBAL_RT_DIV_STATE.getCurrentProtocolVersion(),
4142-
RawBytesStoreDeserializerCache.getInstance(),
4143-
compressor.get(),
4144-
new ChunkedValueManifestContainer());
4145-
if (assembledValue != null) {
4146-
byte[] valueBytes = ByteUtils.extractByteArray(assembledValue);
4147-
executeStorageEngineRunnable(
4148-
partition,
4149-
() -> storageMetadataService.putGlobalRtDivState(kafkaVersionTopic, partition, brokerUrl, valueBytes));
4150-
} else {
4151-
LOGGER.warn("Assembled Global RT DIV value was null for partition: {}, broker: {}", partition, brokerUrl);
4152-
}
4153-
} catch (Exception e) {
4153+
() -> storageMetadataService.putGlobalRtDivState(kafkaVersionTopic, partition, brokerUrl, valueBytes));
4154+
} catch (IOException e) {
41544155
throw new VeniceException(
4155-
"Failed to assemble chunked Global RT DIV state for partition: " + partition + ", broker: " + brokerUrl,
4156+
"Failed to decompress assembled GlobalRtDiv state for partition: " + partition + ", broker: " + brokerUrl,
41564157
e);
41574158
}
4159+
for (ByteBuffer chunkKeyBuf: manifest.keysWithChunkIdSuffix) {
4160+
byte[] chunkKey = ByteUtils.extractByteArray(chunkKeyBuf);
4161+
executeStorageEngineRunnable(partition, () -> storageEngine.deleteGlobalRtDivChunk(partition, chunkKey));
4162+
}
41584163
return;
41594164
}
41604165

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ public abstract class AbstractStorageEngine<Partition extends AbstractStoragePar
5757
private static final byte[] VERSION_METADATA_KEY = "VERSION_METADATA".getBytes();
5858
private static final String PARTITION_METADATA_PREFIX = "P_";
5959
private static final String GLOBAL_RT_DIV_METADATA_PREFIX = "GRTD_";
60+
private static final String GLOBAL_RT_DIV_CHUNK_METADATA_PREFIX = "GRTD_CK_";
6061

6162
// Using a large positive number for metadata partition id instead of -1 can avoid database naming issues.
6263
public static final int METADATA_PARTITION_ID = 1000_000_000;
@@ -721,6 +722,43 @@ public synchronized void clearGlobalRtDivState(int partitionId, String brokerUrl
721722
metadataPartition.delete(getGlobalRtDivMetadataKey(partitionId, brokerUrl));
722723
}
723724

725+
@Override
726+
public synchronized void putGlobalRtDivChunk(int partitionId, byte[] chunkKey, byte[] chunkValue) {
727+
if (!metadataPartitionCreated()) {
728+
throw new StorageInitializationException("Metadata partition not created!");
729+
}
730+
if (partitionId < 0) {
731+
throw new IllegalArgumentException("Invalid partition id argument in putGlobalRtDivChunk");
732+
}
733+
metadataPartition.put(getGlobalRtDivChunkMetadataKey(partitionId, chunkKey), chunkValue);
734+
}
735+
736+
@Override
737+
public synchronized byte[] getGlobalRtDivChunk(int partitionId, byte[] chunkKey) {
738+
if (!metadataPartitionCreated()) {
739+
throw new StorageInitializationException("Metadata partition not created!");
740+
}
741+
if (partitionId < 0) {
742+
throw new IllegalArgumentException("Invalid partition id argument in getGlobalRtDivChunk");
743+
}
744+
return metadataPartition.get(getGlobalRtDivChunkMetadataKey(partitionId, chunkKey));
745+
}
746+
747+
@Override
748+
public synchronized void deleteGlobalRtDivChunk(int partitionId, byte[] chunkKey) {
749+
if (!metadataPartitionCreated()) {
750+
LOGGER.info(
751+
"Metadata partition not created; there is nothing to clear for {} partition {} chunk",
752+
storeVersionName,
753+
partitionId);
754+
return;
755+
}
756+
if (partitionId < 0) {
757+
throw new IllegalArgumentException("Invalid partition id argument in deleteGlobalRtDivChunk");
758+
}
759+
metadataPartition.delete(getGlobalRtDivChunkMetadataKey(partitionId, chunkKey));
760+
}
761+
724762
/**
725763
* Return true or false based on whether a given partition exists within this storage engine
726764
*
@@ -783,6 +821,14 @@ private static byte[] getGlobalRtDivMetadataKey(int partitionId, String brokerUr
783821
return (GLOBAL_RT_DIV_METADATA_PREFIX + partitionId + "_" + brokerUrl).getBytes();
784822
}
785823

824+
private static byte[] getGlobalRtDivChunkMetadataKey(int partitionId, byte[] chunkKey) {
825+
byte[] prefix = (GLOBAL_RT_DIV_CHUNK_METADATA_PREFIX + partitionId + "_").getBytes();
826+
byte[] metaKey = new byte[prefix.length + chunkKey.length];
827+
System.arraycopy(prefix, 0, metaKey, 0, prefix.length);
828+
System.arraycopy(chunkKey, 0, metaKey, prefix.length, chunkKey.length);
829+
return metaKey;
830+
}
831+
786832
private boolean metadataPartitionCreated() {
787833
return metadataPartition != null;
788834
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,21 @@ public void clearGlobalRtDivState(int partitionId, String brokerUrl) {
294294
this.delegate.clearGlobalRtDivState(partitionId, brokerUrl);
295295
}
296296

297+
@Override
298+
public void putGlobalRtDivChunk(int partitionId, byte[] chunkKey, byte[] chunkValue) {
299+
this.delegate.putGlobalRtDivChunk(partitionId, chunkKey, chunkValue);
300+
}
301+
302+
@Override
303+
public byte[] getGlobalRtDivChunk(int partitionId, byte[] chunkKey) {
304+
return this.delegate.getGlobalRtDivChunk(partitionId, chunkKey);
305+
}
306+
307+
@Override
308+
public void deleteGlobalRtDivChunk(int partitionId, byte[] chunkKey) {
309+
this.delegate.deleteGlobalRtDivChunk(partitionId, chunkKey);
310+
}
311+
297312
@Override
298313
public boolean containsPartition(int partitionId) {
299314
return this.delegate.containsPartition(partitionId);

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,21 @@ void putWithReplicationMetadata(int partitionId, byte[] key, ByteBuffer value, b
169169
*/
170170
void clearGlobalRtDivState(int partitionId, String brokerUrl);
171171

172+
/**
173+
* Put a GlobalRtDiv intermediate chunk (with schema header prepended) into the metadata partition.
174+
*/
175+
void putGlobalRtDivChunk(int partitionId, byte[] chunkKey, byte[] chunkValue);
176+
177+
/**
178+
* Retrieve a GlobalRtDiv intermediate chunk from the metadata partition.
179+
*/
180+
byte[] getGlobalRtDivChunk(int partitionId, byte[] chunkKey);
181+
182+
/**
183+
* Delete a GlobalRtDiv intermediate chunk from the metadata partition.
184+
*/
185+
void deleteGlobalRtDivChunk(int partitionId, byte[] chunkKey);
186+
172187
/**
173188
* Return true or false based on whether a given partition exists within this storage engine
174189
*

0 commit comments

Comments
 (0)