All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.infomaximum.rocksdb.RocksDBTransaction Maven / Gradle / Ivy

The newest version!
package com.infomaximum.rocksdb;

import com.google.common.primitives.UnsignedBytes;
import com.infomaximum.database.exception.ColumnFamilyNotFoundException;
import com.infomaximum.database.exception.DatabaseException;
import com.infomaximum.database.exception.SequenceNotFoundException;
import com.infomaximum.database.provider.DBIterator;
import com.infomaximum.database.provider.DBTransaction;
import com.infomaximum.database.provider.KeyPattern;
import com.infomaximum.database.utils.ByteInterval;
import org.rocksdb.ColumnFamilyHandle;
import org.rocksdb.RocksDBException;
import org.rocksdb.RocksIterator;
import org.rocksdb.Transaction;

import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;

public class RocksDBTransaction implements DBTransaction {

    private static final Comparator KEY_COMPARATOR = UnsignedBytes.lexicographicalComparator();

    private final Transaction transaction;
    private final RocksDBProvider rocksDBProvider;
    private final Map compactingKeys = new HashMap<>();

    RocksDBTransaction(Transaction transaction, RocksDBProvider rocksDBProvider) {
        this.transaction = transaction;
        this.rocksDBProvider = rocksDBProvider;
    }

    @Override
    public DBIterator createIterator(String columnFamily) throws DatabaseException {
        return buildIterator(rocksDBProvider.getColumnFamilyHandle(columnFamily));
    }

    @Override
    public long nextId(String sequenceName) throws DatabaseException {
        SequenceManager.Sequence sequence = rocksDBProvider.getSequenceManager().getSequence(sequenceName);
        if (sequence == null) {
            throw new SequenceNotFoundException(sequenceName);
        }
        return sequence.next();
    }

    @Override
    public byte[] getValue(String columnFamily, byte[] key) throws DatabaseException {
        try {
            return transaction.get(rocksDBProvider.getColumnFamilyHandle(columnFamily), rocksDBProvider.getReadOptions(), key);
        } catch (RocksDBException e) {
            throw new DatabaseException(e);
        }
    }

    @Override
    public void put(String columnFamily, byte[] key, byte[] value) throws DatabaseException {
        ColumnFamilyHandle columnFamilyHandle = rocksDBProvider.getColumnFamilyHandle(columnFamily);
        try {
            transaction.put(columnFamilyHandle, key, value);
        } catch (RocksDBException e) {
            throw new DatabaseException(e);
        }
    }

    @Override
    public void delete(String columnFamily, byte[] key) throws DatabaseException {
        delete(columnFamily, key, transaction::delete);

        compactingKeys.computeIfAbsent(columnFamily, s -> new RangeKey()).setKey(key);
    }

    @Override
    public void deleteRange(String columnFamily, byte[] beginKey, byte[] endKey) throws DatabaseException {
        deleteRange(columnFamily, beginKey, endKey, transaction::delete);

        compactingKeys.computeIfAbsent(columnFamily, s -> new RangeKey()).setRange(beginKey, endKey);
    }

    @Override
    public void singleDelete(String columnFamily, byte[] key) throws DatabaseException {
        delete(columnFamily, key, transaction::delete);

        compactingKeys.computeIfAbsent(columnFamily, s -> new RangeKey()).setKey(key);
    }

    @Override
    public void singleDeleteRange(String columnFamily, byte[] beginKey, byte[] endKey) throws DatabaseException {
        deleteRange(columnFamily, beginKey, endKey, transaction::delete);

        compactingKeys.computeIfAbsent(columnFamily, s -> new RangeKey()).setRange(beginKey, endKey);
    }

    @Override
    public void singleDeleteRange(String columnFamily, KeyPattern keyPattern) throws DatabaseException {
        ByteInterval deleteRange = deleteRange(columnFamily, keyPattern, transaction::delete);
        deleteRange.validate();
        if (deleteRange.getBegin() != null && deleteRange.getEnd() != null) {
            compactingKeys.computeIfAbsent(columnFamily, s -> new RangeKey()).setRange(deleteRange.getBegin(), deleteRange.getEnd());
        }
    }

    private void delete(String columnFamily, byte[] key, BiConsumer deleteFunc) throws DatabaseException {
        ColumnFamilyHandle columnFamilyHandle = rocksDBProvider.getColumnFamilyHandle(columnFamily);
        try {
            deleteFunc.accept(columnFamilyHandle, key);
        } catch (RocksDBException e) {
            throw new DatabaseException(e);
        }
    }

    private void deleteRange(String columnFamily, byte[] beginKey, byte[] endKey, BiConsumer deleteFunc) throws DatabaseException {
        ColumnFamilyHandle columnFamilyHandle = rocksDBProvider.getColumnFamilyHandle(columnFamily);

        try (RocksIterator i = transaction.getIterator(rocksDBProvider.getReadOptions(), columnFamilyHandle)) {
            for (i.seek(beginKey); i.isValid(); i.next()) {
                byte[] key = i.key();
                if (key == null || KEY_COMPARATOR.compare(key, endKey) >= 0) {
                    break;
                }

                deleteFunc.accept(columnFamilyHandle, key);
            }

            i.status();
        } catch (RocksDBException e) {
            throw new DatabaseException(e);
        }
    }

    private ByteInterval deleteRange(String columnFamily, KeyPattern keyPattern, BiConsumer deleteFunc) throws DatabaseException {
        ColumnFamilyHandle columnFamilyHandle = rocksDBProvider.getColumnFamilyHandle(columnFamily);
        ByteInterval result = new ByteInterval();
        try (RocksIterator i = transaction.getIterator(rocksDBProvider.getReadOptions(), columnFamilyHandle)) {
            for (i.seek(keyPattern.getPrefix()); i.isValid(); i.next()) {
                byte[] key = i.key();
                if (key == null || keyPattern.match(key) == KeyPattern.MATCH_RESULT_UNSUCCESS) {
                    break;
                }
                result.setBeginIfAbsent(key);
                result.setEnd(key);
                deleteFunc.accept(columnFamilyHandle, key);
            }

            i.status();
        } catch (RocksDBException e) {
            throw new DatabaseException(e);
        }
        return result;
    }

    @Override
    public void commit() throws DatabaseException {
        try {
            transaction.commit();
            compact();
        } catch (RocksDBException e) {
            throw new DatabaseException(e);
        } finally {
            compactingKeys.clear();
        }
    }

    @Override
    public void rollback() throws DatabaseException {
        try {
            transaction.rollback();
        } catch (RocksDBException e) {
            throw new DatabaseException(e);
        } finally {
            compactingKeys.clear();
        }
    }

    @Override
    public void compactRange() throws DatabaseException {
        try {
            rocksDBProvider.getRocksDB().compactRange();
        } catch (RocksDBException e) {
            throw new DatabaseException(e);
        }
    }

    @Override
    public void close() throws DatabaseException {
        transaction.close();
    }

    private RocksDBIterator buildIterator(ColumnFamilyHandle columnFamily) {
        return new RocksDBIterator(transaction.getIterator(rocksDBProvider.getReadOptions(), columnFamily));
    }

    //TODO ULitin V. Временно отключили компакшен - сильно бьет по производительности - необходимо другое решение
    private void compact() throws ColumnFamilyNotFoundException, RocksDBException {
//        for (Map.Entry entry : compactingKeys.entrySet()) {
//            ColumnFamilyHandle columnFamilyHandle = rocksDBProvider.getColumnFamilyHandle(entry.getKey());
//            rocksDBProvider.getRocksDB().compactRange(
//                    columnFamilyHandle,
//                    entry.getValue().begin,
//                    entry.getValue().end,
//                    true, -1, 0);
//        }
    }

    private static class RangeKey {

        byte[] begin = null;
        byte[] end = null;

        void setBegin(byte[] key) {
            if (begin == null || KEY_COMPARATOR.compare(key, begin) < 0) {
                begin = key;
            }
        }

        void setEnd(byte[] key) {
            if (end == null || KEY_COMPARATOR.compare(key, end) > 0) {
                end = key;
            }
        }

        void setRange(byte[] begin, byte[] end) {
            setBegin(begin);
            setEnd(end);
        }

        void setKey(byte[] key) {
            if (begin == null) {
                begin = key;
                end = nextOf(Arrays.copyOf(key, key.length));
            } else {
                int res = KEY_COMPARATOR.compare(key, begin);
                if (res < 0) {
                    begin = key;
                } else if (res != 0) {
                    res = KEY_COMPARATOR.compare(key, end);
                    if (res > 0) {
                        end = nextOf(key);
                    }
                }
            }
        }

        private static byte[] nextOf(byte[] key) {
            int val = UnsignedBytes.toInt(key[key.length - 1]);
            if (val >= 0xff) {
                key = Arrays.copyOf(key, key.length + 1);
                val = 0;
            }
            key[key.length - 1] = UnsignedBytes.checkedCast(++val);
            return key;
        }
    }

    @FunctionalInterface
    private interface BiConsumer {

        void accept(T t, U u) throws RocksDBException;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy