From 0deacb9eea27c366c613ebc6dd55042375ef28ac Mon Sep 17 00:00:00 2001 From: Anton Persson Date: Wed, 13 Dec 2017 13:58:02 +0100 Subject: [PATCH] TreeNodeDynamicSize impl part 8 - split internal and merge leaf Also, RightmostInChain throw instead of setting cursor exception because it does not rely on any reads. --- .../internal/gbptree/InternalTreeLogic.java | 18 +- .../internal/gbptree/RightmostInChain.java | 9 +- .../index/internal/gbptree/TreeNode.java | 6 +- .../internal/gbptree/TreeNodeDynamicSize.java | 362 ++++++++++++++++-- .../internal/gbptree/TreeNodeFixedSize.java | 20 +- .../gbptree/InternalTreeLogicTestBase.java | 7 +- .../gbptree/PageAwareByteArrayCursor.java | 3 +- 7 files changed, 358 insertions(+), 67 deletions(-) diff --git a/community/index/src/main/java/org/neo4j/index/internal/gbptree/InternalTreeLogic.java b/community/index/src/main/java/org/neo4j/index/internal/gbptree/InternalTreeLogic.java index ae3624ce205d5..61450cdf7ffad 100644 --- a/community/index/src/main/java/org/neo4j/index/internal/gbptree/InternalTreeLogic.java +++ b/community/index/src/main/java/org/neo4j/index/internal/gbptree/InternalTreeLogic.java @@ -459,21 +459,10 @@ private void splitInternal( PageCursor cursor, StructurePropagation structu // Find position to insert new key int pos = positionOf( search( cursor, INTERNAL, newKey, readKey, keyCount ) ); - int keyCountAfterInsert = keyCount + 1; - int middlePos = middle( keyCountAfterInsert ); - // Update structurePropagation structurePropagation.hasRightKeyInsert = true; structurePropagation.midChild = current; structurePropagation.rightChild = newRight; - if ( middlePos == pos ) - { - layout.copyKey( newKey, structurePropagation.rightKey ); - } - else - { - bTreeNode.keyAt( cursor, structurePropagation.rightKey, pos < middlePos ? middlePos - 1 : middlePos, INTERNAL ); - } try ( PageCursor rightCursor = cursor.openLinkedCursor( newRight ) ) { @@ -482,11 +471,10 @@ private void splitInternal( PageCursor cursor, StructurePropagation structu bTreeNode.initializeInternal( rightCursor, stableGeneration, unstableGeneration ); TreeNode.setRightSibling( rightCursor, oldRight, stableGeneration, unstableGeneration ); TreeNode.setLeftSibling( rightCursor, current, stableGeneration, unstableGeneration ); - int rightKeyCount = keyCountAfterInsert - middlePos - 1; // -1 because don't keep prim key in internal // Do split - bTreeNode.doSplitInternal( cursor, keyCount, rightCursor, rightKeyCount, pos, newKey, newRightChild, middlePos, stableGeneration, - unstableGeneration ); + bTreeNode.doSplitInternal( cursor, keyCount, rightCursor, pos, newKey, newRightChild, stableGeneration, unstableGeneration, + structurePropagation ); } // Update old right with new left sibling (newRight) @@ -1068,7 +1056,7 @@ else if ( TreeNode.isNode( rightSibling ) ) rightSiblingCursor.next(); int rightSiblingKeyCount = TreeNode.keyCount( rightSiblingCursor ); - if ( bTreeNode.canMergeLeaves( keyCount, rightSiblingKeyCount ) ) + if ( bTreeNode.canMergeLeaves( cursor, keyCount, rightSiblingCursor, rightSiblingKeyCount ) ) { createSuccessorIfNeeded( rightSiblingCursor, structurePropagation, UPDATE_RIGHT_CHILD, stableGeneration, unstableGeneration ); diff --git a/community/index/src/main/java/org/neo4j/index/internal/gbptree/RightmostInChain.java b/community/index/src/main/java/org/neo4j/index/internal/gbptree/RightmostInChain.java index 61938b3b9b1bc..b599102bb3d5b 100644 --- a/community/index/src/main/java/org/neo4j/index/internal/gbptree/RightmostInChain.java +++ b/community/index/src/main/java/org/neo4j/index/internal/gbptree/RightmostInChain.java @@ -68,8 +68,9 @@ void assertNext( PageCursor cursor, long newRightmostNodeGeneration, String errorMessage = errorMessageBuilder.toString(); if ( !errorMessage.equals( "" ) ) { - setPatternException( cursor, newRightmostNodeGeneration, newRightmostLeftSiblingPointer, + errorMessage = addPatternToExceptionMessage( newRightmostNodeGeneration, newRightmostLeftSiblingPointer, newRightmostLeftSiblingPointerGeneration, newRightmostNode, errorMessage ); + throw new IllegalStateException( errorMessage ); } // Update currentRightmostNode = newRightmostNode; @@ -79,16 +80,16 @@ void assertNext( PageCursor cursor, long newRightmostNodeGeneration, currentRightmostRightSiblingPointerGeneration = newRightmostRightSiblingPointerGeneration; } - private void setPatternException( PageCursor cursor, long newRightmostGeneration, long leftSibling, + private String addPatternToExceptionMessage( long newRightmostGeneration, long leftSibling, long leftSiblingGeneration, long newRightmost, String errorMessage ) { - cursor.setCursorException( format( "%s" + + return format( "%s" + " Left siblings view: %s%n" + " Right siblings view: %s%n", errorMessage, leftPattern( currentRightmostNode, currentRightmostNodeGeneration, currentRightmostRightSiblingPointerGeneration, currentRightmostRightSiblingPointer ), - rightPattern( newRightmost, newRightmostGeneration, leftSiblingGeneration, leftSibling ) ) ); + rightPattern( newRightmost, newRightmostGeneration, leftSiblingGeneration, leftSibling ) ); } private String leftPattern( long actualLeftSibling, long actualLeftSiblingGeneration, diff --git a/community/index/src/main/java/org/neo4j/index/internal/gbptree/TreeNode.java b/community/index/src/main/java/org/neo4j/index/internal/gbptree/TreeNode.java index 970024895769d..91b4b7204ffa6 100644 --- a/community/index/src/main/java/org/neo4j/index/internal/gbptree/TreeNode.java +++ b/community/index/src/main/java/org/neo4j/index/internal/gbptree/TreeNode.java @@ -342,7 +342,7 @@ static void goTo( PageCursor cursor, String messageOnError, long nodeId ) */ abstract int canRebalanceLeaves( PageCursor leftCursor, int leftKeyCount, PageCursor rightCursor, int rightKeyCount ); - abstract boolean canMergeLeaves( int leftKeyCount, int rightKeyCount ); + abstract boolean canMergeLeaves( PageCursor leftCursor, int leftKeyCount, PageCursor rightCursor, int rightKeyCount ); /** * Calculate where split should be done and move entries between leaves participating in split. @@ -361,9 +361,9 @@ abstract void doSplitLeaf( PageCursor leftCursor, int leftKeyCount, PageCursor r * * Key count is updated. */ - abstract void doSplitInternal( PageCursor leftCursor, int leftKeyCount, PageCursor rightCursor, int rightKeyCount, int insertPos, + abstract void doSplitInternal( PageCursor leftCursor, int leftKeyCount, PageCursor rightCursor, int insertPos, KEY newKey, - long newRightChild, int middlePos, long stableGeneration, long unstableGeneration ); + long newRightChild, long stableGeneration, long unstableGeneration, StructurePropagation structurePropagation ); /** * Move all rightmost keys and values in left leaf from given position to right node. diff --git a/community/index/src/main/java/org/neo4j/index/internal/gbptree/TreeNodeDynamicSize.java b/community/index/src/main/java/org/neo4j/index/internal/gbptree/TreeNodeDynamicSize.java index 6312d2d9f26ae..d8b431a44f306 100644 --- a/community/index/src/main/java/org/neo4j/index/internal/gbptree/TreeNodeDynamicSize.java +++ b/community/index/src/main/java/org/neo4j/index/internal/gbptree/TreeNodeDynamicSize.java @@ -306,7 +306,7 @@ void defragmentLeaf( PageCursor cursor ) // Mark all offsets PrimitiveIntStack deadKeysOffset = new PrimitiveIntStack(); PrimitiveIntStack aliveKeysOffset = new PrimitiveIntStack(); - recordDeadAndAlive( cursor, deadKeysOffset, aliveKeysOffset ); + recordDeadAndAliveLeaf( cursor, deadKeysOffset, aliveKeysOffset ); /* BEFORE MOVE @@ -403,6 +403,108 @@ void defragmentLeaf( PageCursor cursor ) setDeadSpace( cursor, 0 ); } + private void defragmentInternal( PageCursor cursor ) + { + // Mark all offsets + PrimitiveIntStack deadKeysOffset = new PrimitiveIntStack(); + PrimitiveIntStack aliveKeysOffset = new PrimitiveIntStack(); + recordDeadAndAliveInternal( cursor, deadKeysOffset, aliveKeysOffset ); + + /* + BEFORE MOVE + v aliveRangeOffset + [X][_][_][X][_][X][_][_] + ^ ^ deadRangeOffset + |_____________ moveRangeOffset + + AFTER MOVE + v aliveRangeOffset + [X][_][_][X][X][_][_][_] + ^ deadRangeOffset + + */ + int maxKeyCount = pageSize / (bytesKeySize() + bytesKeyOffset() + childSize()); + int[] oldOffset = new int[maxKeyCount]; + int[] newOffset = new int[maxKeyCount]; + int oldOffsetCursor = 0; + int newOffsetCursor = 0; + int aliveRangeOffset = pageSize; // Everything after this point is alive + int deadRangeOffset; // Everything between this point and aliveRangeOffset is dead space + + // Rightmost alive keys does not need to move + while ( deadKeysOffset.peek() < aliveKeysOffset.peek() ) + { + aliveRangeOffset = aliveKeysOffset.poll(); + } + + do + { + // Locate next range of dead keys + deadRangeOffset = aliveRangeOffset; + while ( aliveKeysOffset.peek() < deadKeysOffset.peek() ) + { + deadRangeOffset = deadKeysOffset.poll(); + } + + // Locate next range of alive keys + int moveOffset = deadRangeOffset; + while ( deadKeysOffset.peek() < aliveKeysOffset.peek() ) + { + int moveKey = aliveKeysOffset.poll(); + oldOffset[oldOffsetCursor++] = moveKey; + moveOffset = moveKey; + } + + // Update offset mapping + int deadRangeSize = aliveRangeOffset - deadRangeOffset; + while ( oldOffsetCursor > newOffsetCursor ) + { + newOffset[newOffsetCursor] = oldOffset[newOffsetCursor] + deadRangeSize; + newOffsetCursor++; + } + + // Do move + while ( moveOffset < (deadRangeOffset - deadRangeSize) ) + { + // Move one block + deadRangeOffset -= deadRangeSize; + aliveRangeOffset -= deadRangeSize; + cursor.copyTo( deadRangeOffset, cursor, aliveRangeOffset, deadRangeSize ); + } + // Move the last piece + int lastBlockSize = deadRangeOffset - moveOffset; + deadRangeOffset -= lastBlockSize; + aliveRangeOffset -= lastBlockSize; + cursor.copyTo( deadRangeOffset, cursor, aliveRangeOffset, lastBlockSize ); + } + while ( !aliveKeysOffset.isEmpty() ); + // Update allocOffset + setAllocOffset( cursor, aliveRangeOffset ); + + // Update offset array + int keyCount = keyCount( cursor ); + keyPos: + for ( int pos = 0; pos < keyCount; pos++ ) + { + int keyPosOffset = keyPosOffsetInternal( pos ); + cursor.setOffset( keyPosOffset ); + int keyOffset = readKeyOffset( cursor ); + for ( int index = 0; index < oldOffsetCursor; index++ ) + { + if ( keyOffset == oldOffset[index] ) + { + // Overwrite with new offset + cursor.setOffset( keyPosOffset ); + putKeyOffset( cursor, newOffset[index] ); + continue keyPos; + } + } + } + + // Update dead space + setDeadSpace( cursor, 0 ); + } + @Override boolean leafUnderflow( PageCursor cursor, int keyCount ) { @@ -417,7 +519,6 @@ boolean leafUnderflow( PageCursor cursor, int keyCount ) @Override int canRebalanceLeaves( PageCursor leftCursor, int leftKeyCount, PageCursor rightCursor, int rightKeyCount ) { - // Know that right has underflow int leftActiveSpace = totalActiveSpace( leftCursor, leftKeyCount ); int rightActiveSpace = totalActiveSpace( rightCursor, rightKeyCount ); @@ -452,9 +553,12 @@ int canRebalanceLeaves( PageCursor leftCursor, int leftKeyCount, PageCursor righ } @Override - boolean canMergeLeaves( int leftKeyCount, int rightKeyCount ) + boolean canMergeLeaves( PageCursor leftCursor, int leftKeyCount, PageCursor rightCursor, int rightKeyCount ) { - throw new UnsupportedOperationException( "Implement me" ); + int leftActiveSpace = totalActiveSpace( leftCursor, leftKeyCount ); + int rightActiveSpace = totalActiveSpace( rightCursor, rightKeyCount ); + int totalSpace = totalSpace( pageSize ); + return totalSpace >= leftActiveSpace + rightActiveSpace; } @Override @@ -462,8 +566,8 @@ void doSplitLeaf( PageCursor leftCursor, int leftKeyCount, PageCursor rightCurso VALUE newValue, StructurePropagation structurePropagation ) { // Find middle - int middlePos = middle( leftCursor, insertPos, newKey, newValue ); int keyCountAfterInsert = leftKeyCount + 1; + int middlePos = middleLeaf( leftCursor, insertPos, newKey, newValue ); if ( middlePos == insertPos ) { @@ -504,6 +608,97 @@ void doSplitLeaf( PageCursor leftCursor, int leftKeyCount, PageCursor rightCurso TreeNode.setKeyCount( rightCursor, rightKeyCount ); } + @Override + void doSplitInternal( PageCursor leftCursor, int leftKeyCount, PageCursor rightCursor, int insertPos, KEY newKey, + long newRightChild, long stableGeneration, long unstableGeneration, StructurePropagation structurePropagation ) + { + int keyCountAfterInsert = leftKeyCount + 1; + int middlePos = middleInternal( leftCursor, insertPos, newKey ); + + if ( middlePos == insertPos ) + { + layout.copyKey( newKey, structurePropagation.rightKey ); + } + else + { + keyAt( leftCursor, structurePropagation.rightKey, insertPos < middlePos ? middlePos - 1 : middlePos, INTERNAL ); + } + int rightKeyCount = keyCountAfterInsert - middlePos - 1; // -1 because don't keep prim key in internal + + if ( insertPos < middlePos ) + { + // v-------v copy + // before key _,_,_,_,_,_,_,_,_,_ + // before child -,-,-,-,-,-,-,-,-,-,- + // insert key _,_,X,_,_,_,_,_,_,_,_ + // insert child -,-,-,x,-,-,-,-,-,-,-,- + // middle key ^ + + moveKeysAndChildren( leftCursor, middlePos, rightCursor, 0, rightKeyCount, true ); + // Rightmost key in left is the one we send up to parent, remove it from here. + removeKeyAndRightChildAt( leftCursor, middlePos - 1, middlePos ); + defragmentInternal( leftCursor ); + insertKeyAndRightChildAt( leftCursor, newKey, newRightChild, insertPos, middlePos - 1, stableGeneration, unstableGeneration ); + } + else + { + // pos > middlePos + // v-v first copy + // v-v-v second copy + // before key _,_,_,_,_,_,_,_,_,_ + // before child -,-,-,-,-,-,-,-,-,-,- + // insert key _,_,_,_,_,_,_,X,_,_,_ + // insert child -,-,-,-,-,-,-,-,x,-,-,- + // middle key ^ + + // pos == middlePos + // first copy + // v-v-v-v-v second copy + // before key _,_,_,_,_,_,_,_,_,_ + // before child -,-,-,-,-,-,-,-,-,-,- + // insert key _,_,_,_,_,X,_,_,_,_,_ + // insert child -,-,-,-,-,-,x,-,-,-,-,- + // middle key ^ + + // Keys + if ( insertPos == middlePos ) + { + int copyFrom = middlePos; + int copyCount = leftKeyCount - copyFrom; + moveKeysAndChildren( leftCursor, copyFrom, rightCursor, 0, copyCount, false ); + defragmentInternal( leftCursor ); + setChildAt( rightCursor, newRightChild, 0, stableGeneration, unstableGeneration ); + } + else + { + int copyFrom = middlePos + 1; + int copyCount = leftKeyCount - copyFrom; + moveKeysAndChildren( leftCursor, copyFrom, rightCursor, 0, copyCount, true ); + // Rightmost key in left is the one we send up to parent, remove it from here. + removeKeyAndRightChildAt( leftCursor, middlePos, middlePos + 1 ); + defragmentInternal( leftCursor ); + insertKeyAndRightChildAt( rightCursor, newKey, newRightChild, insertPos - copyFrom, copyCount, + stableGeneration, unstableGeneration ); + } + } + TreeNode.setKeyCount( leftCursor, middlePos ); + TreeNode.setKeyCount( rightCursor, rightKeyCount ); + } + + @Override + void moveKeyValuesFromLeftToRight( PageCursor leftCursor, int leftKeyCount, PageCursor rightCursor, int rightKeyCount, + int fromPosInLeftNode ) + { + defragmentLeaf( rightCursor ); + int numberOfKeysToMove = leftKeyCount - fromPosInLeftNode; + + // Push keys and values in right sibling to the right + insertSlotsAt( rightCursor, 0, numberOfKeysToMove, rightKeyCount, keyPosOffsetLeaf( 0 ), bytesKeySize() ); + + // Move + moveKeysAndValues( leftCursor, fromPosInLeftNode, rightCursor, 0, numberOfKeysToMove ); + } + private int getAllocSpace( PageCursor cursor, int keyCount, Type type ) { int allocOffset = getAllocOffset( cursor ); @@ -511,7 +706,7 @@ private int getAllocSpace( PageCursor cursor, int keyCount, Type type ) return allocOffset - endOfOffsetArray; } - private void recordDeadAndAlive( PageCursor cursor, PrimitiveIntStack deadKeysOffset, PrimitiveIntStack aliveKeysOffset ) + private void recordDeadAndAliveLeaf( PageCursor cursor, PrimitiveIntStack deadKeysOffset, PrimitiveIntStack aliveKeysOffset ) { int currentOffset = getAllocOffset( cursor ); while ( currentOffset < pageSize ) @@ -534,16 +729,78 @@ private void recordDeadAndAlive( PageCursor cursor, PrimitiveIntStack deadKeysOf } } + private void recordDeadAndAliveInternal( PageCursor cursor, PrimitiveIntStack deadKeysOffset, PrimitiveIntStack aliveKeysOffset ) + { + int currentOffset = getAllocOffset( cursor ); + while ( currentOffset < pageSize ) + { + cursor.setOffset( currentOffset ); + int keySize = readKeySize( cursor ); + boolean dead = hasTombstone( keySize ); + keySize = stripTombstone( keySize ); + + if ( dead ) + { + deadKeysOffset.push( currentOffset ); + } + else + { + aliveKeysOffset.push( currentOffset ); + } + currentOffset += keySize + bytesKeySize(); + } + } + private void moveKeysAndValues( PageCursor fromCursor, int fromPos, PageCursor toCursor, int toPos, int count ) { - int rightAllocOffset = getAllocOffset( toCursor ); + int toAllocOffset = getAllocOffset( toCursor ); for ( int i = 0; i < count; i++, toPos++ ) { - rightAllocOffset = transferRawKeyValue( fromCursor, fromPos + i, toCursor, rightAllocOffset ); + toAllocOffset = transferRawKeyValue( fromCursor, fromPos + i, toCursor, toAllocOffset ); toCursor.setOffset( keyPosOffsetLeaf( toPos ) ); - putKeyOffset( toCursor, rightAllocOffset ); + putKeyOffset( toCursor, toAllocOffset ); } - setAllocOffset( toCursor, rightAllocOffset ); + setAllocOffset( toCursor, toAllocOffset ); + } + + private void moveKeysAndChildren( PageCursor fromCursor, int fromPos, PageCursor toCursor, int toPos, int count, + boolean includeLeftMostChild ) + { + // All children + // This will also copy key offsets but those will be overwritten below. + int childFromOffset = includeLeftMostChild ? childOffset( fromPos ) : childOffset( fromPos + 1 ); + int childToOffset = childOffset( fromPos + count ) + childSize(); + int lengthInBytes = childToOffset - childFromOffset; + int targetOffset = includeLeftMostChild ? childOffset( 0 ) : childOffset( 1 ); + fromCursor.copyTo( childFromOffset, toCursor, targetOffset, lengthInBytes ); + + int toAllocOffset = getAllocOffset( toCursor ); + for ( int i = 0; i < count; i++, toPos++ ) + { + // Key + toAllocOffset = transferRawKey( fromCursor, fromPos + i, toCursor, toAllocOffset ); + toCursor.setOffset( keyPosOffsetInternal( toPos ) ); + putKeyOffset( toCursor, toAllocOffset ); + } + setAllocOffset( toCursor, toAllocOffset ); + } + + private int transferRawKey( PageCursor fromCursor, int fromPos, PageCursor toCursor, int toAllocOffset ) + { + // What to copy? + placeCursorAtActualKey( fromCursor, fromPos, INTERNAL ); + int fromKeyOffset = fromCursor.getOffset(); + int keySize = readKeySize( fromCursor ); + + // Copy + int toCopy = bytesKeySize() + keySize; + toAllocOffset -= toCopy; + fromCursor.copyTo( fromKeyOffset, toCursor, toAllocOffset, toCopy ); + + // Put tombstone + fromCursor.setOffset( fromKeyOffset ); + putTombstone( fromCursor ); + return toAllocOffset; } /** @@ -551,7 +808,7 @@ private void moveKeysAndValues( PageCursor fromCursor, int fromPos, PageCursor t * Mark transferred key as dead. * @return new alloc offset in 'to' */ - private int transferRawKeyValue( PageCursor fromCursor, int fromPos, PageCursor toCursor, int rightAllocOffset ) + private int transferRawKeyValue( PageCursor fromCursor, int fromPos, PageCursor toCursor, int toAllocOffset ) { // What to copy? placeCursorAtActualKey( fromCursor, fromPos, LEAF ); @@ -561,7 +818,7 @@ private int transferRawKeyValue( PageCursor fromCursor, int fromPos, PageCursor // Copy int toCopy = bytesKeySize() + bytesValueSize() + keySize + valueSize; - int newRightAllocSpace = rightAllocOffset - toCopy; + int newRightAllocSpace = toAllocOffset - toCopy; fromCursor.copyTo( fromKeyOffset, toCursor, newRightAllocSpace, toCopy ); // Put tombstone @@ -570,7 +827,42 @@ private int transferRawKeyValue( PageCursor fromCursor, int fromPos, PageCursor return newRightAllocSpace; } - private int middle( PageCursor leftCursor, int insertPos, KEY newKey, VALUE newValue ) + private int middleInternal( PageCursor cursor, int insertPos, KEY newKey ) + { + int halfSpace = halfSpace(); + int middle = 0; + int currentPos = 0; + int middleSpace = childSize(); // Leftmost child will always be included in left side + int currentDelta = Math.abs( middleSpace - halfSpace ); + int prevDelta; + boolean includedNew = false; + + do + { + // We may come closer to split by keeping one more in left + middle++; + currentPos++; + int space; + if ( currentPos == insertPos & !includedNew ) + { + space = totalSpaceOfKeyChild( newKey ); + includedNew = true; + currentPos--; + } + else + { + space = totalSpaceOfKeyChild( cursor, currentPos ); + } + middleSpace += space; + prevDelta = currentDelta; + currentDelta = Math.abs( middleSpace - halfSpace ); + } + while ( currentDelta < prevDelta ); + middle--; // Step back to the pos that most equally divide the available space in two + return middle; + } + + private int middleLeaf( PageCursor cursor, int insertPos, KEY newKey, VALUE newValue ) { int halfSpace = halfSpace(); int middle = 0; @@ -594,7 +886,7 @@ private int middle( PageCursor leftCursor, int insertPos, KEY newKey, VALUE newV } else { - space = totalSpaceOfKeyValue( leftCursor, currentPos ); + space = totalSpaceOfKeyValue( cursor, currentPos ); } middleSpace += space; prevDelta = currentDelta; @@ -640,25 +932,11 @@ private int totalSpaceOfKeyValue( PageCursor cursor, int pos ) return bytesKeyOffset() + bytesKeySize() + bytesValueSize() + keySize + valueSize; } - @Override - void doSplitInternal( PageCursor leftCursor, int leftKeyCount, PageCursor rightCursor, int rightKeyCount, int insertPos, KEY newKey, - long newRightChild, int middlePos, long stableGeneration, long unstableGeneration ) + private int totalSpaceOfKeyChild( PageCursor cursor, int pos ) { - throw new UnsupportedOperationException( "Implement me" ); - } - - @Override - void moveKeyValuesFromLeftToRight( PageCursor leftCursor, int leftKeyCount, PageCursor rightCursor, int rightKeyCount, - int fromPosInLeftNode ) - { - defragmentLeaf( rightCursor ); - int numberOfKeysToMove = leftKeyCount - fromPosInLeftNode; - - // Push keys and values in right sibling to the right - insertSlotsAt( rightCursor, 0, numberOfKeysToMove, rightKeyCount, keyPosOffsetLeaf( 0 ), bytesKeySize() ); - - // Move - moveKeysAndValues( leftCursor, fromPosInLeftNode, rightCursor, 0, numberOfKeysToMove ); + placeCursorAtActualKey( cursor, pos, INTERNAL ); + int keySize = readKeySize( cursor ); + return bytesKeyOffset() + bytesKeySize() + childSize() + keySize; } private void setAllocOffset( PageCursor cursor, int allocOffset ) @@ -697,7 +975,7 @@ private void placeCursorAtActualKey( PageCursor cursor, int pos, Type type ) int keyOffset = readKeyOffset( cursor ); // Verify offset is reasonable - if ( keyOffset > pageSize ) + if ( keyOffset > pageSize || keyOffset < 0 ) { cursor.setCursorException( "Tried to read key on offset " + keyOffset + ". Page size is " + pageSize ); } @@ -765,8 +1043,7 @@ public String toString() return "TreeNodeDynamicSize[pageSize:" + pageSize + ", keyValueSizeCap:" + keyValueSizeCap + "]"; } - @SuppressWarnings( "unused" ) - void printNode( PageCursor cursor, boolean includeValue, long stableGeneration, long unstableGeneration ) + String asString( PageCursor cursor, boolean includeValue, long stableGeneration, long unstableGeneration ) { int currentOffset = cursor.getOffset(); // [header] <- dont care @@ -777,7 +1054,8 @@ void printNode( PageCursor cursor, boolean includeValue, long stableGeneration, // HEADER int allocSpace = getAllocOffset( cursor ); - String additionalHeader = "[allocSpace=" + allocSpace + "]"; + int deadSpace = getDeadSpace( cursor ); + String additionalHeader = "{" + cursor.getCurrentPageId() + "} [allocSpace=" + allocSpace + " deadSpace=" + deadSpace + "] "; // OFFSET ARRAY String offsetArray = readOffsetArray( cursor, stableGeneration, unstableGeneration, type ); @@ -785,7 +1063,7 @@ void printNode( PageCursor cursor, boolean includeValue, long stableGeneration, // KEYS KEY readKey = layout.newKey(); VALUE readValue = layout.newValue(); - StringJoiner keys = new StringJoiner( "][", "[", "]" ); + StringJoiner keys = new StringJoiner( " "); cursor.setOffset( allocSpace ); while ( cursor.getOffset() < cursor.getCurrentPageSize() ) { @@ -824,14 +1102,20 @@ void printNode( PageCursor cursor, boolean includeValue, long stableGeneration, keys.add( singleKey.toString() ); } - System.out.println( additionalHeader + offsetArray + keys ); cursor.setOffset( currentOffset ); + return additionalHeader + offsetArray + " " + keys; + } + + @SuppressWarnings( "unused" ) + void printNode( PageCursor cursor, boolean includeValue, long stableGeneration, long unstableGeneration ) + { + System.out.println( asString( cursor, includeValue, stableGeneration, unstableGeneration ) ); } private String readOffsetArray( PageCursor cursor, long stableGeneration, long unstableGeneration, Type type ) { int keyCount = keyCount( cursor ); - StringJoiner offsetArray = new StringJoiner( ",", "[", "]" ); + StringJoiner offsetArray = new StringJoiner( " " ); for ( int i = 0; i < keyCount; i++ ) { if ( type == INTERNAL ) diff --git a/community/index/src/main/java/org/neo4j/index/internal/gbptree/TreeNodeFixedSize.java b/community/index/src/main/java/org/neo4j/index/internal/gbptree/TreeNodeFixedSize.java index 4b61837f0563a..5cfd3c2f2983d 100644 --- a/community/index/src/main/java/org/neo4j/index/internal/gbptree/TreeNodeFixedSize.java +++ b/community/index/src/main/java/org/neo4j/index/internal/gbptree/TreeNodeFixedSize.java @@ -24,6 +24,7 @@ import static org.neo4j.index.internal.gbptree.GenerationSafePointerPair.read; import static org.neo4j.index.internal.gbptree.Layout.FIXED_SIZE_KEY; import static org.neo4j.index.internal.gbptree.Layout.FIXED_SIZE_VALUE; +import static org.neo4j.index.internal.gbptree.TreeNode.Type.INTERNAL; import static org.neo4j.index.internal.gbptree.TreeNode.Type.LEAF; class TreeNodeFixedSize extends TreeNode @@ -291,7 +292,7 @@ int canRebalanceLeaves( PageCursor leftCursor, int leftKeyCount, PageCursor righ } @Override - boolean canMergeLeaves( int leftKeyCount, int rightKeyCount ) + boolean canMergeLeaves( PageCursor leftCursor, int leftKeyCount, PageCursor rightCursor, int rightKeyCount ) { return leftKeyCount + rightKeyCount <= leafMaxKeyCount(); } @@ -353,9 +354,22 @@ private static int middle( int keyCountAfterInsert ) } @Override - void doSplitInternal( PageCursor leftCursor, int leftKeyCount, PageCursor rightCursor, int rightKeyCount, int insertPos, KEY newKey, - long newRightChild, int middlePos, long stableGeneration, long unstableGeneration ) + void doSplitInternal( PageCursor leftCursor, int leftKeyCount, PageCursor rightCursor, int insertPos, KEY newKey, + long newRightChild, long stableGeneration, long unstableGeneration, StructurePropagation structurePropagation ) { + int keyCountAfterInsert = leftKeyCount + 1; + int middlePos = middle( keyCountAfterInsert ); + + if ( middlePos == insertPos ) + { + layout.copyKey( newKey, structurePropagation.rightKey ); + } + else + { + keyAt( leftCursor, structurePropagation.rightKey, insertPos < middlePos ? middlePos - 1 : middlePos, INTERNAL ); + } + int rightKeyCount = keyCountAfterInsert - middlePos - 1; // -1 because don't keep prim key in internal + if ( insertPos < middlePos ) { // v-------v copy diff --git a/community/index/src/test/java/org/neo4j/index/internal/gbptree/InternalTreeLogicTestBase.java b/community/index/src/test/java/org/neo4j/index/internal/gbptree/InternalTreeLogicTestBase.java index 477cec216ff76..413377a770275 100644 --- a/community/index/src/test/java/org/neo4j/index/internal/gbptree/InternalTreeLogicTestBase.java +++ b/community/index/src/test/java/org/neo4j/index/internal/gbptree/InternalTreeLogicTestBase.java @@ -858,7 +858,8 @@ public void mustPropagateStructureOnMergeToRight() throws Exception // new left child contain keys from old left and old middle goTo( readCursor, oldRightChild ); KEY firstKeyInOldRightChild = keyAt( 0, LEAF ); - List expectedKeysInNewLeftChild = allKeys.subList( 0, allKeys.indexOf( firstKeyInOldRightChild ) ); + int index = indexOf( firstKeyInOldRightChild, allKeys, layout ); + List expectedKeysInNewLeftChild = allKeys.subList( 0, index ); goTo( readCursor, newLeftChild ); assertNodeContainsExpectedKeys( expectedKeysInNewLeftChild, LEAF ); @@ -886,6 +887,7 @@ public void mustPropagateStructureWhenMergingBetweenDifferentSubtrees() throws E i++; } + goTo( readCursor, rootId ); long oldLeft = rightmostLeafInSubtree( rootId, 0 ); long oldRight = leftmostLeafInSubtree( rootId, 1 ); KEY oldSplitter = keyAt( 0, INTERNAL ); @@ -961,7 +963,8 @@ public void modifierMustProduceConsistentTreeWithRandomInserts() throws Exceptio for ( int i = 0; i < numberOfEntries; i++ ) { // when - insert( key( random.nextLong() ), value( random.nextLong() ) ); + long keySeed = random.nextLong(); + insert( key( keySeed ), value( random.nextLong() ) ); if ( i == numberOfEntries / 2 ) { generationManager.checkpoint(); diff --git a/community/index/src/test/java/org/neo4j/index/internal/gbptree/PageAwareByteArrayCursor.java b/community/index/src/test/java/org/neo4j/index/internal/gbptree/PageAwareByteArrayCursor.java index 07f984c9921cd..64919e5070f3e 100644 --- a/community/index/src/test/java/org/neo4j/index/internal/gbptree/PageAwareByteArrayCursor.java +++ b/community/index/src/test/java/org/neo4j/index/internal/gbptree/PageAwareByteArrayCursor.java @@ -126,7 +126,8 @@ public int copyTo( int sourceOffset, PageCursor targetCursor, int targetOffset, { if ( sourceOffset < 0 || targetOffset < 0 || lengthInBytes < 0 ) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException( + String.format( "sourceOffset=%d, targetOffset=%d, lengthInBytes=%d", sourceOffset, targetOffset, lengthInBytes ) ); } int bytesToCopy = Math.min( lengthInBytes, Math.min( current.getCurrentPageSize() - sourceOffset,