Performance Optimization
Tips and techniques for optimizing query performance in Stabilize ORM
1. Use Selective Field Loading
Only load the fields you need instead of fetching entire records:
// ❌ Bad: Loads all columnsconst users = await userRepo.find().execute(dbClient);// ✅ Good: Only load needed fieldsconst users = await userRepo.find().select("id", "email", "name").execute(dbClient);
2. Implement Pagination
Never load all records at once. Use pagination for large datasets:
// ❌ Bad: Loads everythingconst allPosts = await postRepo.find().execute(dbClient);// ✅ Good: Paginate resultsconst page = 1;const perPage = 20;const posts = await postRepo.find().limit(perPage).offset((page - 1) * perPage).execute(dbClient);// Even better: Use cursor-based paginationconst posts = await postRepo.find().where("id > ?", lastSeenId).limit(20).orderBy("id ASC").execute(dbClient);
3. Optimize Relationship Loading
Avoid N+1 queries by eager loading relationships:
// ❌ Bad: N+1 query problemconst users = await userRepo.find().execute(dbClient);for (const user of users) {const posts = await postRepo.find().where("userId = ?", user.id).execute(dbClient);// This runs a query for each user!}// ✅ Good: Eager load relationshipsconst users = await userRepo.find().with("posts").execute(dbClient);
4. Use Indexes Strategically
Add indexes to columns frequently used in WHERE, JOIN, and ORDER BY clauses:
export const User = defineModel({tableName: "users",columns: {id: {type: DataTypes.Integer},email: {type: DataTypes.String,unique: true, // Automatically creates an indexrequired: true,},status: {type: DataTypes.String,index: true, // Add index for frequent filtering},createdAt: {type: DataTypes.Timestamp,index: true, // Index for sorting/filtering by date},},});
5. Enable Query Caching
Cache frequently accessed data with Redis:
import { Stabilize } from "stabilize-orm";const orm = new Stabilize({type: DBType.Postgres,connectionString: process.env.DATABASE_URL!,cache: {enabled: true,redisUrl: process.env.REDIS_URL,ttl: 300, // Cache for 5 minutescachePrefix: "myapp:",strategy: "cache-aside", // or "write-through"},});// Queries are automatically cachedconst user = await userRepo.find().where("id = ?", 1).first();const user2 = await userRepo.find().where("id = ?", 1).first(); // Hits cache// Invalidate cache on updatesawait userRepo.update(1, { name: "New Name" }); // Auto-invalidates cache
6. Use Bulk Operations
Batch multiple operations into single queries:
// ❌ Bad: Multiple individual insertsfor (const userData of users) {await userRepo.create(userData);}// ✅ Good: Single bulk insertawait userRepo.bulkCreate(users);// ✅ Good: Bulk updateawait userRepo.bulkUpdate([{ id: 1, name: "User 1" },{ id: 2, name: "User 2" },]);
7. Optimize Query Conditions
Write efficient WHERE clauses:
// ❌ Bad: Using LIKE with leading wildcard (can't use index)const users = await userRepo.find().where("email LIKE ?", "%@stabilize.xyz").execute(dbClient);// ✅ Good: Use exact matches or trailing wildcardsconst users = await userRepo.find().where("email LIKE ?", "ciniso%@stabilzie.xyz").execute(dbClient);// ❌ Bad: Using OR conditions (harder to optimize)const users = await userRepo.find().where("status = ?", "active").orWhere("status = ?", "pending").execute(dbClient);// ✅ Good: Use IN clauseconst users = await userRepo.find().where("status IN (?, ?)", "active", "pending").execute(dbClient);
8. Use Transactions Wisely
Group related operations in transactions, but keep them short:
// ✅ Good: Short, focused transactionawait dbClient.transaction(async (trx) => {const user = await userRepo.create({ email: "ciniso@stabilzie.xyz" }, trx);await profileRepo.create({ userId: user.id, bio: "..." }, trx);});// ❌ Bad: Long-running transaction with external API callsawait dbClient.transaction(async (trx) => {const user = await userRepo.create({ email: "ciniso@stabilzie.xyz" }, trx);await sendWelcomeEmail(user.email); // Don't do this in transaction!await profileRepo.create({ userId: user.id }, trx);});
9. Monitor Query Performance
Enable query logging to identify slow queries:
import { Stabilize, type LoggerConfig, LogLevel } from "stabilize-orm";const loggerConfig: LoggerConfig = {level: LogLevel.Info,filePath: "logs/stabilize.log",maxFileSize: 5 * 1024 * 1024, // 5MBmaxFiles: 3,};export const orm = new Stabilize(dbConfig, cacheConfig, loggerConfig);
10. Use Query Scopes
Define reusable, optimized query patterns:
const User = defineModel({tableName: "users",columns: {id: { type: DataTypes.Integer, required: true },email: { type: DataTypes.String, length: 100, required: true },isActive: { type: DataTypes.Boolean, required: true },createdAt: { type: DataTypes.DateTime },updatedAt: { type: DataTypes.DateTime },},scopes: {active: (qb) => qb.where("isActive = ?", true),recent: (qb, days: number) => qb.where("createdAt >= ?",new Date(Date.now() - days * 24 * 60 * 60 * 1000)),},});// Use scopes for consistent, optimized queriesconst recentActiveUsers = await userRepo.scope("active").scope("recent").find().execute(dbClient);
Performance Checklist
- ✓ Use selective field loading with
select() - ✓ Implement pagination for large datasets
- ✓ Eager load relationships to avoid N+1 queries
- ✓ Add indexes to frequently queried columns
- ✓ Enable Redis caching for read-heavy workloads
- ✓ Use bulk operations for multiple records
- ✓ Optimize WHERE clauses and use IN instead of OR
- ✓ Keep transactions short and focused
- ✓ Monitor slow queries with logging
- ✓ Define query scopes for common patterns
