Performance Optimization
Maximize MatsushibaDB performance with advanced optimization techniques, monitoring, and best practices.Indexing Strategies
Creating Effective Indexes
Copy
-- Single column indexes
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_created_at ON users(created_at);
CREATE INDEX idx_posts_published ON posts(published);
-- Composite indexes for multi-column queries
CREATE INDEX idx_posts_author_date ON posts(author_id, created_at);
CREATE INDEX idx_orders_customer_status ON orders(customer_id, status, created_at);
-- Partial indexes for filtered queries
CREATE INDEX idx_active_users ON users(email) WHERE status = 'active';
CREATE INDEX idx_published_posts ON posts(created_at) WHERE published = 1;
-- Covering indexes to avoid table lookups
CREATE INDEX idx_posts_covering ON posts(author_id, published, created_at)
INCLUDE (title, content);
-- Unique indexes for data integrity
CREATE UNIQUE INDEX idx_users_username ON users(username);
CREATE UNIQUE INDEX idx_users_email ON users(email);
Index Analysis and Optimization
Copy
// Analyze query performance
function analyzeQuery(sql, params = []) {
try {
const explainQuery = `EXPLAIN QUERY PLAN ${sql}`;
const plan = db.all(explainQuery, params);
console.log('Query Plan:', plan);
// Check for full table scans
const hasTableScan = plan.some(step =>
step.detail.includes('SCAN TABLE') &&
!step.detail.includes('USING INDEX')
);
if (hasTableScan) {
console.warn('Warning: Query may perform full table scan');
}
return {
plan: plan,
hasTableScan: hasTableScan,
recommendations: generateIndexRecommendations(plan)
};
} catch (error) {
throw error;
}
}
// Generate index recommendations
function generateIndexRecommendations(plan) {
const recommendations = [];
plan.forEach(step => {
if (step.detail.includes('SCAN TABLE') && !step.detail.includes('USING INDEX')) {
const tableMatch = step.detail.match(/SCAN TABLE (\w+)/);
if (tableMatch) {
recommendations.push({
type: 'missing_index',
table: tableMatch[1],
suggestion: `Consider creating an index on ${tableMatch[1]} for better performance`
});
}
}
});
return recommendations;
}
// Index usage statistics
function getIndexUsageStats() {
try {
const stats = db.all(`
SELECT
name,
tbl_name,
sql,
CASE
WHEN name LIKE 'sqlite_autoindex_%' THEN 'auto'
ELSE 'manual'
END as index_type
FROM sqlite_master
WHERE type = 'index'
AND name NOT LIKE 'sqlite_%'
ORDER BY tbl_name, name
`);
return stats;
} catch (error) {
throw error;
}
}
Query Optimization
Prepared Statements
Copy
// Use prepared statements for repeated queries
class QueryOptimizer {
constructor(db) {
this.db = db;
this.preparedStatements = new Map();
}
// Prepare statement with caching
prepare(sql) {
if (!this.preparedStatements.has(sql)) {
const stmt = this.db.prepare(sql);
this.preparedStatements.set(sql, stmt);
}
return this.preparedStatements.get(sql);
}
// Execute prepared statement
execute(sql, params) {
const stmt = this.prepare(sql);
return stmt.run(params);
}
// Get single record
get(sql, params) {
const stmt = this.prepare(sql);
return stmt.get(params);
}
// Get all records
all(sql, params) {
const stmt = this.prepare(sql);
return stmt.all(params);
}
// Clean up prepared statements
finalize() {
for (const stmt of this.preparedStatements.values()) {
stmt.finalize();
}
this.preparedStatements.clear();
}
}
// Usage example
const optimizer = new QueryOptimizer(db);
// These queries will reuse the same prepared statement
const user1 = optimizer.get('SELECT * FROM users WHERE id = ?', [1]);
const user2 = optimizer.get('SELECT * FROM users WHERE id = ?', [2]);
const user3 = optimizer.get('SELECT * FROM users WHERE id = ?', [3]);
Query Optimization Techniques
Copy
// Pagination optimization
function getUsersPaginated(page, pageSize) {
const offset = (page - 1) * pageSize;
// Use LIMIT and OFFSET efficiently
return db.all(`
SELECT * FROM users
ORDER BY created_at DESC
LIMIT ? OFFSET ?
`, [pageSize, offset]);
}
// Cursor-based pagination (more efficient for large datasets)
function getUsersCursor(cursor, pageSize) {
const whereClause = cursor ? 'WHERE id < ?' : '';
const params = cursor ? [cursor, pageSize] : [pageSize];
return db.all(`
SELECT * FROM users
${whereClause}
ORDER BY id DESC
LIMIT ?
`, params);
}
// Batch operations
function batchInsertUsers(users) {
try {
db.run('BEGIN TRANSACTION');
const stmt = db.prepare('INSERT INTO users (name, email, age) VALUES (?, ?, ?)');
for (const user of users) {
stmt.run(user.name, user.email, user.age);
}
stmt.finalize();
db.run('COMMIT');
return { success: true, count: users.length };
} catch (error) {
db.run('ROLLBACK');
throw error;
}
}
// Query result caching
class QueryCache {
constructor(ttl = 300000) { // 5 minutes default TTL
this.cache = new Map();
this.ttl = ttl;
}
get(key) {
const item = this.cache.get(key);
if (item && Date.now() - item.timestamp < this.ttl) {
return item.data;
}
this.cache.delete(key);
return null;
}
set(key, data) {
this.cache.set(key, {
data: data,
timestamp: Date.now()
});
}
clear() {
this.cache.clear();
}
}
// Usage with caching
const queryCache = new QueryCache();
function getCachedUsers() {
const cacheKey = 'all_users';
let users = queryCache.get(cacheKey);
if (!users) {
users = db.all('SELECT * FROM users ORDER BY created_at DESC');
queryCache.set(cacheKey, users);
}
return users;
}
Connection Pooling
Advanced Connection Pool Management
Copy
const { MatsushibaPool } = require('matsushibadb');
class AdvancedConnectionPool {
constructor(config) {
this.pool = new MatsushibaPool({
database: config.database,
min: config.minConnections || 5,
max: config.maxConnections || 20,
acquireTimeoutMillis: config.acquireTimeout || 30000,
createTimeoutMillis: config.createTimeout || 30000,
destroyTimeoutMillis: config.destroyTimeout || 5000,
idleTimeoutMillis: config.idleTimeout || 30000,
reapIntervalMillis: config.reapInterval || 1000,
createRetryIntervalMillis: config.createRetryInterval || 200,
propagateCreateError: false
});
this.metrics = {
totalConnections: 0,
activeConnections: 0,
idleConnections: 0,
totalQueries: 0,
averageQueryTime: 0
};
}
async executeQuery(sql, params = []) {
const startTime = Date.now();
const connection = await this.pool.acquire();
try {
const result = await connection.all(sql, params);
this.updateMetrics(Date.now() - startTime);
return result;
} finally {
this.pool.release(connection);
}
}
async executeTransaction(operations) {
const connection = await this.pool.acquire();
try {
await connection.beginTransaction();
const results = [];
for (const operation of operations) {
const result = await connection.run(operation.sql, operation.params);
results.push(result);
}
await connection.commit();
return { success: true, results: results };
} catch (error) {
await connection.rollback();
throw error;
} finally {
this.pool.release(connection);
}
}
updateMetrics(queryTime) {
this.metrics.totalQueries++;
this.metrics.averageQueryTime =
(this.metrics.averageQueryTime * (this.metrics.totalQueries - 1) + queryTime) /
this.metrics.totalQueries;
}
getMetrics() {
return {
...this.metrics,
poolSize: this.pool.size,
availableConnections: this.pool.available,
waitingClients: this.pool.waiting
};
}
async close() {
await this.pool.destroy();
}
}
// Usage
const pool = new AdvancedConnectionPool({
database: 'app.db',
minConnections: 5,
maxConnections: 20
});
// Execute queries
const users = await pool.executeQuery('SELECT * FROM users WHERE active = ?', [1]);
// Execute transactions
const result = await pool.executeTransaction([
{ sql: 'INSERT INTO users (name, email) VALUES (?, ?)', params: ['John', '[email protected]'] },
{ sql: 'INSERT INTO profiles (user_id, bio) VALUES (?, ?)', params: [1, 'New user'] }
]);
Performance Monitoring
Database Performance Metrics
Copy
class PerformanceMonitor {
constructor(db) {
this.db = db;
this.metrics = {
queryCount: 0,
totalQueryTime: 0,
slowQueries: [],
connectionCount: 0,
cacheHits: 0,
cacheMisses: 0
};
}
// Monitor query performance
async monitorQuery(sql, params, operation) {
const startTime = Date.now();
try {
const result = await operation();
const executionTime = Date.now() - startTime;
this.metrics.queryCount++;
this.metrics.totalQueryTime += executionTime;
// Track slow queries
if (executionTime > 1000) { // Queries taking more than 1 second
this.metrics.slowQueries.push({
sql: sql,
params: params,
executionTime: executionTime,
timestamp: new Date()
});
}
return result;
} catch (error) {
console.error('Query failed:', sql, error);
throw error;
}
}
// Get performance statistics
getStats() {
const averageQueryTime = this.metrics.queryCount > 0
? this.metrics.totalQueryTime / this.metrics.queryCount
: 0;
return {
totalQueries: this.metrics.queryCount,
averageQueryTime: averageQueryTime,
slowQueriesCount: this.metrics.slowQueries.length,
cacheHitRate: this.metrics.cacheHits / (this.metrics.cacheHits + this.metrics.cacheMisses) || 0,
databaseSize: this.getDatabaseSize(),
indexUsage: this.getIndexUsageStats()
};
}
// Get database size
getDatabaseSize() {
try {
const result = this.db.get(`
SELECT page_count * page_size as size
FROM pragma_page_count(), pragma_page_size()
`);
return result.size;
} catch (error) {
return 0;
}
}
// Get index usage statistics
getIndexUsageStats() {
try {
return this.db.all(`
SELECT
name,
tbl_name,
sql
FROM sqlite_master
WHERE type = 'index'
AND name NOT LIKE 'sqlite_%'
`);
} catch (error) {
return [];
}
}
// Analyze slow queries
analyzeSlowQueries() {
const analysis = {
mostCommonSlowQueries: {},
averageSlowQueryTime: 0,
recommendations: []
};
if (this.metrics.slowQueries.length === 0) {
return analysis;
}
// Group slow queries by SQL pattern
this.metrics.slowQueries.forEach(query => {
const pattern = query.sql.replace(/\d+/g, '?'); // Normalize parameters
if (!analysis.mostCommonSlowQueries[pattern]) {
analysis.mostCommonSlowQueries[pattern] = {
count: 0,
totalTime: 0,
averageTime: 0
};
}
analysis.mostCommonSlowQueries[pattern].count++;
analysis.mostCommonSlowQueries[pattern].totalTime += query.executionTime;
});
// Calculate averages
Object.keys(analysis.mostCommonSlowQueries).forEach(pattern => {
const stats = analysis.mostCommonSlowQueries[pattern];
stats.averageTime = stats.totalTime / stats.count;
});
// Generate recommendations
Object.keys(analysis.mostCommonSlowQueries).forEach(pattern => {
const stats = analysis.mostCommonSlowQueries[pattern];
if (stats.count > 5 && stats.averageTime > 500) {
analysis.recommendations.push({
query: pattern,
issue: 'Frequent slow query',
suggestion: 'Consider adding indexes or optimizing the query'
});
}
});
return analysis;
}
}
// Usage
const monitor = new PerformanceMonitor(db);
// Wrap queries with monitoring
async function getUsers() {
return await monitor.monitorQuery(
'SELECT * FROM users WHERE active = ?',
[1],
() => db.all('SELECT * FROM users WHERE active = ?', [1])
);
}
Memory Optimization
Memory Management Techniques
Copy
// Memory-efficient data processing
class MemoryOptimizer {
constructor(db) {
this.db = db;
this.batchSize = 1000; // Process in batches
}
// Process large datasets in chunks
async processLargeDataset(sql, params, processor) {
const totalCount = this.db.get(`SELECT COUNT(*) as count FROM (${sql})`, params).count;
let processed = 0;
while (processed < totalCount) {
const batch = this.db.all(`
${sql}
LIMIT ? OFFSET ?
`, [...params, this.batchSize, processed]);
await processor(batch);
processed += batch.length;
// Force garbage collection if available
if (global.gc) {
global.gc();
}
}
}
// Stream processing for very large datasets
async streamProcess(sql, params, processor) {
const stmt = this.db.prepare(sql);
return new Promise((resolve, reject) => {
stmt.each(
params,
(row) => {
try {
processor(row);
} catch (error) {
reject(error);
}
},
(err, count) => {
if (err) {
reject(err);
} else {
resolve(count);
}
stmt.finalize();
}
);
});
}
// Optimize database settings for memory
optimizeMemorySettings() {
// Set cache size based on available memory
const cacheSize = Math.min(2000, Math.floor(process.memoryUsage().heapTotal / 1024 / 1024));
this.db.run(`PRAGMA cache_size = ${cacheSize}`);
this.db.run('PRAGMA temp_store = MEMORY');
this.db.run('PRAGMA mmap_size = 268435456'); // 256MB
}
}
// Usage
const optimizer = new MemoryOptimizer(db);
optimizer.optimizeMemorySettings();
// Process large user dataset
await optimizer.processLargeDataset(
'SELECT * FROM users WHERE created_at > ?',
['2024-01-01'],
async (batch) => {
// Process batch
console.log(`Processing ${batch.length} users`);
// ... processing logic
}
);
Performance Tuning
Database Configuration Optimization
Copy
-- Performance optimization settings
PRAGMA journal_mode = WAL; -- Better concurrency
PRAGMA synchronous = NORMAL; -- Balance between safety and speed
PRAGMA cache_size = 2000; -- Increase cache size
PRAGMA temp_store = MEMORY; -- Use memory for temp tables
PRAGMA mmap_size = 268435456; -- 256MB memory mapping
PRAGMA optimize; -- Run query optimizer
-- Analyze tables for better query planning
ANALYZE;
-- Vacuum database to reclaim space
VACUUM;
-- Check database integrity
PRAGMA integrity_check;
Application-Level Optimizations
Copy
// Connection pooling configuration
const poolConfig = {
min: 5, // Minimum connections
max: 20, // Maximum connections
acquireTimeoutMillis: 30000,
createTimeoutMillis: 30000,
destroyTimeoutMillis: 5000,
idleTimeoutMillis: 30000,
reapIntervalMillis: 1000,
createRetryIntervalMillis: 200
};
// Query optimization middleware
function queryOptimizer(req, res, next) {
const originalSend = res.send;
res.send = function(data) {
// Log slow responses
const responseTime = Date.now() - req.startTime;
if (responseTime > 1000) {
console.warn(`Slow response: ${req.method} ${req.path} - ${responseTime}ms`);
}
originalSend.call(this, data);
};
next();
}
// Database health check
async function healthCheck() {
try {
const startTime = Date.now();
await db.get('SELECT 1');
const responseTime = Date.now() - startTime;
return {
status: 'healthy',
responseTime: responseTime,
timestamp: new Date().toISOString()
};
} catch (error) {
return {
status: 'unhealthy',
error: error.message,
timestamp: new Date().toISOString()
};
}
}
Best Practices
1
Create Appropriate Indexes
Create indexes on frequently queried columns and composite indexes for multi-column queries.
2
Use Prepared Statements
Always use prepared statements for repeated queries to improve performance and security.
3
Implement Connection Pooling
Use connection pooling for production applications to manage database connections efficiently.
4
Monitor Performance
Implement comprehensive performance monitoring to identify bottlenecks and optimize accordingly.
5
Optimize Queries
Use EXPLAIN QUERY PLAN to analyze and optimize query performance.
6
Use Pagination
Implement proper pagination for large result sets to avoid memory issues.
7
Cache Frequently Used Data
Implement caching strategies for frequently accessed data to reduce database load.
8
Regular Maintenance
Perform regular database maintenance including VACUUM and ANALYZE operations.
Performance optimization is an ongoing process. Regularly monitor your database performance, analyze slow queries, and implement optimizations based on your specific use case and data patterns.