Implement atomic operations with BinaryDB transactions
Transactions in BinaryDB provide a way to group multiple operations together and ensure they either all succeed or all fail atomically. This is essential for maintaining data consistency.
Begin a new transaction by creating a snapshot of the current database state.
database.py:201-213
def begin(self) -> None: """ Begin a transaction. All changes are kept in memory until commit or rollback. """ self._ensure_open() if self._in_transaction: raise TransactionError("Transaction already active") self._tx_snapshot = self._data.copy() self._in_transaction = True
Commit the active transaction and persist changes to disk.
database.py:230-241
def end(self) -> None: """ Commit the active transaction. """ self._ensure_open() if not self._in_transaction: raise TransactionError("No active transaction") self._tx_snapshot = None self._in_transaction = False self.commit()
What happens when you call end():
Clears the snapshot (_tx_snapshot = None)
Exits transaction mode (_in_transaction = False)
Calls commit() to write changes to disk
Example:
db = Database("users.pkl")db.load()db.begin()db.set("user:1", {"name": "Alice", "role": "admin"})db.set("user:2", {"name": "Bob", "role": "user"})db.end() # Both users are saved atomically
end() automatically calls commit() to persist changes. You don’t need to call commit() separately.
Discard all changes made during the transaction and restore the database to its state when begin() was called.
database.py:215-228
def rollback(self) -> None: """ Roll back the active transaction. """ self._ensure_open() if not self._in_transaction: raise TransactionError("No active transaction") if self._tx_snapshot is not None: self._data = self._tx_snapshot self._tx_snapshot = None self._in_transaction = False self._dirty = False
What happens during rollback:
Restores _data from the snapshot
Clears the snapshot
Exits transaction mode
Resets the dirty flag (no changes to persist)
Example:
db = Database("inventory.pkl")db.load()# Initial statedb.set("product:1", {"name": "Widget", "stock": 100})db.commit()db.begin()db.update("product:1", {"stock": 0}) # Reduce stock to 0db.set("product:2", {"name": "Gadget", "stock": 50})# Something went wrong, rollback!db.rollback()# Data is restored to pre-transaction stateprint(db.get("product:1")) # {"name": "Widget", "stock": 100}print(db.exists("product:2")) # False - product:2 was never committed
Use rollback() when validation fails or an error occurs during transaction processing.
db = Database("mydata.pkl")db.load()db.begin()db.set("key1", "value1")try: db.begin() # Trying to start nested transactionexcept TransactionError as e: print(f"Error: {e}") # "Transaction already active"db.end() # Commit the first transaction
Calling end() or rollback() without an active transaction raises an error:
db = Database("mydata.pkl")db.load()try: db.end() # No transaction startedexcept TransactionError as e: print(e) # "No active transaction"try: db.rollback() # No transaction startedexcept TransactionError as e: print(e) # "No active transaction"
The snapshot is a shallow copy. If your data contains nested mutable objects, modifications to those objects affect both the snapshot and current data.
Minimize the time spent in a transaction to reduce memory overhead and improve responsiveness:
# Good: Short transactiondb.begin()db.set("key", value)db.end()# Bad: Long-running transactiondb.begin()for i in range(1000000): db.set(f"key:{i}", expensive_computation())db.end()
Don't forget to end or rollback
Always close your transaction with either end() or rollback(). Leaving a transaction open blocks the dirty flag mechanism:
db.begin()db.set("key", "value")# Forgot to call end() or rollback()!# Later operations won't auto-commitdb.set("another_key", "value") # _dirty stays False!
Validate before committing
Perform all validation before calling end() to avoid partial commits:
db.begin()try: # Perform all operations db.set("user", user_data) db.set("profile", profile_data) # Validate if not validate_data(user_data, profile_data): raise ValueError("Validation failed") db.end()except Exception: db.rollback() raise