Caching
Learn how to optimize performance with Sequelize Guard's built-in caching system.
Overview
Sequelize Guard includes a built-in caching layer using node-cache to improve performance by reducing database queries for frequently accessed authorization data.
Configuration
Enable Caching
const guard = new SequelizeGuard(sequelize, {
cache: {
enabled: true,
ttl: 300, // Time to live in seconds (default: 300)
checkPeriod: 600, // Check period for expired keys (default: 600)
},
});Disable Caching
const guard = new SequelizeGuard(sequelize, {
cache: {
enabled: false,
},
});How Caching Works
Cached Operations
The following operations are cached:
- User role lookups
- Role permission lookups
- Permission checks
- User permission lookups
Cache Keys
Keys are generated automatically based on:
- User IDs
- Role IDs
- Permission actions and resources
Example cache keys:
user:123:roles
role:456:permissions
permission:user:123:create:postsCache Management
Clear All Cache
guard.cache.flushAll();Clear Specific Keys
// Clear user's roles cache
guard.cache.del(`user:${userId}:roles`);
// Clear role's permissions cache
guard.cache.del(`role:${roleId}:permissions`);Get Cache Statistics
const stats = guard.cache.getStats();
console.log('Cache hits:', stats.hits);
console.log('Cache misses:', stats.misses);
console.log('Cache keys:', stats.keys);Cache Invalidation
Manual Invalidation
When you modify roles or permissions, invalidate the cache:
// After assigning a role to a user
await guard.users.assignRole(userId, roleId);
guard.cache.del(`user:${userId}:roles`);
guard.cache.del(`user:${userId}:permissions`);
// After assigning a permission to a role
await guard.roles.assignPermission(roleId, permissionId);
guard.cache.del(`role:${roleId}:permissions`);Automatic Invalidation
Create a helper class for automatic cache invalidation:
class CachedGuard {
constructor(private guard: SequelizeGuard) {}
async assignRole(userId: string, roleId: number) {
await this.guard.users.assignRole(userId, roleId);
// Invalidate cache
this.guard.cache.del(`user:${userId}:roles`);
this.guard.cache.del(`user:${userId}:permissions`);
}
async removeRole(userId: string, roleId: number) {
await this.guard.users.removeRole(userId, roleId);
// Invalidate cache
this.guard.cache.del(`user:${userId}:roles`);
this.guard.cache.del(`user:${userId}:permissions`);
}
async assignPermission(roleId: number, permissionId: number) {
await this.guard.roles.assignPermission(roleId, permissionId);
// Invalidate role cache
this.guard.cache.del(`role:${roleId}:permissions`);
// Invalidate all users with this role
const users = await this.getUsersWithRole(roleId);
users.forEach((userId) => {
this.guard.cache.del(`user:${userId}:permissions`);
});
}
private async getUsersWithRole(roleId: number): Promise<string[]> {
// Implementation to get all users with a specific role
return [];
}
}Best Practices
1. Use Longer TTL in Production
const guard = new SequelizeGuard(sequelize, {
cache: {
enabled: true,
ttl: process.env.NODE_ENV === 'production' ? 600 : 60,
},
});2. Disable Cache in Tests
const guard = new SequelizeGuard(sequelize, {
cache: {
enabled: process.env.NODE_ENV !== 'test',
},
});3. Clear Cache After Bulk Operations
async function bulkAssignRoles(userIds: string[], roleId: number) {
for (const userId of userIds) {
await guard.users.assignRole(userId, roleId);
}
// Clear cache for all affected users
userIds.forEach((userId) => {
guard.cache.del(`user:${userId}:roles`);
guard.cache.del(`user:${userId}:permissions`);
});
}4. Monitor Cache Performance
// Log cache statistics periodically
setInterval(() => {
const stats = guard.cache.getStats();
console.log('Cache statistics:', {
hits: stats.hits,
misses: stats.misses,
hitRate: stats.hits / (stats.hits + stats.misses),
keys: stats.keys,
});
}, 60000); // Every minutePerformance Tips
1. Batch Permission Checks
// Instead of checking permissions sequentially
const canCreate = await guard.authorize.checkPermission(
userId,
'create',
'posts',
);
const canUpdate = await guard.authorize.checkPermission(
userId,
'update',
'posts',
);
const canDelete = await guard.authorize.checkPermission(
userId,
'delete',
'posts',
);
// Check in parallel to benefit from cache
const [canCreate, canUpdate, canDelete] = await Promise.all([
guard.authorize.checkPermission(userId, 'create', 'posts'),
guard.authorize.checkPermission(userId, 'update', 'posts'),
guard.authorize.checkPermission(userId, 'delete', 'posts'),
]);2. Preload User Permissions
async function preloadUserPermissions(userId: string) {
// This will cache the user's roles and permissions
await guard.users.getUserWithRoles(userId);
// Subsequent permission checks will use the cache
}3. Cache Warming
async function warmCache() {
// Preload common roles
const commonRoles = ['admin', 'editor', 'user'];
await Promise.all(
commonRoles.map((roleName) => guard.roles.getRole(roleName)),
);
// Preload common permissions
const commonPermissions = [
{ action: 'create', resource: 'posts' },
{ action: 'read', resource: 'posts' },
{ action: 'update', resource: 'posts' },
{ action: 'delete', resource: 'posts' },
];
await Promise.all(
commonPermissions.map((p) =>
guard.permissions.getPermission(p.action, p.resource),
),
);
}
// Run on application startup
warmCache();Advanced Caching
Custom Cache Implementation
You can extend Guard with a custom cache implementation:
import Redis from 'ioredis';
class RedisCache {
private redis: Redis;
constructor() {
this.redis = new Redis();
}
async get(key: string): Promise<any> {
const value = await this.redis.get(key);
return value ? JSON.parse(value) : null;
}
async set(key: string, value: any, ttl: number): Promise<void> {
await this.redis.setex(key, ttl, JSON.stringify(value));
}
async del(key: string): Promise<void> {
await this.redis.del(key);
}
async flushAll(): Promise<void> {
await this.redis.flushall();
}
}
// Use custom cache
const customCache = new RedisCache();
// Implement wrapper to use customCache with GuardTroubleshooting
Cache Not Working
- Verify cache is enabled:
console.log(guard.cache.enabled);- Check cache statistics:
const stats = guard.cache.getStats();
console.log('Cache stats:', stats);Stale Data
If you're seeing stale data, ensure you're invalidating the cache properly:
// Always clear cache after modifications
await guard.users.assignRole(userId, roleId);
guard.cache.del(`user:${userId}:roles`);High Memory Usage
Reduce TTL or clear cache more frequently:
const guard = new SequelizeGuard(sequelize, {
cache: {
enabled: true,
ttl: 60, // Shorter TTL
checkPeriod: 120,
},
});
// Clear cache periodically
setInterval(() => {
guard.cache.flushAll();
}, 300000); // Every 5 minutes