Class BranchedKVTransaction
- All Implemented Interfaces:
CloseableKVStore
,KVStore
,KVTransaction
,Closeable
,AutoCloseable
KVTransaction
that is based on a snapshot from an original KVTransaction
and that can, at some arbitrary
later time, be merged back into a new KVTransaction
from the same database, assuming no conflicting changes
have occurred in the meantime.
In effect, this class gives the appearance of a regular KVTransaction
that can stay open for an arbitrarily long time,
with the same consistency guarantees as a normal single transaction. It can be useful in certain scenarios, for example,
to support editing an entity in a GUI application where the user may take several minutes to complete a form, while also
ensuring that everything the user sees while editing the form is still up to date when the form is actually submitted.
Instances support readOnlySnapshot()
and withWeakConsistency()
.
Instances do not support setTimeout()
or watchKey()
.
Transaction Management
New instances must be explicitly opened via open()
. At that time a regular database transaction is opened,
a snapshot of the database taken, and then that transaction is immediately closed.
Reads and writes in this transaction are then tracked and kept entirely in memory; no regular transaction remains open.
Later, when this transaction is commit()
'ed, a new regular database transaction is opened, a conflict check is
performed (to determine whether any of the keys read by this transaction have since changed), and then if there are no
conflicts, all of this transaction's accumulated writes are applied; otherwise, TransactionConflictException
is thrown.
The conflict check can also be performed on demand at any time while leaving this transaction open via
checkForConflicts()
.
Because open instances retain an underlying snaphot which consumes resources, callers should ensure that instances are always
eventually committed or rolled back as soon as they are no longer needed. Note that close()
can be used as a synonym
for rollback()
.
Underlying Transaction Configuration
The real transactions that are used at open and commit time are configurable via the usual transaction options.
In addition, this class adds to those options RETRY_PROVIDER_OPTION
which allows the configuration of
automatic retries for these transactions; for some key/value technologies, retry logic is important for robustness.
The RETRY_PROVIDER_OPTION
option's value is a RetryTransactionProvider
.
Caveats
This class only works with KVDatabase
's that support readOnlySnapshot()
.
The probability for a conflict increases in proportion to the number of distinct keys (or key ranges) read in this transaction
and/or the amount of writes committed to the underlying database while this transaction remains open. Of course, this also
depends greatly on the problem domain, i.e., whether this and other transactions are doing work that overlaps.
Reads and writes are tracked via MutableView
; the amount of memory required is as described in that class.
The work required for the conflict check scales in proportion to the number of distinct keys (or key ranges) read in the transaction.
- See Also:
-
Field Summary
Modifier and TypeFieldDescriptionstatic final String
Transaction option for aRetryTransactionProvider
to use with the underlying open and/or commit transactions. -
Constructor Summary
ConstructorDescriptionConstructor.BranchedKVTransaction
(KVDatabase kvdb, Map<String, ?> openOptions, Map<String, ?> syncOptions) Constructor. -
Method Summary
Modifier and TypeMethodDescriptionvoid
adjustCounter
(byte[] key, long amount) Adjust the counter at the given key by the given amount.void
Check for conflicts between this transaction and the current database contents.void
close()
Close this transaction.void
commit()
Commit this transaction.long
decodeCounter
(byte[] value) Decode a counter value previously encoded byencodeCounter()
.byte[]
encodeCounter
(long value) Encode a counter value into abyte[]
value suitable for use withdecodeCounter()
and/oradjustCounter()
.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.Get theKVDatabase
with which this instance is associated.getRange
(byte[] minKey, byte[] maxKey, boolean reverse) Iterate the key/value pairs in the specified range.boolean
Determine whether this transaction is read-only.void
open()
Open this transaction.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
rollback()
Cancel this transaction, if not already canceled.void
setReadOnly
(boolean readOnly) Enable or disable read-only mode.void
setTimeout
(long timeout) Change the timeout for this transaction from its default value (optional operation).watchKey
(byte[] key) Watch a key to monitor for changes in its value.void
withWeakConsistency
(Runnable action) Apply weaker transaction consistency while performing the given action, if supported.Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
Methods inherited from interface io.permazen.kv.KVStore
apply, getRange, getRange, removeRange
-
Field Details
-
RETRY_PROVIDER_OPTION
Transaction option for aRetryTransactionProvider
to use with the underlying open and/or commit transactions.- See Also:
-
-
Constructor Details
-
BranchedKVTransaction
Constructor.This instance must be
open()
'ed before use.- Parameters:
kvdb
- database- Throws:
IllegalArgumentException
- ifkvdb
is null
-
BranchedKVTransaction
Constructor.This instance must be
open()
'ed before use.Any
RETRY_PROVIDER_OPTION
option inopenOptions
and/orsyncOptions
is removed (i.e., not passed downkvdb
) and used to automatically retry those transactions.- Parameters:
kvdb
- databaseopenOptions
- transaction options for opening transaction; may be nullsyncOptions
- transaction options for sync/commit transaction(s); may be null- Throws:
IllegalArgumentException
- ifkvdb
is null
-
-
Method Details
-
open
public void open()Open this transaction.This results in a snapshot being taken of the database.
- Throws:
UnsupportedOperationException
- if the database doesn't supportKVTransaction.readOnlySnapshot()
IllegalStateException
- if this method has already been invokedKVDatabaseException
- if the underlying transaction fails
-
commit
public void commit()Description copied from interface:KVTransaction
Commit this transaction.Note that if this method throws a
RetryKVTransactionException
, 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 interfaceKVTransaction
-
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()
orKVTransaction.rollback()
, in which case the invocation will be ignored. In particular, this method must not throwStaleKVTransactionException
.- Specified by:
rollback
in interfaceKVTransaction
-
close
public void close()Close this transaction.Equivalent to
rollback()
.- Specified by:
close
in interfaceAutoCloseable
- Specified by:
close
in interfaceCloseable
- Specified by:
close
in interfaceCloseableKVStore
-
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. -
getAtLeast
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; ifmaxKey <= minKey
, null is always returned.If keys starting with
0xff
are not supported by this instance, andminKey
starts with0xff
, then this method returns null.Modifications to the returned
byte[]
arrays do not affect this instance.- Specified by:
getAtLeast
in interfaceKVStore
- 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
andkey < maxKey
, or null if none exists
-
getAtMost
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); ifminKey >= maxKey
, null is always returned.If keys starting with
0xff
are not supported by this instance, andmaxKey
starts with0xff
, then this method behaves as ifmaxKey
were null.Modifications to the returned
byte[]
arrays do not affect this instance.- Specified by:
getAtMost
in interfaceKVStore
- 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
andkey >= minKey
, or null if none exists
-
getRange
Description copied from interface:KVStore
Iterate the key/value pairs in the specified range. The returnedCloseableIterator
'sremove()
method must be supported and should have the same effect as invokingremove()
on the corresponding key.If keys starting with
0xff
are not supported by this instance, andminKey
starts with0xff
, then this method returns an empty iteration.If keys starting with
0xff
are not supported by this instance, andmaxKey
starts with0xff
, then this method behaves as ifmaxKey
were null.The returned
CloseableIterator
is weakly consistent (seejava.util.concurrent
). In short, the returnedCloseableIterator
must not throwConcurrentModificationException
; 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 theCloseableIterator
, 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 valuebyte[]
arrays do not affect this instance.- Specified by:
getRange
in interfaceKVStore
- 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) tomaxKey
(exclusive)
-
put
public void put(byte[] key, byte[] value) Description copied from interface:KVStore
Set the value associated with the given key. -
remove
public void remove(byte[] key) Description copied from interface:KVStore
Remove the key/value pair with the given key, if it exists. -
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 tomaxKey
; 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 with0xff
, then no change occurs - If
maxKey
starts with0xff
, then this method behaves as ifmaxKey
were null
- Specified by:
removeRange
in interfaceKVStore
- Parameters:
minKey
- minimum key (inclusive), or null for no minimummaxKey
- maximum key (exclusive), or null for no maximum
- If
-
encodeCounter
public byte[] encodeCounter(long value) Description copied from interface:KVStore
Encode a counter value into abyte[]
value suitable for use withdecodeCounter()
and/oradjustCounter()
.- Specified by:
encodeCounter
in interfaceKVStore
- Parameters:
value
- desired counter value- Returns:
- encoded counter value
-
decodeCounter
public long decodeCounter(byte[] value) Description copied from interface:KVStore
Decode a counter value previously encoded byencodeCounter()
.- Specified by:
decodeCounter
in interfaceKVStore
- Parameters:
value
- encoded counter value- Returns:
- decoded counter value
-
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
, orkey
's value is not a valid counter encoding as would be acceptable todecodeCounter()
, then how this operation affectskey
's value is undefined.- Specified by:
adjustCounter
in interfaceKVStore
- Parameters:
key
- keyamount
- amount to adjust counter value by
-
getKVDatabase
Description copied from interface:KVTransaction
Get theKVDatabase
with which this instance is associated.- Specified by:
getKVDatabase
in interfaceKVTransaction
- Returns:
- associated database
-
setTimeout
public void setTimeout(long timeout) Description copied from interface:KVTransaction
Change the timeout for this transaction from its default value (optional operation).- Specified by:
setTimeout
in interfaceKVTransaction
- Parameters:
timeout
- transaction timeout in milliseconds, or zero for unlimited
-
isReadOnly
public boolean isReadOnly()Description copied from interface:KVTransaction
Determine whether this transaction is read-only.Default is false.
- Specified by:
isReadOnly
in interfaceKVTransaction
- Returns:
- true if this instance is read-only
-
setReadOnly
public void setReadOnly(boolean readOnly) Description copied from interface:KVTransaction
Enable or disable read-only mode.Read-only transactions allow mutations, but all changes are discarded on
KVTransaction.commit()
.Some implementations may impose one or more of the following restrictions on this method:
setReadOnly()
may only be invoked prior to accessing data;setReadOnly()
may only be invoked prior to mutating data; and/or- Once set to read-only, a transaction may not be set back to read-write
IllegalStateException
is thrown.Note: for some implementations, the data read from a transaction that is never
KVTransaction.commit()
'ed is not guaranteed to be up to date, even if that transaction is read-only.Default is false.
- Specified by:
setReadOnly
in interfaceKVTransaction
- Parameters:
readOnly
- read-only setting
-
watchKey
Description copied from interface:KVTransaction
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 returnedFuture
completes if and when a different value forkey
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 aKVDatabase
isKVDatabase.stop()
'ed, all outstanding key watches are implicitlycancel()
'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 singleCounter
field, whose key is then watched (to determine the key corresponding to a Java model object field, usePermazenField.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 theFuture
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 aListenableFuture
. However, listeners must not perform any long running or blocking operations. Also, because the semantics ofRetryKVTransactionException
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 becancel()
'ed to avoid memory leaks.Key watch support is indepdendent of whether the transaction is read-only.
- Specified by:
watchKey
in interfaceKVTransaction
- Parameters:
key
- the key to watch- Returns:
- a
Future
that returnskey
when the value associated withkey
is modified - See Also:
-
withWeakConsistency
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 performsaction
normally.- Specified by:
withWeakConsistency
in interfaceKVTransaction
- Parameters:
action
- the action to perform
-
readOnlySnapshot
Description copied from interface:KVTransaction
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 returnedCloseableKVStore
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 promplyclose()
'd when no longer needed to release any underlying resources. In particular, the caller must ensure that theCloseableKVStore
isclose()
'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.- Specified by:
readOnlySnapshot
in interfaceKVTransaction
- Returns:
- independent, read-only copy of this transaction's entire database content
-
checkForConflicts
public void checkForConflicts()Check for conflicts between this transaction and the current database contents.This method performs the conflict check that normally occurs during
commit()
, but without actually flushing any writes back to the underlying database. This transaction instance remains open.This checks whether any of the keys read by this transaction have changed since the original snapshot was taken. If so, then the writes from this transaction cannot be consistently merged back into the database and so an exception is immediately thrown.
- Throws:
TransactionConflictException
- if any conflicts are found
-