CavalliumDBEngine/src/main/java/it/cavallium/dbengine/database/disk/OptimisticRocksDBColumn.java

253 lines
8.2 KiB
Java
Raw Normal View History

2021-10-20 01:51:34 +02:00
package it.cavallium.dbengine.database.disk;
import static it.cavallium.dbengine.database.LLUtils.MARKER_ROCKSDB;
2022-03-30 18:36:07 +02:00
import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.MeterRegistry;
2022-03-16 13:47:56 +01:00
import io.netty5.buffer.api.Buffer;
import io.netty5.buffer.api.BufferAllocator;
import io.netty5.buffer.api.MemoryManager;
import io.netty5.buffer.api.Send;
2021-10-20 01:51:34 +02:00
import it.cavallium.dbengine.database.LLDelta;
import it.cavallium.dbengine.database.LLUtils;
import it.cavallium.dbengine.lucene.ExponentialPageLimits;
import java.io.IOException;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.locks.Lock;
2021-12-26 12:47:00 +01:00
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.StampedLock;
2021-10-20 01:51:34 +02:00
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.rocksdb.ColumnFamilyHandle;
import org.rocksdb.OptimisticTransactionDB;
import org.rocksdb.ReadOptions;
import org.rocksdb.RocksDBException;
import org.rocksdb.Status.Code;
import org.rocksdb.Transaction;
2022-05-12 19:14:27 +02:00
import org.rocksdb.TransactionOptions;
2021-10-20 01:51:34 +02:00
import org.rocksdb.WriteBatch;
import org.rocksdb.WriteOptions;
import reactor.core.scheduler.Schedulers;
public final class OptimisticRocksDBColumn extends AbstractRocksDBColumn<OptimisticTransactionDB> {
private static final boolean ALWAYS_PRINT_OPTIMISTIC_RETRIES = false;
2022-03-30 18:36:07 +02:00
private final DistributionSummary optimisticAttempts;
2021-10-20 01:51:34 +02:00
public OptimisticRocksDBColumn(OptimisticTransactionDB db,
boolean nettyDirect,
2021-10-20 01:51:34 +02:00
BufferAllocator alloc,
2022-03-30 18:36:07 +02:00
String databaseName,
ColumnFamilyHandle cfh,
MeterRegistry meterRegistry,
StampedLock closeLock) {
super(db, nettyDirect, alloc, databaseName, cfh, meterRegistry, closeLock);
2022-03-30 18:36:07 +02:00
this.optimisticAttempts = DistributionSummary
.builder("db.optimistic.attempts.distribution")
.publishPercentiles(0.2, 0.5, 0.95)
.baseUnit("times")
.scale(1)
.publishPercentileHistogram()
.tags("db.name", databaseName, "db.column", columnName)
.register(meterRegistry);
2021-10-20 01:51:34 +02:00
}
@Override
protected boolean commitOptimistically(Transaction tx) throws RocksDBException {
2021-10-20 01:51:34 +02:00
try {
tx.commit();
2021-10-20 01:51:34 +02:00
return true;
} catch (RocksDBException ex) {
var status = ex.getStatus() != null ? ex.getStatus().getCode() : Code.Ok;
if (status == Code.Busy || status == Code.TryAgain) {
return false;
}
throw ex;
}
}
@Override
protected Transaction beginTransaction(@NotNull WriteOptions writeOptions,
TransactionOptions txOpts) {
return getDb().beginTransaction(writeOptions);
2021-10-20 01:51:34 +02:00
}
@Override
public void write(WriteOptions writeOptions, WriteBatch writeBatch) throws RocksDBException {
getDb().write(writeOptions, writeBatch);
2021-10-20 01:51:34 +02:00
}
@Override
public @NotNull UpdateAtomicResult updateAtomicImpl(@NotNull ReadOptions readOptions,
@NotNull WriteOptions writeOptions,
Buffer key,
BinarySerializationFunction updater,
UpdateAtomicResultMode returnMode) throws IOException {
long initNanoTime = System.nanoTime();
try {
var cfh = getCfh();
var keyArray = LLUtils.toArray(key);
if (Schedulers.isInNonBlockingThread()) {
throw new UnsupportedOperationException("Called update in a nonblocking thread");
}
try (var txOpts = new TransactionOptions();
2022-05-12 19:14:27 +02:00
var tx = beginTransaction(writeOptions, txOpts)) {
boolean committedSuccessfully;
int retries = 0;
ExponentialPageLimits retryTime = null;
2022-05-20 23:59:56 +02:00
Buffer prevData = null;
Buffer newData = null;
try {
boolean changed;
do {
if (prevData != null && prevData.isAccessible()) {
prevData.close();
}
if (newData != null && newData.isAccessible()) {
newData.close();
}
var prevDataArray = tx.getForUpdate(readOptions, cfh, keyArray, true);
if (logger.isTraceEnabled()) {
logger.trace(MARKER_ROCKSDB,
"Reading {}: {} (before update)",
LLUtils.toStringSafe(key),
LLUtils.toStringSafe(prevDataArray)
);
}
if (prevDataArray != null) {
prevData = MemoryManager.unsafeWrap(prevDataArray);
prevDataArray = null;
} else {
prevData = null;
}
Buffer prevDataToSendToUpdater;
if (prevData != null) {
prevDataToSendToUpdater = prevData.copy().makeReadOnly();
2021-10-20 01:51:34 +02:00
} else {
prevDataToSendToUpdater = null;
2021-10-20 01:51:34 +02:00
}
2022-05-20 18:31:05 +02:00
try {
newData = updater.apply(prevDataToSendToUpdater);
} finally {
if (prevDataToSendToUpdater != null && prevDataToSendToUpdater.isAccessible()) {
prevDataToSendToUpdater.close();
}
}
2022-05-20 23:59:56 +02:00
var newDataArray = newData == null ? null : LLUtils.toArray(newData);
if (logger.isTraceEnabled()) {
logger.trace(MARKER_ROCKSDB,
"Updating {}. previous data: {}, updated data: {}",
LLUtils.toStringSafe(key),
LLUtils.toStringSafe(prevDataArray),
LLUtils.toStringSafe(newDataArray)
);
}
if (prevData != null && newData == null) {
if (logger.isTraceEnabled()) {
logger.trace(MARKER_ROCKSDB, "Deleting {} (after update)", LLUtils.toStringSafe(key));
}
tx.delete(cfh, keyArray, true);
changed = true;
committedSuccessfully = commitOptimistically(tx);
} else if (newData != null && (prevData == null || !LLUtils.equals(prevData, newData))) {
if (logger.isTraceEnabled()) {
logger.trace(MARKER_ROCKSDB,
2022-05-20 23:59:56 +02:00
"Writing {}: {} (after update)",
LLUtils.toStringSafe(key),
2022-05-20 23:59:56 +02:00
LLUtils.toStringSafe(newData)
);
2021-10-20 01:51:34 +02:00
}
2022-05-20 23:59:56 +02:00
tx.put(cfh, keyArray, newDataArray);
changed = true;
committedSuccessfully = commitOptimistically(tx);
} else {
changed = false;
committedSuccessfully = true;
tx.rollback();
}
if (!committedSuccessfully) {
tx.undoGetForUpdate(cfh, keyArray);
tx.rollback();
retries++;
2022-05-20 23:59:56 +02:00
if (retries == 1) {
retryTime = new ExponentialPageLimits(0, 2, 2000);
}
long retryNs = 1000000L * retryTime.getPageLimit(retries);
2022-05-20 23:59:56 +02:00
// +- 30%
retryNs = retryNs + ThreadLocalRandom.current().nextLong(-retryNs * 30L / 100L, retryNs * 30L / 100L);
2022-05-20 23:59:56 +02:00
if (retries >= 5 && retries % 5 == 0 || ALWAYS_PRINT_OPTIMISTIC_RETRIES) {
logger.warn(MARKER_ROCKSDB, "Failed optimistic transaction {} (update):"
+ " waiting {} ms before retrying for the {} time", LLUtils.toStringSafe(key), retryNs / 1000000d, retries);
} else if (logger.isDebugEnabled(MARKER_ROCKSDB)) {
logger.debug(MARKER_ROCKSDB, "Failed optimistic transaction {} (update):"
+ " waiting {} ms before retrying for the {} time", LLUtils.toStringSafe(key), retryNs / 1000000d, retries);
}
// Wait for n milliseconds
if (retryNs > 0) {
LockSupport.parkNanos(retryNs);
2021-10-20 01:51:34 +02:00
}
}
2022-05-20 23:59:56 +02:00
} while (!committedSuccessfully);
if (retries > 5) {
logger.warn(MARKER_ROCKSDB, "Took {} retries to update key {}", retries, LLUtils.toStringSafe(key));
2021-10-20 01:51:34 +02:00
}
2022-05-20 23:59:56 +02:00
recordAtomicUpdateTime(changed, prevData != null, newData != null, initNanoTime);
optimisticAttempts.record(retries);
return switch (returnMode) {
case NOTHING -> {
if (prevData != null) {
prevData.close();
}
if (newData != null) {
newData.close();
}
yield RESULT_NOTHING;
2021-10-20 01:51:34 +02:00
}
2022-05-20 23:59:56 +02:00
case CURRENT -> {
if (prevData != null) {
prevData.close();
}
yield new UpdateAtomicResultCurrent(newData);
2021-10-20 01:51:34 +02:00
}
2022-05-20 23:59:56 +02:00
case PREVIOUS -> {
if (newData != null) {
newData.close();
}
yield new UpdateAtomicResultPrevious(prevData);
2021-10-20 01:51:34 +02:00
}
2022-05-20 23:59:56 +02:00
case BINARY_CHANGED -> {
if (prevData != null) {
prevData.close();
}
if (newData != null) {
newData.close();
}
yield new UpdateAtomicResultBinaryChanged(changed);
}
2022-05-20 23:59:56 +02:00
case DELTA -> new UpdateAtomicResultDelta(LLDelta.of(prevData, newData));
};
} catch (Throwable ex) {
if (prevData != null && prevData.isAccessible()) {
prevData.close();
}
2022-05-20 23:59:56 +02:00
if (newData != null && newData.isAccessible()) {
newData.close();
2022-05-20 18:31:05 +02:00
}
2022-05-20 23:59:56 +02:00
throw ex;
}
2021-10-20 01:51:34 +02:00
}
} catch (Throwable ex) {
throw new IOException("Failed to update key " + LLUtils.toStringSafe(key), ex);
2021-10-20 01:51:34 +02:00
}
}
@Override
public boolean supportsTransactions() {
return true;
}
}