Skip to content

Commit c09cae0

Browse files
committed
search all maps to dedupe;
1 parent 10e3667 commit c09cae0

File tree

1 file changed

+53
-57
lines changed

1 file changed

+53
-57
lines changed

hollow/src/main/java/com/netflix/hollow/core/write/HollowTypeWriteState.java

Lines changed: 53 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)