
com.arcadedb.server.ha.ReplicatedDatabase Maven / Gradle / Ivy
The newest version!
/*
* Copyright © 2021-present Arcade Data Ltd ([email protected])
*
* 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.
*
* SPDX-FileCopyrightText: 2021-present Arcade Data Ltd ([email protected])
* SPDX-License-Identifier: Apache-2.0
*/
package com.arcadedb.server.ha;
import com.arcadedb.ContextConfiguration;
import com.arcadedb.GlobalConfiguration;
import com.arcadedb.database.Binary;
import com.arcadedb.database.Database;
import com.arcadedb.database.DatabaseContext;
import com.arcadedb.database.DatabaseInternal;
import com.arcadedb.database.DocumentCallback;
import com.arcadedb.database.DocumentIndexer;
import com.arcadedb.database.EmbeddedModifier;
import com.arcadedb.database.LocalDatabase;
import com.arcadedb.database.MutableDocument;
import com.arcadedb.database.MutableEmbeddedDocument;
import com.arcadedb.database.RID;
import com.arcadedb.database.Record;
import com.arcadedb.database.RecordCallback;
import com.arcadedb.database.RecordEvents;
import com.arcadedb.database.RecordFactory;
import com.arcadedb.database.TransactionContext;
import com.arcadedb.database.async.DatabaseAsyncExecutor;
import com.arcadedb.database.async.ErrorCallback;
import com.arcadedb.database.async.OkCallback;
import com.arcadedb.engine.ComponentFile;
import com.arcadedb.engine.ErrorRecordCallback;
import com.arcadedb.engine.FileManager;
import com.arcadedb.engine.PageManager;
import com.arcadedb.engine.TransactionManager;
import com.arcadedb.engine.WALFile;
import com.arcadedb.engine.WALFileFactory;
import com.arcadedb.exception.ConfigurationException;
import com.arcadedb.exception.NeedRetryException;
import com.arcadedb.exception.TransactionException;
import com.arcadedb.graph.Edge;
import com.arcadedb.graph.GraphEngine;
import com.arcadedb.graph.MutableVertex;
import com.arcadedb.graph.Vertex;
import com.arcadedb.index.IndexCursor;
import com.arcadedb.log.LogManager;
import com.arcadedb.network.binary.ServerIsNotTheLeaderException;
import com.arcadedb.query.QueryEngine;
import com.arcadedb.query.select.Select;
import com.arcadedb.query.sql.executor.ResultSet;
import com.arcadedb.query.sql.parser.ExecutionPlanCache;
import com.arcadedb.query.sql.parser.StatementCache;
import com.arcadedb.schema.Schema;
import com.arcadedb.security.SecurityDatabaseUser;
import com.arcadedb.serializer.BinarySerializer;
import com.arcadedb.serializer.json.JSONObject;
import com.arcadedb.server.ArcadeDBServer;
import com.arcadedb.server.ha.message.CommandForwardRequest;
import com.arcadedb.server.ha.message.DatabaseAlignRequest;
import com.arcadedb.server.ha.message.DatabaseAlignResponse;
import com.arcadedb.server.ha.message.DatabaseChangeStructureRequest;
import com.arcadedb.server.ha.message.InstallDatabaseRequest;
import com.arcadedb.server.ha.message.TxForwardRequest;
import com.arcadedb.server.ha.message.TxRequest;
import java.io.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
import java.util.logging.*;
public class ReplicatedDatabase implements DatabaseInternal {
private final ArcadeDBServer server;
private final LocalDatabase proxied;
private final HAServer.QUORUM quorum;
private final long timeout;
public ReplicatedDatabase(final ArcadeDBServer server, final LocalDatabase proxied) {
if (!server.getConfiguration().getValueAsBoolean(GlobalConfiguration.TX_WAL))
throw new ConfigurationException("Cannot use replicated database if transaction WAL is disabled");
this.server = server;
this.proxied = proxied;
this.timeout = proxied.getConfiguration().getValueAsLong(GlobalConfiguration.HA_QUORUM_TIMEOUT);
this.proxied.setWrappedDatabaseInstance(this);
HAServer.QUORUM quorum;
final String quorumValue = proxied.getConfiguration().getValueAsString(GlobalConfiguration.HA_QUORUM)
.toUpperCase(Locale.ENGLISH);
try {
quorum = HAServer.QUORUM.valueOf(quorumValue);
} catch (Exception e) {
LogManager.instance()
.log(this, Level.SEVERE, "Error on setting quorum to '%s' for database '%s'. Setting it to MAJORITY", e, quorumValue,
getName());
quorum = HAServer.QUORUM.MAJORITY;
}
this.quorum = quorum;
}
@Override
public void commit() {
proxied.incrementStatsTxCommits();
final boolean isLeader = isLeader();
proxied.executeInReadLock(() -> {
proxied.checkTransactionIsActive(false);
final DatabaseContext.DatabaseContextTL current = DatabaseContext.INSTANCE.getContext(proxied.getDatabasePath());
final TransactionContext tx = current.getLastTransaction();
try {
final TransactionContext.TransactionPhase1 phase1 = tx.commit1stPhase(isLeader);
try {
if (phase1 != null) {
final Binary bufferChanges = phase1.result;
if (isLeader)
replicateTx(tx, phase1, bufferChanges);
else {
// USE A BIGGER TIMEOUT CONSIDERING THE DOUBLE LATENCY
final TxForwardRequest command = new TxForwardRequest(ReplicatedDatabase.this, getTransactionIsolationLevel(),
tx.getBucketRecordDelta(), bufferChanges, tx.getIndexChanges().toMap());
server.getHA().forwardCommandToLeader(command, timeout * 2);
tx.reset();
}
} else {
tx.reset();
}
} catch (final NeedRetryException | TransactionException e) {
rollback();
throw e;
} catch (final Exception e) {
rollback();
throw new TransactionException("Error on commit distributed transaction", e);
}
if (getSchema().getEmbedded().isDirty())
getSchema().getEmbedded().saveConfiguration();
} finally {
current.popIfNotLastTransaction();
}
return null;
});
}
public void replicateTx(final TransactionContext tx, final TransactionContext.TransactionPhase1 phase1,
final Binary bufferChanges) {
final int configuredServers = server.getHA().getConfiguredServers();
final int reqQuorum = quorum.quorum(configuredServers);
final TxRequest req = new TxRequest(getName(), tx.getBucketRecordDelta(), bufferChanges, reqQuorum > 1);
final DatabaseChangeStructureRequest changeStructureRequest = getChangeStructure(-1);
if (changeStructureRequest != null) {
// RESET STRUCTURE CHANGES FROM THIS POINT ONWARDS
proxied.getFileManager().stopRecordingChanges();
proxied.getFileManager().startRecordingChanges();
req.changeStructure = changeStructureRequest;
}
server.getHA().sendCommandToReplicasWithQuorum(req, reqQuorum, timeout);
// COMMIT 2ND PHASE ONLY IF THE QUORUM HAS BEEN REACHED
tx.commit2ndPhase(phase1);
}
@Override
public DatabaseInternal getWrappedDatabaseInstance() {
return this;
}
@Override
public Map getWrappers() {
return proxied.getWrappers();
}
@Override
public void setWrapper(final String name, final Object instance) {
proxied.setWrapper(name, instance);
}
@Override
public void checkPermissionsOnDatabase(final SecurityDatabaseUser.DATABASE_ACCESS access) {
proxied.checkPermissionsOnDatabase(access);
}
@Override
public void checkPermissionsOnFile(final int fileId, final SecurityDatabaseUser.ACCESS access) {
proxied.checkPermissionsOnFile(fileId, access);
}
@Override
public long getResultSetLimit() {
return proxied.getResultSetLimit();
}
@Override
public long getReadTimeout() {
return proxied.getReadTimeout();
}
@Override
public Map getStats() {
return proxied.getStats();
}
@Override
public LocalDatabase getEmbedded() {
return proxied;
}
@Override
public DatabaseContext.DatabaseContextTL getContext() {
return proxied.getContext();
}
@Override
public void close() {
proxied.close();
}
@Override
public void drop() {
throw new UnsupportedOperationException("Server proxied database instance cannot be drop");
}
@Override
public void registerCallback(final CALLBACK_EVENT event, final Callable callback) {
proxied.registerCallback(event, callback);
}
@Override
public void unregisterCallback(final CALLBACK_EVENT event, final Callable callback) {
proxied.unregisterCallback(event, callback);
}
@Override
public void executeCallbacks(final CALLBACK_EVENT event) throws IOException {
proxied.executeCallbacks(event);
}
@Override
public GraphEngine getGraphEngine() {
return proxied.getGraphEngine();
}
@Override
public TransactionManager getTransactionManager() {
return proxied.getTransactionManager();
}
@Override
public void createRecord(final MutableDocument record) {
proxied.createRecord(record);
}
@Override
public void createRecord(final Record record, final String bucketName) {
proxied.createRecord(record, bucketName);
}
@Override
public void createRecordNoLock(final Record record, final String bucketName, final boolean discardRecordAfter) {
proxied.createRecordNoLock(record, bucketName, discardRecordAfter);
}
@Override
public void updateRecord(final Record record) {
proxied.updateRecord(record);
}
@Override
public void updateRecordNoLock(final Record record, final boolean discardRecordAfter) {
proxied.updateRecordNoLock(record, discardRecordAfter);
}
@Override
public void deleteRecordNoLock(final Record record) {
proxied.deleteRecordNoLock(record);
}
@Override
public DocumentIndexer getIndexer() {
return proxied.getIndexer();
}
@Override
public void kill() {
proxied.kill();
}
@Override
public WALFileFactory getWALFileFactory() {
return proxied.getWALFileFactory();
}
@Override
public StatementCache getStatementCache() {
return proxied.getStatementCache();
}
@Override
public ExecutionPlanCache getExecutionPlanCache() {
return proxied.getExecutionPlanCache();
}
@Override
public int getNewEdgeListSize(final int previousSize) {
return proxied.getNewEdgeListSize(previousSize);
}
@Override
public String getName() {
return proxied.getName();
}
@Override
public ComponentFile.MODE getMode() {
return proxied.getMode();
}
@Override
public DatabaseAsyncExecutor async() {
return proxied.async();
}
@Override
public String getDatabasePath() {
return proxied.getDatabasePath();
}
@Override
public String getCurrentUserName() {
return proxied.getCurrentUserName();
}
@Override
public Select select() {
return proxied.select();
}
@Override
public ContextConfiguration getConfiguration() {
return proxied.getConfiguration();
}
@Override
public Record invokeAfterReadEvents(final Record record) {
return record;
}
@Override
public TransactionContext getTransactionIfExists() {
return proxied.getTransactionIfExists();
}
@Override
public boolean isTransactionActive() {
return proxied.isTransactionActive();
}
@Override
public int getNestedTransactions() {
return proxied.getNestedTransactions();
}
@Override
public boolean checkTransactionIsActive(final boolean createTx) {
return proxied.checkTransactionIsActive(createTx);
}
@Override
public boolean isAsyncProcessing() {
return proxied.isAsyncProcessing();
}
@Override
public void transaction(final TransactionScope txBlock) {
proxied.transaction(txBlock);
}
@Override
public boolean isAutoTransaction() {
return proxied.isAutoTransaction();
}
@Override
public void setAutoTransaction(final boolean autoTransaction) {
proxied.setAutoTransaction(autoTransaction);
}
@Override
public void begin() {
proxied.begin();
}
@Override
public void begin(final TRANSACTION_ISOLATION_LEVEL isolationLevel) {
proxied.begin(isolationLevel);
}
@Override
public void rollback() {
proxied.rollback();
}
@Override
public void rollbackAllNested() {
proxied.rollbackAllNested();
}
@Override
public void scanType(final String typeName, final boolean polymorphic, final DocumentCallback callback) {
proxied.scanType(typeName, polymorphic, callback);
}
@Override
public void scanType(final String typeName, final boolean polymorphic, final DocumentCallback callback,
final ErrorRecordCallback errorRecordCallback) {
proxied.scanType(typeName, polymorphic, callback, errorRecordCallback);
}
@Override
public void scanBucket(final String bucketName, final RecordCallback callback) {
proxied.scanBucket(bucketName, callback);
}
@Override
public void scanBucket(final String bucketName, final RecordCallback callback, final ErrorRecordCallback errorRecordCallback) {
proxied.scanBucket(bucketName, callback, errorRecordCallback);
}
@Override
public boolean existsRecord(RID rid) {
return proxied.existsRecord(rid);
}
@Override
public Record lookupByRID(final RID rid, final boolean loadContent) {
return proxied.lookupByRID(rid, loadContent);
}
@Override
public Iterator iterateType(final String typeName, final boolean polymorphic) {
return proxied.iterateType(typeName, polymorphic);
}
@Override
public Iterator iterateBucket(final String bucketName) {
return proxied.iterateBucket(bucketName);
}
@Override
public IndexCursor lookupByKey(final String type, final String keyName, final Object keyValue) {
return proxied.lookupByKey(type, keyName, keyValue);
}
@Override
public IndexCursor lookupByKey(final String type, final String[] keyNames, final Object[] keyValues) {
return proxied.lookupByKey(type, keyNames, keyValues);
}
@Override
public void deleteRecord(final Record record) {
proxied.deleteRecord(record);
}
@Override
public long countType(final String typeName, final boolean polymorphic) {
return proxied.countType(typeName, polymorphic);
}
@Override
public long countBucket(final String bucketName) {
return proxied.countBucket(bucketName);
}
@Override
public MutableDocument newDocument(final String typeName) {
return proxied.newDocument(typeName);
}
@Override
public MutableEmbeddedDocument newEmbeddedDocument(final EmbeddedModifier modifier, final String typeName) {
return proxied.newEmbeddedDocument(modifier, typeName);
}
@Override
public MutableVertex newVertex(final String typeName) {
return proxied.newVertex(typeName);
}
@Override
public Edge newEdgeByKeys(final Vertex sourceVertex, final String destinationVertexType, final String[] destinationVertexKeyNames,
final Object[] destinationVertexKeyValues, final boolean createVertexIfNotExist, final String edgeType,
final boolean bidirectional, final Object... properties) {
return proxied.newEdgeByKeys(sourceVertex, destinationVertexType, destinationVertexKeyNames, destinationVertexKeyValues,
createVertexIfNotExist, edgeType, bidirectional, properties);
}
@Override
public QueryEngine getQueryEngine(final String language) {
return proxied.getQueryEngine(language);
}
@Override
public Edge newEdgeByKeys(final String sourceVertexType, final String[] sourceVertexKeyNames,
final Object[] sourceVertexKeyValues, final String destinationVertexType, final String[] destinationVertexKeyNames,
final Object[] destinationVertexKeyValues, final boolean createVertexIfNotExist, final String edgeType,
final boolean bidirectional, final Object... properties) {
return proxied.newEdgeByKeys(sourceVertexType, sourceVertexKeyNames, sourceVertexKeyValues, destinationVertexType,
destinationVertexKeyNames, destinationVertexKeyValues, createVertexIfNotExist, edgeType, bidirectional, properties);
}
@Override
public Schema getSchema() {
return proxied.getSchema();
}
@Override
public RecordEvents getEvents() {
return proxied.getEvents();
}
@Override
public FileManager getFileManager() {
return proxied.getFileManager();
}
@Override
public boolean transaction(final TransactionScope txBlock, final boolean joinActiveTx) {
return proxied.transaction(txBlock, joinActiveTx);
}
@Override
public boolean transaction(final TransactionScope txBlock, final boolean joinCurrentTx, final int retries) {
return proxied.transaction(txBlock, joinCurrentTx, retries);
}
@Override
public boolean transaction(final TransactionScope txBlock, final boolean joinCurrentTx, final int retries, final OkCallback ok,
final ErrorCallback error) {
return proxied.transaction(txBlock, joinCurrentTx, retries, ok, error);
}
@Override
public RecordFactory getRecordFactory() {
return proxied.getRecordFactory();
}
@Override
public BinarySerializer getSerializer() {
return proxied.getSerializer();
}
@Override
public PageManager getPageManager() {
return proxied.getPageManager();
}
@Override
public int hashCode() {
return proxied.hashCode();
}
public boolean equals(final Object o) {
if (this == o)
return true;
if (!(o instanceof Database))
return false;
final Database other = (Database) o;
return Objects.equals(getDatabasePath(), other.getDatabasePath());
}
@Override
public ResultSet command(final String language, final String query, final ContextConfiguration configuration,
final Object... args) {
if (!isLeader()) {
final QueryEngine queryEngine = proxied.getQueryEngineManager().getInstance(language, this);
if (queryEngine.isExecutedByTheLeader() || queryEngine.analyze(query).isDDL()) {
// USE A BIGGER TIMEOUT CONSIDERING THE DOUBLE LATENCY
final CommandForwardRequest command = new CommandForwardRequest(ReplicatedDatabase.this, language, query, null, args);
return (ResultSet) server.getHA().forwardCommandToLeader(command, timeout * 2);
}
return proxied.command(language, query, configuration, args);
}
return proxied.command(language, query, configuration, args);
}
@Override
public ResultSet command(final String language, final String query, final Object... args) {
return command(language, query, server.getConfiguration(), args);
}
@Override
public ResultSet command(final String language, final String query, final Map args) {
return command(language, query, server.getConfiguration(), args);
}
@Override
public ResultSet command(final String language, final String query, final ContextConfiguration configuration,
final Map args) {
if (!isLeader()) {
final QueryEngine queryEngine = proxied.getQueryEngineManager().getInstance(language, this);
if (queryEngine.isExecutedByTheLeader() || queryEngine.analyze(query).isDDL()) {
// USE A BIGGER TIMEOUT CONSIDERING THE DOUBLE LATENCY
final CommandForwardRequest command = new CommandForwardRequest(ReplicatedDatabase.this, language, query, args, null);
return (ResultSet) server.getHA().forwardCommandToLeader(command, timeout * 2);
}
}
return proxied.command(language, query, configuration, args);
}
@Override
public ResultSet query(final String language, final String query, final Object... args) {
return proxied.query(language, query, args);
}
@Override
public ResultSet query(final String language, final String query, final Map args) {
return proxied.query(language, query, args);
}
@Deprecated
@Override
public ResultSet execute(final String language, final String script, final Object... args) {
return proxied.execute(language, script, args);
}
@Deprecated
@Override
public ResultSet execute(final String language, final String script, final Map args) {
return proxied.execute(language, script, server.getConfiguration(), args);
}
@Override
public RET executeInReadLock(final Callable callable) {
return proxied.executeInReadLock(callable);
}
@Override
public RET executeInWriteLock(final Callable callable) {
return proxied.executeInWriteLock(callable);
}
@Override
public RET executeLockingFiles(final Collection fileIds, final Callable callable) {
return proxied.executeLockingFiles(fileIds, callable);
}
@Override
public boolean isReadYourWrites() {
return proxied.isReadYourWrites();
}
@Override
public Database setReadYourWrites(final boolean value) {
proxied.setReadYourWrites(value);
return this;
}
@Override
public Database setTransactionIsolationLevel(final TRANSACTION_ISOLATION_LEVEL level) {
return proxied.setTransactionIsolationLevel(level);
}
@Override
public TRANSACTION_ISOLATION_LEVEL getTransactionIsolationLevel() {
return proxied.getTransactionIsolationLevel();
}
@Override
public int getEdgeListSize() {
return proxied.getEdgeListSize();
}
@Override
public Database setEdgeListSize(final int size) {
proxied.setEdgeListSize(size);
return this;
}
@Override
public Database setUseWAL(final boolean useWAL) {
return proxied.setUseWAL(useWAL);
}
@Override
public Database setWALFlush(final WALFile.FLUSH_TYPE flush) {
return proxied.setWALFlush(flush);
}
@Override
public boolean isAsyncFlush() {
return proxied.isAsyncFlush();
}
@Override
public Database setAsyncFlush(final boolean value) {
return proxied.setAsyncFlush(value);
}
@Override
public boolean isOpen() {
return proxied.isOpen();
}
@Override
public String toString() {
return proxied.toString() + "[" + server.getServerName() + "]";
}
public RET recordFileChanges(final Callable
© 2015 - 2025 Weber Informatics LLC | Privacy Policy