This guide provides strategies for optimizing the performance of NebulaDB in your applications.
NebulaDB is designed to be fast by default, with in-memory operations and efficient data structures. However, as your data grows or your application becomes more complex, you may need to optimize for specific use cases.
Several factors affect NebulaDB's performance:
Before optimizing, establish a baseline by measuring current performance:
// Measure query performance
console.time('Query');
const results = await collection.find({ /* your query */ });
console.timeEnd('Query');
// Measure insert performance
console.time('Insert');
await collection.insert({ /* your document */ });
console.timeEnd('Insert');
// Measure update performance
console.time('Update');
await collection.update({ /* query */ }, { /* update */ });
console.timeEnd('Update');
// Measure delete performance
console.time('Delete');
await collection.delete({ /* query */ });
console.timeEnd('Delete');
For more comprehensive benchmarking, use the benchmarking tools in the benchmarks
directory.
Indexes are the most effective way to improve query performance:
// Create an index on frequently queried fields
collection.createIndex({
name: 'email_idx',
fields: ['email'],
type: 'unique'
});
// Create a compound index for queries that filter on multiple fields
collection.createIndex({
name: 'user_location_idx',
fields: ['country', 'city'],
type: 'compound'
});
Guidelines for effective indexing:
Write efficient queries:
// GOOD: Specific query that can use an index
await users.find({ email: 'user@example.com' });
// AVOID: Complex queries that can't use indexes efficiently
await users.find({
$or: [
{ name: { $regex: '^A' } },
{ age: { $gt: 30 } }
]
});
Tips for efficient queries:
$or
For bulk operations, use batching:
// Instead of this
for (const item of items) {
await collection.insert(item);
}
// Do this
const promises = items.map(item => collection.insert(item));
await Promise.all(promises);
Different adapters have different performance characteristics:
The Cache Plugin can significantly improve performance for read-heavy workloads:
import { createCachePlugin } from '@nebula/plugin-cache';
const cachePlugin = createCachePlugin({
maxCacheSize: 100,
ttl: 60000 // 1 minute
});
const db = createDb({
adapter: new MemoryAdapter(),
plugins: [cachePlugin]
});
Plugins add functionality but can impact performance:
Document structure affects performance:
When dealing with large result sets, use pagination:
async function getPage(collection, query, page, pageSize) {
// Get all matching documents
const allDocs = await collection.find(query);
// Calculate start and end indices
const start = (page - 1) * pageSize;
const end = start + pageSize;
// Return the page
return allDocs.slice(start, end);
}
// Usage
const page1 = await getPage(users, { active: true }, 1, 10);
Optimize reactive subscriptions:
// Create a subscription
const unsubscribe = collection.subscribe(query, callback);
// Later, when no longer needed
unsubscribe();
For large datasets, be mindful of memory usage:
For specific performance requirements, consider creating a custom adapter:
class OptimizedAdapter implements Adapter {
// Implement with optimizations specific to your use case
}
For very large datasets, consider sharding:
// Create multiple databases for different data subsets
const userDb = createDb({ adapter: new MemoryAdapter() });
const productDb = createDb({ adapter: new MemoryAdapter() });
const orderDb = createDb({ adapter: new MemoryAdapter() });
For CPU-intensive operations, consider using worker threads:
// In main thread
const worker = new Worker('worker.js');
worker.postMessage({ action: 'query', collection: 'users', query: { /* complex query */ } });
worker.onmessage = (e) => {
const results = e.data;
// Process results
};
// In worker.js
self.onmessage = async (e) => {
const { action, collection, query } = e.data;
if (action === 'query') {
const db = createDb({ adapter: new MemoryAdapter() });
const results = await db.collection(collection).find(query);
self.postMessage(results);
}
};
Continuously monitor performance:
Before Optimization:
After Optimization:
completed
fielddueDate
fieldBefore Optimization:
After Optimization:
Performance optimization is an iterative process. Start with the simplest optimizations (like adding indexes) and measure the impact before moving to more complex strategies. Remember that premature optimization can lead to unnecessary complexity, so optimize based on actual performance measurements rather than assumptions.