Class RaftKVTransaction

java.lang.Object
io.permazen.kv.raft.RaftKVTransaction
All Implemented Interfaces:
KVStore, KVTransaction

@ThreadSafe public class RaftKVTransaction extends Object implements KVTransaction
RaftKVDatabase transaction.
  • Method Summary

    Modifier and Type
    Method
    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 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
     
    byte[]
    get(byte[] key)
    Get the value associated with the given key, if any.
    getAtLeast(byte[] minKey, byte[] maxKey)
    Get the key/value pair having the smallest key greater than or equal to the given minimum, if any.
    getAtMost(byte[] maxKey, byte[] minKey)
    Get the key/value pair having the largest key strictly less than the given maximum, if any.
    long
    Get the index of the log entry on which this transaction is based.
    long
    Get the term of the log entry on which this transaction is based.
    long
    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
    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.
    Get the cluster configuration change associated with this transaction, if any.
    Get the consistency level for this transaction.
    Get the KVDatabase with which this instance is associated.
    Get the Timestamp of the most recent state change.
    getRange(byte[] minKey, byte[] maxKey, boolean reverse)
    Iterate the key/value pairs in the specified range.
    Get the state of this transaction.
    long
    Get the locally unique ID of this transaction.
    boolean
    Determine whether this transaction is the high priority transaction for this node.
    boolean
    Determine whether this transaction is configured as read-only.
    void
    put(byte[] key, byte[] value)
    Set the value associated with the given key.
    Create a read-only snapshot of the database content represented by this transaction.
    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
    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.
     
    watchKey(byte[] key)
    Watch a key to monitor for changes in its value.
    void
    Apply weaker transaction consistency while performing the given action, if supported.

    Methods inherited from class java.lang.Object

    clone, equals, getClass, hashCode, notify, notifyAll, wait, wait, wait

    Methods inherited from interface io.permazen.kv.KVStore

    getRange, getRange, removeRange
  • Method Details

    • getTxId

      public long getTxId()
      Get the locally unique ID of this transaction.
      Returns:
      transaction ID
    • getState

      public TxState getState()
      Get the state of this transaction.
      Returns:
      transaction state
    • getLastStateChangeTime

      public Timestamp getLastStateChangeTime()
      Get the Timestamp of the most recent state change.
      Returns:
      timestamp of most recent state change
    • getBaseTerm

      public long getBaseTerm()
      Get the term of the log entry on which this transaction is based.
      Returns:
      associated base log term
    • getBaseIndex

      public long getBaseIndex()
      Get the index of the log entry on which this transaction is based.
      Returns:
      associated base log index
    • getCommitTerm

      public 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.

      For Consistency.UNCOMMITTED transactions, this will always return zero.

      Returns:
      associated commit log entry index, or zero if not yet determined
    • getCommitIndex

      public 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.

      For Consistency.UNCOMMITTED transactions, this will always return zero.

      Returns:
      associated commit log entry term, or zero if not yet determined
    • getConsistency

      public Consistency getConsistency()
      Get the consistency level for this transaction.

      The default consistency level is Consistency.LINEARIZABLE.

      Returns:
      transaction consistency level
    • isReadOnly

      public boolean isReadOnly()
      Determine whether this transaction is configured as read-only.

      Default is false.

      Specified by:
      isReadOnly in interface KVTransaction
      Returns:
      true if this transaction is configured read-only
    • setHighPriority

      public boolean setHighPriority(boolean highPriority)
      Configure whether this transaction is the high priority transaction for this node.

      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.

      Parameters:
      highPriority - true for high priority, otherwise false
      Returns:
      true if successful, false if this transaction is no longer alive or is not Consistency.LINEARIZABLE
    • isHighPriority

      public boolean isHighPriority()
      Determine whether this transaction is the high priority transaction for this node.

      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.

      Returns:
      true if high priority, otherwise false
      See Also:
    • setReadOnly

      public void setReadOnly(boolean readOnly)
      Set whether this transaction should be read-only.

      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.

      Specified by:
      setReadOnly in interface KVTransaction
      Parameters:
      readOnly - true to discard mutations on commit, false to apply mutations on commit
      Throws:
      IllegalArgumentException - if readOnly is false and this transaction's consistency is not Consistency.LINEARIZABLE
      IllegalArgumentException - if readOnly is false and this transaction is currently read-only
      StaleTransactionException - if this transaction is no longer open
    • configChange

      public void configChange(String identity, String address)
      Include a cluster configuration change when this transaction is committed.

      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:

      • By receiving a message from a leader of some existing cluster, in which case the node joins that cluster based on the provided configuration; or
      • By this method being invoked with 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.

      Parameters:
      identity - the identity of the node to add or remove
      address - the network address of the node if adding, or null if removing
      Throws:
      IllegalStateException - if this method has been invoked previously on this instance
      IllegalStateException - if this transaction is read-only
      IllegalArgumentException - if identity is null
    • getConfigChange

      public String[] getConfigChange()
      Get the cluster configuration change associated with this transaction, if any.

      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.

      Returns:
      cluster config change, or null if there is none
    • get

      public byte[] get(byte[] key)
      Description copied from interface: KVStore
      Get the value associated with the given key, if any.

      Modifications to the returned byte[] array do not affect this instance.

      Specified by:
      get in interface KVStore
      Parameters:
      key - key
      Returns:
      value associated with key, or null if not found
    • getAtLeast

      public KVPair getAtLeast(byte[] minKey, byte[] maxKey)
      Description copied from interface: KVStore
      Get the key/value pair having the smallest key greater than or equal to the given minimum, if any.

      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.

      Specified by:
      getAtLeast in interface KVStore
      Parameters:
      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)
      Returns:
      smallest key/value pair with key >= minKey and key < maxKey, or null if none exists
    • getAtMost

      public KVPair getAtMost(byte[] maxKey, byte[] minKey)
      Description copied from interface: KVStore
      Get the key/value pair having the largest key strictly less than the given maximum, if any.

      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.

      Specified by:
      getAtMost in interface KVStore
      Parameters:
      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)
      Returns:
      largest key/value pair with key < maxKey and key >= minKey, or null if none exists
    • getRange

      public CloseableIterator<KVPair> getRange(byte[] minKey, byte[] maxKey, boolean reverse)
      Description copied from interface: KVStore
      Iterate the key/value pairs in the specified range. The returned CloseableIterator'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 CloseableIterator is weakly consistent (see java.util.concurrent). In short, the returned CloseableIterator must not throw ConcurrentModificationException; however, whether or not a "live" CloseableIterator reflects any modifications made after its creation is implementation dependent. Implementations that do make post-creation updates visible in the CloseableIterator, even if the update occurs after some delay, must preserve the order in which the modifications actually occurred.

      The returned CloseableIterator itself is not guaranteed to be thread safe; is should only be used in the thread that created it.

      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.

      Specified by:
      getRange in interface KVStore
      Parameters:
      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)
      Returns:
      iteration of key/value pairs in the range minKey (inclusive) to maxKey (exclusive)
    • put

      public void put(byte[] key, byte[] value)
      Description copied from interface: KVStore
      Set the value associated with the given key.
      Specified by:
      put in interface KVStore
      Parameters:
      key - key
      value - value
    • remove

      public void remove(byte[] key)
      Description copied from interface: KVStore
      Remove the key/value pair with the given key, if it exists.
      Specified by:
      remove in interface KVStore
      Parameters:
      key - key
    • removeRange

      public void removeRange(byte[] minKey, byte[] maxKey)
      Description copied from interface: KVStore
      Remove all key/value pairs whose keys are in a given range.

      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:

      • If minKey starts with 0xff, then no change occurs
      • If maxKey starts with 0xff, then this method behaves as if maxKey were null
      Specified by:
      removeRange in interface KVStore
      Parameters:
      minKey - minimum key (inclusive), or null for no minimum
      maxKey - maximum key (exclusive), or null for no maximum
    • adjustCounter

      public void adjustCounter(byte[] key, long amount)
      Description copied from interface: KVStore
      Adjust the counter at the given key by the given amount.

      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.

      Specified by:
      adjustCounter in interface KVStore
      Parameters:
      key - key
      amount - amount to adjust counter value by
    • encodeCounter

      public byte[] encodeCounter(long value)
      Description copied from interface: KVStore
      Encode a counter value into a byte[] value suitable for use with decodeCounter() and/or adjustCounter().
      Specified by:
      encodeCounter in interface KVStore
      Parameters:
      value - desired counter value
      Returns:
      encoded counter value
    • decodeCounter

      public long decodeCounter(byte[] bytes)
      Description copied from interface: KVStore
      Decode a counter value previously encoded by encodeCounter().
      Specified by:
      decodeCounter in interface KVStore
      Parameters:
      bytes - encoded counter value
      Returns:
      decoded counter value
    • apply

      public void apply(Mutations mutations)
      Description copied from interface: KVStore
      Apply all the given 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.

      Unlike AtomicKVStore.apply(), this method is not required to apply the mutations atomically.

      Specified by:
      apply in interface KVStore
      Parameters:
      mutations - mutations to apply
    • getKVDatabase

      public RaftKVDatabase getKVDatabase()
      Description copied from interface: KVTransaction
      Get the KVDatabase with which this instance is associated.
      Specified by:
      getKVDatabase in interface KVTransaction
      Returns:
      associated database
    • setTimeout

      public void setTimeout(long timeout)
      Set the commit timeout for this instance.

      RaftKVTransactions 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).

      Specified by:
      setTimeout in interface KVTransaction
      Parameters:
      timeout - transaction commit timeout in milliseconds, or zero for unlimited
      Throws:
      IllegalArgumentException - if timeout is negative
      StaleTransactionException - if this transaction is no longer open
    • watchKey

      public ListenableFuture<Void> watchKey(byte[] key)
      Watch a key to monitor for changes in its value.

      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 PermazenField.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 Futures associated with failed transactions complete, so the Futures 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 Futures 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.

      Specified by:
      watchKey in interface KVTransaction
      Parameters:
      key - the key to watch
      Returns:
      a Future that returns key when the value associated with key is modified
      Throws:
      StaleTransactionException - if this transaction is no longer usable
      RetryTransactionException - if this transaction must be retried and is no longer usable
      KVDatabaseException - if an unexpected error occurs
      UnsupportedOperationException - if this instance does not support key watches
      IllegalArgumentException - if key is null
      See Also:
    • commit

      public void commit()
      Description copied from interface: KVTransaction
      Commit this transaction.

      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.

      Specified by:
      commit in interface KVTransaction
    • rollback

      public void rollback()
      Description copied from interface: KVTransaction
      Cancel this transaction, if not already canceled.

      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 must not throw StaleTransactionException.

      Specified by:
      rollback in interface KVTransaction
    • withWeakConsistency

      public void withWeakConsistency(Runnable action)
      Description copied from interface: KVTransaction
      Apply weaker transaction consistency while performing the given action, if supported.

      Some implementations support reads with weaker consistency guarantees. These reads generate fewer transaction conflicts but return possibly out-of-date information. Depending on the implementation, when operating in this mode writes may not be supported and may generate an IllegalStateException or just be ignored.

      The weaker consistency is only applied for the current thread, and it ends when this method returns.

      This method is for experts only; inappropriate use can result in a corrupted database. You should not make any changes to the database after this method returns based on any information read by the action.

      The implementation in KVTransaction just performs action normally.

      Specified by:
      withWeakConsistency in interface KVTransaction
      Parameters:
      action - the action to perform
    • readOnlySnapshot

      public CloseableKVStore readOnlySnapshot()
      Create a read-only snapshot of the database content represented by this transaction.

      The returned CloseableKVStore should be treated as read-only. It may not actually be read-only, but if it's not, then any changes should have no effect on this instance. The returned CloseableKVStore must be completely independent from this transaction (subsequent changes to either one do not affect the other).

      Note: 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.

      Read-only snapshots are supported by RaftKVTransaction.

      Specified by:
      readOnlySnapshot in interface KVTransaction
      Returns:
      independent, read-only copy of this transaction's entire database content
      Throws:
      UnsupportedOperationException - if this method is not supported
      StaleTransactionException - if this transaction is no longer usable
      RetryTransactionException - if this transaction must be retried and is no longer usable
    • toString

      public String toString()
      Overrides:
      toString in class Object
    • finalize

      protected void finalize() throws Throwable
      Overrides:
      finalize in class Object
      Throws:
      Throwable