package com.jivesoftware.os.amza.service.storage.binary;
import com.google.common.base.Preconditions;
import com.jivesoftware.os.amza.api.IoStats;
import com.jivesoftware.os.amza.api.filer.UIO;
import com.jivesoftware.os.amza.api.scan.RowStream;
import com.jivesoftware.os.amza.api.stream.Fps;
import com.jivesoftware.os.amza.api.stream.RowType;
import com.jivesoftware.os.amza.api.wal.RowIO;
import com.jivesoftware.os.mlogger.core.MetricLogger;
import com.jivesoftware.os.mlogger.core.MetricLoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author jonathan.colt
*/
public class BinaryRowIO implements RowIO {
private static final MetricLogger LOG = MetricLoggerFactory.getLogger();
private final File key;
private final String name;
private final BinaryRowReader rowReader;
private final BinaryRowWriter rowWriter;
private final int updatesBetweenLeaps;
private final int maxLeaps;
private final AtomicReference<LeapFrog> latestLeapFrog = new AtomicReference<>();
private final AtomicLong updatesSinceLeap = new AtomicLong(0);
private final AtomicBoolean initializedLeaps = new AtomicBoolean(false);
public BinaryRowIO(File key,
String name,
BinaryRowReader rowReader,
BinaryRowWriter rowWriter,
int updatesBetweenLeaps,
int maxLeaps) throws Exception {
this.key = key;
this.name = name;
this.rowReader = rowReader;
this.rowWriter = rowWriter;
this.updatesBetweenLeaps = updatesBetweenLeaps;
this.maxLeaps = maxLeaps;
}
@Override
public File getKey() {
return key;
}
@Override
public String getName() {
return name;
}
@Override
public void initLeaps(IoStats ioStats, long fpOfLastLeap, long updates) throws Exception {
Preconditions.checkState(updatesBetweenLeaps > 0);
if (fpOfLastLeap > -1) {
try {
rowReader.read(ioStats,
fpStream -> fpStream.stream(fpOfLastLeap),
(rowFP, rowTxId, rowType, row) -> {
if (rowType == RowType.system) {
ByteBuffer buf = ByteBuffer.wrap(row);
byte[] keyBytes = new byte[8];
buf.get(keyBytes);
long key = UIO.bytesLong(keyBytes);
if (key == RowType.LEAP_KEY) {
buf.rewind();
latestLeapFrog.set(new LeapFrog(rowFP, Leaps.fromByteBuffer(buf)));
return false;
}
}
throw new IllegalStateException("Invalid leapFp:" + fpOfLastLeap);
});
} catch (Throwable t) {
LOG.error("Failed to initialize leaps at fp:{} length:{}", fpOfLastLeap, rowReader.length());
throw t;
}
}
initializedLeaps.set(true);
updatesSinceLeap.addAndGet(updates);
}
@Override
public long getUpdatesSinceLeap() {
Preconditions.checkState(updatesBetweenLeaps > 0);
return updatesSinceLeap.get();
}
@Override
public long getFpOfLastLeap() {
Preconditions.checkState(updatesBetweenLeaps > 0);
LeapFrog frog = latestLeapFrog.get();
return (frog == null) ? -1 : frog.fp;
}
@Override
public void validate(IoStats ioStats,
boolean backwardScan,
boolean truncateToLastRowFp,
ValidationStream backward,
ValidationStream forward,
PreTruncationNotifier preTruncationNotifier) throws Exception {
rowReader.validate(ioStats, backwardScan, truncateToLastRowFp, backward, forward, preTruncationNotifier);
}
@Override
public void hackTruncation(int numBytes) {
rowReader.hackTruncation(numBytes);
}
@Override
public boolean scan(IoStats ioStats, long offsetFp, boolean allowRepairs, RowStream rowStream) throws Exception {
return rowReader.scan(ioStats, offsetFp, allowRepairs, rowStream);
}
@Override
public long getInclusiveStartOfRow(long transactionId) throws Exception {
Preconditions.checkState(updatesBetweenLeaps > 0);
LeapFrog leapFrog = latestLeapFrog.get();
Leaps leaps = (leapFrog != null) ? leapFrog.leaps : null;
long closestFP = 0;
while (leaps != null) {
Leaps next = null;
for (int i = 0; i < leaps.transactionIds.length; i++) {
if (leaps.transactionIds[i] < transactionId) {
closestFP = Math.max(closestFP, leaps.fpIndex[i]);
} else {
byte[] typeByteTxIdAndRow = readTypeByteTxIdAndRow(leaps.fpIndex[i]);
//TODO the leaps are basically fixed, so it would make sense to cache the fp -> leaps
next = Leaps.fromBytes(typeByteTxIdAndRow, 1 + 8, typeByteTxIdAndRow.length - (1 + 8));
break;
}
}
leaps = next;
}
return closestFP;
}
@Override
public boolean reverseScan(IoStats ioStats, RowStream rowStream) throws Exception {
return rowReader.reverseScan(ioStats, rowStream);
}
@Override
public byte[] readTypeByteTxIdAndRow(long fp) throws Exception {
return rowReader.readTypeByteTxIdAndRow(fp);
}
@Override
public boolean read(IoStats ioStats, Fps fps, RowStream rowStream) throws Exception {
return rowReader.read(ioStats, fps, rowStream);
}
@Override
public long writeHighwater(IoStats ioStats, byte[] row) throws Exception {
return rowWriter.writeHighwater(ioStats, row);
}
@Override
public long writeSystem(IoStats ioStats, byte[] row) throws Exception {
return rowWriter.writeSystem(ioStats, row);
}
@Override
public int write(IoStats ioStats,
long txId,
RowType rowType,
int estimatedNumberOfRows,
int estimatedSizeInBytes,
RawRows rows,
IndexableKeys indexableKeys,
TxKeyPointerFpStream stream,
boolean addToLeapCount,
boolean hardFsyncBeforeLeapBoundary) throws Exception {
int count = rowWriter.write(ioStats,
txId,
rowType,
estimatedNumberOfRows,
estimatedSizeInBytes,
rows,
indexableKeys,
stream,
addToLeapCount,
hardFsyncBeforeLeapBoundary);
if (addToLeapCount && updatesBetweenLeaps > 0 && updatesSinceLeap.addAndGet(count) >= updatesBetweenLeaps) {
Preconditions.checkState(initializedLeaps.get(), "Leaps not yet initialized");
rowWriter.flush(hardFsyncBeforeLeapBoundary);
LeapFrog latest = latestLeapFrog.get();
Leaps leaps = computeNextLeaps(txId, latest, maxLeaps);
long leapFp = rowWriter.writeSystem(ioStats, leaps.toBytes());
latestLeapFrog.set(new LeapFrog(leapFp, leaps));
updatesSinceLeap.set(0);
}
return count;
}
@Override
public long getEndOfLastRow() throws Exception {
return rowWriter.getEndOfLastRow();
}
@Override
public long sizeInBytes() throws Exception {
return rowWriter.length();
}
@Override
public void flush(boolean fsync) throws Exception {
rowWriter.flush(fsync);
}
@Override
public void close() throws IOException {
rowReader.close();
rowWriter.close();
}
static private Leaps computeNextLeaps(long lastTransactionId, BinaryRowIO.LeapFrog latest, int maxLeaps) {
long[] fpIndex;
long[] transactionIds;
if (latest == null) {
fpIndex = new long[0];
transactionIds = new long[0];
} else if (latest.leaps.fpIndex.length < maxLeaps) {
int numLeaps = latest.leaps.fpIndex.length + 1;
fpIndex = new long[numLeaps];
transactionIds = new long[numLeaps];
System.arraycopy(latest.leaps.fpIndex, 0, fpIndex, 0, latest.leaps.fpIndex.length);
System.arraycopy(latest.leaps.transactionIds, 0, transactionIds, 0, latest.leaps.transactionIds.length);
fpIndex[numLeaps - 1] = latest.fp;
transactionIds[numLeaps - 1] = latest.leaps.lastTransactionId;
} else {
fpIndex = new long[0];
transactionIds = new long[maxLeaps];
long[] idealFpIndex = new long[maxLeaps];
// b^n = fp
// b^32 = 123_456
// ln b^32 = ln 123_456
// 32 ln b = ln 123_456
// ln b = ln 123_456 / 32
// b = e^(ln 123_456 / 32)
double base = Math.exp(Math.log(latest.fp) / maxLeaps);
for (int i = 0; i < idealFpIndex.length; i++) {
idealFpIndex[i] = latest.fp - (long) Math.pow(base, (maxLeaps - i - 1));
}
double smallestDistance = Double.MAX_VALUE;
for (int i = 0; i < latest.leaps.fpIndex.length; i++) {
long[] testFpIndex = new long[maxLeaps];
System.arraycopy(latest.leaps.fpIndex, 0, testFpIndex, 0, i);
System.arraycopy(latest.leaps.fpIndex, i + 1, testFpIndex, i, maxLeaps - 1 - i);
testFpIndex[maxLeaps - 1] = latest.fp;
double distance = euclidean(testFpIndex, idealFpIndex);
if (distance < smallestDistance) {
fpIndex = testFpIndex;
System.arraycopy(latest.leaps.transactionIds, 0, transactionIds, 0, i);
System.arraycopy(latest.leaps.transactionIds, i + 1, transactionIds, i, maxLeaps - 1 - i);
transactionIds[maxLeaps - 1] = latest.leaps.lastTransactionId;
smallestDistance = distance;
}
}
//System.out.println("@" + latest.fp + " base: " + base);
//System.out.println("@" + latest.fp + " ideal: " + Arrays.toString(idealFpIndex));
//System.out.println("@" + latest.fp + " next: " + Arrays.toString(fpIndex));
}
return new Leaps(lastTransactionId, fpIndex, transactionIds);
}
static private double euclidean(long[] a, long[] b) {
double v = 0;
for (int i = 0; i < a.length; i++) {
double d = a[i] - b[i];
v += d * d;
}
return Math.sqrt(v);
}
private static class LeapFrog {
private final long fp;
private final Leaps leaps;
public LeapFrog(long fp, Leaps leaps) {
this.fp = fp;
this.leaps = leaps;
}
}
private static class Leaps {
private final long lastTransactionId;
private final long[] fpIndex;
private final long[] transactionIds;
public Leaps(long lastTransactionId, long[] fpIndex, long[] transactionIds) {
this.lastTransactionId = lastTransactionId;
this.fpIndex = fpIndex;
this.transactionIds = transactionIds;
}
private byte[] toBytes() {
ByteBuffer buf = ByteBuffer.wrap(new byte[8 + 8 + 4 + fpIndex.length * 16]);
buf.put(UIO.longBytes(RowType.LEAP_KEY));
buf.putLong(lastTransactionId);
buf.putInt(fpIndex.length);
for (int i = 0; i < fpIndex.length; i++) {
buf.putLong(fpIndex[i]);
buf.putLong(transactionIds[i]);
}
return buf.array();
}
private static Leaps fromBytes(byte[] bytes, int offset, int length) {
ByteBuffer buf = ByteBuffer.wrap(bytes, offset, length);
return fromByteBuffer(buf);
}
private static Leaps fromByteBuffer(ByteBuffer buf) {
buf.getLong(); // just read over 8 bytes
long lastTransactionId = buf.getLong();
int length = buf.getInt();
long[] fpIndex = new long[length];
long[] transactionIds = new long[length];
for (int i = 0; i < length; i++) {
fpIndex[i] = buf.getLong();
transactionIds[i] = buf.getLong();
}
return new Leaps(lastTransactionId, fpIndex, transactionIds);
}
}
}