@ThreadSafe public class RaftKVTransaction extends Object implements KVTransaction, ReadTracking
RaftKVDatabase
transaction.Modifier and Type | Method and Description |
---|---|
void |
adjustCounter(byte[] key,
long amount)
Adjust the counter at the given key by the given amount.
|
void |
apply(Mutations mutations)
Apply all the given
Mutations to this instance. |
void |
commit()
Commit this transaction.
|
void |
configChange(String identity,
String address)
Include a cluster configuration change when this transaction is committed.
|
long |
decodeCounter(byte[] bytes)
Decode a counter value previously encoded by
encodeCounter() . |
byte[] |
encodeCounter(long value)
Encode a counter value into a
byte[] value suitable for use with decodeCounter()
and/or adjustCounter() . |
protected void |
finalize() |
byte[] |
get(byte[] key)
Get the value associated with the given key, if any.
|
KVPair |
getAtLeast(byte[] minKey,
byte[] maxKey)
Get the key/value pair having the smallest key greater than or equal to the given minimum, if any.
|
KVPair |
getAtMost(byte[] maxKey,
byte[] minKey)
Get the key/value pair having the largest key strictly less than the given maximum, if any.
|
long |
getBaseIndex()
Get the index of the log entry on which this transaction is based.
|
long |
getBaseTerm()
Get the term of the log entry on which this transaction is based.
|
long |
getCommitIndex()
Get the index of the Raft log entry on which this transaction is waiting to be committed (in the Raft sense)
before it can complete.
|
long |
getCommitTerm()
Get the term of the Raft log entry on which this transaction is waiting to be committed (in the Raft sense)
before it can complete.
|
String[] |
getConfigChange()
Get the cluster configuration change associated with this transaction, if any.
|
Consistency |
getConsistency()
Get the consistency level for this transaction.
|
RaftKVDatabase |
getKVDatabase()
Get the
KVDatabase with which this instance is associated. |
Timestamp |
getLastStateChangeTime()
Get the
Timestamp of the most recent state change. |
CloseableIterator<KVPair> |
getRange(byte[] minKey,
byte[] maxKey,
boolean reverse)
Iterate the key/value pairs in the specified range.
|
AtomicBoolean |
getReadTrackingControl()
Get an
AtomicBoolean that can be used to temporarily pause/un-pause read tracking. |
TxState |
getState()
Get the state of this transaction.
|
long |
getTxId()
Get the locally unique ID of this transaction.
|
boolean |
isHighPriority()
Determine whether this transaction is the high priority transaction for this node.
|
boolean |
isReadOnly()
Determine whether this transaction is configured as read-only.
|
CloseableKVStore |
mutableSnapshot()
Create a mutable copy of the database content represented by this transaction.
|
void |
put(byte[] key,
byte[] value)
Set the value associated with the given key.
|
void |
remove(byte[] key)
Remove the key/value pair with the given key, if it exists.
|
void |
removeRange(byte[] minKey,
byte[] maxKey)
Remove all key/value pairs whose keys are in a given range.
|
void |
rollback()
Cancel this transaction, if not already canceled.
|
boolean |
setHighPriority(boolean highPriority)
Configure whether this transaction is the high priority transaction for this node.
|
void |
setReadOnly(boolean readOnly)
Set whether this transaction should be read-only.
|
void |
setTimeout(long timeout)
Set the commit timeout for this instance.
|
String |
toString() |
ListenableFuture<Void> |
watchKey(byte[] key)
Watch a key to monitor for changes in its value.
|
clone, equals, getClass, hashCode, notify, notifyAll, wait, wait, wait
getRange, getRange, removeRange
public long getTxId()
public TxState getState()
public Timestamp getLastStateChangeTime()
Timestamp
of the most recent state change.public long getBaseTerm()
public long getBaseIndex()
public long getCommitTerm()
For Consistency.UNCOMMITTED
transactions, this will always return zero.
public long getCommitIndex()
For Consistency.UNCOMMITTED
transactions, this will always return zero.
public Consistency getConsistency()
The default consistency level is Consistency.LINEARIZABLE
.
public boolean isReadOnly()
Default is false.
isReadOnly
in interface KVTransaction
public boolean setHighPriority(boolean highPriority)
At most one open transaction on a Raft node can be high priority at a time; if two concurrent transactions are configured as high priority on the same node, the most recent invocation of this method wins.
High priority transactions are handled specially on leaders: the high priority transaction always wins when there is a conflict with another transaction, whether the other transaction is local or remote (i.e., sent from a follower), so a high priority transaction can never fail due to a conflict (of course there are other reasons a high priority transaction could still fail, e.g., leadership change or inability to communicate with a majority). So this provides a simple "all or nothing" prioritization scheme.
On followers, configuring a transaction as high priority simply forces an immediate leader election, as if by
FollowerRole.startElection()
. This causes the node to become the leader with
high probability, where it can then prioritize this transaction as described above (if the transaction detects
a conflict before the election completes, a subsequent retry after the completed election should succeed).
Warning: overly-agressive use of this method can cause a flurry of elections and poor performance, therefore, this method should be used sparingly, otherwise an election storm could result. In any case, followers will not force a new election for this purpose unless there is an established leader.
This method is only valid for Consistency.LINEARIZABLE
transactions.
Default is false.
highPriority
- true for high priority, otherwise falseConsistency.LINEARIZABLE
public boolean isHighPriority()
A high priority transaction is automatically reverted sometime after commit()
is invoked, so this method
is only reliable while this transaction in state TxState.EXECUTING
.
setHighPriority(boolean)
public void setReadOnly(boolean readOnly)
Read-only transactions support modifications during the transaction, and these modifications will be visible
when read back, but they are discarded on commit()
.
By default, Consistency.LINEARIZABLE
transactions are read-write. They may be changed to read-only via
this method, and once changed, may not be changed back. Non-Consistency.LINEARIZABLE
are always read-only.
setReadOnly
in interface KVTransaction
readOnly
- true to discard mutations on commit, false to apply mutations on commitIllegalArgumentException
- if readOnly
is false and
this transaction's consistency is not Consistency.LINEARIZABLE
IllegalArgumentException
- if readOnly
is false and this transaction is currently read-onlyStaleTransactionException
- if this transaction is no longer openpublic void configChange(String identity, String address)
The change will have been applied once this transaction is successfully committed.
Raft supports configuration changes that add or remove one node at a time to/from the cluster. If this method is invoked more than once in a single transaction, all but the last invocation are ignored.
Initially, nodes are unconfigured. An unconfigured node becomes configured in one of two ways:
identity
equal to this node's identity and a non-null address
,
which creates a new cluster and adds this node to it.Therefore, this method must be used to intialize a new cluster.
identity
- the identity of the node to add or removeaddress
- the network address of the node if adding, or null if removingIllegalStateException
- if this method has been invoked previously on this instanceIllegalStateException
- if this transaction is read-onlyIllegalArgumentException
- if identity
is nullpublic String[] getConfigChange()
The returned array has length two and contains the identity
and address
parameters passed to configChange()
.
The returned array is a copy; changes have no effect on this instance.
public byte[] get(byte[] key)
KVStore
Modifications to the returned byte[]
array do not affect this instance.
public KVPair getAtLeast(byte[] minKey, byte[] maxKey)
KVStore
An optional (exclusive) maximum key may also be specified; if maxKey
is null, there is no upper bound;
if maxKey <= minKey
, null is always returned.
If keys starting with 0xff
are not supported by this instance, and minKey
starts with 0xff
,
then this method returns null.
Modifications to the returned byte[]
arrays do not affect this instance.
getAtLeast
in interface KVStore
minKey
- minimum key (inclusive), or null for no minimum (get the smallest key)maxKey
- maximum key (exclusive), or null for no maximum (no upper bound)key >= minKey
and key < maxKey
, or null if none existspublic KVPair getAtMost(byte[] maxKey, byte[] minKey)
KVStore
An optional (inclusive) minimum key may also be specified; if minKey
is null, there is no lower bound
(equivalent to a lower bound of the empty byte array); if minKey >= maxKey
, null is always returned.
If keys starting with 0xff
are not supported by this instance, and maxKey
starts with 0xff
,
then this method behaves as if maxKey
were null.
Modifications to the returned byte[]
arrays do not affect this instance.
getAtMost
in interface KVStore
maxKey
- maximum key (exclusive), or null for no maximum (get the largest key)minKey
- minimum key (inclusive), or null for no minimum (no lower bound)key < maxKey
and key >= minKey
, or null if none existspublic CloseableIterator<KVPair> getRange(byte[] minKey, byte[] maxKey, boolean reverse)
KVStore
Iterator
's
remove()
method must be supported and should have the same effect as
invoking remove()
on the corresponding key.
If keys starting with 0xff
are not supported by this instance, and minKey
starts with 0xff
,
then this method returns an empty iteration.
If keys starting with 0xff
are not supported by this instance, and maxKey
starts with 0xff
,
then this method behaves as if maxKey
were null.
The returned Iterator
must not throw ConcurrentModificationException
;
however, whether or not a "live" Iterator
reflects any modifications made after its creation is
implementation dependent. Implementations that do make post-creation updates visible in the Iterator
,
even if the update occurs after some delay, must preserve the order in which the modifications actually occurred.
The returned Iterator
itself is not guaranteed to be thread safe.
Invokers of this method are encouraged to close()
the returned iterators,
though this is not required for correct behavior.
Modifications to the returned KVPair
key and value byte[]
arrays do not affect this instance.
getRange
in interface KVStore
minKey
- minimum key (inclusive), or null for no minimum (start at the smallest key)maxKey
- maximum key (exclusive), or null for no maximum (end at the largest key)reverse
- true to return key/value pairs in reverse order (i.e., keys descending)minKey
(inclusive) to maxKey
(exclusive)public void put(byte[] key, byte[] value)
KVStore
public void remove(byte[] key)
KVStore
public void removeRange(byte[] minKey, byte[] maxKey)
KVStore
The minKey
must be less than or equal to maxKey
; if they equal (and not null)
then nothing happens; if they are both null then all entries are deleted.
If keys starting with 0xff
are not supported by this instance, then:
minKey
starts with 0xff
, then no change occursmaxKey
starts with 0xff
, then this method behaves as if maxKey
were nullremoveRange
in interface KVStore
minKey
- minimum key (inclusive), or null for no minimummaxKey
- maximum key (exclusive), or null for no maximumpublic void adjustCounter(byte[] key, long amount)
KVStore
Ideally this operation should behave in a lock-free manner, so that concurrent transactions can invoke it without conflict. However, when lock-free behavior occurs (if at all) depends on the implementation.
If there is no value associated with key
, or key
's value is not a valid counter encoding as
would be acceptable to decodeCounter()
, then how this operation affects key
's
value is undefined.
adjustCounter
in interface KVStore
key
- keyamount
- amount to adjust counter value bypublic byte[] encodeCounter(long value)
KVStore
byte[]
value suitable for use with decodeCounter()
and/or adjustCounter()
.encodeCounter
in interface KVStore
value
- desired counter valuepublic long decodeCounter(byte[] bytes)
KVStore
encodeCounter()
.decodeCounter
in interface KVStore
bytes
- encoded counter valuepublic void apply(Mutations mutations)
KVStore
Mutations
to this instance.
Mutations are always to be applied in this order: removes, puts, counter adjustments.
The implementation in KVStore
simply iterates over the individual changes and applies them
via remove()
(for removals of a single key), removeRange()
,
put()
, and/or adjustCounter()
. Implementations that can process
batch updates more efficiently are encouraged to override this method.
public AtomicBoolean getReadTrackingControl()
ReadTracking
AtomicBoolean
that can be used to temporarily pause/un-pause read tracking.
By default the returned control is true. While set to false, read tracking is disabled; setting back to true re-enables read tracking.
For re-entrance safety, this should be done as follows:
final boolean previous = kv.getReadTrackingControl().getAndSet(false);
try {
// do something without tracking reads...
} finally {
kv.getReadTrackingControl().set(previous);
}
getReadTrackingControl
in interface ReadTracking
public RaftKVDatabase getKVDatabase()
KVTransaction
KVDatabase
with which this instance is associated.getKVDatabase
in interface KVTransaction
public void setTimeout(long timeout)
RaftKVTransaction
s do not block while the transaction is open; the configured value is used
as a timeout for the commit()
operation only. If commit()
takes longer than timeout
milliseconds, a RetryTransactionException
is thrown.
The default value for all transactions is configured by RaftKVDatabase.setCommitTimeout(int)
.
setTimeout
in interface KVTransaction
timeout
- transaction commit timeout in milliseconds, or zero for unlimitedIllegalArgumentException
- if timeout
is negativeStaleTransactionException
- if this transaction is no longer openpublic ListenableFuture<Void> watchKey(byte[] key)
When this method is invoked, key
's current value (if any) as read by this transaction is remembered. The returned
Future
completes if and when a different value for key
is subsequently committed by some transaction,
including possibly this one. This includes creation or deletion of the key.
Key watches outlive the transaction in which they are created, persisting until they complete or are
cancel()
'ed. When a KVDatabase
is KVDatabase.stop()
'ed, all outstanding
key watches are implicitly cancel()
'ed.
Caveats
Key watches are not without overhead; applications should avoid overuse. For example, consider creating a
single key that is used to consolidate modifications to some set of keys; at the Permazen layer, modification
to multiple objects and/or fields can detected and consolidated using an
@OnChange
method that increments a single Counter
field, whose key is then watched (to determine the key corresponding to a Java model object field, use
JTransaction.getKey()
).
Conceptually, detection of changes behaves as if by a background thread that periodically creates a new transaction and reads the key's value (the actual implementation will likely be more efficient). This means a change that is quickly reverted could be missed, and that multiple changes could occur before notification. In addition, spurious notifications may occur, where the key's value has not changed.
A key watch is only guaranteed to be valid if the transaction in which it was created successfully commits.
In particular, nothing is specified about how or whether Future
s associated with failed transactions complete,
so the Future
s returned by this method should not be relied on until after a successful commit (perhaps with
the help of a transaction callback).
Key watch support is optional; instances that don't support key watches throw UnsupportedOperationException
.
Some implementations may only support watching a key that already exists.
Note: many KVDatabase
implementations actually return a
ListenableFuture
. However, listeners must not perform any
long running or blocking operations. Also, because the semantics of RetryTransactionException
allow for
the possibility that the transaction actually did commit, "duplicate" listener notifications could occur.
Key watch Future
s that have not completed yet, but are no longer needed, must be cancel()
'ed
to avoid memory leaks.
Key watch support is indepdendent of whether the transaction is read-only.
Key watches are supported by RaftKVTransaction
.
Raft key watches are compatible with all Consistency
levels, in that if a key watch fires due
to a mutation to some key, then a subsequent transaction will see that mutation, no matter what
Consistency
level is configured for that transaction.
Listeners registered on the returned ListenableFuture
must not perform any long running
or blocking operations.
watchKey
in interface KVTransaction
key
- the key to watchFuture
that returns key
when the value associated with key
is modifiedStaleTransactionException
- if this transaction is no longer usableRetryTransactionException
- if this transaction must be retried and is no longer usableKVDatabaseException
- if an unexpected error occursUnsupportedOperationException
- if this instance does not support key watchesIllegalArgumentException
- if key
is nullJTransaction.getKey()
public void commit()
KVTransaction
Note that if this method throws a RetryTransactionException
,
the transaction was either successfully committed or rolled back. In either case,
this instance is no longer usable.
Note also for some implementations, even read-only transactions must be KVTransaction.commit()
'ed in order for the
data accessed during the transaction to be guaranteed to be up to date.
commit
in interface KVTransaction
public void rollback()
KVTransaction
After this method returns, this instance is no longer usable.
Note: for some implementations, rolling back a transaction invalidates guarantees about the the data read
during the transaction being up to date, even if the transaction was setReadOnly()
.
This method may be invoked at any time, even after a previous invocation of
KVTransaction.commit()
or KVTransaction.rollback()
, in which case the invocation will be ignored.
In particular, this method should not throw StaleTransactionException
.
rollback
in interface KVTransaction
public CloseableKVStore mutableSnapshot()
The returned CloseableKVStore
should be mutable, but all changes should remain private until
close()
is invoked, at which time they should be discarded.
That is, the CloseableKVStore
it is completely independent from this transaction
(subsequent changes to either one do not affect the other).
Note that as with any other information extracted from a KVTransaction
, the returned content
should not be considered valid until this transaction has been successfully committed.
The returned CloseableKVStore
should be promply close()
'd when no longer
needed to release any underlying resources. In particular, the caller must ensure that the CloseableKVStore
is close()
'd even if this transaction's commit fails. This may require
adding a transaction synchronization callback, etc.
This is an optional method; only some underlying key/value store technologies can efficiently support it.
Implementations should throw UnsupportedOperationException
if not supported.
Mutable snapshots are supported by RaftKVTransaction
.
mutableSnapshot
in interface KVTransaction
UnsupportedOperationException
- if this method is not supportedStaleTransactionException
- if this transaction is no longer usableRetryTransactionException
- if this transaction must be retried and is no longer usableCopyright © 2022. All rights reserved.