Transactions
pyrex.TransactionDB opens RocksDB with pessimistic transaction support. Use
it when multiple writes and reads must be grouped into one atomic unit.
Basic Transaction
Transactions use explicit commit semantics. The context manager rolls back if
the transaction is still active when the with block exits.
import pyrex
with pyrex.TransactionDB("example_txn_db") as db:
with db.transaction() as txn:
txn.put(b"k", b"v")
assert txn.get(b"k") == b"v"
txn.commit()
assert db.get(b"k") == b"v"
Rollback On Exception
If an exception leaves the transaction block before commit() is called,
__exit__ rolls the transaction back.
import pyrex
with pyrex.TransactionDB("example_txn_db") as db:
try:
with db.transaction() as txn:
txn.put(b"k", b"temporary")
raise RuntimeError("abort")
except RuntimeError:
pass
assert db.get(b"k") is None
Write Batch Inside A Transaction
Existing PyWriteBatch objects can be applied inside a transaction. The
current transaction batch support covers default column-family put and
delete operations.
import pyrex
with pyrex.TransactionDB("example_txn_db") as db:
batch = pyrex.PyWriteBatch()
batch.put(b"a", b"1")
batch.delete(b"old")
with db.transaction() as txn:
txn.write(batch)
txn.commit()
Transaction Iterators
Transaction iterators see the transaction view, including local writes and deletes where RocksDB supports them. Prefix scans are performed by seeking to a prefix and stopping when keys no longer match.
import pyrex
with pyrex.TransactionDB("example_txn_db") as db:
with db.transaction() as txn:
txn.put(b"user:1", b"alice")
txn.put(b"user:2", b"bob")
it = txn.new_iterator()
it.seek(b"user:")
while it.valid() and it.key().startswith(b"user:"):
print(it.key(), it.value())
it.next()
txn.rollback()
Read For Update
get_for_update reads a key and tracks it for transaction conflict checking.
Pass read_value=False to lock or track the key without fetching its value.
import pyrex
with pyrex.TransactionDB("example_txn_db") as db:
db.put(b"account:alice", b"100")
with db.transaction() as txn:
assert txn.get_for_update(b"account:alice") == b"100"
txn.get_for_update(b"account:bob", read_value=False)
txn.commit()
Transaction Options
TransactionDBOptions controls database-level transaction settings such as
lock timeouts and lock table sizing. TransactionOptions controls each
transaction.
import pyrex
db_options = pyrex.TransactionDBOptions()
db_options.default_lock_timeout = 1000
db_options.transaction_lock_timeout = 1000
txn_options = pyrex.TransactionOptions()
txn_options.set_snapshot = True
txn_options.lock_timeout = 1000
txn_options.expiration = 30000
txn_options.deadlock_detect = True
with pyrex.TransactionDB("example_txn_db", None, db_options) as db:
txn = db.begin_transaction(None, txn_options)
txn.put(b"k", b"v")
txn.commit()
Write Options And Durability
WriteOptions can be supplied when a transaction begins and when it commits.
disable_wal is preserved, but disable_wal=True means a successful commit
is not fully durable across process or machine crashes.
import pyrex
write_options = pyrex.WriteOptions()
write_options.sync = True
with pyrex.TransactionDB("example_txn_db") as db:
with db.transaction(write_options) as txn:
txn.put(b"durable", b"value")
txn.commit(write_options)
Retryable Errors
Transaction lock conflicts and timeouts are mapped to specific Python exception subclasses so callers can decide whether to retry.
import pyrex
try:
with pyrex.TransactionDB("example_txn_db") as db:
with db.transaction() as txn:
txn.put(b"k", b"v")
txn.commit()
except (pyrex.RocksDBBusyError,
pyrex.RocksDBTimeoutError,
pyrex.RocksDBConflictError):
# Retry according to the application's policy.
raise
Complete Example
1import os
2import shutil
3
4import pyrex
5
6
7db_path = "/tmp/pyrex_example_transactions"
8if os.path.exists(db_path):
9 shutil.rmtree(db_path)
10
11
12with pyrex.TransactionDB(db_path) as db:
13 # Context-manager transactions are explicit-commit: if commit() is not
14 # called, __exit__ rolls the transaction back.
15 with db.transaction() as txn:
16 txn.put(b"account:alice", b"100")
17 txn.put(b"account:bob", b"50")
18 assert txn.get(b"account:alice") == b"100"
19 txn.commit()
20
21 print(db.get(b"account:alice").decode()) # 100
22
23 # Rollback discards uncommitted changes.
24 with db.transaction() as txn:
25 txn.put(b"account:alice", b"0")
26
27 print(db.get(b"account:alice").decode()) # 100
28
29 # Existing PyWriteBatch objects can be applied inside a transaction.
30 batch = pyrex.PyWriteBatch()
31 batch.put(b"account:carol", b"25")
32 batch.delete(b"account:bob")
33
34 txn = db.begin_transaction()
35 txn.write(batch)
36 txn.commit()
37
38 print(db.get(b"account:carol").decode()) # 25
39 print(db.get(b"account:bob")) # None
40
41 # Transaction iterators include transaction-local writes.
42 with db.transaction() as txn:
43 txn.put(b"account:dave", b"75")
44 it = txn.new_iterator()
45 it.seek(b"account:")
46 while it.valid() and it.key().startswith(b"account:"):
47 print(it.key(), it.value())
48 it.next()
49 txn.rollback()
50
51shutil.rmtree(db_path)