/* Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * Created on Oct 13, 2009 */ package com.bigdata.btree; import java.util.UUID; import org.apache.log4j.Level; import junit.framework.TestCase2; import com.bigdata.rawstore.SimpleMemoryRawStore; /** * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ */ public class TestLeafSplitShortestSeparatorKey extends TestCase2 { /** * */ public TestLeafSplitShortestSeparatorKey() { } /** * @param name */ public TestLeafSplitShortestSeparatorKey(String name) { super(name); } /** * On reflection, I suspect that this is an edge case which is simply not * covered by the code. I think that I can construct a unit test which will * demonstrate the failure and then work out the logic for the edge case. * <p> * The exception is being thrown when a leaf is full. The leaf is in a * temporary overflow condition. In order to split the leaf into a left * sibling and a right sibling, the tuples in the leaf are divided into two * by choosing the index of the first key which will enter the right * sibling. A separator key is chosen which guides search into either the * left sibling or the right sibling as appropriate and is then inserted * into the parent. The assertion was tripped because the chosen separator * key already existed in the parent. The code (Leaf#802) is attempting to * choose the shortest separator key because that makes the keys in the * nodes short, which reduces their on disk requirements. In fact, since we * also use prefix compression (front-coding of the keys) there is less * benefit to this than might otherwise be the case. Regardless, I believe * that the source of the error is the failure to consider the existing * separator key in the parent of the leaf which was directing search into * the leaf. In this case, it appears that the shortest separator key when * considering the last key in the left sibling and the first key in the * right sibling is identical to the pre-existing separator key in the * parent for the leaf before it is split. To fix this, I need to create a * unit test with data in a tree which replicates the assertion as an * existence proof of the problem and then change the logic to consider * three keys when determining the shortest separator key -- the existing * separator key in the parent node, the last key in the new left sibling, * and the first key in the new right sibling. * * As a workaround, you can replace line 802: * * <pre> * final byte[] separatorKey = BytesUtil.getSeparatorKey(// * getKeys().get(splitIndex),// * getKeys().get(splitIndex - 1)// * ); * </pre> * * with * * <pre> * final byte[] separatorKey = getKeys().get(splitIndex); * </pre> * * This will use the first key in the new right sibling as the new separator * key in the parent between the new left and right sibling. That should be * correct, just a longer separator key. */ public void test_shortestSeparatorKey() { final int m = 3; final IndexMetadata metadata = new IndexMetadata(UUID.randomUUID()); metadata.setBranchingFactor(m); // metadata.setDeleteMarkers(true); metadata.setWriteRetentionQueueCapacity(1000); final BTree btree = BTree.create(new SimpleMemoryRawStore(), metadata); btree.insert(new byte[] { 1 }, (byte[]) null); // System.out.println("----------------------"); // assert btree.dump(Level.DEBUG,System.out); btree.insert(new byte[] { 10 }, (byte[]) null); // System.out.println("----------------------"); // assert btree.dump(Level.DEBUG,System.out); btree.insert(new byte[] { 20, 10 }, (byte[]) null); // System.out.println("----------------------"); // assert btree.dump(Level.DEBUG,System.out); // causes split. btree.insert(new byte[] { 20 }, (byte[]) null); System.out.println("----------------------"); assertTrue( btree.dump(Level.DEBUG,System.out) ); // add to right edge of right sibling btree.insert(new byte[] { 20, 20 }, (byte[]) null); // remove left edge of right sibling (EQ to separator key). btree.remove(new byte[] { 20}); System.out.println("----------------------"); assertTrue( btree.dump(Level.DEBUG,System.out)); // insert deleted key -- causes split for existing separator key. btree.insert(new byte[] { 20 }, (byte[]) null); System.out.println("----------------------"); assertTrue( btree.dump(Level.DEBUG,System.out) ); } }