3030import com .linkedin .davinci .stats .ingestion .heartbeat .HeartbeatMonitoringService ;
3131import com .linkedin .davinci .storage .StorageService ;
3232import com .linkedin .davinci .storage .chunking .ChunkedValueManifestContainer ;
33- import com .linkedin .davinci .storage .chunking .GenericChunkingAdapter ;
33+ import com .linkedin .davinci .storage .chunking .ChunkingUtils ;
3434import com .linkedin .davinci .storage .chunking .GenericRecordChunkingAdapter ;
35+ import com .linkedin .davinci .storage .chunking .RawBytesChunkingAdapter ;
3536import com .linkedin .davinci .store .StorageEngine ;
3637import com .linkedin .davinci .store .StoragePartitionAdjustmentTrigger ;
3738import com .linkedin .davinci .store .cache .backend .ObjectCacheBackend ;
112113import it .unimi .dsi .fastutil .ints .Int2ObjectMap ;
113114import java .io .IOException ;
114115import java .nio .ByteBuffer ;
116+ import java .nio .charset .StandardCharsets ;
115117import java .util .ArrayList ;
116118import java .util .Arrays ;
117119import java .util .Collections ;
177179 */
178180public class LeaderFollowerStoreIngestionTask extends StoreIngestionTask {
179181 private static final Logger LOGGER = LogManager .getLogger (LeaderFollowerStoreIngestionTask .class );
180- public static final String GLOBAL_RT_DIV_KEY_PREFIX = "GLOBAL_RT_DIV_KEY." ;
181182 static final long VIEW_WRITER_CLOSE_TIMEOUT_IN_MS = 60000 ; // 60s
182183
183184 /**
@@ -3827,14 +3828,17 @@ private void produceToLocalKafkaHelper(
38273828 }
38283829
38293830 /**
3830- * The Global RT DIV is produced on a per-broker basis, so the name includes the broker URL for differentiation.
3831+ * The Global RT DIV key includes the partition ID to prevent collisions when a server hosts multiple partitions,
3832+ * and the broker URL to distinguish between different upstream brokers.
38313833 */
3832- public static String getGlobalRtDivKeyName (String brokerUrl ) {
3833- return GLOBAL_RT_DIV_KEY_PREFIX + brokerUrl ;
3834+ public static String getGlobalRtDivKeyName (int partitionId , String brokerUrl ) {
3835+ return GLOBAL_RT_DIV_KEY_PREFIX + partitionId + "." + brokerUrl ;
38343836 }
38353837
3836- public byte [] getGlobalRtDivKeyBytes (String brokerUrl ) {
3837- return globalRtDivKeyBytesCache .computeIfAbsent (brokerUrl , url -> getGlobalRtDivKeyName (url ).getBytes ());
3838+ public byte [] getGlobalRtDivKeyBytes (int partitionId , String brokerUrl ) {
3839+ return globalRtDivKeyBytesCache .computeIfAbsent (
3840+ partitionId + "." + brokerUrl ,
3841+ k -> getGlobalRtDivKeyName (partitionId , brokerUrl ).getBytes (StandardCharsets .UTF_8 ));
38383842 }
38393843
38403844 /**
@@ -3852,7 +3856,7 @@ void sendGlobalRtDivMessage(
38523856 long beforeProcessingRecordTimestampNs ,
38533857 LeaderMetadataWrapper leaderMetadataWrapper ,
38543858 LeaderProducedRecordContext context ) {
3855- final byte [] keyBytes = getGlobalRtDivKeyBytes (brokerUrl );
3859+ final byte [] keyBytes = getGlobalRtDivKeyBytes (partition , brokerUrl );
38563860 final PubSubTopicPartition topicPartition = previousMessage .getTopicPartition ();
38573861 TopicType realTimeTopicType = TopicType .of (REALTIME_TOPIC_TYPE , brokerUrl );
38583862
@@ -3878,10 +3882,17 @@ void sendGlobalRtDivMessage(
38783882 topicPartition ,
38793883 vtDiv );
38803884
3881- // Get the old value manifest which contains the list of old chunks, so they can be deleted
3882- final int schemaId = AvroProtocolDefinition .GLOBAL_RT_DIV_STATE .getCurrentProtocolVersion ();
3885+ // Read the old manifest (if any) so VeniceWriter can delete orphaned old chunks in Kafka.
38833886 ChunkedValueManifestContainer valueManifestContainer = new ChunkedValueManifestContainer ();
3884- readGlobalRtDivState (keyBytes , schemaId , topicPartition , valueManifestContainer );
3887+ byte [] manifestKey = ChunkingUtils .KEY_WITH_CHUNKING_SUFFIX_SERIALIZER .serializeNonChunkedKey (keyBytes );
3888+ byte [] oldRaw = storageEngine .getGlobalRtDivMetadata (manifestKey );
3889+ if (oldRaw != null && oldRaw .length >= ValueRecord .SCHEMA_HEADER_LENGTH ) {
3890+ int schemaId = ValueRecord .parseSchemaId (oldRaw );
3891+ if (schemaId == AvroProtocolDefinition .CHUNKED_VALUE_MANIFEST .getCurrentProtocolVersion ()) {
3892+ valueManifestContainer
3893+ .setManifest (ChunkingUtils .CHUNKED_VALUE_MANIFEST_SERIALIZER .deserialize (oldRaw , schemaId ));
3894+ }
3895+ }
38853896
38863897 // TODO: remove. this is a temporary log for debugging while the feature is in its infancy
38873898 LOGGER .info (
@@ -3986,61 +3997,74 @@ private LeaderProducerCallback createGlobalRtDivCallback(
39863997 }
39873998
39883999 /**
3989- * Reads a GlobalRtDivState value from the storage engine. Returns null if the value does not exist.
4000+ * Reads a GlobalRtDivState value from the metadata storage partition.
4001+ * Returns null if the key does not carry the expected prefix or the value does not exist.
4002+ * For chunked states, chunk assembly is performed at read time via {@link RawBytesChunkingAdapter}.
39904003 *
3991- * @param keyBytes the serialized key for the value
3992- * @param readerValueSchemaID the schemaId to use for deserialization
4004+ * @param keyBytes the serialized key for the value (without any chunking suffix)
4005+ * @param readerValueSchemaID the schemaId to use when deserializing the assembled value
39934006 * @param topicPartition the topic/partition for the value
3994- * @param manifestContainer a container to store any manifest information retrieved from the storage engine
4007+ * @param manifestContainer populated with the {@link ChunkedValueManifest} if the value was chunked
39954008 * @return the deserialized GlobalRtDivState object, or null if the value does not exist
39964009 */
39974010 GlobalRtDivState readGlobalRtDivState (
39984011 byte [] keyBytes ,
39994012 int readerValueSchemaID ,
40004013 PubSubTopicPartition topicPartition ,
40014014 ChunkedValueManifestContainer manifestContainer ) {
4002- ByteBuffer valueBytes ;
4015+ final String key = new String (keyBytes , StandardCharsets .UTF_8 );
4016+ if (!key .startsWith (GLOBAL_RT_DIV_KEY_PREFIX )) {
4017+ return null ;
4018+ }
4019+ final int partitionId = topicPartition .getPartitionNumber ();
4020+ // Extract brokerUrl for logging (key format: GLOBAL_RT_DIV_KEY.{partitionId}.{brokerUrl})
4021+ final String afterPrefix = key .substring (GLOBAL_RT_DIV_KEY_PREFIX .length ());
4022+ final int dotIdx = afterPrefix .indexOf ('.' );
4023+ final String brokerUrl = dotIdx >= 0 ? afterPrefix .substring (dotIdx + 1 ) : afterPrefix ;
4024+
4025+ final BiFunction <Integer , ByteBuffer , byte []> metadataGetter =
4026+ (part , keyBuf ) -> storageEngine .getGlobalRtDivMetadata (ByteUtils .extractByteArray (keyBuf ));
4027+ ByteBuffer assembledBytes ;
40034028 try {
4004- valueBytes = (ByteBuffer ) GenericChunkingAdapter .INSTANCE .get (
4005- storageEngine ,
4006- topicPartition .getPartitionNumber (),
4029+ assembledBytes = RawBytesChunkingAdapter .INSTANCE .get (
4030+ metadataGetter ,
4031+ storageEngine .getStoreVersionName (),
4032+ partitionId ,
40074033 ByteBuffer .wrap (keyBytes ),
4008- isChunked ,
4034+ true , // must be true because Global RT DIV could always be large and require chunking
40094035 null ,
40104036 null ,
40114037 NoOpReadResponseStats .SINGLETON ,
40124038 readerValueSchemaID ,
40134039 RawBytesStoreDeserializerCache .getInstance (),
40144040 compressor .get (),
40154041 manifestContainer );
4016- } catch (Exception e ) {
4017- // TODO: evaluate whether these logs can be set to debug
4042+ } catch (VeniceException e ) {
40184043 LOGGER .error (
4019- "Unable to retrieve the stored value bytes for key: {}, topic-partition: {}" ,
4020- new String (keyBytes ),
4044+ "Unable to read Global RT DIV state from metadata storage for topic-partition: {}, brokerUrl: {}" ,
40214045 topicPartition ,
4046+ brokerUrl ,
40224047 e );
40234048 return null ;
40244049 }
4025-
4026- if (valueBytes == null ) {
4027- // TODO: evaluate whether these logs can be set to debug
4028- LOGGER .warn (
4029- "No value found in the storage engine for key: {}, topic-partition: {}" ,
4030- new String (keyBytes ),
4031- topicPartition );
4050+ if (assembledBytes == null ) {
40324051 return null ;
40334052 }
4053+ return deserializeGlobalRtDivState (ByteUtils .extractByteArray (assembledBytes ), keyBytes , topicPartition );
4054+ }
40344055
4056+ private GlobalRtDivState deserializeGlobalRtDivState (
4057+ byte [] serializedValueBytes ,
4058+ byte [] keyBytes ,
4059+ PubSubTopicPartition topicPartition ) {
40354060 try {
4036- return globalRtDivStateSerializer .deserialize (
4037- ByteUtils .extractByteArray (valueBytes ),
4038- AvroProtocolDefinition .GLOBAL_RT_DIV_STATE .getCurrentProtocolVersion ());
4061+ return globalRtDivStateSerializer
4062+ .deserialize (serializedValueBytes , AvroProtocolDefinition .GLOBAL_RT_DIV_STATE .getCurrentProtocolVersion ());
40394063 } catch (Exception e ) {
40404064 // TODO: evaluate whether these logs can be set to debug
40414065 LOGGER .error (
40424066 "Unable to deserialize stored value bytes for key: {}, topic-partition: {}" ,
4043- new String (keyBytes ),
4067+ new String (keyBytes , StandardCharsets . UTF_8 ),
40444068 topicPartition ,
40454069 e );
40464070 return null ;
@@ -4147,14 +4171,13 @@ void loadGlobalRtDiv(int partition, String brokerUrl) {
41474171 final PubSubTopic topic = pcs .getOffsetRecord ().getLeaderTopic (getPubSubTopicRepository ());
41484172 final PubSubTopicPartition topicPartition = new PubSubTopicPartitionImpl (topic , pcs .getPartition ());
41494173
4150- String globalRtDivKey = getGlobalRtDivKeyName (brokerUrl );
4151- byte [] keyBytes = globalRtDivKey .getBytes ();
4152- final ChunkedValueManifestContainer valueManifestContainer = new ChunkedValueManifestContainer ();
4174+ byte [] keyBytes = getGlobalRtDivKeyBytes (partition , brokerUrl );
41534175 GlobalRtDivState globalRtDivState = readGlobalRtDivState (
41544176 keyBytes ,
41554177 AvroProtocolDefinition .GLOBAL_RT_DIV_STATE .getCurrentProtocolVersion (),
41564178 topicPartition ,
4157- valueManifestContainer );
4179+ new ChunkedValueManifestContainer ());
4180+
41584181 if (globalRtDivState == null ) {
41594182 // If the GlobalRtDivState is not present, it could be acceptable if this could be the first leader to be elected
41604183 // Object not existing could be problematic if this isn't the first leader (detected via nonzero leaderPosition)
0 commit comments