From 7bf66b2c2eedcc0902bc17eea6ff515893fe87c3 Mon Sep 17 00:00:00 2001 From: Anton Persson Date: Mon, 13 Feb 2017 13:07:18 +0100 Subject: [PATCH] GBPTree - Merge implemented An underflow in some node has occured and we want to merge node L into node R. L is left sibling to R and vice versa. It is done like this: Phase 1 (Move data) 1. Left sibling of L is LL. Set right sibling of LL to be R and left sibling of R to be LL. Effectively removing L from the leaf chain. 2. Set new generation of L to be R. 3. Add L to freelist. 4. Copy all keys and values from L to R. 5. Update partent pointers to match changes, new pointer to both L and R. Phase 2 (Update splitter key in internal nodes) Parent of L and R (potentially same internal node) now both hold pointer to the same leaf, R. This is ok for now, because it just means we have to different paths to the same leaf. We do however want to remove one of those paths together with some key to not store more than what we need among the internal nodes. L and R are part of different subtrees, subL and subR. (Note that it is possible that subL and subR only contain L och R respectively.) Because L and R are sibling, we know that subL and subR are splitted by exactly one key, the 'splitter', which is located in the closest common parent of subL and subR. subL is located just to the left of splitter and subR just to the right. splitter should be replaced by the key that previously would guide a search towards L. This key is the rightmost key in subL. If no such key can be found, if subL only contains L or if none of the internal nodes in subL has any keys, then splitter is removed together with subL. So: 6. Bubble up rightmost key from subL. 7. Every internal node on the bubble path that contains zero keys can be added to freelist. 8. Replace splitter with bubble key or remove if no bubble key could be found. 9. If root end up with zero keys, shrink the tree. --- .../neo4j/index/internal/gbptree/GBPTree.java | 5 +- .../internal/gbptree/InternalTreeLogic.java | 388 ++++++++++++++++-- .../index/internal/gbptree/SeekCursor.java | 2 +- .../gbptree/StructurePropagation.java | 20 +- .../index/internal/gbptree/TreeNode.java | 9 +- .../gbptree/ConsistencyCheckerTest.java | 3 +- .../gbptree/GBPTreeConcurrencyIT.java | 11 +- .../gbptree/InternalTreeLogicTest.java | 12 +- .../internal/gbptree/SeekCursorTest.java | 2 +- 9 files changed, 392 insertions(+), 60 deletions(-) diff --git a/community/index/src/main/java/org/neo4j/index/internal/gbptree/GBPTree.java b/community/index/src/main/java/org/neo4j/index/internal/gbptree/GBPTree.java index 4f2e79e6cedaa..31dd76a0bc33d 100644 --- a/community/index/src/main/java/org/neo4j/index/internal/gbptree/GBPTree.java +++ b/community/index/src/main/java/org/neo4j/index/internal/gbptree/GBPTree.java @@ -795,7 +795,7 @@ private class SingleWriter implements Writer SingleWriter( InternalTreeLogic treeLogic ) { - this.structurePropagation = new StructurePropagation<>( layout.newKey(), layout.newKey() ); + this.structurePropagation = new StructurePropagation<>( layout.newKey(), layout.newKey(), layout.newKey() ); this.treeLogic = treeLogic; } @@ -843,8 +843,9 @@ else if ( structurePropagation.hasMidChildUpdate ) checkOutOfBounds( cursor ); } - private void setRoot( long rootId ) + private void setRoot( long rootPointer ) { + long rootId = GenSafePointerPair.pointer( rootPointer ); GBPTree.this.setRoot( rootId, unstableGeneration ); treeLogic.initialize( cursor ); } 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 425fdb89f01e0..bff232333d8b7 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 @@ -27,6 +27,10 @@ import static org.neo4j.index.internal.gbptree.KeySearch.isHit; import static org.neo4j.index.internal.gbptree.KeySearch.positionOf; +import static org.neo4j.index.internal.gbptree.StructurePropagation.KeyReplaceStrategy.BUBBLE; +import static org.neo4j.index.internal.gbptree.StructurePropagation.KeyReplaceStrategy.REPLACE; +import static org.neo4j.index.internal.gbptree.StructurePropagation.UPDATE_MID_CHILD; +import static org.neo4j.index.internal.gbptree.StructurePropagation.UPDATE_RIGHT_CHILD; /** * Implementation of GB+ tree insert/remove algorithms. @@ -352,9 +356,7 @@ void insert( PageCursor cursor, StructurePropagation structurePropagation, if ( structurePropagation.hasMidChildUpdate ) { - structurePropagation.hasMidChildUpdate = false; - bTreeNode.setChildAt( - cursor, structurePropagation.midChild, pos, stableGeneration, unstableGeneration ); + updateMidChild( cursor, structurePropagation, pos, stableGeneration, unstableGeneration ); } if ( structurePropagation.hasRightKeyInsert ) { @@ -405,7 +407,7 @@ private void insertInInternal( PageCursor cursor, StructurePropagation stru KEY primKey, long rightChild, long stableGeneration, long unstableGeneration ) throws IOException { - createUnstableVersionIfNeeded( cursor, structurePropagation, StructurePropagation.UPDATE_MID_CHILD, + createUnstableVersionIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, stableGeneration, unstableGeneration ); if ( keyCount < bTreeNode.internalMaxKeyCount() ) { @@ -608,7 +610,7 @@ private void insertInLeaf( PageCursor cursor, StructurePropagation structur VALUE mergedValue = valueMerger.merge( readValue, value ); if ( mergedValue != null ) { - createUnstableVersionIfNeeded( cursor, structurePropagation, StructurePropagation.UPDATE_MID_CHILD, + createUnstableVersionIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, stableGeneration, unstableGeneration ); // simple, just write the merged value right in there bTreeNode.setValueAt( cursor, mergedValue, pos ); @@ -616,7 +618,7 @@ private void insertInLeaf( PageCursor cursor, StructurePropagation structur return; // No split has occurred } - createUnstableVersionIfNeeded( cursor, structurePropagation, StructurePropagation.UPDATE_MID_CHILD, + createUnstableVersionIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, stableGeneration, unstableGeneration ); if ( keyCount < bTreeNode.leafMaxKeyCount() ) @@ -794,11 +796,11 @@ private void splitLeaf( PageCursor cursor, StructurePropagation structurePr bTreeNode.setRightSibling( cursor, newRight, stableGeneration, unstableGeneration ); } - private void copyKeysAndValues( PageCursor cursor, int fromPos, PageCursor rightCursor, int toPos, int count ) + private void copyKeysAndValues( PageCursor fromCursor, int fromPos, PageCursor toCursor, int toPos, int count ) { - cursor.copyTo( bTreeNode.keyOffset( fromPos ), rightCursor, bTreeNode.keyOffset( toPos ), + fromCursor.copyTo( bTreeNode.keyOffset( fromPos ), toCursor, bTreeNode.keyOffset( toPos ), count * bTreeNode.keySize() ); - cursor.copyTo( bTreeNode.valueOffset( fromPos ), rightCursor, bTreeNode.valueOffset( toPos ), + fromCursor.copyTo( bTreeNode.valueOffset( fromPos ), toCursor, bTreeNode.valueOffset( toPos ), count * bTreeNode.valueSize() ); } @@ -837,9 +839,11 @@ VALUE remove( PageCursor cursor, StructurePropagation structurePropagation, return null; } - while ( structurePropagation.hasMidChildUpdate || + while ( structurePropagation.hasLeftChildUpdate || + structurePropagation.hasMidChildUpdate || + structurePropagation.hasRightChildUpdate || structurePropagation.hasLeftKeyReplace || - structurePropagation.hasLeftChildUpdate ) + structurePropagation.hasRightKeyReplace ) { int pos = levels[currentLevel].childPos; if ( !popLevel( cursor ) ) @@ -848,24 +852,12 @@ VALUE remove( PageCursor cursor, StructurePropagation structurePropagation, break; } - if ( structurePropagation.hasMidChildUpdate ) - { - structurePropagation.hasMidChildUpdate = false; - bTreeNode.setChildAt( cursor, structurePropagation.midChild, pos, - stableGeneration, unstableGeneration ); - } - if ( structurePropagation.hasLeftKeyReplace && levels[currentLevel].covers( structurePropagation.leftKey ) ) - { - structurePropagation.hasLeftKeyReplace = false; - replaceKeyInInternal( cursor, structurePropagation, structurePropagation.leftKey, pos - 1, - stableGeneration, unstableGeneration ); - } if ( structurePropagation.hasLeftChildUpdate ) { structurePropagation.hasLeftChildUpdate = false; if ( pos == 0 ) { - updateRightMostChildInLeftSibling( cursor, structurePropagation.leftChild, + updateRightmostChildInLeftSibling( cursor, structurePropagation.leftChild, stableGeneration, unstableGeneration ); } else @@ -874,12 +866,216 @@ VALUE remove( PageCursor cursor, StructurePropagation structurePropagation, stableGeneration, unstableGeneration ); } } + + if ( structurePropagation.hasMidChildUpdate ) + { + updateMidChild( cursor, structurePropagation, pos, stableGeneration, unstableGeneration ); + } + + if ( structurePropagation.hasRightChildUpdate ) + { + structurePropagation.hasRightChildUpdate = false; + int keyCount = bTreeNode.keyCount( cursor ); + if ( pos == keyCount ) + { + updateLeftmostChildInRightSibling( cursor, structurePropagation.rightChild, + stableGeneration, unstableGeneration ); + } + else + { + bTreeNode.setChildAt( cursor, structurePropagation.rightChild, pos + 1, + stableGeneration, unstableGeneration ); + } + } + + if ( structurePropagation.hasLeftKeyReplace && + levels[currentLevel].covers( structurePropagation.leftKey ) ) + { + structurePropagation.hasLeftKeyReplace = false; + switch ( structurePropagation.keyReplaceStrategy ) + { + case REPLACE: + createUnstableVersionIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, + stableGeneration, unstableGeneration ); + bTreeNode.setKeyAt( cursor, structurePropagation.leftKey, pos - 1 ); + break; + case BUBBLE: + replaceKeyByBubbleRightmostFromSubtree( cursor, structurePropagation, pos - 1, + stableGeneration, unstableGeneration ); + break; + default: + throw new IllegalArgumentException( "Unknown KeyReplaceStrategy " + + structurePropagation.keyReplaceStrategy ); + } + } + + if ( structurePropagation.hasRightKeyReplace && + levels[currentLevel].covers( structurePropagation.rightKey ) ) + { + structurePropagation.hasRightKeyReplace = false; + switch ( structurePropagation.keyReplaceStrategy ) + { + case REPLACE: + createUnstableVersionIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, + stableGeneration, unstableGeneration ); + bTreeNode.setKeyAt( cursor, structurePropagation.rightKey, pos ); + break; + case BUBBLE: + replaceKeyByBubbleRightmostFromSubtree( cursor, structurePropagation, pos, + stableGeneration, unstableGeneration ); + break; + default: + throw new IllegalArgumentException( "Unknown KeyReplaceStrategy " + + structurePropagation.keyReplaceStrategy ); + } + } + } + + if ( currentLevel <= 0 ) + { + tryShrinkTree( cursor, structurePropagation, stableGeneration, unstableGeneration ); } return into; } - private void updateRightMostChildInLeftSibling( PageCursor cursor, long child, long stableGeneration, + private void tryShrinkTree( PageCursor cursor, StructurePropagation structurePropagation, + long stableGeneration, long unstableGeneration ) throws IOException + { + // New root will be propagated out. If rootKeyCount is 0 we can shrink the tree. + int rootKeyCount = bTreeNode.keyCount( cursor ); + + while ( rootKeyCount == 0 && TreeNode.isInternal( cursor ) ) + { + long oldRoot = cursor.getCurrentPageId(); + long onlyChildOfRoot = bTreeNode.childAt( cursor, 0, stableGeneration, unstableGeneration ); + PointerChecking.checkPointer( onlyChildOfRoot, false ); + + structurePropagation.hasMidChildUpdate = true; + structurePropagation.midChild = onlyChildOfRoot; + + idProvider.releaseId( stableGeneration, unstableGeneration, oldRoot ); + bTreeNode.goTo( cursor, "child", onlyChildOfRoot ); + + rootKeyCount = bTreeNode.keyCount( cursor ); + } + } + + private void updateMidChild( PageCursor cursor, StructurePropagation structurePropagation, int childPos, + long stableGeneration, long unstableGeneration ) + { + structurePropagation.hasMidChildUpdate = false; + bTreeNode.setChildAt( cursor, structurePropagation.midChild, childPos, + stableGeneration, unstableGeneration ); + } + + private void replaceKeyByBubbleRightmostFromSubtree( PageCursor cursor, + StructurePropagation structurePropagation, int subtreePosition, + long stableGeneration, long unstableGeneration ) throws IOException + { + long currentPageId = cursor.getCurrentPageId(); + long subtree = bTreeNode.childAt( cursor, subtreePosition, stableGeneration, unstableGeneration ); + PointerChecking.checkPointer( subtree, false ); + + bTreeNode.goTo( cursor, "child", subtree ); + boolean foundKeyBelow = bubbleRightmostKeyRecursive( cursor, structurePropagation, currentPageId, + stableGeneration, unstableGeneration ); + + // Propagate structurePropagation from below + if ( structurePropagation.hasMidChildUpdate ) + { + updateMidChild( cursor, structurePropagation, subtreePosition, stableGeneration, unstableGeneration ); + } + + if ( foundKeyBelow ) + { + // A key has been bubble up to us. + // It's in structurePropagation.leftKey and should be inserted in subtreePosition. + createUnstableVersionIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, + stableGeneration, unstableGeneration ); + bTreeNode.setKeyAt( cursor, structurePropagation.bubbleKey, subtreePosition ); + } + else + { + // No key could be found in subtree, it's completely empty and can be removed. + // We shift keys and children in this internal node to the left (potentially creating new version of this + // node). + createUnstableVersionIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, + stableGeneration, unstableGeneration); + int keyCount = bTreeNode.keyCount( cursor ); + simplyRemoveFromInternal( cursor, keyCount, subtreePosition, subtreePosition ); + } + } + + private boolean bubbleRightmostKeyRecursive( PageCursor cursor, StructurePropagation structurePropagation, + long previousNode, long stableGeneration, long unstableGeneration ) throws IOException + { + try + { + if ( TreeNode.isLeaf( cursor ) ) + { + // Base case + return false; + } + // Recursive case + long currentPageId = cursor.getCurrentPageId(); + int keyCount = bTreeNode.keyCount( cursor ); + long rightmostSubtree = bTreeNode.childAt( cursor, keyCount, stableGeneration, unstableGeneration ); + PointerChecking.checkPointer( rightmostSubtree, false ); + + bTreeNode.goTo( cursor, "child", rightmostSubtree ); + + + boolean foundKeyBelow = bubbleRightmostKeyRecursive( cursor, structurePropagation, currentPageId, + stableGeneration, unstableGeneration ); + + // Propagate structurePropagation from below + if ( structurePropagation.hasMidChildUpdate ) + { + updateMidChild( cursor, structurePropagation, keyCount, stableGeneration, unstableGeneration ); + } + + if ( foundKeyBelow ) + { + return true; + } + + if ( keyCount == 0 ) + { + // This subtree does not contain anything any more + // Repoint sibling and add to freelist and return false + connectLeftAndRightSibling( cursor, stableGeneration, unstableGeneration ); + idProvider.releaseId( stableGeneration, unstableGeneration, currentPageId ); + return false; + } + + // Create new version of node, save rightmost key in structurePropagation, remove rightmost key and child + createUnstableVersionIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, + stableGeneration, unstableGeneration ); + bTreeNode.keyAt( cursor, structurePropagation.bubbleKey, keyCount - 1 ); + simplyRemoveFromInternal( cursor, keyCount, keyCount - 1, keyCount ); + + return true; + } + finally + { + bTreeNode.goTo( cursor, "back to previous node", previousNode ); + } + } + + private int simplyRemoveFromInternal( PageCursor cursor, int keyCount, int keyPos, int childPos ) + { + // Remove key and child + bTreeNode.removeKeyAt( cursor, keyPos, keyCount ); + bTreeNode.removeChildAt( cursor, childPos, keyCount ); + + // Decrease key count + int newKeyCount = keyCount - 1; + bTreeNode.setKeyCount( cursor, newKeyCount ); + return newKeyCount; + } + + private void updateRightmostChildInLeftSibling( PageCursor cursor, long childPointer, long stableGeneration, long unstableGeneration ) throws IOException { long currentPageId = cursor.getCurrentPageId(); @@ -889,18 +1085,23 @@ private void updateRightMostChildInLeftSibling( PageCursor cursor, long child, l bTreeNode.goTo( cursor, "left sibling", leftSibling ); int keyCount = bTreeNode.keyCount( cursor ); - bTreeNode.setChildAt( cursor, child, keyCount, stableGeneration, unstableGeneration ); + bTreeNode.setChildAt( cursor, childPointer, keyCount, stableGeneration, unstableGeneration ); bTreeNode.goTo( cursor, "back to current from left sibling", currentPageId ); } - private void replaceKeyInInternal( PageCursor cursor, StructurePropagation structurePropagation, KEY key, - int keyPosition, long stableGeneration, long unstableGeneration ) throws IOException + private void updateLeftmostChildInRightSibling( PageCursor cursor, long childPointer, long stableGeneration, + long unstableGeneration ) throws IOException { - createUnstableVersionIfNeeded( cursor, structurePropagation, StructurePropagation.UPDATE_MID_CHILD, - stableGeneration, unstableGeneration ); + long currentPageId = cursor.getCurrentPageId(); + long rightSibling = bTreeNode.rightSibling( cursor, stableGeneration, unstableGeneration ); + // Left sibling is not allowed to be NO_NODE here because that means there is a child node with no parent + PointerChecking.checkPointer( rightSibling, false ); + + bTreeNode.goTo( cursor, "right sibling", rightSibling ); + bTreeNode.setChildAt( cursor, childPointer, 0, stableGeneration, unstableGeneration ); - bTreeNode.setKeyAt( cursor, key, keyPosition ); + bTreeNode.goTo( cursor, "back to current from right sibling", currentPageId ); } /** @@ -933,7 +1134,7 @@ private boolean removeFromLeaf( PageCursor cursor, StructurePropagation str return false; } - createUnstableVersionIfNeeded( cursor, structurePropagation, StructurePropagation.UPDATE_MID_CHILD, + createUnstableVersionIfNeeded( cursor, structurePropagation, UPDATE_MID_CHILD, stableGeneration, unstableGeneration ); keyCount = simplyRemoveFromLeaf( cursor, into, keyCount, pos ); @@ -951,6 +1152,8 @@ private void underflowInLeaf( PageCursor cursor, StructurePropagation struc { long leftSibling = bTreeNode.leftSibling( cursor, stableGeneration, unstableGeneration ); PointerChecking.checkPointer( leftSibling, true ); + long rightSibling = bTreeNode.rightSibling( cursor, stableGeneration, unstableGeneration ); + PointerChecking.checkPointer( rightSibling, true ); if ( TreeNode.isNode( leftSibling ) ) { @@ -966,8 +1169,108 @@ private void underflowInLeaf( PageCursor cursor, StructurePropagation struc StructurePropagation.UPDATE_LEFT_CHILD, stableGeneration, unstableGeneration ); rebalanceLeaf( cursor, leftSiblingCursor, structurePropagation, keyCount, leftSiblingKeyCount ); } + else + { + // No need to create new unstable version of left sibling. + // Parent pointer will be updated later. + mergeFromLeftSiblingLeaf( cursor, leftSiblingCursor, structurePropagation, keyCount, + leftSiblingKeyCount, stableGeneration, unstableGeneration ); + } } } + else if ( TreeNode.isNode( rightSibling ) ) + { + try ( PageCursor rightSiblingCursor = cursor.openLinkedCursor( + GenSafePointerPair.pointer( rightSibling ) ) ) + { + rightSiblingCursor.next(); + int rightSiblingKeyCount = bTreeNode.keyCount( rightSiblingCursor ); + + if ( keyCount + rightSiblingKeyCount <= bTreeNode.leafMaxKeyCount() ) + { + createUnstableVersionIfNeeded( rightSiblingCursor, structurePropagation, UPDATE_RIGHT_CHILD, + stableGeneration, unstableGeneration ); + mergeToRightSiblingLeaf( cursor, rightSiblingCursor, structurePropagation, keyCount, + rightSiblingKeyCount, stableGeneration, unstableGeneration); + } + } + } + } + + private void connectLeftAndRightSibling( PageCursor cursor, long stableGeneration, long unstableGeneration ) + throws IOException + { + long currentId = cursor.getCurrentPageId(); + long leftSibling = bTreeNode.leftSibling( cursor, stableGeneration, unstableGeneration ); + PointerChecking.checkPointer( leftSibling, true ); + long rightSibling = bTreeNode. rightSibling( cursor, stableGeneration, unstableGeneration ); + PointerChecking.checkPointer( rightSibling, true ); + if ( TreeNode.isNode( leftSibling ) ) + { + bTreeNode.goTo( cursor, "left sibling", leftSibling ); + bTreeNode.setRightSibling( cursor, rightSibling, stableGeneration, unstableGeneration ); + } + if ( TreeNode.isNode( rightSibling ) ) + { + bTreeNode.goTo( cursor, "right sibling", rightSibling ); + bTreeNode.setLeftSibling( cursor, leftSibling, stableGeneration, unstableGeneration ); + } + + bTreeNode.goTo( cursor, "back to origin after repointing siblings", currentId ); + } + + private void mergeToRightSiblingLeaf( PageCursor cursor, PageCursor rightSiblingCursor, + StructurePropagation structurePropagation, int keyCount, int rightSiblingKeyCount, + long stableGeneration, long unstableGeneration ) throws IOException + { + merge( cursor, keyCount, rightSiblingCursor, rightSiblingKeyCount, stableGeneration, unstableGeneration ); + + // Propagate change + // mid child has been merged into right child + // right key was separator key + structurePropagation.hasMidChildUpdate = true; + structurePropagation.midChild = rightSiblingCursor.getCurrentPageId(); + structurePropagation.hasRightKeyReplace = true; + structurePropagation.keyReplaceStrategy = BUBBLE; + rightSiblingKeyCount = bTreeNode.keyCount( rightSiblingCursor ); + bTreeNode.keyAt( rightSiblingCursor, structurePropagation.rightKey, rightSiblingKeyCount - 1 ); + } + + private void mergeFromLeftSiblingLeaf( PageCursor cursor, PageCursor leftSiblingCursor, + StructurePropagation structurePropagation, int keyCount, int leftSiblingKeyCount, + long stableGeneration, long unstableGeneration ) throws IOException + { + // Move stuff and update key count + merge( leftSiblingCursor, leftSiblingKeyCount, cursor, keyCount, stableGeneration, unstableGeneration ); + + // Propagate change + // left child has been merged into mid child + // left key was separator key + structurePropagation.hasLeftChildUpdate = true; + structurePropagation.leftChild = cursor.getCurrentPageId(); + structurePropagation.hasLeftKeyReplace = true; + structurePropagation.keyReplaceStrategy = BUBBLE; + bTreeNode.keyAt( cursor, structurePropagation.leftKey, 0 ); + } + + private void merge( PageCursor leftSiblingCursor, int leftSiblingKeyCount, PageCursor rightSiblingCursor, + int rightSiblingKeyCount, long stableGeneration, long unstableGeneration ) throws IOException + { + // Push keys in right sibling to the right + bTreeNode.insertKeySlotsAt( rightSiblingCursor, 0, leftSiblingKeyCount, rightSiblingKeyCount ); + bTreeNode.insertValueSlotsAt( rightSiblingCursor, 0, leftSiblingKeyCount, rightSiblingKeyCount ); + + // Move keys and values from left sibling to right sibling + copyKeysAndValues( leftSiblingCursor, 0, rightSiblingCursor, 0, leftSiblingKeyCount ); + bTreeNode.setKeyCount( rightSiblingCursor, rightSiblingKeyCount + leftSiblingKeyCount ); + + // Update new gen of left sibling to be right sibling + bTreeNode.setNewGen( leftSiblingCursor, rightSiblingCursor.getCurrentPageId(), + stableGeneration, unstableGeneration ); + + // Add left sibling to free list + connectLeftAndRightSibling( leftSiblingCursor, stableGeneration, unstableGeneration ); + idProvider.releaseId( stableGeneration, unstableGeneration, leftSiblingCursor.getCurrentPageId() ); } private void rebalanceLeaf( PageCursor cursor, PageCursor leftSiblingCursor, @@ -981,22 +1284,14 @@ private void rebalanceLeaf( PageCursor cursor, PageCursor leftSiblingCursor, bTreeNode.insertKeySlotsAt( cursor, 0, numberOfKeysToMove, keyCount ); bTreeNode.insertValueSlotsAt( cursor, 0, numberOfKeysToMove, keyCount ); - // Move keys from left sibling to right sibling - int sourceOffsetKey = bTreeNode.keyOffset( keyCountInLeftSiblingAfterRebalance ); - int targetOffsetKey = bTreeNode.keyOffset( 0 ); - int bytesToCopyKey = numberOfKeysToMove * bTreeNode.keySize(); - leftSiblingCursor.copyTo( sourceOffsetKey, cursor, targetOffsetKey, bytesToCopyKey ); + // Move keys and values from left sibling to right sibling + copyKeysAndValues( leftSiblingCursor, keyCountInLeftSiblingAfterRebalance, cursor, 0, numberOfKeysToMove ); bTreeNode.setKeyCount( cursor, keyCount + numberOfKeysToMove ); - - // Move values from left sibling to right sibling - int sourceOffsetValue = bTreeNode.valueOffset( keyCountInLeftSiblingAfterRebalance ); - int targetOffsetValue = bTreeNode.valueOffset( 0 ); - int bytesToCopyValue = numberOfKeysToMove * bTreeNode.keySize(); - leftSiblingCursor.copyTo( sourceOffsetValue, cursor, targetOffsetValue, bytesToCopyValue ); bTreeNode.setKeyCount( leftSiblingCursor, leftSiblingKeyCount - numberOfKeysToMove ); // Propagate change structurePropagation.hasLeftKeyReplace = true; + structurePropagation.keyReplaceStrategy = REPLACE; bTreeNode.keyAt( cursor, structurePropagation.leftKey, 0 ); } @@ -1023,6 +1318,13 @@ private int simplyRemoveFromLeaf( PageCursor cursor, VALUE into, int keyCount, i return newKeyCount; } + void applySecondPhaseUpdate( PageCursor cursor, StructurePropagation structurePropagation, + long stableGeneration, long unstableGeneration ) + { + // todo + throw new UnsupportedOperationException( "Implement me" ); + } + /** * Create a new node and copy content from current node (where {@code cursor} sits) if current node is not already * of {@code unstableGeneration}. diff --git a/community/index/src/main/java/org/neo4j/index/internal/gbptree/SeekCursor.java b/community/index/src/main/java/org/neo4j/index/internal/gbptree/SeekCursor.java index 55c0d468f45ee..0e605fe029760 100644 --- a/community/index/src/main/java/org/neo4j/index/internal/gbptree/SeekCursor.java +++ b/community/index/src/main/java/org/neo4j/index/internal/gbptree/SeekCursor.java @@ -560,7 +560,7 @@ else if ( !saneRead() ) continue; // in the read loop above so that we can continue reading from next sibling } } - else if ( insideEndRange() ) + else if ( 0 <= pos && pos < keyCount && insideEndRange() ) { if ( isResultKey() ) { diff --git a/community/index/src/main/java/org/neo4j/index/internal/gbptree/StructurePropagation.java b/community/index/src/main/java/org/neo4j/index/internal/gbptree/StructurePropagation.java index 1498c9e39390c..5357ba510d823 100644 --- a/community/index/src/main/java/org/neo4j/index/internal/gbptree/StructurePropagation.java +++ b/community/index/src/main/java/org/neo4j/index/internal/gbptree/StructurePropagation.java @@ -59,20 +59,26 @@ */ class StructurePropagation { + // First level of updates, used when bubbling up the tree boolean hasLeftChildUpdate; + boolean hasRightChildUpdate; boolean hasMidChildUpdate; boolean hasRightKeyInsert; boolean hasLeftKeyReplace; + boolean hasRightKeyReplace; final KEY leftKey; final KEY rightKey; + final KEY bubbleKey; long leftChild; long midChild; long rightChild; + KeyReplaceStrategy keyReplaceStrategy; - StructurePropagation( KEY leftKey, KEY rightKey ) + StructurePropagation( KEY leftKey, KEY rightKey, KEY bubbleKey ) { this.leftKey = leftKey; this.rightKey = rightKey; + this.bubbleKey = bubbleKey; } /** @@ -81,9 +87,11 @@ class StructurePropagation void clear() { hasLeftChildUpdate = false; + hasRightChildUpdate = false; hasMidChildUpdate = false; hasRightKeyInsert = false; hasLeftKeyReplace = false; + hasRightKeyReplace = false; } interface StructureUpdate @@ -100,4 +108,14 @@ interface StructureUpdate sp.hasMidChildUpdate = true; sp.midChild = childId; }; + + static final StructureUpdate UPDATE_RIGHT_CHILD = ( sp, childId ) -> { + sp.hasRightChildUpdate = true; + sp.rightChild = childId; + }; + + enum KeyReplaceStrategy + { + REPLACE, BUBBLE + } } 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 4d2005d4ca38e..da23975a32571 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 @@ -247,10 +247,10 @@ void removeKeyAt( PageCursor cursor, int pos, int keyCount ) removeSlotAt( cursor, pos, keyCount, keyOffset( 0 ), keySize ); } - private void removeSlotAt( PageCursor cursor, int pos, int keyCount, int baseOffset, int itemSize ) + private void removeSlotAt( PageCursor cursor, int pos, int itemCount, int baseOffset, int itemSize ) { for ( int posToMoveLeft = pos + 1, offset = baseOffset + posToMoveLeft * itemSize; - posToMoveLeft < keyCount; posToMoveLeft++, offset += itemSize ) + posToMoveLeft < itemCount; posToMoveLeft++, offset += itemSize ) { cursor.copyTo( offset, cursor, offset - itemSize, itemSize ); } @@ -299,6 +299,11 @@ void insertChildAt( PageCursor cursor, long child, int pos, int keyCount, setChildAt( cursor, child, pos, stableGeneration, unstableGeneration ); } + void removeChildAt( PageCursor cursor, int pos, int keyCount ) + { + removeSlotAt( cursor, pos, keyCount + 1, childOffset( 0 ), childSize() ); + } + void setChildAt( PageCursor cursor, long child, int pos, long stableGeneration, long unstableGeneration ) { cursor.setOffset( childOffset( pos ) ); diff --git a/community/index/src/test/java/org/neo4j/index/internal/gbptree/ConsistencyCheckerTest.java b/community/index/src/test/java/org/neo4j/index/internal/gbptree/ConsistencyCheckerTest.java index 9ff17b34ad4a9..3b159a2705bd1 100644 --- a/community/index/src/test/java/org/neo4j/index/internal/gbptree/ConsistencyCheckerTest.java +++ b/community/index/src/test/java/org/neo4j/index/internal/gbptree/ConsistencyCheckerTest.java @@ -88,7 +88,8 @@ public void shouldDetectUnusedPages() throws Exception cursor.next( idProvider.acquireNewId( stableGeneration, unstableGeneration ) ); node.initializeLeaf( cursor, stableGeneration, unstableGeneration ); logic.initialize( cursor ); - StructurePropagation structure = new StructurePropagation<>( layout.newKey(), layout.newKey() ); + StructurePropagation structure = new StructurePropagation<>( layout.newKey(), layout.newKey(), + layout.newKey() ); MutableLong key = layout.newKey(); for ( int g = 0, k = 0; g < 3; g++ ) { diff --git a/community/index/src/test/java/org/neo4j/index/internal/gbptree/GBPTreeConcurrencyIT.java b/community/index/src/test/java/org/neo4j/index/internal/gbptree/GBPTreeConcurrencyIT.java index 40fed6ea34c7a..a034ed5f8cd57 100644 --- a/community/index/src/test/java/org/neo4j/index/internal/gbptree/GBPTreeConcurrencyIT.java +++ b/community/index/src/test/java/org/neo4j/index/internal/gbptree/GBPTreeConcurrencyIT.java @@ -21,7 +21,6 @@ import org.apache.commons.lang3.mutable.MutableLong; import org.junit.After; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.RuleChain; @@ -138,8 +137,6 @@ public void shouldReadForwardCorrectlyWithConcurrentRemove() throws Throwable shouldReadCorrectlyWithConcurrentUpdates( testCoordinator ); } - @Ignore( "Test will leave tree nodes empty because merge is still missing in implementation. " + - "When seeking backwards this will cause infinite loops in SeekCursor." ) @Test public void shouldReadBackwardCorrectlyWithConcurrentRemove() throws Throwable { @@ -331,7 +328,7 @@ private List generateUpdatesForNextIteration() } else if ( toRemove.isEmpty() ) { - operation = new WriteOperation( toAdd.poll() ); + operation = new PutOperation( toAdd.poll() ); } else { @@ -342,7 +339,7 @@ else if ( toRemove.isEmpty() ) } else { - operation = new WriteOperation( toAdd.poll() ); + operation = new PutOperation( toAdd.poll() ); } } updateOperations.add( operation ); @@ -394,9 +391,9 @@ private abstract class UpdateOperation abstract boolean isInsert(); } - private class WriteOperation extends UpdateOperation + private class PutOperation extends UpdateOperation { - WriteOperation( long key ) + PutOperation( long key ) { super( key ); } diff --git a/community/index/src/test/java/org/neo4j/index/internal/gbptree/InternalTreeLogicTest.java b/community/index/src/test/java/org/neo4j/index/internal/gbptree/InternalTreeLogicTest.java index 007d0e73ed9a2..cbfdb4ac2e5f9 100644 --- a/community/index/src/test/java/org/neo4j/index/internal/gbptree/InternalTreeLogicTest.java +++ b/community/index/src/test/java/org/neo4j/index/internal/gbptree/InternalTreeLogicTest.java @@ -71,7 +71,7 @@ public class InternalTreeLogicTest private final MutableLong readKey = new MutableLong(); private final MutableLong readValue = new MutableLong(); private final StructurePropagation structurePropagation = new StructurePropagation<>( - layout.newKey(), layout.newKey() ); + layout.newKey(), layout.newKey(), layout.newKey() ); private static long stableGen = GenSafePointer.MIN_GENERATION; private static long unstableGen = stableGen + 1; @@ -913,15 +913,23 @@ public void shouldCreateNewVersionWhenRemoveInStableLeaf() throws Exception initialize(); long targetLastId = id.lastId() + 3; // 2 splits and 1 new allocated root long i = 0; - for ( ; id.lastId() < targetLastId; i++ ) + for ( ; id.lastId() < targetLastId; i += 2 ) { insert( i, i ); } goTo( readCursor, rootId ); assertEquals( 2, keyCount() ); + long leftChild = childAt( readCursor, 0, stableGen, unstableGen ); long middleChild = childAt( readCursor, 1, stableGen, unstableGen ); long rightChild = childAt( readCursor, 2, stableGen, unstableGen ); + + // add some more keys to middleChild to not have remove trigger a merge + goTo( readCursor, middleChild ); + Long firstKeyInMiddleChild = keyAt( 0 ); + insert( firstKeyInMiddleChild + 1, firstKeyInMiddleChild + 1 ); + goTo( readCursor, rootId ); + assertSiblings( leftChild, middleChild, rightChild ); // WHEN diff --git a/community/index/src/test/java/org/neo4j/index/internal/gbptree/SeekCursorTest.java b/community/index/src/test/java/org/neo4j/index/internal/gbptree/SeekCursorTest.java index 64fdc8a63965c..51e9ec08a1de7 100644 --- a/community/index/src/test/java/org/neo4j/index/internal/gbptree/SeekCursorTest.java +++ b/community/index/src/test/java/org/neo4j/index/internal/gbptree/SeekCursorTest.java @@ -66,7 +66,7 @@ public long getAsLong() private final TreeNode node = new TreeNode<>( PAGE_SIZE, layout ); private final InternalTreeLogic treeLogic = new InternalTreeLogic<>( id, node, layout ); private final StructurePropagation structurePropagation = - new StructurePropagation<>( layout.newKey(), layout.newKey() ); + new StructurePropagation<>( layout.newKey(), layout.newKey(), layout.newKey() ); private final PageAwareByteArrayCursor cursor = new PageAwareByteArrayCursor( PAGE_SIZE ); private final int maxKeyCount = node.leafMaxKeyCount();