package com.bigdata.htree;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import com.bigdata.btree.Checkpoint;
import com.bigdata.btree.HTreeIndexMetadata;
import com.bigdata.io.DirectBufferPool;
import com.bigdata.rawstore.IRawStore;
import com.bigdata.rwstore.sector.MemStore;
/**
* Tests recycling of HTree storage.
*/
public class TestHTreeRecycle extends AbstractHTreeTestCase {
/**
*
*/
public TestHTreeRecycle() {
}
/**
* @param name
*/
public TestHTreeRecycle(String name) {
super(name);
}
class MemStoreListener extends MemStore {
MemStoreListener() {
super(DirectBufferPool.INSTANCE);
}
private final Set<Long> addrs = new HashSet<Long>();
/**
* Add an address which should be deleted to the set of such addresses.
*
* @param addr
* An address which should be deleted.
*/
public void expectDelete(final long addr) {
if (addr == IRawStore.NULL)
fail("Not allowed to expect a NULL address");
if (!addrs.add(addr)) {
fail("Address already in expectedDelete set: " + addr);
}
}
/**
* Assert that all addresses which should have been deleted were in fact
* deleted.
*/
public void assertDeleteSetEmpty() {
if (!addrs.isEmpty())
fail("expectedDeleteAddrs is not empty: " + addrs);
}
@Override
public void delete(final long addr) {
if(addr == IRawStore.NULL) {
fail("Not allowed to delete a NULL address");
}
if (!addrs.remove(addr)) {
fail("Not expecting delete: addr=" + addr);
}
super.delete(addr);
}
}
public void testRecycle() {
final MemStoreListener store = new MemStoreListener();
final byte[] key0 = new byte[] { 1, 2, 3 };
final byte[] val0 = new byte[] { 1, 2, 3 };
Checkpoint lastCheckpoint = null;
try {
final HTree htree;
{
final HTreeIndexMetadata md = new HTreeIndexMetadata(getName(),
UUID.randomUUID());
md.setAddressBits(6);
htree = HTree.create(store, md);
}
// Get the current checkpoint record.
lastCheckpoint = htree.getCheckpoint();
/*
* Initial checkpoint required because the htree root did not exist
* when we initialized the store. Therefore it is dirty
* and will be written out now.
*/
{
// HTree is dirty.
assertTrue(htree.needsCheckpoint());
// Checkpoint record should be recycled.
store.expectDelete(lastCheckpoint.getCheckpointAddr());
// Checkpoint the index.
final Checkpoint newCheckpoint = htree.writeCheckpoint2();
// Everything which should have been deleted was deleted.
store.assertDeleteSetEmpty();
// Verify that a new checkpoint was written.
assertTrue(lastCheckpoint != newCheckpoint);
lastCheckpoint = newCheckpoint;
// No longer reports that the B+Tree is dirty.
assertFalse(htree.needsCheckpoint());
}
/*
* Attempting to write a checkpoint on a clean HTree should return
* the old checkpoint reference.
*/
{
final Checkpoint checkpoint3 = htree.writeCheckpoint2();
assertTrue(checkpoint3 == lastCheckpoint);
}
/*
* Make the counter dirty. Verify that the HTree needs a checkpoint
* and verify that the checkpoint recycles only the correct records.
*/
{
// HTree is clean.
assertFalse(htree.needsCheckpoint());
// Make the counter dirty.
htree.getCounter().incrementAndGet();
// HTree needs checkpoint.
assertTrue(htree.needsCheckpoint());
// Checkpoint record should be recycled.
store.expectDelete(lastCheckpoint.getCheckpointAddr());
// Checkpoint the index.
final Checkpoint newCheckpoint = htree.writeCheckpoint2();
// Everything which should have been deleted was deleted.
store.assertDeleteSetEmpty();
// Verify that a new checkpoint was written.
assertTrue(lastCheckpoint != newCheckpoint);
lastCheckpoint = newCheckpoint;
// No longer reports that the HTree is dirty.
assertFalse(htree.needsCheckpoint());
}
/*
* Test where just the root has changed (insert or delete tuple).
*/
{
// Checkpoint is not required.
assertFalse(htree.needsCheckpoint());
// Insert will delete root node and first child BucketPage.
store.expectDelete(htree.getRoot().getChildAddr(0));
htree.insert(key0, val0);
// Everything which should have been deleted was deleted.
store.assertDeleteSetEmpty();
// Checkpoint is required.
assertTrue(htree.needsCheckpoint());
// Should recycle the old checkpoint record.
store.expectDelete(lastCheckpoint.getCheckpointAddr());
store.expectDelete(lastCheckpoint.getRootAddr());
// Checkpoint the index.
final Checkpoint newCheckpoint = htree.writeCheckpoint2();
// Everything which should have been deleted was deleted.
store.assertDeleteSetEmpty();
// Root has new address.
assertNotSame(newCheckpoint.getRootAddr(),
lastCheckpoint.getRootAddr());
// Everything which should have been deleted was deleted.
store.assertDeleteSetEmpty();
// Verify that a new checkpoint was written.
assertTrue(lastCheckpoint != newCheckpoint);
lastCheckpoint = newCheckpoint;
// No longer reports that the B+Tree is dirty.
assertFalse(htree.needsCheckpoint());
}
/*
* Unit test where the {@link IndexMetadata} has changed.
*
* Note: There are not a lot of ways in which you are allowed to
* change the IndexMetadata once the index has been created. This
* picks one of them.
*/
if (true) {
// HTree is clean.
assertFalse(htree.needsCheckpoint());
final HTreeIndexMetadata md = htree.getIndexMetadata().clone();
md.setIndexSegmentBranchingFactor(40);
htree.setIndexMetadata(md);
// HTree needs checkpoint.
assertTrue(htree.needsCheckpoint());
// Checkpoint record should be deleted.
store.expectDelete(lastCheckpoint.getCheckpointAddr());
// IndexMetadata record should be deleted.
store.expectDelete(lastCheckpoint.getMetadataAddr());
// Checkpoint the index.
final Checkpoint newCheckpoint = htree.writeCheckpoint2();
// Everything which should have been deleted was deleted.
store.assertDeleteSetEmpty();
// Root has new address.
assertNotSame(newCheckpoint.getRootAddr(),
lastCheckpoint.getRootAddr());
// IndexMetadata has new address.
assertNotSame(newCheckpoint.getMetadataAddr(),
lastCheckpoint.getMetadataAddr());
// Verify that a new checkpoint was written.
assertTrue(lastCheckpoint != newCheckpoint);
lastCheckpoint = newCheckpoint;
// No longer reports that the HTree is dirty.
assertFalse(htree.needsCheckpoint());
}
} finally {
store.destroy();
}
}
}