Search in sources :

Example 1 with KCVSLog

use of com.thinkaurelius.titan.diskstorage.log.kcvs.KCVSLog in project titan by thinkaurelius.

the class StandardTitanGraph method commit.

public void commit(final Collection<InternalRelation> addedRelations, final Collection<InternalRelation> deletedRelations, final StandardTitanTx tx) {
    if (addedRelations.isEmpty() && deletedRelations.isEmpty())
        return;
    //1. Finalize transaction
    log.debug("Saving transaction. Added {}, removed {}", addedRelations.size(), deletedRelations.size());
    if (!tx.getConfiguration().hasCommitTime())
        tx.getConfiguration().setCommitTime(times.getTime());
    final Instant txTimestamp = tx.getConfiguration().getCommitTime();
    final long transactionId = txCounter.incrementAndGet();
    //2. Assign TitanVertex IDs
    if (!tx.getConfiguration().hasAssignIDsImmediately())
        idAssigner.assignIDs(addedRelations);
    //3. Commit
    BackendTransaction mutator = tx.getTxHandle();
    final boolean acquireLocks = tx.getConfiguration().hasAcquireLocks();
    final boolean hasTxIsolation = backend.getStoreFeatures().hasTxIsolation();
    final boolean logTransaction = config.hasLogTransactions() && !tx.getConfiguration().hasEnabledBatchLoading();
    final KCVSLog txLog = logTransaction ? backend.getSystemTxLog() : null;
    final TransactionLogHeader txLogHeader = new TransactionLogHeader(transactionId, txTimestamp, times);
    ModificationSummary commitSummary;
    try {
        //3.1 Log transaction (write-ahead log) if enabled
        if (logTransaction) {
            //[FAILURE] Inability to log transaction fails the transaction by escalation since it's likely due to unavailability of primary
            //storage backend.
            txLog.add(txLogHeader.serializeModifications(serializer, LogTxStatus.PRECOMMIT, tx, addedRelations, deletedRelations), txLogHeader.getLogKey());
        }
        //3.2 Commit schema elements and their associated relations in a separate transaction if backend does not support
        //    transactional isolation
        boolean hasSchemaElements = !Iterables.isEmpty(Iterables.filter(deletedRelations, SCHEMA_FILTER)) || !Iterables.isEmpty(Iterables.filter(addedRelations, SCHEMA_FILTER));
        Preconditions.checkArgument(!hasSchemaElements || (!tx.getConfiguration().hasEnabledBatchLoading() && acquireLocks), "Attempting to create schema elements in inconsistent state");
        if (hasSchemaElements && !hasTxIsolation) {
            /*
                 * On storage without transactional isolation, create separate
                 * backend transaction for schema aspects to make sure that
                 * those are persisted prior to and independently of other
                 * mutations in the tx. If the storage supports transactional
                 * isolation, then don't create a separate tx.
                 */
            final BackendTransaction schemaMutator = openBackendTransaction(tx);
            try {
                //[FAILURE] If the preparation throws an exception abort directly - nothing persisted since batch-loading cannot be enabled for schema elements
                commitSummary = prepareCommit(addedRelations, deletedRelations, SCHEMA_FILTER, schemaMutator, tx, acquireLocks);
                assert commitSummary.hasModifications && !commitSummary.has2iModifications;
            } catch (Throwable e) {
                //Roll back schema tx and escalate exception
                schemaMutator.rollback();
                throw e;
            }
            try {
                schemaMutator.commit();
            } catch (Throwable e) {
                //[FAILURE] Primary persistence failed => abort and escalate exception, nothing should have been persisted
                log.error("Could not commit transaction [" + transactionId + "] due to storage exception in system-commit", e);
                throw e;
            }
        }
        //[FAILURE] Exceptions during preparation here cause the entire transaction to fail on transactional systems
        //or just the non-system part on others. Nothing has been persisted unless batch-loading
        commitSummary = prepareCommit(addedRelations, deletedRelations, hasTxIsolation ? NO_FILTER : NO_SCHEMA_FILTER, mutator, tx, acquireLocks);
        if (commitSummary.hasModifications) {
            String logTxIdentifier = tx.getConfiguration().getLogIdentifier();
            boolean hasSecondaryPersistence = logTxIdentifier != null || commitSummary.has2iModifications;
            //    This should not throw an exception since the mutations are just cached. If it does, it will be escalated since its critical
            if (logTransaction) {
                txLog.add(txLogHeader.serializePrimary(serializer, hasSecondaryPersistence ? LogTxStatus.PRIMARY_SUCCESS : LogTxStatus.COMPLETE_SUCCESS), txLogHeader.getLogKey(), mutator.getTxLogPersistor());
            }
            try {
                mutator.commitStorage();
            } catch (Throwable e) {
                //[FAILURE] If primary storage persistence fails abort directly (only schema could have been persisted)
                log.error("Could not commit transaction [" + transactionId + "] due to storage exception in commit", e);
                throw e;
            }
            if (hasSecondaryPersistence) {
                LogTxStatus status = LogTxStatus.SECONDARY_SUCCESS;
                Map<String, Throwable> indexFailures = ImmutableMap.of();
                boolean userlogSuccess = true;
                try {
                    //2. Commit indexes - [FAILURE] all exceptions are collected and logged but nothing is aborted
                    indexFailures = mutator.commitIndexes();
                    if (!indexFailures.isEmpty()) {
                        status = LogTxStatus.SECONDARY_FAILURE;
                        for (Map.Entry<String, Throwable> entry : indexFailures.entrySet()) {
                            log.error("Error while commiting index mutations for transaction [" + transactionId + "] on index: " + entry.getKey(), entry.getValue());
                        }
                    }
                    //3. Log transaction if configured - [FAILURE] is recorded but does not cause exception
                    if (logTxIdentifier != null) {
                        try {
                            userlogSuccess = false;
                            final Log userLog = backend.getUserLog(logTxIdentifier);
                            Future<Message> env = userLog.add(txLogHeader.serializeModifications(serializer, LogTxStatus.USER_LOG, tx, addedRelations, deletedRelations));
                            if (env.isDone()) {
                                try {
                                    env.get();
                                } catch (ExecutionException ex) {
                                    throw ex.getCause();
                                }
                            }
                            userlogSuccess = true;
                        } catch (Throwable e) {
                            status = LogTxStatus.SECONDARY_FAILURE;
                            log.error("Could not user-log committed transaction [" + transactionId + "] to " + logTxIdentifier, e);
                        }
                    }
                } finally {
                    if (logTransaction) {
                        // needs to be cleaned up later
                        try {
                            txLog.add(txLogHeader.serializeSecondary(serializer, status, indexFailures, userlogSuccess), txLogHeader.getLogKey());
                        } catch (Throwable e) {
                            log.error("Could not tx-log secondary persistence status on transaction [" + transactionId + "]", e);
                        }
                    }
                }
            } else {
                //This just closes the transaction since there are no modifications
                mutator.commitIndexes();
            }
        } else {
            //Just commit everything at once
            //[FAILURE] This case only happens when there are no non-system mutations in which case all changes
            //are already flushed. Hence, an exception here is unlikely and should abort
            mutator.commit();
        }
    } catch (Throwable e) {
        log.error("Could not commit transaction [" + transactionId + "] due to exception", e);
        try {
            //Clean up any left-over transaction handles
            mutator.rollback();
        } catch (Throwable e2) {
            log.error("Could not roll-back transaction [" + transactionId + "] after failure due to exception", e2);
        }
        if (e instanceof RuntimeException)
            throw (RuntimeException) e;
        else
            throw new TitanException("Unexpected exception", e);
    }
}
Also used : Message(com.thinkaurelius.titan.diskstorage.log.Message) KCVSLog(com.thinkaurelius.titan.diskstorage.log.kcvs.KCVSLog) KCVSLog(com.thinkaurelius.titan.diskstorage.log.kcvs.KCVSLog) Log(com.thinkaurelius.titan.diskstorage.log.Log) Instant(java.time.Instant) TransactionLogHeader(com.thinkaurelius.titan.graphdb.database.log.TransactionLogHeader) ExecutionException(java.util.concurrent.ExecutionException) LogTxStatus(com.thinkaurelius.titan.graphdb.database.log.LogTxStatus) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap)

Aggregations

Log (com.thinkaurelius.titan.diskstorage.log.Log)1 Message (com.thinkaurelius.titan.diskstorage.log.Message)1 KCVSLog (com.thinkaurelius.titan.diskstorage.log.kcvs.KCVSLog)1 LogTxStatus (com.thinkaurelius.titan.graphdb.database.log.LogTxStatus)1 TransactionLogHeader (com.thinkaurelius.titan.graphdb.database.log.TransactionLogHeader)1 Instant (java.time.Instant)1 ConcurrentHashMap (java.util.concurrent.ConcurrentHashMap)1 ExecutionException (java.util.concurrent.ExecutionException)1