com.apple.foundationdb.Transaction Maven / Gradle / Ivy
/*
* Transaction.java
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
*
* 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.
*/
package com.apple.foundationdb;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import com.apple.foundationdb.tuple.Tuple;
/**
* A Transaction represents a FoundationDB database transaction. All operations on FoundationDB
* take place, explicitly or implicitly, through a Transaction.
*
* In FoundationDB, a transaction is a mutable snapshot of a database. All read and write operations
* on a transaction see and modify an otherwise-unchanging version of the database and only change
* the underlying database if and when the transaction is committed. Read operations do see the
* effects of previous write operations on the same transaction. Committing a transaction usually
* succeeds in the absence of
* conflicts.
*
* Transactions group operations into a unit with the properties of atomicity, isolation, and
* durability. Transactions also provide the ability to maintain an application's invariants or
* integrity constraints, supporting the property of consistency. Together these properties are
* known as ACID.
*
* Transactions are also causally consistent: once a transaction has been successfully committed,
* all subsequently created transactions will see the modifications made by it.
* The most convenient way for a developer to manage the lifecycle and retrying
* of a {@code Transaction} is to use {@link Database#run(Function)}. Otherwise, the client
* must have retry logic for fatal failures, failures to commit, and other transient errors.
*
* Keys and values in FoundationDB are byte arrays. To encode other data types, see the
* {@link Tuple Tuple API} and
* tuple layer documentation.
*
* When used as a {@link TransactionContext}, the methods {@code run()} and
* {@code runAsync()} on a {@code Transaction} will simply attempt the operations
* without any retry loop.
*
* Note: Client must call {@link #commit()} and wait on the result on all transactions, even
* ones that only read. This is done automatically when using the retry loops from
* {@link Database#run(Function)}. This is because outstanding reads originating from a
* {@code Transaction} will be cancelled when a {@code Transaction} is garbage collected.
* Since the garbage collector reserves the right to collect an in-scope object if it
* determines that there are no subsequent references it it, this can happen in seemingly
* innocuous situations. {@code CompletableFuture}s returned from {@code commit()} will block until
* all reads are complete, thereby saving the calling code from this potentially confusing
* situation.
*
* Note: All keys with a first byte of {@code 0xff} are reserved for internal use.
*
* Note: Java transactions automatically set the {@link TransactionOptions#setUsedDuringCommitProtectionDisable}
* option. This is because the Java bindings disallow use of {@code Transaction} objects after {@link #onError}
* is called.
*
* Note: {@code Transaction} objects must be {@link #close closed} when no longer
* in use in order to free any associated resources.
*/
public interface Transaction extends AutoCloseable, ReadTransaction, TransactionContext {
/**
* Adds a range of keys to the transaction's read conflict ranges as if you
* had read the range. As a result, other transactions that write a key in
* this range could cause the transaction to fail with a conflict.
*
* @param keyBegin the first key in the range (inclusive)
* @param keyEnd the ending key for the range (exclusive)
*/
void addReadConflictRange(byte[] keyBegin, byte[] keyEnd);
/**
* Adds a key to the transaction's read conflict ranges as if you had read
* the key. As a result, other transactions that concurrently write this key
* could cause the transaction to fail with a conflict.
*
* @param key the key to be added to the read conflict range set
*/
void addReadConflictKey(byte[] key);
/**
* Adds a range of keys to the transaction's write conflict ranges as if you
* had cleared the range. As a result, other transactions that concurrently
* read a key in this range could fail with a conflict.
*
* @param keyBegin the first key in the range (inclusive)
* @param keyEnd the ending key for the range (exclusive)
*/
void addWriteConflictRange(byte[] keyBegin, byte[] keyEnd);
/**
* Adds a key to the transaction's write conflict ranges as if you had
* written the key. As a result, other transactions that concurrently read
* this key could fail with a conflict.
*
* @param key the key to be added to the range
*/
void addWriteConflictKey(byte[] key);
/**
* Sets the value for a given key. This will not affect the
* database until {@link #commit} is called.
*
* @param key the key whose value is to be set
* @param value the value to set in the database
*
* @throws IllegalArgumentException if {@code key} or {@code value} is {@code null}
* @throws FDBException if the set operation otherwise fails
*/
void set(byte[] key, byte[] value);
/**
* Clears a given key from the database. This will not affect the
* database until {@link #commit} is called.
*
* @param key the key whose value is to be cleared
*
* @throws IllegalArgumentException if {@code key} is {@code null}
* @throws FDBException if clear operation otherwise fails
*/
void clear(byte[] key);
/**
* Clears a range of keys in the database. The upper bound of the range is
* exclusive; that is, the key (if one exists) that is specified as the end
* of the range will NOT be cleared as part of this operation. Range clears are
* efficient with FoundationDB -- clearing large amounts of data will be fast.
* This will not affect the database until {@link #commit} is called.
*
* @param beginKey the first clear
* @param endKey the key one past the last key to clear
*
* @throws IllegalArgumentException if {@code beginKey} or {@code endKey} is {@code null}
* @throws FDBException if the clear operation otherwise fails
*/
void clear(byte[] beginKey, byte[] endKey);
/**
* Clears a range of keys in the database. The upper bound of the range is
* exclusive; that is, the key (if one exists) that is specified as the end
* of the range will NOT be cleared as part of this operation. Range clears are
* efficient with FoundationDB -- clearing large amounts of data will be fast.
* However, this will not immediately free up disk - data for the deleted range
* is cleaned up in the background.
* This will not affect the database until {@link #commit} is called.
*
* For purposes of computing the transaction size, only the begin and end keys of a clear range are counted.
* The size of the data stored in the range does not count against the transaction size limit.
*
*
* @param range the range of keys to clear
*
* @throws FDBException if the clear operation fails
*/
void clear(Range range);
/**
* Replace with calls to {@link #clear(Range)} with a parameter from a call to
* {@link Range#startsWith(byte[])}.
*
* @param prefix the starting bytes from the keys to be cleared.
*
* @throws FDBException if the clear-range operation fails
*/
@Deprecated
void clearRangeStartsWith(byte[] prefix);
/**
* An atomic operation is a single database command that carries out several
* logical steps: reading the value of a key, performing a transformation on
* that value, and writing the result. Different atomic operations perform
* different transformations. Like other database operations, an atomic
* operation is used within a transaction.
*
* Atomic operations do not expose the current value of the key to the client
* but simply send the database the transformation to apply. In regard to
* conflict checking, an atomic operation is equivalent to a write without a
* read. It can only cause other transactions performing reads of the key
* to conflict.
*
* By combining these logical steps into a single, read-free operation,
* FoundationDB can guarantee that the transaction will not conflict due to
* the operation. This makes atomic operations ideal for operating on keys
* that are frequently modified. A common example is the use of a key-value
* pair as a counter.
*
* Note: If a transaction uses both an atomic operation and a serializable
* read on the same key, the benefits of using the atomic operation (for both
* conflict checking and performance) are lost.
*
* The behavior of each {@link MutationType} is documented at its definition.
*
* @param optype the operation to perform
* @param key the target of the operation
* @param param the value with which to modify the key
*/
void mutate(MutationType optype, byte[] key, byte[] param);
/**
* Commit this {@code Transaction}. See notes in class description. Consider using
* {@code Database}'s {@link Database#run(Function) run()} calls for managing
* transactional access to FoundationDB.
*
*
* As with other client/server databases, in some failure scenarios a client may
* be unable to determine whether a transaction succeeded. In these cases, an
* {@link FDBException} will be thrown with error code {@code commit_unknown_result} (1021).
* The {@link #onError} function regards this exception as a retryable one, so
* retry loops that don't specifically detect {@code commit_unknown_result} could end
* up executing a transaction twice. For more information, see the FoundationDB
* Developer Guide documentation.
*
*
*
* If any operation is performed on a transaction after a commit has been
* issued but before it has returned, both the commit and the operation will
* throw an error code {@code used_during_commit} (2017). In this case, all
* subsequent operations on this transaction will throw this error.
*
*
* @return a {@code CompletableFuture} that, when set without error, guarantees the
* {@code Transaction}'s modifications committed durably to the
* database. If the commit failed, it will throw an {@link FDBException}.
*/
CompletableFuture commit();
/**
* Gets the version number at which a successful commit modified the database.
* This must be called only after the successful (non-error) completion of a call
* to {@link #commit()} on this {@code Transaction}, or the behavior is undefined.
* Read-only transactions do not modify the database when committed and will have
* a committed version of -1. Keep in mind that a transaction which reads keys and
* then sets them to their current values may be optimized to a read-only transaction.
*
* @return the database version at which the commit succeeded
*/
Long getCommittedVersion();
/**
* Returns a future which will contain the versionstamp which was used by any versionstamp
* operations in this transaction. The future will be ready only after the successful
* completion of a call to {@link #commit()} on this {@code Transaction}. Read-only
* transactions do not modify the database when committed and will result in the future
* completing with an error. Keep in mind that a transaction which reads keys and then sets
* them to their current values may be optimized to a read-only transaction.
*
* @return a future containing the versionstamp which was used for any versionstamp operations
* in this transaction
*/
CompletableFuture getVersionstamp();
/**
* Returns a future that will contain the approximated size of the commit, which is the
* summation of mutations, read conflict ranges, and write conflict ranges. This can be
* called multiple times before transaction commit.
*
* @return a future that will contain the approximated size of the commit.
*/
CompletableFuture getApproximateSize();
/**
* Resets a transaction and returns a delayed signal for error recovery. If the error
* encountered by the {@code Transaction} could not be recovered from, the returned
* {@code CompletableFuture} will be set to an error state.
*
* The current {@code Transaction} object will be invalidated by this call and will throw errors
* when used. The newly reset {@code Transaction} will be returned through the {@code CompletableFuture}
* if the error was retryable.
*
* If the error is not retryable, then no reset {@code Transaction} is returned, leaving this
* {@code Transaction} permanently invalidated.
*
* @param e the error caught while executing get()s and set()s on this {@code Transaction}
* @return a {@code CompletableFuture} to be set with a reset {@code Transaction} object to retry the transaction
*/
CompletableFuture onError(Throwable e);
/**
* Cancels the {@code Transaction}. All pending and any future uses of the
* {@code Transaction} will throw an {@link RuntimeException}.
*/
void cancel();
/**
* Creates a watch that will become ready when it reports a change to
* the value of the specified key.
*
* A watch's behavior is relative to the transaction that created it. A
* watch will report a change in relation to the key's value as readable by
* that transaction. The initial value used for comparison is either that of
* the transaction's read version or the value as modified by the transaction
* itself prior to the creation of the watch. If the value changes and then
* changes back to its initial value, the watch might not report the change.
*
* Until the transaction that created it has been committed, a watch will
* not report changes made by other transactions. In contrast, a watch
* will immediately report changes made by the transaction itself. Watches
* cannot be created if the transaction has set
* {@link TransactionOptions#setReadYourWritesDisable()}, and an attempt to do
* so will raise a {@code watches_disabled} exception.
*
* If the transaction used to create a watch encounters an exception during
* commit, then the watch will be set with that exception. A transaction whose
* commit result is unknown will set all of its watches with the
* {@code commit_unknown_result} exception. If an uncommitted transaction is
* reset via {@link #onError} or destroyed, then any watches it created will be set
* with the {@code transaction_cancelled} exception.
*
* By default, each database connection can have no more than 10,000 watches
* that have not yet reported a change. When this number is exceeded, an
* attempt to create a watch will raise a {@code too_many_watches} exception.
* Because a watch outlives the transaction that creates it, any watch that is no
* longer needed should be cancelled.
*
* NOTE: calling code must call {@link Transaction#commit()} for
* the watch to be registered with the database.
*
* @param key the key to watch for changes in value
*
* @return a {@code CompletableFuture} that will become ready when the value changes
*
* @throws FDBException if too many watches have been created on this database. The
* limit defaults to 10,000 and can be modified with a call to
* {@link DatabaseOptions#setMaxWatches(long)}.
*/
CompletableFuture watch(byte[] key) throws FDBException;
/**
* Returns the {@link Database} that this {@code Transaction} is interacting
* with.
*
* @return the {@link Database} object
*/
Database getDatabase();
/**
* Run a function once against this {@code Transaction}. This call blocks while
* user code is executing, returning the result of that code on completion.
*
* @param retryable the block of logic to execute against this {@code Transaction}
* @param the return type of {@code retryable}
*
* @return a result of the single call to {@code retryable}
*/
@Override
T run(Function super Transaction, T> retryable);
/**
* Run a function once against this {@code Transaction}. This call returns
* immediately with a {@code CompletableFuture} handle to the result.
*
* @param retryable the block of logic to execute against this {@code Transaction}
* @param the return type of {@code retryable}
*
* @return a {@code CompletableFuture} that will be set to the return value of {@code retryable}
*/
@Override
CompletableFuture runAsync(
Function super Transaction, ? extends CompletableFuture> retryable);
/**
* Close the {@code Transaction} object and release any associated resources. This must be called at
* least once after the {@code Transaction} object is no longer in use. This can be called multiple
* times, but care should be taken that it is not in use in another thread at the time of the call.
*/
@Override
void close();
}