Aligned every public doc to the 8.0 contract: filesystem + memory adapters
only, vector index provider terminology (config.vector with recall +
quantization + persistMode knobs), no cloud storage adapters, no closed-
source product names.
Tier 1 — heavier rewrites:
- docs/architecture/storage-architecture.md
- docs/architecture/data-storage-architecture.md
- docs/architecture/distributed-storage.md DELETED — content was 100%
cloud-coordination examples with no 8.0 substance.
- docs/guides/distributed-system.md DELETED — same reason; no inbound refs.
- docs/SCALING.md rewritten for single-node guidance.
- docs/PLUGINS.md, docs/augmentations/{COMPLETE-REFERENCE,README}.md:
HnswProvider→VectorIndexProvider, hnsw→vector key.
- docs/PERFORMANCE.md, docs/BATCHING.md cloud-detection + sharding
sections replaced with single-node vector tuning + filesystem framing.
Tier 2 — surgical renames + cloud-section deletions:
- architecture/{index,initialization-and-rebuild,overview}.md
- transactions.md, DEVELOPER_LEARNING_PATH.md
- vfs/{VFS_API_GUIDE,COMMON_PATTERNS}.md
- api/README.md, guides/{inspection,import-flow}.md
Tier 3 — light edits:
- docs/README.md, architecture/augmentation-system-audit.md
MIGRATION-V3-TO-V4.md untouched (internal migration doc, no stale terms).
15 KiB
Transaction System
Status: ✅ Production Ready
Overview
Brainy's transaction system provides atomic operations with automatic rollback on failure. All operations within a transaction either succeed completely or fail completely - there are no partial failures.
Key Benefits
- Atomicity: All operations succeed or all rollback
- Consistency: Indexes and storage remain consistent
- Automatic: Transparently used by all
brain.add(),brain.update(),brain.delete(), andbrain.relate()operations - Compatible: Works seamlessly with COW, sharding, and type-aware storage
Architecture
User Code (brain.add(), brain.update(), etc.)
↓
Transaction Manager (orchestration)
↓
Operations (SaveNounMetadataOperation, SaveNounOperation, etc.)
↓
Storage Adapter (COW, sharding, type-aware routing)
How It Works
Every write operation in Brainy automatically uses transactions:
// Internally, this uses a transaction
const id = await brain.add({
data: { name: 'Alice', role: 'Engineer' },
type: NounType.Person
})
Transaction Flow:
- Begin Transaction: TransactionManager creates new transaction
- Add Operations: Operations added to transaction (SaveNounMetadataOperation, SaveNounOperation)
- Execute: Each operation executes in sequence
- Commit: All operations succeeded → changes persist
- Rollback: Any operation failed → all changes reverted
Rollback Mechanism
Each operation implements both execute and undo:
class SaveNounMetadataOperation {
async execute(): Promise<void> {
// Save new metadata
await this.storage.saveNounMetadata(this.id, this.metadata)
}
async undo(): Promise<void> {
// Restore previous metadata (or delete if new entity)
if (this.previousMetadata) {
await this.storage.saveNounMetadata(this.id, this.previousMetadata)
} else {
await this.storage.deleteNounMetadata(this.id)
}
}
}
On failure:
- Operations rolled back in reverse order
- Previous state fully restored
- Indexes updated to reflect rollback
Compatibility with Advanced Features
COW (Copy-on-Write)
✅ Fully Compatible
Transactions work transparently with COW branches:
// Create branch
await brain.cow.createBranch('feature-branch')
await brain.cow.checkout('feature-branch')
// 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
How It Works:
- Transactions use
StorageAdapterinterface - COW operates at storage layer (refManager, blobStorage, commitLog)
- Branch isolation prevents cross-branch contamination
- Rollback = discard uncommitted changes (COW makes this trivial)
Sharding
✅ Fully Compatible
Transactions work across multiple shards:
// Entities with different UUID prefixes go to different shards
const id1 = 'aaa00000-1111-4111-8111-111111111111' // Shard: aaa
const id2 = 'bbb00000-2222-4222-8222-222222222222' // Shard: bbb
await brain.add({ id: id1, data: { name: 'Entity A' }, type: NounType.Thing })
await brain.relate({ from: id1, to: id2, type: VerbType.RelatesTo })
// Transaction handles cross-shard atomicity automatically
How It Works:
- Sharding is transparent to transactions
analyzeKey()method routes to correct shard based on UUID- Transaction operations don't need to know about shards
- Rollback works across all shards involved
ID-First Storage
✅ Fully Compatible
Transactions work with direct ID-first paths - no type routing needed!
// Entities stored with direct ID-first paths
const personId = await brain.add({
data: { name: 'John Doe' },
type: NounType.Person // → entities/nouns/{shard}/{id}/metadata.json
})
const orgId = await brain.add({
data: { name: 'Acme Corp' },
type: NounType.Organization // → entities/nouns/{shard}/{id}/metadata.json
})
// Type changes handled atomically (type is just metadata)
await brain.update({
id: personId,
type: NounType.Organization, // Type change
data: { name: 'Doe Corp' }
})
How It Works:
- Type information stored in metadata.noun field
- Storage layer uses O(1) ID-first path construction
- No type cache needed (removed in a previous version)
- Type counters adjusted on commit/rollback
- 40x faster path lookups (eliminates 42-type search)
Storage Adapter Interface
✅ Fully Compatible
Transactions go through the StorageAdapter interface, so both shipped adapters (filesystem, memory) and any custom plugin adapter inherit the same atomicity guarantees:
const brain = new Brainy({
storage: { type: 'filesystem', rootDirectory: './data' }
})
await brain.add({ data: { name: 'Entity' }, type: NounType.Thing })
How It Works:
- Transactions operate through
StorageAdapterinterface - Custom adapters registered via the plugin system implement the same interface
- Atomicity guaranteed at the write-coordinator level
- Read-after-write consistency maintained inside a single Brainy process
Examples
Basic Add Operation
import { Brainy } from '@soulcraft/brainy'
import { NounType } from '@soulcraft/brainy/types'
const brain = new Brainy()
await brain.init()
// Automatically uses transaction
const id = await brain.add({
data: { name: 'Alice', role: 'Engineer' },
type: NounType.Person
})
// If add fails, all changes rolled back automatically
Update with Type Change
// Original entity
const id = await brain.add({
data: { name: 'John Smith', category: 'individual' },
type: NounType.Person
})
// Update with type change (atomic)
await brain.update({
id,
type: NounType.Organization, // Type change
data: { name: 'Smith Corp', category: 'business' }
})
// If update fails, original type and data restored
Creating Relationships
const personId = await brain.add({
data: { name: 'Alice' },
type: NounType.Person
})
const projectId = await brain.add({
data: { name: 'Project X' },
type: NounType.Thing
})
// Create relationship (atomic)
await brain.relate({
from: personId,
to: projectId,
type: VerbType.WorksOn
})
// If relate fails, no partial relationship created
Batch Operations
// Multiple operations, all atomic
for (let i = 0; i < 100; i++) {
await brain.add({
data: { name: `Entity ${i}`, index: i },
type: NounType.Thing
})
}
// Each add() is a separate transaction
// If any add fails, only that specific add is rolled back
Delete with Cascade
const personId = await brain.add({
data: { name: 'Bob' },
type: NounType.Person
})
const projectId = await brain.add({
data: { name: 'Project Y' },
type: NounType.Thing
})
await brain.relate({
from: personId,
to: projectId,
type: VerbType.WorksOn
})
// Delete person (atomic - deletes entity + relationships)
await brain.delete(personId)
// If delete fails, both entity and relationships remain
Error Handling
Transactions automatically handle errors and rollback:
try {
await brain.add({
data: { name: 'Test Entity' },
type: NounType.Thing,
vector: [1, 2, 3] // Wrong dimension → error
})
} catch (error) {
// Transaction automatically rolled back
// No partial data in storage or indexes
console.error('Add failed:', error.message)
}
Common Error Scenarios:
- Invalid vector dimension: Automatic rollback
- Type validation failure: Automatic rollback
- Storage write failure: Automatic rollback
- Index update failure: Automatic rollback
Performance Considerations
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)
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
// Get transaction statistics
const stats = brain.transactionManager?.getStats()
console.log(stats)
// {
// totalTransactions: 1234,
// successfulTransactions: 1200,
// failedTransactions: 34,
// rollbacks: 34,
// averageOperationsPerTransaction: 4.2
// }
Metrics Available:
totalTransactions: Total number of transactions executedsuccessfulTransactions: Number of successful commitsfailedTransactions: Number of rollbacksrollbacks: Total rollback countaverageOperationsPerTransaction: Average operations per transaction
Best Practices
1. Let Brainy Handle Transactions
// ✅ Recommended: Use Brainy's API (transactions automatic)
await brain.add({ data, type })
await brain.update({ id, data })
await brain.delete(id)
// ❌ Avoid: Direct storage access bypasses transactions
await brain.storage.saveNoun(noun) // No transaction protection
2. Handle Errors Gracefully
// ✅ Recommended: Catch errors, transaction rolls back automatically
try {
const id = await brain.add({ data, type })
return id
} catch (error) {
console.error('Add failed, rolled back:', error)
// Decide how to handle (retry, log, alert user)
}
3. Validate Before Operations
// ✅ Recommended: Validate early to avoid unnecessary rollbacks
if (!isValidVector(vector, brain.dimension)) {
throw new Error(`Vector must have ${brain.dimension} dimensions`)
}
await brain.add({ data, type, vector })
4. Monitor Transaction Statistics
// ✅ 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
5. Understand Atomicity Guarantees
What Transactions GUARANTEE:
- ✅ Atomicity within a single Brainy process
- ✅ Consistent state across all indexes
- ✅ Automatic rollback on failure
- ✅ Works with all storage adapters (filesystem, memory, custom plugin adapters)
What Transactions DON'T Provide:
- ❌ Two-phase commit across multiple Brainy instances
- ❌ Distributed locking across processes
- ❌ Cross-datacenter ACID guarantees
Design: Transactions ensure atomicity at the write-coordinator level inside one process. Cross-instance coordination, if you need it, lives in your service layer.
Testing Transactions
Unit Tests
import { describe, it, expect } from 'vitest'
import { Brainy } from '@soulcraft/brainy'
describe('Transaction Tests', () => {
it('should rollback on failure', async () => {
const brain = new Brainy()
await brain.init()
const id1 = await brain.add({ data: { name: 'Entity 1' }, type: NounType.Thing })
try {
await brain.add({
data: null as any, // Invalid - will fail
type: NounType.Thing
})
} catch (e) {
// Expected failure
}
// First entity should still exist (rollback didn't affect it)
const entity1 = await brain.get(id1)
expect(entity1).toBeTruthy()
})
})
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)
Troubleshooting
High Rollback Rate
Symptom: failedTransactions / totalTransactions > 5%
Possible Causes:
- Invalid vector dimensions
- Type validation errors
- Storage write failures (disk full, network issues)
- Index corruption
Solutions:
- Validate data before operations
- Check storage adapter health
- Monitor disk space and network connectivity
- Review error logs for patterns
Slow Transaction Performance
Symptom: Operations take > 100ms per transaction
Possible Causes:
- Large metadata objects
- Remote storage latency
- Many indexes enabled
- Disk I/O bottleneck
Solutions:
- Optimize metadata size
- Use local caching for remote storage
- Disable unused indexes
- Use SSD storage
Transaction Statistics Missing
Symptom: brain.transactionManager?.getStats() returns undefined
Cause: TransactionManager not initialized
Solution:
// Ensure Brainy is initialized
await brain.init()
// Then access stats
const stats = brain.transactionManager?.getStats()
Architecture Details
Transaction Lifecycle
1. BEGIN
↓
2. ADD OPERATIONS
- SaveNounMetadataOperation
- SaveNounOperation
- UpdateGraphIndexOperation
↓
3. EXECUTE (sequential)
- Execute operation 1 → Success
- Execute operation 2 → Success
- Execute operation 3 → FAILURE
↓
4. ROLLBACK (reverse order)
- Undo operation 2
- Undo operation 1
↓
5. THROW ERROR
Operation Types
| Operation | Description | Undo Behavior |
|---|---|---|
SaveNounMetadataOperation |
Save entity metadata | Restore previous metadata or delete if new |
SaveNounOperation |
Save entity data | Restore previous data or delete if new |
UpdateGraphIndexOperation |
Update graph index | Restore previous index state |
SaveVerbMetadataOperation |
Save relationship metadata | Restore previous metadata or delete if new |
SaveVerbOperation |
Save relationship data | Restore previous data or delete if new |
Storage Adapter Integration
Transactions use the StorageAdapter interface:
interface StorageAdapter {
saveNounMetadata(id: string, metadata: NounMetadata): Promise<void>
saveNoun(noun: Noun): Promise<void>
deleteNounMetadata(id: string): Promise<void>
deleteNoun(id: string): Promise<void>
// ... other methods
}
Key Insight: Both shipped storage adapters (filesystem, memory) — and any custom plugin adapter — implement this interface. Transactions work with any storage adapter automatically.
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
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.
Key Takeaways:
- ✅ Automatic: No manual transaction management needed
- ✅ Atomic: All operations succeed or all rollback
- ✅ 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)
Start using transactions today - they're already built into brain.add(), brain.update(), brain.delete(), and brain.relate()!