@@ -138,30 +138,42 @@ public int add(HollowWriteRecord rec) {
138138 return ordinal ;
139139 }
140140
141+ /**
142+ * Search all maps in the given array for a record matching the scratch bytes.
143+ * @return the global ordinal if found, or -1 if not found
144+ */
145+ private int searchAllMaps (ByteArrayOrdinalMap [] maps , ByteDataArray scratch , int hash ) {
146+ for (int i = 0 ; i < maps .length ; i ++) {
147+ int found = maps [i ].get (scratch , hash );
148+ if (found != -1 ) {
149+ return (found << ordinalMapIndexBits ) | i ;
150+ }
151+ }
152+ return -1 ;
153+ }
154+
141155 /*
142156 * Assign an ordinal to a record. Use the record hash to determine the which ordinal map it belongs to and
143157 * its ordinal within that map (localOrdinal). Then convert the local ordinal to a global ordinal and return.
144- *
158+ *
145159 * @param rec the record to assign an ordinal to
146160 * @return the ordinal of the record
147161 */
148162 private int assignOrdinal (HollowWriteRecord rec ) {
149163 ByteDataArray scratch = scratch ();
150164 rec .writeDataTo (scratch );
151-
152- // Compute hash once for both routing and deduplication
153165 int hash = HashCodes .hashCode (scratch );
154- int mapIndex = hash & ordinalMapIndexMask ;
155166
156- // Get local ordinal from the selected map, passing pre-computed hash
157- // NOTE: non empty ordinal value is in [0, 2<<29 -1], hence even shifted left by 2, it'd still be a positive
158- // integer.
159- int localOrdinal = ordinalMaps [mapIndex ].getOrAssignOrdinal (scratch , hash , -1 );
167+ // Search ALL maps for existing record (dedup across maps)
168+ int existingGlobal = searchAllMaps (ordinalMaps , scratch , hash );
169+ if (existingGlobal != -1 ) {
170+ scratch .reset ();
171+ return existingGlobal ;
172+ }
160173
161- // Convert local ordinal to global interleaved ordinal
162- // Map 0: local 0,1,2 → global 0,8,16
163- // Map 1: local 0,1,2 → global 1,9,17
164- // etc.
174+ // Not found — assign in hash-routed map
175+ int mapIndex = hash & ordinalMapIndexMask ;
176+ int localOrdinal = ordinalMaps [mapIndex ].getOrAssignOrdinal (scratch , hash , -1 );
165177 int globalOrdinal = (localOrdinal << ordinalMapIndexBits ) | mapIndex ;
166178
167179 scratch .reset ();
@@ -172,60 +184,44 @@ private int assignOrdinal(HollowWriteRecord rec) {
172184 private int reuseOrdinalFromRestoredState (HollowWriteRecord rec ) {
173185 ByteDataArray scratch = scratch ();
174186
175- int ordinal ;
176-
187+ // Serialize with restored-compatible format and find preferred ordinal
188+ int preferredGlobal ;
177189 if (restoredSchema instanceof HollowObjectSchema ) {
178190 ((HollowObjectWriteRecord )rec ).writeDataTo (scratch , (HollowObjectSchema )restoredSchema );
191+ } else if (rec instanceof HollowHashableWriteRecord ) {
192+ ((HollowHashableWriteRecord ) rec ).writeDataTo (scratch , IGNORED_HASHES );
193+ } else {
194+ rec .writeDataTo (scratch );
195+ }
196+ preferredGlobal = searchAllMaps (restoredMaps , scratch , HashCodes .hashCode (scratch ));
179197
180- // Compute hash to route to correct map
181- int hash = HashCodes .hashCode (scratch );
182- int mapIndex = hash & ordinalMapIndexMask ;
183-
184- // Look up in restored map for this index (returns local ordinal)
185- int preferredLocalOrdinal = restoredMaps [mapIndex ].get (scratch , hash );
186-
187- // rewrite the byte representation, as the current schema can be different from the restored schema.
188- // i.e. new fields added into this type.
198+ // Re-serialize with current schema if it could differ from the restored format
199+ if (restoredSchema instanceof HollowObjectSchema || rec instanceof HollowHashableWriteRecord ) {
189200 scratch .reset ();
190201 rec .writeDataTo (scratch );
191- hash = HashCodes .hashCode (scratch );
192- mapIndex = hash & ordinalMapIndexMask ;
193-
194- int newLocalOrdinal = ordinalMaps [mapIndex ].getOrAssignOrdinal (scratch , hash , preferredLocalOrdinal );
195- ordinal = (newLocalOrdinal << ordinalMapIndexBits ) | mapIndex ;
196- } else {
197- if (rec instanceof HollowHashableWriteRecord ) {
198- ((HollowHashableWriteRecord ) rec ).writeDataTo (scratch , IGNORED_HASHES );
199-
200- int hash = HashCodes .hashCode (scratch );
201- int mapIndex = hash & ordinalMapIndexMask ;
202- int preferredLocalOrdinal = restoredMaps [mapIndex ].get (scratch , hash );
203-
204- scratch .reset ();
205- rec .writeDataTo (scratch );
206-
207- hash = HashCodes .hashCode (scratch );
208- mapIndex = hash & ordinalMapIndexMask ;
209-
210- int newLocalOrdinal = ordinalMaps [mapIndex ].getOrAssignOrdinal (scratch , hash , preferredLocalOrdinal );
211- ordinal = (newLocalOrdinal << ordinalMapIndexBits ) | mapIndex ;
212- } else {
213- rec .writeDataTo (scratch );
214-
215- int hash = HashCodes .hashCode (scratch );
216- int mapIndex = hash & ordinalMapIndexMask ;
217- int preferredLocalOrdinal = restoredMaps [mapIndex ].get (scratch , hash );
218-
219- int newLocalOrdinal = ordinalMaps [mapIndex ].getOrAssignOrdinal (scratch , hash , preferredLocalOrdinal );
220- ordinal = (newLocalOrdinal << ordinalMapIndexBits ) | mapIndex ;
221- }
222202 }
223203
204+ int ordinal = assignToPreferredMap (scratch , preferredGlobal );
224205 scratch .reset ();
225-
226206 return ordinal ;
227207 }
228208
209+ /**
210+ * Assign a record (already serialized in scratch) into the ordinal map corresponding to
211+ * the preferred global ordinal, or hash-route if no preferred ordinal exists.
212+ */
213+ private int assignToPreferredMap (ByteDataArray scratch , int preferredGlobal ) {
214+ int hash = HashCodes .hashCode (scratch );
215+ int mapIndex = preferredGlobal != -1
216+ ? (preferredGlobal & ordinalMapIndexMask )
217+ : (hash & ordinalMapIndexMask );
218+ int preferredLocal = preferredGlobal != -1
219+ ? (preferredGlobal >>> ordinalMapIndexBits )
220+ : -1 ;
221+ int newLocal = ordinalMaps [mapIndex ].getOrAssignOrdinal (scratch , hash , preferredLocal );
222+ return (newLocal << ordinalMapIndexBits ) | mapIndex ;
223+ }
224+
229225 /**
230226 * Resets this write state to empty (i.e. as if prepareForNextCycle() had just been called)
231227 */
@@ -387,8 +383,8 @@ public void prepareForNextCycle() {
387383 ordinalMaps [i ].resetLogSoftLimitsBreach ();
388384 }
389385
390- // Save current maps as restored maps for next cycle
391- restoredMaps = ordinalMaps . clone () ;
386+ // Null out restored maps — cycle 2+ uses assignOrdinal() with search-all dedup
387+ restoredMaps = null ;
392388
393389 ThreadSafeBitSet temp = previousCyclePopulated ;
394390 previousCyclePopulated = currentCyclePopulated ;
0 commit comments