docs(8.0): consistency-model concept + snapshots guide — Db API replaces branching docs
This commit is contained in:
parent
e5feae4104
commit
cc8037db10
23 changed files with 1053 additions and 1871 deletions
|
|
@ -1,3 +1,16 @@
|
|||
---
|
||||
title: Transactions & Atomicity
|
||||
slug: guides/transactions
|
||||
public: true
|
||||
category: guides
|
||||
template: guide
|
||||
order: 10
|
||||
description: How Brainy keeps every write atomic — automatic per-operation transactions with rollback, and brain.transact() for atomic multi-write batches with compare-and-swap.
|
||||
next:
|
||||
- concepts/consistency-model
|
||||
- guides/optimistic-concurrency
|
||||
---
|
||||
|
||||
# Transaction System
|
||||
|
||||
**Status:** ✅ Production Ready
|
||||
|
|
@ -11,18 +24,18 @@ Brainy's transaction system provides **atomic operations** with automatic rollba
|
|||
- **Atomicity**: All operations succeed or all rollback
|
||||
- **Consistency**: Indexes and storage remain consistent
|
||||
- **Automatic**: Transparently used by all `brain.add()`, `brain.update()`, `brain.delete()`, and `brain.relate()` operations
|
||||
- **Compatible**: Works seamlessly with COW, sharding, and type-aware storage
|
||||
- **Composable**: `brain.transact()` runs a multi-write batch through the same machinery as exactly one atomic commit
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
User Code (brain.add(), brain.update(), etc.)
|
||||
User Code (brain.add(), brain.update(), brain.transact(), etc.)
|
||||
↓
|
||||
Transaction Manager (orchestration)
|
||||
↓
|
||||
Operations (SaveNounMetadataOperation, SaveNounOperation, etc.)
|
||||
↓
|
||||
Storage Adapter (COW, sharding, type-aware routing)
|
||||
Storage Adapter (sharding, ID-first routing)
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
|
@ -74,31 +87,41 @@ class SaveNounMetadataOperation {
|
|||
|
||||
## Compatibility with Advanced Features
|
||||
|
||||
### COW (Copy-on-Write)
|
||||
### Multi-Write Batches: `brain.transact()`
|
||||
|
||||
✅ **Fully Compatible**
|
||||
✅ **The 8.0 path for atomic multi-entity writes**
|
||||
|
||||
Transactions work transparently with COW branches:
|
||||
Single-operation methods each commit their own transaction. When several
|
||||
writes must succeed or fail **together**, use `brain.transact()` — a
|
||||
declarative batch that commits as exactly one generation, with optional
|
||||
whole-store compare-and-swap and durable transaction metadata:
|
||||
|
||||
```typescript
|
||||
// Create branch
|
||||
await brain.cow.createBranch('feature-branch')
|
||||
await brain.cow.checkout('feature-branch')
|
||||
const db = await brain.transact([
|
||||
{ op: 'add', id: orderId, type: NounType.Document, subtype: 'order', data: 'Order #1042' },
|
||||
{ op: 'update', id: customerId, metadata: { lastOrderAt: Date.now() }, ifRev: customer._rev },
|
||||
{ op: 'relate', from: customerId, to: orderId, type: VerbType.Creates, subtype: 'purchase' }
|
||||
], { meta: { author: 'order-service' } })
|
||||
|
||||
// Add entity (uses transaction on this branch)
|
||||
const id = await brain.add({
|
||||
data: { name: 'Feature Entity' },
|
||||
type: NounType.Thing
|
||||
})
|
||||
|
||||
// On rollback: Branch remains clean, no partial commits
|
||||
db.receipt.ids // resolved id per operation, in input order
|
||||
```
|
||||
|
||||
**How It Works:**
|
||||
- Transactions use `StorageAdapter` interface
|
||||
- COW operates at storage layer (refManager, blobStorage, commitLog)
|
||||
- Branch isolation prevents cross-branch contamination
|
||||
- Rollback = discard uncommitted changes (COW makes this trivial)
|
||||
- The batch executes through the same TransactionManager as single
|
||||
operations, wrapped in the generational commit protocol: before-images are
|
||||
staged and fsynced first, and the atomic manifest rename is the commit
|
||||
point — a crash anywhere before it rolls back to the exact
|
||||
pre-transaction bytes.
|
||||
- Per-entity `ifRev` and whole-store `ifAtGeneration` provide
|
||||
compare-and-swap at two granularities; any conflict rejects the entire
|
||||
batch before anything is staged.
|
||||
- The returned `Db` is a pinned, snapshot-isolated view of the committed
|
||||
state.
|
||||
|
||||
See the **[consistency model](concepts/consistency-model.md)** for the
|
||||
full guarantees (snapshot isolation, time travel, snapshots) and
|
||||
**[Snapshots & Time Travel](guides/snapshots-and-time-travel.md)** for
|
||||
recipes.
|
||||
|
||||
### Sharding
|
||||
|
||||
|
|
@ -306,37 +329,31 @@ try {
|
|||
|
||||
### Transaction Overhead
|
||||
|
||||
**MEASURED Performance Impact:**
|
||||
- Average overhead: ~2-5ms per transaction (measured: `tests/transaction/transaction.bench.ts`)
|
||||
- Operations per transaction: 2-8 (metadata + data + indexes)
|
||||
- Rollback cost: ~1-3ms (restore previous state)
|
||||
**What a transaction costs:**
|
||||
- A typical single-operation write wraps 2-8 operations (metadata + data + indexes) in one transaction
|
||||
- The overhead is bookkeeping (operation objects + undo state), not extra I/O on the success path
|
||||
- Rollback cost is proportional to the operations already applied (each is undone in reverse order)
|
||||
|
||||
**Optimization:**
|
||||
- Operations executed sequentially (not parallel) for consistency
|
||||
- Rollback only happens on failure (success path is fast)
|
||||
- Index updates batched within transaction
|
||||
|
||||
### Statistics and Monitoring
|
||||
### Auditing Committed Batches
|
||||
|
||||
Every committed `brain.transact()` batch is recorded in the transaction
|
||||
log, newest first:
|
||||
|
||||
```typescript
|
||||
// Get transaction statistics
|
||||
const stats = brain.transactionManager?.getStats()
|
||||
console.log(stats)
|
||||
// {
|
||||
// totalTransactions: 1234,
|
||||
// successfulTransactions: 1200,
|
||||
// failedTransactions: 34,
|
||||
// rollbacks: 34,
|
||||
// averageOperationsPerTransaction: 4.2
|
||||
// }
|
||||
await brain.transact(ops, { meta: { author: 'import-job' } })
|
||||
|
||||
const entries = await brain.transactionLog({ limit: 10 })
|
||||
// [{ generation: 1042, timestamp: 1765432100000, meta: { author: 'import-job' } }]
|
||||
```
|
||||
|
||||
**Metrics Available:**
|
||||
- `totalTransactions`: Total number of transactions executed
|
||||
- `successfulTransactions`: Number of successful commits
|
||||
- `failedTransactions`: Number of rollbacks
|
||||
- `rollbacks`: Total rollback count
|
||||
- `averageOperationsPerTransaction`: Average operations per transaction
|
||||
Single-operation writes advance the generation counter but do not append
|
||||
log entries — see the [consistency model](concepts/consistency-model.md)
|
||||
for the history-granularity contract.
|
||||
|
||||
## Best Practices
|
||||
|
||||
|
|
@ -376,19 +393,18 @@ if (!isValidVector(vector, brain.dimension)) {
|
|||
await brain.add({ data, type, vector })
|
||||
```
|
||||
|
||||
### 4. Monitor Transaction Statistics
|
||||
### 4. Batch Related Writes with `transact()`
|
||||
|
||||
```typescript
|
||||
// ✅ Recommended: Monitor in production
|
||||
setInterval(() => {
|
||||
const stats = brain.transactionManager?.getStats()
|
||||
if (stats) {
|
||||
const failureRate = stats.failedTransactions / stats.totalTransactions
|
||||
if (failureRate > 0.05) { // > 5% failure rate
|
||||
console.warn('High transaction failure rate:', failureRate)
|
||||
}
|
||||
}
|
||||
}, 60000) // Check every minute
|
||||
// ✅ Recommended: writes that must land together go in one batch
|
||||
await brain.transact([
|
||||
{ op: 'add', id: orderId, type: NounType.Document, subtype: 'order', data: 'Order #1042' },
|
||||
{ op: 'relate', from: customerId, to: orderId, type: VerbType.Creates, subtype: 'purchase' }
|
||||
])
|
||||
|
||||
// ❌ Avoid: sequential single operations when partial application is unacceptable
|
||||
const id = await brain.add({ ... }) // commits alone
|
||||
await brain.relate({ ... }) // a crash here leaves the entity unlinked
|
||||
```
|
||||
|
||||
### 5. Understand Atomicity Guarantees
|
||||
|
|
@ -440,15 +456,19 @@ describe('Transaction Tests', () => {
|
|||
### Integration Tests
|
||||
|
||||
See `tests/transaction/integration/` for comprehensive integration tests covering:
|
||||
- COW integration (`cow-transactions.test.ts`)
|
||||
- Sharding integration (`sharding-transactions.test.ts`)
|
||||
- TypeAware integration (`typeaware-transactions.test.ts`)
|
||||
- Type-aware integration (`typeaware-transactions.test.ts`)
|
||||
- Distributed scenarios (`distributed-transactions.test.ts`)
|
||||
|
||||
The atomicity guarantees of `brain.transact()` — including crash recovery
|
||||
through the real recovery path — are proven in
|
||||
`tests/integration/db-mvcc.test.ts`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### High Rollback Rate
|
||||
|
||||
**Symptom:** `failedTransactions` / `totalTransactions` > 5%
|
||||
**Symptom:** a high share of writes throw and roll back
|
||||
|
||||
**Possible Causes:**
|
||||
1. Invalid vector dimensions
|
||||
|
|
@ -478,21 +498,6 @@ See `tests/transaction/integration/` for comprehensive integration tests coverin
|
|||
- Disable unused indexes
|
||||
- Use SSD storage
|
||||
|
||||
### Transaction Statistics Missing
|
||||
|
||||
**Symptom:** `brain.transactionManager?.getStats()` returns `undefined`
|
||||
|
||||
**Cause:** TransactionManager not initialized
|
||||
|
||||
**Solution:**
|
||||
```typescript
|
||||
// Ensure Brainy is initialized
|
||||
await brain.init()
|
||||
|
||||
// Then access stats
|
||||
const stats = brain.transactionManager?.getStats()
|
||||
```
|
||||
|
||||
## Architecture Details
|
||||
|
||||
### Transaction Lifecycle
|
||||
|
|
@ -545,28 +550,19 @@ interface StorageAdapter {
|
|||
|
||||
## Additional Resources
|
||||
|
||||
- **Unit Tests:** `tests/transaction/transaction.test.ts` (36 passing tests)
|
||||
- **Integration Tests:** `tests/transaction/integration/` (35 test scenarios)
|
||||
- **Compatibility Analysis:** `.strategy/TRANSACTION_COMPATIBILITY_ANALYSIS.md` (internal)
|
||||
- **Performance Benchmarks:** `tests/transaction/transaction.bench.ts`
|
||||
|
||||
## Version History
|
||||
|
||||
- Initial transaction system release
|
||||
- Atomic operations with rollback
|
||||
- Compatible with COW, sharding, type-aware storage
|
||||
- 36/36 unit tests passing
|
||||
- 35 integration test scenarios
|
||||
- **Unit Tests:** `tests/transaction/Transaction.test.ts`, `tests/transaction/TransactionManager.test.ts`
|
||||
- **Integration Tests:** `tests/transaction/integration/`
|
||||
- **MVCC Proofs:** `tests/integration/db-mvcc.test.ts` (atomicity, CAS, crash recovery for `brain.transact()`)
|
||||
- **Consistency Model:** [docs/concepts/consistency-model.md](concepts/consistency-model.md)
|
||||
|
||||
## Summary
|
||||
|
||||
Brainy's transaction system provides **production-ready atomic operations** with automatic rollback. It works transparently with all Brainy APIs and is fully compatible with advanced features like COW, sharding, and type-aware storage.
|
||||
Brainy's transaction system provides **production-ready atomic operations** with automatic rollback. Every single-operation write is transactional out of the box, and `brain.transact()` extends the same guarantee to multi-write batches — one atomic commit, with compare-and-swap and durable transaction metadata.
|
||||
|
||||
**Key Takeaways:**
|
||||
- ✅ **Automatic**: No manual transaction management needed
|
||||
- ✅ **Atomic**: All operations succeed or all rollback
|
||||
- ✅ **Automatic**: No manual transaction management needed for single operations
|
||||
- ✅ **Atomic**: All operations succeed or all rollback — per operation and per `transact()` batch
|
||||
- ✅ **Compatible**: Works with all storage adapters and features
|
||||
- ✅ **Production-Ready**: Tested with 71 test scenarios (36 unit + 35 integration)
|
||||
- ✅ **Performant**: ~2-5ms overhead per transaction (measured)
|
||||
- ✅ **Coordinated**: Per-entity `ifRev` and whole-store `ifAtGeneration` CAS reject conflicting batches before anything is staged
|
||||
|
||||
Start using transactions today - they're already built into `brain.add()`, `brain.update()`, `brain.delete()`, and `brain.relate()`!
|
||||
Start using transactions today - they're already built into `brain.add()`, `brain.update()`, `brain.delete()`, and `brain.relate()` — and reach for `brain.transact()` whenever several writes must land together.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue