/**
* Copyright 2011-2012 Akiban Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.persistit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import com.persistit.exception.PersistitException;
import com.persistit.util.Util;
public class MVCCPruneBufferTest extends MVCCTestBase {
@Test
public void testPrunePrimordialAntiValues() throws PersistitException {
trx1.begin();
ex1.getValue().put(RED_FOX);
for (int i = 0; i < 5000; i++) {
ex1.to(i).store();
}
for (int i = 2; i < 5000; i += 2) {
ex1.to(i).remove();
}
ex1.to(2500);
final Buffer buffer1 = ex1.fetchBufferCopy(0);
final int mvvCount = buffer1.getMvvCount();
assertTrue(mvvCount > 0);
final int available = buffer1.getAvailableSize();
final int keys = buffer1.getKeyCount();
buffer1.claim(true);
buffer1.pruneMvvValues(null, true, null);
assertEquals(keys, buffer1.getKeyCount());
assertTrue(buffer1.getMvvCount() > 0);
assertEquals(available, buffer1.getAvailableSize());
trx1.commit();
trx1.end();
_persistit.getTransactionIndex().updateActiveTransactionCache();
buffer1.pruneMvvValues(null, true, null);
assertEquals(0, _persistit.getCleanupManager().getAcceptedCount());
assertTrue("Pruning should have removed primordial Anti-values", keys > buffer1.getKeyCount());
// mvvCount is 1 because there is still a leading primordial AntiValue
assertEquals("Pruning should leave mvvCount==1", 1, buffer1.getMvvCount());
assertTrue("Pruning should liberate space", buffer1.getAvailableSize() > available);
}
@Test
public void testPruneCleanup() throws Exception {
final CleanupManager cm = _persistit.getCleanupManager();
trx1.begin();
ex1.getValue().put(RED_FOX);
for (int i = 0; i < 5000; i++) {
ex1.to(i).store();
}
ex1.removeAll();
ex1.to(2500);
final Buffer buffer1 = ex1.fetchBufferCopy(0);
final int mvvCount = buffer1.getMvvCount();
assertTrue(mvvCount > 0);
final int available = buffer1.getAvailableSize();
final int keys = buffer1.getKeyCount();
buffer1.claim(true);
buffer1.pruneMvvValues(null, true, null);
buffer1.release();
assertEquals(keys, buffer1.getKeyCount());
assertTrue(buffer1.getMvvCount() > 0);
assertEquals(available, buffer1.getAvailableSize());
trx1.commit();
trx1.end();
_persistit.getTransactionIndex().updateActiveTransactionCache();
ex1.ignoreMVCCFetch(true);
int antiValueCount1 = 0;
ex1.to(Key.BEFORE);
while (ex1.next()) {
antiValueCount1++;
}
assertTrue(antiValueCount1 > 0);
/*
* Prune without enqueuing edge key AntiValues
*/
final long pageCount = ex1.getVolume().getStorage().getNextAvailablePage();
for (long page = 1; page < pageCount; page++) {
final Buffer buffer = ex1.getBufferPool().get(ex1.getVolume(), page, true, true);
try {
buffer.pruneMvvValues(null, true, null);
} finally {
buffer.release();
}
}
int antiValueCount2 = 0;
ex1.to(Key.BEFORE);
while (ex1.next()) {
antiValueCount2++;
}
assertTrue(antiValueCount2 > 0);
assertTrue(antiValueCount2 < antiValueCount1);
cm.setPollInterval(-1);
/*
* Prune with enqueuing edge key AntiValues
*/
for (long page = 1; page < pageCount; page++) {
final Buffer buffer = ex1.getBufferPool().get(ex1.getVolume(), page, true, true);
try {
buffer.pruneMvvValues(ex1.getTree(), true, null);
} finally {
buffer.release();
}
}
assertTrue(cm.getAcceptedCount() > 0);
cm.poll();
assertEquals("Should have performed all actions", cm.getAcceptedCount(), cm.getPerformedCount());
int antiValueCount3 = 0;
ex1.to(Key.BEFORE);
while (ex1.next()) {
antiValueCount3++;
}
assertEquals(0, antiValueCount3);
}
@Test
public void testPruneLongRecordsSplit() throws Exception {
disableBackgroundCleanup();
ex1.to("x").store();
trx1.begin();
for (int k = 2;; k += 2) {
if (ex1.fetchBufferCopy(0).getAvailableSize() < 200) {
break;
}
trx1.setStep(0);
storeLongMVV(ex1, k);
trx1.setStep(1);
store(ex1, k, RED_FOX);
}
trx1.commit();
trx1.end();
trx1.begin();
for (int k = 1; k < 20; k += 2) {
store(ex1, k, RED_FOX.toUpperCase());
}
trx1.commit();
trx1.end();
ex1.to(Key.BEFORE);
while (ex1.next()) {
System.out.println(String.format("%10s %s", ex1.getKey(), ex1.getValue()));
}
_persistit.getTransactionIndex().cleanup();
assertTrue("Should no longer be an MVV", !ex1.isValueLongMVV());
}
@Test
public void testPruneLongRecordsWithRollback() throws Exception {
disableBackgroundCleanup();
/*
* Start a concurrent transaction to prevent pruning during the store
* operations.
*/
final Exchange ex0 = createUniqueExchange();
ex0.getTransaction().begin();
trx2.begin();
storeLongMVV(ex2, "x");
trx2.commit();
trx2.end();
trx1.begin();
storeLongMVV(ex1, "x");
trx1.flushTransactionBuffer(true);
trx1.rollback();
trx1.end();
ex0.getTransaction().commit();
ex0.getTransaction().end();
_persistit.getTransactionIndex().cleanup();
ex1.prune();
assertTrue("Should no longer be an MVV", !ex1.isValueLongMVV());
}
@Test
public void induceBug1006576() throws Exception {
disableBackgroundCleanup();
trx1.begin();
storeLongMVV(ex1, "x");
storeLongMVV(ex1, "y");
trx1.commit();
trx1.end();
_persistit.getTransactionIndex().cleanup();
ex1.prune();
assertTrue("Should no longer be an MVV", !ex1.isValueLongMVV());
trx1.begin();
storeLongMVV(ex1, "x");
trx1.commit();
trx1.end();
_persistit.getTransactionIndex().cleanup();
ex1.prune();
assertTrue("Should no longer be an MVV", !ex1.isValueLongMVV());
}
@Test
public void induceBug1005206() throws Exception {
disableBackgroundCleanup();
trx1.begin();
storeLongMVV(ex1, "x");
trx2.begin();
storeLongMVV(ex2, "y");
trx1.commit();
trx2.rollback();
trx1.end();
trx2.end();
_persistit.getTransactionIndex().cleanup();
ex1.prune();
assertTrue("Should no longer be an MVV", !ex1.isValueLongMVV());
}
@Test
public void testComplexPruning() throws Exception {
_persistit.getCheckpointManager().setCheckpointIntervalNanos(5000);
final Thread[] threads = new Thread[500];
for (int cycle = 0; cycle < threads.length; cycle++) {
Thread.sleep(50);
final int myCycle = cycle;
threads[cycle] = new Thread(new Runnable() {
@Override
public void run() {
final Transaction txn = _persistit.getTransaction();
try {
txn.begin();
switch (myCycle % 3) {
case 0:
storeNewVersion(myCycle);
break;
case 1:
removeKeys(myCycle);
break;
case 2:
countKeys();
break;
}
if ((myCycle % 7) < 3) {
txn.rollback();
} else {
txn.commit();
}
} catch (final Exception e) {
e.printStackTrace();
} finally {
txn.end();
}
}
});
threads[cycle].start();
}
for (int cycle = 0; cycle < threads.length; cycle++) {
threads[cycle].join();
}
}
@Test
public void testDeadlock() throws Exception {
final Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int cycle = 0; cycle < 200; cycle++) {
if ((cycle % 10) == 0) {
System.out.println("cycle " + cycle);
}
final Transaction txn = _persistit.getTransaction();
try {
txn.begin();
storeNewVersion(cycle);
Util.sleep(10);
txn.rollback();
System.gc();
} catch (final Exception e) {
e.printStackTrace();
} finally {
txn.end();
}
}
}
});
thread.start();
while (thread.isAlive()) {
_persistit.checkpoint();
Util.sleep(100);
}
}
@Test
public void testWritePagePrune() throws Exception {
_persistit.getJournalManager().setWritePagePruningEnabled(false);
final Exchange exchange = exchange(1);
final Transaction txn = _persistit.getTransaction();
try {
txn.begin();
storeNewVersion(1);
txn.commit();
} finally {
txn.end();
}
final long pageAddr = exchange.fetchBufferCopy(0).getPageAddress();
final Buffer buffer = exchange.getBufferPool().get(exchange.getVolume(), pageAddr, true, true);
assertTrue("Should have multiple MVV records", buffer.getMvvCount() > 2);
_persistit.getJournalManager().setWritePagePruningEnabled(true);
_persistit.getTransactionIndex().updateActiveTransactionCache();
buffer.setDirtyAtTimestamp(_persistit.getCurrentTimestamp());
buffer.writePage();
assertTrue("Should no more than one MVV record", buffer.getMvvCount() < 2);
buffer.release();
}
private void storeNewVersion(final int cycle) throws Exception {
final Exchange exchange = exchange(cycle);
exchange.getValue().put(String.format("%s%04d", RED_FOX, cycle));
for (int i = 1; i <= 100; i++) {
exchange.to(i).store();
}
}
private Exchange exchange(final int cycle) throws PersistitException {
return _persistit.getExchange(TEST_VOLUME_NAME, String.format("%s%04d", TEST_TREE_NAME, cycle), true);
}
private void removeKeys(final int cycle) throws Exception {
final Exchange exchange = _persistit.getExchange(TEST_VOLUME_NAME, TEST_TREE_NAME, true);
for (int i = (cycle % 2) + 1; i <= 100; i += 2) {
exchange.to(i).remove();
}
}
private int countKeys() throws Exception {
Thread.sleep(2000);
final Exchange exchange = _persistit.getExchange(TEST_VOLUME_NAME, TEST_TREE_NAME, true);
exchange.clear().append(Key.BEFORE);
int count = 0;
while (exchange.next()) {
count++;
}
return count;
}
public static void main(final String[] args) throws Exception {
int repeat = 100;
if (args.length > 0) {
repeat = Integer.parseInt(args[0]);
}
for (int i = 1; i <= repeat; i++) {
final MVCCPruneBufferTest test = new MVCCPruneBufferTest();
System.out.println("Cycle " + i);
test.setUp();
test.testPruneCleanup();
test.tearDown();
}
}
}