2021-10-20 01:51:34 +02:00
|
|
|
package it.cavallium.dbengine.database.disk;
|
|
|
|
|
|
|
|
import static it.cavallium.dbengine.database.LLUtils.MARKER_ROCKSDB;
|
|
|
|
|
2021-10-30 11:13:46 +02:00
|
|
|
import io.micrometer.core.instrument.MeterRegistry;
|
2021-10-20 01:51:34 +02:00
|
|
|
import io.net5.buffer.api.Buffer;
|
|
|
|
import io.net5.buffer.api.BufferAllocator;
|
|
|
|
import io.net5.buffer.api.MemoryManager;
|
|
|
|
import io.net5.buffer.api.Send;
|
|
|
|
import it.cavallium.dbengine.client.DatabaseOptions;
|
|
|
|
import it.cavallium.dbengine.database.LLDelta;
|
|
|
|
import it.cavallium.dbengine.database.LLUtils;
|
|
|
|
import it.cavallium.dbengine.database.serialization.SerializationFunction;
|
|
|
|
import it.cavallium.dbengine.lucene.ExponentialPageLimits;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.util.concurrent.ThreadLocalRandom;
|
2021-12-26 12:47:00 +01:00
|
|
|
import java.util.concurrent.locks.LockSupport;
|
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;
|
|
|
|
import org.rocksdb.WriteBatch;
|
|
|
|
import org.rocksdb.WriteOptions;
|
|
|
|
import reactor.core.scheduler.Schedulers;
|
|
|
|
|
|
|
|
public final class OptimisticRocksDBColumn extends AbstractRocksDBColumn<OptimisticTransactionDB> {
|
|
|
|
|
2021-10-30 11:13:46 +02:00
|
|
|
private static final boolean ALWAYS_PRINT_OPTIMISTIC_RETRIES = false;
|
|
|
|
|
2021-10-20 01:51:34 +02:00
|
|
|
public OptimisticRocksDBColumn(OptimisticTransactionDB db,
|
|
|
|
DatabaseOptions databaseOptions,
|
|
|
|
BufferAllocator alloc,
|
2021-10-30 11:13:46 +02:00
|
|
|
ColumnFamilyHandle cfh,
|
|
|
|
MeterRegistry meterRegistry) {
|
|
|
|
super(db, databaseOptions, alloc, cfh, meterRegistry);
|
2021-10-20 01:51:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected boolean commitOptimistically(Transaction tx) throws RocksDBException {
|
|
|
|
try {
|
|
|
|
tx.commit();
|
|
|
|
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) {
|
|
|
|
return getDb().beginTransaction(writeOptions);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void write(WriteOptions writeOptions, WriteBatch writeBatch) throws RocksDBException {
|
|
|
|
getDb().write(writeOptions, writeBatch);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public @NotNull UpdateAtomicResult updateAtomic(@NotNull ReadOptions readOptions,
|
|
|
|
@NotNull WriteOptions writeOptions,
|
|
|
|
Send<Buffer> keySend,
|
2021-11-08 16:33:41 +01:00
|
|
|
SerializationFunction<@Nullable Send<Buffer>, @Nullable Buffer> updater,
|
2021-10-20 01:51:34 +02:00
|
|
|
boolean existsAlmostCertainly,
|
|
|
|
UpdateAtomicResultMode returnMode) throws IOException, RocksDBException {
|
|
|
|
try (Buffer key = keySend.receive()) {
|
2021-12-29 00:31:35 +01:00
|
|
|
try {
|
|
|
|
var cfh = getCfh();
|
|
|
|
var keyArray = LLUtils.toArray(key);
|
|
|
|
if (Schedulers.isInNonBlockingThread()) {
|
|
|
|
throw new UnsupportedOperationException("Called update in a nonblocking thread");
|
|
|
|
}
|
|
|
|
try (var tx = beginTransaction(writeOptions)) {
|
|
|
|
boolean committedSuccessfully;
|
|
|
|
int retries = 0;
|
|
|
|
ExponentialPageLimits retryTime = null;
|
|
|
|
Send<Buffer> sentPrevData;
|
|
|
|
Send<Buffer> sentCurData;
|
|
|
|
boolean changed;
|
|
|
|
do {
|
|
|
|
var prevDataArray = tx.getForUpdate(readOptions, cfh, keyArray, true);
|
|
|
|
if (logger.isTraceEnabled()) {
|
|
|
|
logger.trace(MARKER_ROCKSDB,
|
|
|
|
"Reading {}: {} (before update)",
|
|
|
|
LLUtils.toStringSafe(key),
|
|
|
|
LLUtils.toStringSafe(prevDataArray)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
Buffer prevData;
|
|
|
|
if (prevDataArray != null) {
|
|
|
|
prevData = MemoryManager.unsafeWrap(prevDataArray);
|
2021-10-20 01:51:34 +02:00
|
|
|
} else {
|
2021-12-29 00:31:35 +01:00
|
|
|
prevData = null;
|
2021-10-20 01:51:34 +02:00
|
|
|
}
|
2021-12-29 00:31:35 +01:00
|
|
|
try (prevData) {
|
|
|
|
Buffer prevDataToSendToUpdater;
|
|
|
|
if (prevData != null) {
|
|
|
|
prevDataToSendToUpdater = prevData.copy();
|
|
|
|
} else {
|
|
|
|
prevDataToSendToUpdater = null;
|
|
|
|
}
|
2021-10-20 01:51:34 +02:00
|
|
|
|
2021-12-29 00:31:35 +01:00
|
|
|
@Nullable Buffer newData;
|
|
|
|
try (var sentData = prevDataToSendToUpdater == null ? null : prevDataToSendToUpdater.send()) {
|
|
|
|
newData = updater.apply(sentData);
|
2021-10-20 01:51:34 +02:00
|
|
|
}
|
2021-12-29 00:31:35 +01:00
|
|
|
try (newData) {
|
|
|
|
var newDataArray = newData == null ? null : LLUtils.toArray(newData);
|
2021-10-20 01:51:34 +02:00
|
|
|
if (logger.isTraceEnabled()) {
|
|
|
|
logger.trace(MARKER_ROCKSDB,
|
2021-12-29 00:31:35 +01:00
|
|
|
"Updating {}. previous data: {}, updated data: {}",
|
2021-10-20 01:51:34 +02:00
|
|
|
LLUtils.toStringSafe(key),
|
2021-12-29 00:31:35 +01:00
|
|
|
LLUtils.toStringSafe(prevDataArray),
|
|
|
|
LLUtils.toStringSafe(newDataArray)
|
2021-10-20 01:51:34 +02:00
|
|
|
);
|
|
|
|
}
|
2021-12-29 00:31:35 +01:00
|
|
|
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,
|
|
|
|
"Writing {}: {} (after update)",
|
|
|
|
LLUtils.toStringSafe(key),
|
|
|
|
LLUtils.toStringSafe(newData)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
tx.put(cfh, keyArray, newDataArray);
|
|
|
|
changed = true;
|
|
|
|
committedSuccessfully = commitOptimistically(tx);
|
|
|
|
} else {
|
|
|
|
changed = false;
|
|
|
|
committedSuccessfully = true;
|
|
|
|
tx.rollback();
|
2021-10-20 01:51:34 +02:00
|
|
|
}
|
2021-12-29 00:31:35 +01:00
|
|
|
sentPrevData = prevData == null ? null : prevData.send();
|
|
|
|
sentCurData = newData == null ? null : newData.send();
|
|
|
|
if (!committedSuccessfully) {
|
|
|
|
tx.undoGetForUpdate(cfh, keyArray);
|
|
|
|
tx.rollback();
|
|
|
|
if (sentPrevData != null) {
|
|
|
|
sentPrevData.close();
|
|
|
|
}
|
|
|
|
if (sentCurData != null) {
|
|
|
|
sentCurData.close();
|
|
|
|
}
|
|
|
|
retries++;
|
2021-10-30 11:13:46 +02:00
|
|
|
|
2021-12-29 00:31:35 +01:00
|
|
|
if (retries == 1) {
|
|
|
|
retryTime = new ExponentialPageLimits(0, 2, 2000);
|
|
|
|
}
|
|
|
|
long retryNs = 1000000L * retryTime.getPageLimit(retries);
|
2021-10-30 11:13:46 +02:00
|
|
|
|
2021-12-29 00:31:35 +01:00
|
|
|
// +- 30%
|
|
|
|
retryNs = retryNs + ThreadLocalRandom.current().nextLong(-retryNs * 30L / 100L, retryNs * 30L / 100L);
|
2021-10-30 11:13:46 +02:00
|
|
|
|
2021-12-29 00:31:35 +01: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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-12-29 00:31:35 +01: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
|
|
|
}
|
2021-12-29 00:31:35 +01:00
|
|
|
return switch (returnMode) {
|
|
|
|
case NOTHING -> {
|
|
|
|
if (sentPrevData != null) {
|
|
|
|
sentPrevData.close();
|
|
|
|
}
|
|
|
|
if (sentCurData != null) {
|
|
|
|
sentCurData.close();
|
|
|
|
}
|
|
|
|
yield RESULT_NOTHING;
|
2021-10-20 01:51:34 +02:00
|
|
|
}
|
2021-12-29 00:31:35 +01:00
|
|
|
case CURRENT -> {
|
|
|
|
if (sentPrevData != null) {
|
|
|
|
sentPrevData.close();
|
|
|
|
}
|
|
|
|
yield new UpdateAtomicResultCurrent(sentCurData);
|
2021-10-20 01:51:34 +02:00
|
|
|
}
|
2021-12-29 00:31:35 +01:00
|
|
|
case PREVIOUS -> {
|
|
|
|
if (sentCurData != null) {
|
|
|
|
sentCurData.close();
|
|
|
|
}
|
|
|
|
yield new UpdateAtomicResultPrevious(sentPrevData);
|
2021-10-20 01:51:34 +02:00
|
|
|
}
|
2021-12-29 00:31:35 +01:00
|
|
|
case BINARY_CHANGED -> new UpdateAtomicResultBinaryChanged(changed);
|
|
|
|
case DELTA -> new UpdateAtomicResultDelta(LLDelta.of(sentPrevData, sentCurData).send());
|
|
|
|
};
|
|
|
|
}
|
|
|
|
} 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;
|
|
|
|
}
|
|
|
|
}
|