/*
* Copyright 2013 Jive Software, 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.jivesoftware.os.amza.service.storage.binary;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
import com.jivesoftware.os.amza.api.filer.UIO;
import com.jivesoftware.os.amza.api.scan.RowStream;
import com.jivesoftware.os.amza.api.stream.RowType;
import com.jivesoftware.os.amza.api.wal.WALWriter;
import com.jivesoftware.os.amza.service.filer.HeapByteBufferFactory;
import com.jivesoftware.os.amza.service.filer.MultiAutoGrowingByteBufferBackedFiler;
import com.jivesoftware.os.amza.api.IoStats;
import com.jivesoftware.os.amza.service.storage.filer.DiskBackedWALFiler;
import com.jivesoftware.os.amza.service.storage.filer.MemoryBackedWALFiler;
import com.jivesoftware.os.amza.service.storage.filer.WALFiler;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.commons.lang.mutable.MutableInt;
import org.testng.Assert;
import org.testng.annotations.Test;
/**
* @author jonathan
*/
public class BinaryRowReaderWriterTest {
private final WALWriter.IndexableKeys indexableKeys = stream -> true;
private final WALWriter.TxKeyPointerFpStream txKeyPointerFpStream = (txId, prefix, key, value, valueTimestamp, valueTombstoned, valueVersion, fp) -> true;
@Test
public void testValidateDiskBacked() throws Exception {
File dir = Files.createTempDir();
IoStats ioStats = new IoStats();
DiskBackedWALFiler filer = new DiskBackedWALFiler(new File(dir, "booya").getAbsolutePath(), "rw", false, 0);
validate(filer, ioStats);
}
@Test
public void testValidateHeapBacked() throws Exception {
IoStats ioStats = new IoStats();
WALFiler filer = new MemoryBackedWALFiler(new MultiAutoGrowingByteBufferBackedFiler(1_024, 1_024 * 1_024,
new HeapByteBufferFactory()));
validate(filer, ioStats);
}
private void validate(WALFiler filer, IoStats ioStats) throws Exception {
BinaryRowReader binaryRowReader = new BinaryRowReader(filer);
BinaryRowWriter binaryRowWriter = new BinaryRowWriter(filer);
binaryRowWriter.write(ioStats, 0L, RowType.primary, 1, 4, stream -> stream.stream(new byte[] { 1, 2, 3, 4 }), indexableKeys, txKeyPointerFpStream, true,
false);
binaryRowWriter.write(ioStats, 1L, RowType.primary, 1, 4, stream -> stream.stream(new byte[] { 1, 2, 3, 5 }), indexableKeys, txKeyPointerFpStream, true,
false);
binaryRowWriter.write(ioStats, 2L, RowType.primary, 1, 4, stream -> stream.stream(new byte[] { 1, 2, 3, 6 }), indexableKeys, txKeyPointerFpStream, true,
false);
binaryRowWriter.write(ioStats, -1L, RowType.end_of_merge, 1, 4, stream -> stream.stream(new byte[] { 1, 2, 3, 7 }), indexableKeys, txKeyPointerFpStream,
true,
false);
MutableInt truncations = new MutableInt();
binaryRowReader.validate(ioStats, true, true,
(long rowFP, long rowTxId, RowType rowType, byte[] row) -> rowType == RowType.end_of_merge ? rowFP : -1,
(long rowFP, long rowTxId, RowType rowType, byte[] row) -> rowType == RowType.end_of_merge ? -(rowFP + 1) : -1,
(truncatedAtFP) -> truncations.increment());
binaryRowReader.validate(ioStats, false, false,
(long rowFP, long rowTxId, RowType rowType, byte[] row) -> rowType == RowType.end_of_merge ? rowFP : -1,
(long rowFP, long rowTxId, RowType rowType, byte[] row) -> rowType == RowType.end_of_merge ? -(rowFP + 1) : -1,
(truncatedAtFP) -> truncations.increment());
Assert.assertEquals(truncations.intValue(), 0);
//filer.seek(filer.length());
long corruptionOffset = filer.length();
UIO.writeByte(filer.appender(), (byte) 56, "corrupt");
binaryRowReader.validate(ioStats, true, true,
(long rowFP, long rowTxId, RowType rowType, byte[] row) -> rowType == RowType.end_of_merge ? rowFP : -1,
(long rowFP, long rowTxId, RowType rowType, byte[] row) -> rowType == RowType.end_of_merge ? -(rowFP + 1) : -1,
(truncatedAtFP) -> {
Assert.assertEquals(truncatedAtFP, corruptionOffset);
truncations.increment();
});
Assert.assertEquals(truncations.intValue(), 1);
UIO.writeByte(filer.appender(), (byte) 56, "corrupt");
binaryRowReader.validate(ioStats, false, false,
(long rowFP, long rowTxId, RowType rowType, byte[] row) -> rowType == RowType.end_of_merge ? rowFP : -1,
(long rowFP, long rowTxId, RowType rowType, byte[] row) -> rowType == RowType.end_of_merge ? -(rowFP + 1) : -1,
(truncatedAtFP) -> {
Assert.assertEquals(truncatedAtFP, corruptionOffset);
truncations.increment();
});
Assert.assertEquals(truncations.intValue(), 2);
}
@Test
public void testDiskBackedRead() throws Exception {
File dir = Files.createTempDir();
IoStats ioStats = new IoStats();
DiskBackedWALFiler filer = new DiskBackedWALFiler(new File(dir, "booya").getAbsolutePath(), "rw", false, 0);
read(filer, ioStats);
}
@Test
public void testMemoryBackedRead() throws Exception {
IoStats ioStats = new IoStats();
WALFiler filer = new MemoryBackedWALFiler(new MultiAutoGrowingByteBufferBackedFiler(1_024, 1_024 * 1_024,
new HeapByteBufferFactory()));
read(filer, ioStats);
}
private void read(WALFiler filer, IoStats ioStats) throws Exception {
BinaryRowReader binaryRowReader = new BinaryRowReader(filer);
BinaryRowWriter binaryRowWriter = new BinaryRowWriter(filer);
ReadStream readStream = new ReadStream();
binaryRowReader.reverseScan(ioStats, readStream);
Assert.assertTrue(readStream.rows.isEmpty());
readStream.clear();
binaryRowReader.scan(ioStats, 0, false, readStream);
Assert.assertTrue(readStream.rows.isEmpty());
readStream.clear();
binaryRowWriter.write(ioStats, 0L, RowType.primary, 1, 4, stream -> stream.stream(new byte[] { 1, 2, 3, 4 }), indexableKeys, txKeyPointerFpStream, true,
false);
binaryRowReader.scan(ioStats, 0, false, readStream);
Assert.assertEquals(readStream.rows.size(), 1);
readStream.clear();
binaryRowReader.reverseScan(ioStats, readStream);
Assert.assertEquals(readStream.rows.size(), 1);
readStream.clear();
binaryRowWriter.write(ioStats, 2L, RowType.primary, 1, 4, stream -> stream.stream(new byte[] { 2, 3, 4, 5 }), indexableKeys, txKeyPointerFpStream, true,
false);
binaryRowReader.scan(ioStats, 0, false, readStream);
Assert.assertEquals(readStream.rows.size(), 2);
readStream.clear();
binaryRowReader.reverseScan(ioStats, readStream);
Assert.assertEquals(readStream.rows.size(), 2);
Assert.assertTrue(Arrays.equals(readStream.rows.get(0), new byte[] { 2, 3, 4, 5 }));
Assert.assertTrue(Arrays.equals(readStream.rows.get(1), new byte[] { 1, 2, 3, 4 }));
readStream.clear();
}
@Test(enabled = false)
public void testReverseReadPerformance() throws Exception {
File dir = Files.createTempDir();
IoStats ioStats = new IoStats();
DiskBackedWALFiler filer = new DiskBackedWALFiler(new File(dir, "booya").getAbsolutePath(), "rw", false, 0);
BinaryRowReader binaryRowReader = new BinaryRowReader(filer);
BinaryRowWriter binaryRowWriter = new BinaryRowWriter(filer);
ReadStream readStream = new ReadStream();
binaryRowReader.reverseScan(ioStats, readStream);
Assert.assertTrue(readStream.rows.isEmpty());
readStream.clear();
int numEntries = 1_000_000;
for (long i = 0; i < numEntries; i++) {
byte[] row = (String.valueOf(i) + "k" + (i % 1000)).getBytes();
binaryRowWriter.write(ioStats, i, RowType.primary, 1, row.length, stream -> stream.stream(row), indexableKeys, txKeyPointerFpStream, true, false);
}
for (int i = 0; i < 10; i++) {
long start = System.currentTimeMillis();
binaryRowReader.scan(ioStats, 0, false, readStream);
System.out.println("Forward scan in " + (System.currentTimeMillis() - start) + " ms");
Assert.assertEquals(readStream.rows.size(), numEntries);
readStream.clear();
start = System.currentTimeMillis();
binaryRowReader.reverseScan(ioStats, readStream);
System.out.println("Reverse scan in " + (System.currentTimeMillis() - start) + " ms");
Assert.assertEquals(readStream.rows.size(), numEntries);
readStream.clear();
}
}
@Test
public void testOpenCloseAppend() throws Exception {
File dir = Files.createTempDir();
IoStats ioStats = new IoStats();
Random rand = new Random();
for (long i = 0; i < 1000; i++) {
DiskBackedWALFiler filer = new DiskBackedWALFiler(new File(dir, "foo").getAbsolutePath(), "rw", false, 0);
BinaryRowReader binaryRowReader = new BinaryRowReader(filer);
BinaryRowWriter binaryRowWriter = new BinaryRowWriter(filer);
ReadStream readStream = new ReadStream();
if (i > 0) {
binaryRowReader.reverseScan(ioStats, readStream);
Assert.assertEquals(readStream.rows.size(), i);
}
readStream.clear();
byte[] row = new byte[4];
rand.nextBytes(row);
binaryRowWriter.write(ioStats, i, RowType.primary, 1, row.length, stream -> stream.stream(row), indexableKeys, txKeyPointerFpStream, true, false);
filer.close();
}
}
@Test(enabled = false)
public void testConcurrency() throws Exception {
MemoryBackedWALFiler walFiler = new MemoryBackedWALFiler(new MultiAutoGrowingByteBufferBackedFiler(32, 1_024 * 1_024, new HeapByteBufferFactory()));
IoStats ioStats = new IoStats();
BinaryRowReader binaryRowReader = new BinaryRowReader(walFiler);
BinaryRowWriter binaryRowWriter = new BinaryRowWriter(walFiler);
ExecutorService executors = Executors.newFixedThreadPool(9);
AtomicBoolean running = new AtomicBoolean(true);
AtomicLong scanned = new AtomicLong();
List<Future<?>> futures = Lists.newArrayList();
for (int i = 0; i < 8; i++) {
futures.add(executors.submit(() -> {
try {
while (running.get()) {
binaryRowReader.scan(ioStats, 0, false, (rowFP, rowTxId, rowType, row) -> {
scanned.incrementAndGet();
return true;
});
}
return true;
} catch (Throwable t) {
t.printStackTrace();
throw t;
}
}));
}
futures.add(executors.submit(() -> {
try {
for (int i = 0; i < 1_000_000; i++) {
byte[] row = UIO.intBytes(i);
binaryRowWriter.write(ioStats, i, RowType.primary, 1, 16,
stream -> stream.stream(row),
stream -> true,
(txId, prefix, key, value, valueTimestamp, valueTombstoned, valueVersion, fp) -> true,
false,
false);
if (i % 10_000 == 0) {
System.out.println("Finished i:" + i + " scanned:" + scanned.get());
}
}
} finally {
running.set(false);
}
return null;
}));
for (Future<?> future : futures) {
future.get();
}
}
static class ReadStream implements RowStream {
int clears = 0;
final ArrayList<byte[]> rows = new ArrayList<>();
@Override
public boolean row(long rowFp, long rwoTxId, RowType rowType, byte[] row) throws Exception {
rows.add(row);
return true;
}
void clear() {
clears++;
rows.clear();
}
}
}