Batch Operations
Overview
Batch operations in AD4M allow you to group multiple perspective mutations into a single atomic transaction. This is useful when you need to perform multiple related changes that should either all succeed or all fail together, and when you want to optimize performance by reducing internal engine updates (SurrealDB indexing, and Prolog inference if activated) and network operations.
Key Benefits
1. Atomicity
- All operations in a batch are applied together or none are applied
- Prevents partial updates that could leave your data in an inconsistent state
- Ideal for complex operations that require multiple related changes
2. Performance Optimization
- Reduces the number of internal engine updates (SurrealDB indexing, query subscriptions, and Prolog inference if activated)
- Minimizes the number of commits to the neighbourhood's link language
- Batches network operations for shared perspectives
- Improves overall system responsiveness when making multiple changes
Using Transactions
The recommended way to use batch operations is Ad4mModel.transaction(), which creates a batch, runs your callback, and commits only on success:
await Ad4mModel.transaction(perspective, async (tx) => {
const recipe = await Recipe.create(perspective, { name: "Cake" }, { batchId: tx.batchId });
const comment = new Comment(perspective);
comment.body = "Looks great!";
await comment.save(tx.batchId);
// Relation methods also accept batchId
await recipe.addComments(comment, tx.batchId);
});
// All operations commit together, or none do if an error is thrownIf any operation inside the callback throws, the batch is never committed and no changes are persisted.
Complex Data Models
When your application needs to maintain relationships between multiple entities:
await Ad4mModel.transaction(perspective, async (tx) => {
const userProfile = await UserProfile.create(perspective, { name: "Alice" }, { batchId: tx.batchId });
const userSettings = await UserSettings.create(perspective, { theme: "dark" }, { batchId: tx.batchId });
for (const preference of userPreferences) {
await preference.save(tx.batchId);
}
});Bulk Operations
When performing operations on multiple items:
await Ad4mModel.transaction(perspective, async (tx) => {
for (const task of tasks) {
task.status = 'completed';
await task.save(tx.batchId);
}
});State Transitions
When an operation requires multiple coordinated changes:
await Ad4mModel.transaction(perspective, async (tx) => {
await sourceList.removeTask(task, tx.batchId);
await targetList.addTask(task, tx.batchId);
task.status = 'moved';
await task.save(tx.batchId);
});Creating Related Subjects
await Ad4mModel.transaction(perspective, async (tx) => {
// Create main subject
const post = await BlogPost.create(perspective, { title: "My First Post" }, { batchId: tx.batchId });
// Create related subjects
const comments = initialComments.map(text => {
const c = new Comment(perspective);
c.content = text;
c.postId = post.id;
return c;
});
// Save all related subjects in the same transaction
await Promise.all(comments.map(c => c.save(tx.batchId)));
});Validation Before Commit
Validate data before any writes — if validation fails, the thrown error prevents commit:
await Ad4mModel.transaction(perspective, async (tx) => {
// Validate all ingredients first
for (const ingredient of ingredients) {
if (!await validateIngredient(ingredient)) {
throw new Error(`Invalid ingredient: ${ingredient}`);
}
}
// If all valid, proceed with updates
recipe.ingredients = ingredients;
await recipe.save(tx.batchId);
// Create ingredient references
for (const ingredient of ingredients) {
const ref = new IngredientReference(perspective);
ref.recipeId = recipe.id;
ref.ingredientId = ingredient.id;
await ref.save(tx.batchId);
}
});
// Nothing is persisted if validation threwAdvanced: Manual Batch Control
For lower-level control (e.g. mixing model operations with raw link operations), you can manage batches directly via PerspectiveProxy:
const batchId = await perspective.createBatch();
try {
// Add multiple links in the batch
await perspective.add(link1, 'shared', batchId);
await perspective.add(link2, 'shared', batchId);
// Remove a link in the same batch
await perspective.remove(oldLink, batchId);
// Update a link in the batch
await perspective.update(existingLink, newLink, batchId);
// Model operations also accept batchId
const recipe = new Recipe(perspective);
recipe.title = "Chocolate Cake";
await recipe.save(batchId);
// Commit all changes atomically
const result = await perspective.commitBatch(batchId);
console.log('Added:', result.additions.length);
console.log('Removed:', result.removals.length);
} catch (error) {
// If any operation fails, none of the changes are applied
console.error('Batch operation failed:', error);
}Under the hood, Ad4mModel.transaction() calls createBatch() and commitBatch() for you.
Technical Details
Batch Storage
- Batches are stored in memory until committed
- Each batch has a unique UUID
- Changes are tracked separately for shared and local links
Engine Synchronization
- Internal indexes (SurrealDB, query subscriptions, and Prolog facts if activated) are updated only once per batch commit
- Updates are performed using a oneshot channel to ensure completion
- The engine state remains consistent with all changes
Link Language Integration
- For shared perspectives, commits to the link language happen only once per batch
- Reduces network operations in distributed scenarios
Best Practices
-
Prefer
transaction(): UseAd4mModel.transaction()over manualcreateBatch()/commitBatch()— if the callback throws, the batch is not committed and no changes are persisted. -
Batch Size: Keep batches focused on related operations. Avoid extremely large batches that could impact memory usage — split very large operations into multiple smaller batches.
-
Error Handling: With
transaction(), errors thrown inside the callback automatically prevent commit. With manual batches, wrap operations in try/catch. -
Resource Management: Don't keep batches open longer than necessary. With
transaction()this is handled for you. -
Testing: Test both successful and failed batch scenarios. Verify data consistency after batch operations.
Limitations and Considerations
-
Memory Usage
- Batches store changes in memory until committed
- Very large batches may impact system performance
-
Concurrency
- Batch operations don't provide explicit locking
- Consider application-level synchronization for concurrent operations
-
Network Connectivity
- For shared perspectives, ensure stable network connectivity before committing large batches
- Consider implementing retry logic for network-related failures
Migration Guide
Existing code using manual createBatch()/commitBatch() will continue to work. To modernize:
// Before: manual batch management
const batchId = await perspective.createBatch();
try {
await recipe.save(batchId);
await comment.save(batchId);
await perspective.commitBatch(batchId);
} catch (error) {
console.error(error);
}
// After: transaction() handles it
await Ad4mModel.transaction(perspective, async (tx) => {
await recipe.save(tx.batchId);
await comment.save(tx.batchId);
});No schema changes or data migration is required.