Patterns · 2 of 4
Atomic transactions
Every procedure call is one OCC transaction. All steps either commit together or roll back together. Two rules to remember.
Rule 1: abort rolls back everything
proc atomic_demo(account_id, amount) {
update("accounts", {_id: account_id}, {$inc: {balance: -amount}})
insert("audit", {action: "debit", amount: amount})
if amount > 10000 {
abort "amount over daily limit"
}
return {ok: true}
}
If the abort fires, the update AND the insert are both rolled back.
Rule 2: merge per-document updates
OCC validates a version per document, not per field. Two updates on the same document inside one proc will conflict.
WRONG — will conflict
update("accounts", {_id: id}, {$inc: {balance: -amount}})
update("accounts", {_id: id}, {$inc: {tx_count: 1}})
update("accounts", {_id: id}, {$set: {last_tx: "now"}})
RIGHT — single update with combined operators
update("accounts", {_id: id}, {
$inc: {balance: -amount, tx_count: 1},
$set: {last_tx: "now"}
})
Atomic transfer
proc transfer(from, to, amount) {
let sender = find_one("accounts", {_id: from})
if sender == null { abort "sender not found" }
if sender.balance < amount { abort "insufficient funds" }
update("accounts", {_id: from}, {$inc: {balance: -amount, tx_count: 1}})
update("accounts", {_id: to}, {$inc: {balance: amount, tx_count: 1}})
insert("ledger", {from: from, to: to, amount: amount})
return {ok: true}
}
Three writes across two collections, one transaction. Either all happen or none.
Reads inside a transaction
Reads inside a procedure see a consistent snapshot. The version captured at find_one is what OCC validates against on commit.
proc reserve_seat(event_id, user_id) {
let event = find_one("events", {_id: event_id})
if event.seats_left <= 0 { abort "sold out" }
update("events", {_id: event_id}, {$inc: {seats_left: -1}})
insert("reservations", {event_id: event_id, user_id: user_id})
return {ok: true, seats_left: event.seats_left - 1}
}
If two clients hit this at the same time, only one wins — the other gets an OCC retry-able error.
Multi-document atomic update
Different documents can be touched freely; only same-doc multiple updates are the problem.
proc batch_credit(account_ids, amount) {
for id in account_ids {
update("accounts", {_id: id}, {$inc: {balance: amount}})
}
return {ok: true, credited: amount}
}
When to NOT use OxiScript
- Long-running batch jobs that touch millions of docs — break into smaller calls or use the pipeline.
- Operations that need to commit partial results — OxiScript is all-or-nothing.
- Workflows that wait on external systems mid-procedure — don't pause inside a transaction.