@@ -149,6 +149,8 @@ private static void Set(DbAddress at, in NibblePath key, in ReadOnlySpan<byte> d
149149 current = childAddr ;
150150 }
151151
152+ return ;
153+
152154 static bool TryTurnToFanOut ( Page page , Page child , int bucket , IBatchContext batch )
153155 {
154156 if ( page . Header . Level % 2 != 0 || page . Header . PageType != PageType . DataPage )
@@ -174,6 +176,8 @@ static bool TryTurnToFanOut(Page page, Page child, int bucket, IBatchContext bat
174176 var children = payload . Buckets ;
175177 payload . Buckets . Clear ( ) ;
176178
179+ // First, set all the grand-children right
180+
177181 for ( byte i = 0 ; i < Payload . NotFanOutBucketCount ; i ++ )
178182 {
179183 var p = batch . GetAt ( children [ i ] ) ;
@@ -197,14 +201,9 @@ static bool TryTurnToFanOut(Page page, Page child, int bucket, IBatchContext bat
197201 }
198202
199203 // The child data page is no longer needed and can be recycled with its Merkle side-cars
200- if ( dp . Data . MerkleLeft . IsNull == false )
204+ if ( dp . Data . Merkle . IsNull == false )
201205 {
202- batch . RegisterForFutureReuse ( batch . GetAt ( dp . Data . MerkleLeft ) , true ) ;
203- }
204-
205- if ( dp . Data . MerkleRight . IsNull == false )
206- {
207- batch . RegisterForFutureReuse ( batch . GetAt ( dp . Data . MerkleRight ) , true ) ;
206+ batch . RegisterForFutureReuse ( batch . GetAt ( dp . Data . Merkle ) , true ) ;
208207 }
209208
210209 batch . RegisterForFutureReuse ( dp . AsPage ( ) , true ) ;
@@ -231,16 +230,14 @@ private static bool TrySetAtBottom(in NibblePath key, ReadOnlySpan<byte> data, I
231230 /// </summary>
232231 private const int MerkleInMapToNibble = 3 ;
233232
234- private const int MerkleInRightFromInclusive = 9 ;
235-
236233 private static void SetLocally ( in NibblePath key , ReadOnlySpan < byte > data , IBatchContext batch , ref Payload payload ,
237234 PageHeader header )
238235 {
239236 // The key should be kept locally
240237 var map = new SlottedArray ( payload . DataSpan ) ;
241238
242239 // Check if deletion with empty local
243- if ( data . IsEmpty && payload . MerkleLeft . IsNull )
240+ if ( data . IsEmpty && payload . Merkle . IsNull )
244241 {
245242 map . Delete ( key ) ;
246243 return ;
@@ -252,88 +249,24 @@ private static void SetLocally(in NibblePath key, ReadOnlySpan<byte> data, IBatc
252249 return ;
253250 }
254251
255- var left = batch . EnsureWritableOrGetNew < ChildBottomPage > ( ref payload . MerkleLeft , header . Level ) ;
256-
257- ChildBottomPage right ;
258- if ( payload . MerkleRight . IsNull )
259- {
260- // Only left exist, try to move everything there.
261- foreach ( var item in map . EnumerateAll ( ) )
262- {
263- // We keep these three values
264- if ( ShouldBeKeptLocalInMap ( item . Key ) )
265- continue ;
266-
267- if ( left . Map . TrySet ( item . Key , item . RawData ) )
268- {
269- map . Delete ( item ) ;
270- }
271- }
272-
273- if ( map . TrySet ( key , data ) )
274- {
275- // All good, map can hold the data.
276- return ;
277- }
278-
279- // Not enough space. Create the right and perform the split
280- right = batch . GetNewPage < ChildBottomPage > ( out payload . MerkleRight , header . Level ) ;
281-
282- // Only left exist, try to move everything there.
283- foreach ( var item in left . Map . EnumerateAll ( ) )
284- {
285- // We keep these three values
286- if ( ShouldBeKeptInRight ( item . Key ) )
287- {
288- right . Map . Set ( item . Key , item . RawData ) ;
289- left . Map . Delete ( item ) ;
290- }
291- }
292- }
293-
294- right = new ChildBottomPage ( batch . EnsureWritableCopy ( ref payload . MerkleRight ) ) ;
252+ var merkle = payload . Merkle . IsNull
253+ ? batch . GetNewCleanPage < ChildBottomPage > ( out payload . Merkle , header . Level ) . AsPage ( )
254+ : batch . EnsureWritableCopy ( ref payload . Merkle ) ;
295255
296- // Redistribute keys again
256+ // Move all that are not local to the Merkle page.
297257 foreach ( var item in map . EnumerateAll ( ) )
298258 {
299259 // We keep these three values
300260 if ( ShouldBeKeptLocalInMap ( item . Key ) )
301261 continue ;
302262
303- if ( ShouldBeKeptInRight ( item . Key ) )
304- {
305- if ( item . RawData . IsEmpty )
306- right . Map . Delete ( item . Key ) ;
307- else
308- right . Map . Set ( item . Key , item . RawData ) ;
309- }
310- else
311- {
312- if ( item . RawData . IsEmpty )
313- left . Map . Delete ( item . Key ) ;
314- else
315- left . Map . Set ( item . Key , item . RawData ) ;
316- }
317-
263+ merkle . Set ( item . Key , item . RawData , batch ) ;
318264 map . Delete ( item ) ;
319265 }
320266
321- if ( ShouldBeKeptLocalInMap ( key ) )
322- {
323- map . Set ( key , data ) ;
324- }
325- else if ( ShouldBeKeptInRight ( key ) )
326- {
327- right . Map . Set ( key , data ) ;
328- }
329- else
330- {
331- left . Map . Set ( key , data ) ;
332- }
267+ map . Set ( key , data ) ;
333268 }
334269
335- private static bool ShouldBeKeptInRight ( in NibblePath key ) => key . Nibble0 >= MerkleInRightFromInclusive ;
336-
337270 private static bool ShouldBeKeptLocal ( in NibblePath key ) => key . IsEmpty || key . Length == 1 ;
338271
339272 /// <summary>
@@ -357,19 +290,15 @@ public struct Payload
357290 /// <summary>
358291 /// The size of the raw byte data held in this page. Must be long aligned.
359292 /// </summary>
360- private const int DataSize = Size - BucketSize - DbAddress . Size * 2 - sizeof ( int ) ;
293+ private const int DataSize = Size - BucketSize - DbAddress . Size - sizeof ( int ) ;
361294
362295 private const int DataOffset = Size - DataSize ;
363296
364-
365297 [ FieldOffset ( 0 ) ] public DbAddressList . Of256 Buckets ;
366298
367- [ FieldOffset ( BucketSize ) ] public DbAddress MerkleLeft ;
299+ [ FieldOffset ( BucketSize ) ] public DbAddress Merkle ;
368300
369301 [ FieldOffset ( BucketSize + DbAddress . Size ) ]
370- public DbAddress MerkleRight ;
371-
372- [ FieldOffset ( BucketSize + DbAddress . Size * 2 ) ]
373302 public int ChildDataPages ;
374303
375304 /// <summary>
@@ -385,14 +314,12 @@ public struct Payload
385314 public void Clear ( )
386315 {
387316 new SlottedArray ( DataSpan ) . Clear ( ) ;
388- MerkleLeft = default ;
389- MerkleRight = default ;
317+ Merkle = default ;
390318 Buckets . Clear ( ) ;
391319 ChildDataPages = 0 ;
392320 }
393321
394- public bool IsClean => MerkleLeft . IsNull &&
395- MerkleRight . IsNull &&
322+ public bool IsClean => Merkle . IsNull &&
396323 ChildDataPages == 0 &&
397324 new SlottedArray ( DataSpan ) . IsEmpty &&
398325 Buckets . IsClean ;
@@ -469,18 +396,12 @@ private bool TryGetLocally(scoped in NibblePath key, IPageResolver batch, out Re
469396 return true ;
470397 }
471398
472- // TODO: potential IO optimization to search left only if the right does not exist or the key does not belong to the right
473- if ( Data . MerkleLeft . IsNull == false && batch . GetAt ( Data . MerkleLeft ) . TryGet ( batch , key , out result ) )
399+ if ( Data . Merkle . IsNull )
474400 {
475- return true ;
476- }
477-
478- if ( Data . MerkleRight . IsNull == false && batch . GetAt ( Data . MerkleRight ) . TryGet ( batch , key , out result ) )
479- {
480- return true ;
401+ return false ;
481402 }
482403
483- return false ;
404+ return batch . GetAt ( Data . Merkle ) . TryGet ( batch , key , out result ) ;
484405 }
485406
486407 public SlottedArray Map => new ( Data . DataSpan ) ;
0 commit comments