# Optimistic Transactions
Optimistic Transactions provide light-weight optimistic concurrency control for workloads that do not expect high contention between multiple transactions.
## Optimistic vs Pessimistic Transactions
| Feature | Optimistic | Pessimistic |
|---------|------------|-------------|
| Locking | Validation at commit | Lock on write/GetForUpdate |
| Conflict handling | Retry on commit failure | Block/timeout on lock acquisition |
| Best for | Low contention workloads | High contention workloads |
| Deadlock | N/A | Detection & timeout |
| Performance | Better for read-heavy | Better for write-heavy |
Choose Optimistic Transactions when:
- Write conflicts are rare
- You have many non-transactional writes alongside transactions
- You prefer retry logic over blocking
Choose [Pessimistic Transactions](pessimistic_transactions.md) when:
- Multiple transactions frequently update the same keys
- You need strict locking guarantees
- You want automatic deadlock detection
## Opening an Optimistic Transaction Database
```erlang
%% Basic open
{ok, Db, [DefaultCF]} = rocksdb:open_optimistic_transaction_db(
"my_db",
[{create_if_missing, true}],
[{"default", []}]
).
%% With column families
CfOpts = [],
{ok, Db, [DefaultCF, DataCF]} = rocksdb:open_optimistic_transaction_db(
"my_db",
[{create_if_missing, true}],
[{"default", CfOpts}, {"data", CfOpts}]
).
```
## Basic Operations
### Creating a Transaction
```erlang
%% Create a transaction with default options
{ok, Txn} = rocksdb:transaction(Db, []).
%% Create with write options
WriteOpts = [{sync, true}],
{ok, Txn} = rocksdb:transaction(Db, WriteOpts).
```
### Put, Get, Delete
```erlang
{ok, Txn} = rocksdb:transaction(Db, []),
%% Put a key-value pair
ok = rocksdb:transaction_put(Txn, <<"key1">>, <<"value1">>),
%% Get a value
{ok, Value} = rocksdb:transaction_get(Txn, <<"key1">>, []),
%% Delete a key
ok = rocksdb:transaction_delete(Txn, <<"key1">>),
%% Commit the transaction
ok = rocksdb:transaction_commit(Txn).
```
### Column Family Support
All operations support column families:
```erlang
{ok, Txn} = rocksdb:transaction(Db, []),
%% Operations with column family
ok = rocksdb:transaction_put(Txn, CfHandle, <<"key">>, <<"value">>),
{ok, Value} = rocksdb:transaction_get(Txn, CfHandle, <<"key">>, []),
ok = rocksdb:transaction_delete(Txn, CfHandle, <<"key">>),
ok = rocksdb:transaction_commit(Txn).
```
## Reading Uncommitted Data
Transactions can read their own uncommitted changes:
```erlang
%% Write to database first
ok = rocksdb:put(Db, <<"a">>, <<"old_a">>, []),
ok = rocksdb:put(Db, <<"b">>, <<"old_b">>, []),
%% Start transaction
{ok, Txn} = rocksdb:transaction(Db, []),
%% Modify within transaction
ok = rocksdb:transaction_put(Txn, <<"a">>, <<"new_a">>),
%% Read sees uncommitted change
{ok, <<"new_a">>} = rocksdb:transaction_get(Txn, <<"a">>, []),
%% Read sees committed value for unmodified key
{ok, <<"old_b">>} = rocksdb:transaction_get(Txn, <<"b">>, []),
ok = rocksdb:transaction_commit(Txn).
```
## Iterators
Create an iterator that sees uncommitted changes in the transaction:
```erlang
{ok, Txn} = rocksdb:transaction(Db, []),
%% Add uncommitted data
ok = rocksdb:transaction_put(Txn, <<"key1">>, <<"value1">>),
ok = rocksdb:transaction_put(Txn, <<"key2">>, <<"value2">>),
%% Create iterator - sees both committed and uncommitted data
{ok, Iter} = rocksdb:transaction_iterator(Txn, []),
%% Use standard iterator operations
{ok, Key, Value} = rocksdb:iterator_move(Iter, first),
{ok, NextKey, NextValue} = rocksdb:iterator_move(Iter, next),
ok = rocksdb:iterator_close(Iter),
ok = rocksdb:transaction_commit(Txn).
```
With column family:
```erlang
{ok, Iter} = rocksdb:transaction_iterator(Txn, CfHandle, []).
```
## Conflict Detection and Retry
Optimistic transactions validate at commit time. If another transaction modified the same keys, commit will fail:
```erlang
retry_transaction(Db, Key, UpdateFun) ->
retry_transaction(Db, Key, UpdateFun, 3).
retry_transaction(_Db, _Key, _UpdateFun, 0) ->
{error, max_retries};
retry_transaction(Db, Key, UpdateFun, Retries) ->
{ok, Txn} = rocksdb:transaction(Db, []),
try
case rocksdb:transaction_get(Txn, Key, []) of
{ok, Value} ->
NewValue = UpdateFun(Value),
ok = rocksdb:transaction_put(Txn, Key, NewValue),
case rocksdb:transaction_commit(Txn) of
ok -> ok;
{error, {busy, _}} ->
%% Conflict detected, retry
retry_transaction(Db, Key, UpdateFun, Retries - 1)
end;
not_found ->
{error, not_found}
end
catch
_:Error ->
{error, Error}
end.
```
## Error Handling
Optimistic transactions can return these errors on commit:
- `{error, {busy, Reason}}` - Write conflict detected at commit time
- `{error, {try_again, Reason}}` - Transient error, retry the operation
```erlang
case rocksdb:transaction_commit(Txn) of
ok ->
%% Success
ok;
{error, {busy, _}} ->
%% Another transaction modified the same keys
%% Rollback and retry with fresh data
handle_conflict();
{error, Reason} ->
%% Other error
{error, Reason}
end.
```
## Complete Example
```erlang
increment_counter(Db, CounterKey) ->
{ok, Txn} = rocksdb:transaction(Db, []),
try
Value = case rocksdb:transaction_get(Txn, CounterKey, []) of
{ok, V} -> binary_to_integer(V);
not_found -> 0
end,
NewValue = integer_to_binary(Value + 1),
ok = rocksdb:transaction_put(Txn, CounterKey, NewValue),
case rocksdb:transaction_commit(Txn) of
ok ->
{ok, Value + 1};
{error, {busy, _}} ->
%% Conflict - retry
increment_counter(Db, CounterKey)
end
catch
_:Error ->
{error, Error}
end.
```