/** 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 */ package com.bigdata.btree; import java.util.Iterator; import java.util.Map; import java.util.Random; import java.util.TreeMap; import java.util.UUID; import junit.framework.TestCase2; import org.apache.log4j.Level; import com.bigdata.Banner; import com.bigdata.btree.keys.TestKeyBuilder; import com.bigdata.io.DirectBufferPool; import com.bigdata.io.SerializerUtil; import com.bigdata.rawstore.IRawStore; import com.bigdata.rwstore.sector.MemStore; public class StressTestBTreeRemove extends TestCase2 { //AbstractBTreeTestCase { public StressTestBTreeRemove() { } public StressTestBTreeRemove(String name) { super(name); } private Random r; private IRawStore store; private boolean useRawRecords; @Override protected void setUp() throws Exception { super.setUp(); r = new Random(); store = new MemStore(DirectBufferPool.INSTANCE); useRawRecords = r.nextBoolean(); } @Override protected void tearDown() throws Exception { if (store != null) { store.close(); store = null; } r = null; super.tearDown(); } private boolean useRawRecords() { return useRawRecords; } // @Override private BTree getBTree(final int branchingFactor) { return getBTree(branchingFactor , DefaultTupleSerializer.newInstance()); } // @Override private BTree getBTree(final int branchingFactor, final ITupleSerializer tupleSer) { final IndexMetadata metadata = new IndexMetadata(UUID.randomUUID()); if (useRawRecords()) { /* * Randomly test with raw records enabled, using a small value for * the maximum record length to force heavy use of raw records when * they are chosen. * * TODO Rather than doing this randomly, it would be better to do * this systematically. This will make any problems reported by CI * easier to interpret. */ metadata.setRawRecords(true); metadata.setMaxRecLen(8); // Set large retention queue metadata.setWriteRetentionQueueCapacity(6000); } metadata.setBranchingFactor(branchingFactor); metadata.setTupleSerializer(tupleSer); return BTree.create(store, metadata); } /** * Stress test of insert, removal and lookup of keys in the tree (allows * splitting of the root leaf). * * TODO Alternative insert/remove patterns which remove many things at once * and then insert many things. The insert/removes should be in a region of * the index, but need not be perfectly dense (though that might make it * more interesting). * * TODO Multi-threaded insert/remove pattern using the Unisolated read-write * index. Might be difficult to track ground truth. Just look for errors in * the index. Might need to do occasional checkpoints/commits also. */ /* * Note: passes with ntrials=100k, mtuples=10M @ m={3,4,5,16} against * memstore. Runs for 1745s on Mac AirBook. */ public void test_insertLookupRemoveKeyTreeStressTest() { Banner.banner(); final int ntrials = 5; final int mtuples = 10000; doInsertLookupRemoveStressTestMGC(4, mtuples, ntrials); doInsertLookupRemoveStressTestMGC(5, mtuples, ntrials); doInsertLookupRemoveStressTestMGC(16, mtuples, ntrials); doInsertLookupRemoveStressTestMGC(3, mtuples, ntrials); doInsertLookupRemoveStressTest(3, mtuples, ntrials); doInsertLookupRemoveStressTest(4, mtuples, ntrials); doInsertLookupRemoveStressTest(5, mtuples, ntrials); doInsertLookupRemoveStressTest(16, mtuples, ntrials); } /** * MGC variant that populates, removes at random and then re-inserts in reverse order. * * The idea is to try to force a problem where a deleted leaf (and its parent) have not * been ejected and then re-referenced for an insert, * * @param m * The branching factor * @param nkeys * The #of distinct keys. * @param ntrials * The #of trials. */ // @Override private void doInsertLookupRemoveStressTestMGC(final int m, final int nkeys, final int ntrials) { // if (log.isInfoEnabled()) // log.info System.out.println("m=" + m + ", nkeys=" + nkeys + ", ntrials=" + ntrials); // Fixture under test. final BTree btree = getBTree(m); // Ground truth. // final Map<Integer, SimpleEntry> expected = new TreeMap<Integer, SimpleEntry>(); // All possible keys. final Integer[] keys = new Integer[nkeys]; // All possible values. final SimpleEntry[] vals = new SimpleEntry[nkeys]; long start = System.currentTimeMillis(); for (int i = 0; i < nkeys; i++) { // Generate random keys. keys[i] = r.nextInt(); vals[i] = new SimpleEntry(); btree.insert(keys[i], vals[i]); } log.trace("First insert took " + (System.currentTimeMillis()-start) + "ms for " + nkeys + " inserts"); try { /* * Run test. */ for (int trial = 0; trial < ntrials; trial++) { final int mod = 3 * (trial + 1); // ratio of keys unreplaced log.trace("Start trial " + trial + " leaf count: " + btree.getLeafCount()); for (int i = nkeys-1; i >= 0; i--) { if (i % mod != 0) // exclude 1/4 btree.remove(keys[i]); } log.trace("After removes %" + mod + ", leaf count: " + btree.getLeafCount()); for (int i = mod; i < nkeys; i++) { if (i % mod != 0) // exclude 1/4 btree.insert(keys[i], vals[i]); } } assertTrue(btree.dump(System.err)); if (log.isInfoEnabled()) log.info(btree.getBtreeCounters().toString()); btree.removeAll(); } finally { btree.close(); } } /** * Stress test helper performs random inserts, removal and lookup operations * and compares the behavior of the {@link BTree} against ground truth as * tracked by a {@link TreeMap}. * * Note: This test uses dense keys, but that is not a requirement. * * @param m * The branching factor * @param nkeys * The #of distinct keys. * @param ntrials * The #of trials. */ // @Override private void doInsertLookupRemoveStressTest(final int m, final int nkeys, final int ntrials) { // if (log.isInfoEnabled()) // log.info System.out.println("m=" + m + ", nkeys=" + nkeys + ", ntrials=" + ntrials); // Fixture under test. final BTree btree = getBTree(m); // Ground truth. final Map<Integer, SimpleEntry> expected = new TreeMap<Integer, SimpleEntry>(); // All possible keys. final Integer[] keys = new Integer[nkeys]; // All possible values. final SimpleEntry[] vals = new SimpleEntry[nkeys]; for (int i = 0; i < nkeys; i++) { // Note: this produces dense keys with origin (1). keys[i] = i + 1; vals[i] = new SimpleEntry(); if (i % 4 == 0) { /* * Populate every Nth tuple for the starting condition. */ expected.put(keys[i], vals[i]); btree.insert(keys[i], vals[i]); } } try { /* * Run test. */ for (int trial = 0; trial < ntrials; trial++) { final boolean insert = r.nextBoolean(); /* * Choose the starting index at random, leaving at least one * index before the end of the original range. */ final int fromIndex = r.nextInt(nkeys - 2); /* * Choose final index from the remaining range. */ final int toIndex = fromIndex + ((nkeys - fromIndex) / (r.nextInt(1000) + 1)); // The #of possible integer positions in that range. final int range = toIndex - fromIndex; // The #of actual tuples in that range. final long rangeCount = btree.rangeCount(fromIndex, toIndex); if(log.isTraceEnabled()) log.trace("trial=" + trial + ", " + (insert ? "insert" : "remove") + ", fromIndex=" + fromIndex + ", toIndex=" + toIndex + ", range=" + range + ", rangeCount=" + rangeCount); for (int j = fromIndex; j < toIndex; j++) { final Integer ikey = keys[j]; final SimpleEntry val = vals[j]; final byte[] key = TestKeyBuilder.asSortKey(ikey); if (insert) { // System.err.println("insert("+ikey+", "+val+")"); final SimpleEntry old = expected.put(ikey, val); final SimpleEntry old2 = (SimpleEntry) btree.insert( key, val); assertEquals(old, old2); } else { // System.err.println("remove("+ikey+")"); final SimpleEntry old = expected.remove(ikey); final SimpleEntry old2 = (SimpleEntry) SerializerUtil .deserialize(btree.remove(key)); assertEquals(old, old2); } } if (trial % 100 == 0) { /* * Validate the keys and entries. */ assertEquals("#entries", expected.size(), btree.getEntryCount()); final Iterator<Map.Entry<Integer, SimpleEntry>> itr = expected .entrySet().iterator(); while (itr.hasNext()) { final Map.Entry<Integer, SimpleEntry> entry = itr.next(); final byte[] tmp = TestKeyBuilder.asSortKey(entry .getKey()); assertEquals("lookup(" + entry.getKey() + ")", entry.getValue(), btree.lookup(tmp)); } assertTrue(btree.dump(Level.ERROR, System.err)); } } assertTrue(btree.dump(System.err)); if (log.isInfoEnabled()) log.info(btree.getBtreeCounters().toString()); btree.removeAll(); /* * TODO try more insert/removes after removeAll (or removeAll in * some key range). Clear the same keys out of the groundTruth * map and revalidate. */ } finally { btree.close(); } } }