Performance Optimization Guide for Mifty Applications
Master performance optimization techniques to make your Mifty applications lightning-fast, from database queries to API responses and everything in between.
What You'll Learn
This comprehensive guide covers:
- 🚀 Database Optimization - Query optimization, indexing, and connection pooling
- ⚡ API Performance - Response time optimization and caching strategies
- 💾 Memory Management - Memory leak prevention and garbage collection tuning
- 🔄 Caching Strategies - Redis, in-memory, and CDN caching
- 📊 Monitoring & Profiling - Performance monitoring and bottleneck identification
- 🌐 Network Optimization - Compression, CDN, and request optimization
- 🏗️ Architecture Patterns - Scalable architecture and microservices optimization
- 📈 Load Testing - Performance testing and capacity planning
Step 1: Database Performance Optimization
1.1 Query Optimization
Identify Slow Queries
// src/utils/query-optimizer.ts
import { PrismaService } from '../services/prisma.service';
export class QueryOptimizer {
constructor(private prisma: PrismaService) {
this.setupQueryMonitoring();
}
private setupQueryMonitoring(): void {
this.prisma.$on('query', (e) => {
const duration = e.duration;
if (duration > 100) {
console.warn(`Slow query detected (${duration}ms):`, {
query: e.query,
params: e.params,
suggestions: this.analyzeQuery(e.query)
});
}
});
}
private analyzeQuery(query: string): string[] {
const suggestions: string[] = [];
// Check for missing WHERE clauses
if (query.includes('SELECT') && !query.includes('WHERE') && !query.includes('LIMIT')) {
suggestions.push('Add WHERE clause or LIMIT to avoid full table scans');
}
// Check for N+1 queries
if (query.includes('SELECT') && query.includes('IN (')) {
suggestions.push('Consider using JOIN instead of IN clause for better performance');
}
// Check for missing indexes
if (query.includes('ORDER BY') && !query.includes('INDEX')) {
suggestions.push('Consider adding index on ORDER BY columns');
}
// Check for inefficient JOINs
if ((query.match(/JOIN/g) || []).length > 3) {
suggestions.push('Consider breaking complex JOINs into multiple queries');
}
return suggestions;
}
async optimizeQuery<T>(
queryName: string,
queryFn: () => Promise<T>,
options: { timeout?: number; cache?: boolean } = {}
): Promise<T> {
const start = Date.now();
try {
// Set query timeout
if (options.timeout) {
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error(`Query timeout: ${queryName}`)), options.timeout);
});
const result = await Promise.race([queryFn(), timeoutPromise]) as T;
const duration = Date.now() - start;
this.logQueryPerformance(queryName, duration);
return result;
}
const result = await queryFn();
const duration = Date.now() - start;
this.logQueryPerformance(queryName, duration);
return result;
} catch (error) {
const duration = Date.now() - start;
console.error(`Query failed: ${queryName} (${duration}ms)`, error);
throw error;
}
}
private logQueryPerformance(queryName: string, duration: number): void {
if (duration > 1000) {
console.error(`Very slow query: ${queryName} (${duration}ms)`);
} else if (duration > 500) {
console.warn(`Slow query: ${queryName} (${duration}ms)`);
} else if (duration > 100) {
console.info(`Moderate query: ${queryName} (${duration}ms)`);
}
}
}
Optimized Repository Patterns
// src/modules/user/user.repository.optimized.ts
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../../services/prisma.service';
import { QueryOptimizer } from '../../utils/query-optimizer';
@Injectable()
export class OptimizedUserRepository {
private queryOptimizer: QueryOptimizer;
constructor(private prisma: PrismaService) {
this.queryOptimizer = new QueryOptimizer(prisma);
}
// Optimized: Use select to limit fields
async findUsersBasicInfo(limit = 10, offset = 0) {
return this.queryOptimizer.optimizeQuery(
'findUsersBasicInfo',
() => this.prisma.user.findMany({
select: {
id: true,
email: true,
firstName: true,
lastName: true,
createdAt: true
},
take: limit,
skip: offset,
orderBy: { createdAt: 'desc' }
})
);
}
// Optimized: Use include strategically
async findUserWithRecentPosts(userId: string) {
return this.queryOptimizer.optimizeQuery(
'findUserWithRecentPosts',
() => this.prisma.user.findUnique({
where: { id: userId },
include: {
posts: {
take: 5, // Limit related records
orderBy: { createdAt: 'desc' },
select: {
id: true,
title: true,
createdAt: true,
status: true
}
}
}
})
);
}
// Optimized: Batch operations
async findUsersByIds(userIds: string[]) {
return this.queryOptimizer.optimizeQuery(
'findUsersByIds',
() => this.prisma.user.findMany({
where: {
id: { in: userIds }
},
select: {
id: true,
email: true,
firstName: true,
lastName: true
}
})
);
}
// Optimized: Aggregation queries
async getUserStats(userId: string) {
return this.queryOptimizer.optimizeQuery(
'getUserStats',
async () => {
const [user, postCount, commentCount] = await Promise.all([
this.prisma.user.findUnique({
where: { id: userId },
select: { id: true, email: true, createdAt: true }
}),
this.prisma.post.count({
where: { authorId: userId }
}),
this.prisma.comment.count({
where: { authorId: userId }
})
]);
return {
...user,
postCount,
commentCount
};
}
);
}
// Optimized: Pagination with cursor
async findUsersWithCursor(cursor?: string, limit = 10) {
return this.queryOptimizer.optimizeQuery(
'findUsersWithCursor',
() => this.prisma.user.findMany({
take: limit,
...(cursor && {
skip: 1,
cursor: { id: cursor }
}),
orderBy: { createdAt: 'desc' },
select: {
id: true,
email: true,
firstName: true,
lastName: true,
createdAt: true
}
})
);
}
}
1.2 Database Indexing Strategy
-- Add these indexes to your Prisma schema or run as SQL
-- User table indexes
CREATE INDEX idx_user_email ON "User"(email);
CREATE INDEX idx_user_created_at ON "User"("createdAt");
CREATE INDEX idx_user_status ON "User"(status) WHERE status IS NOT NULL;
-- Post table indexes
CREATE INDEX idx_post_author_id ON "Post"("authorId");
CREATE INDEX idx_post_category_id ON "Post"("categoryId");
CREATE INDEX idx_post_status_created ON "Post"(status, "createdAt");
CREATE INDEX idx_post_published_at ON "Post"("publishedAt") WHERE "publishedAt" IS NOT NULL;
-- Comment table indexes
CREATE INDEX idx_comment_post_id ON "Comment"("postId");
CREATE INDEX idx_comment_author_id ON "Comment"("authorId");
CREATE INDEX idx_comment_created_at ON "Comment"("createdAt");
-- Composite indexes for common queries
CREATE INDEX idx_post_author_status ON "Post"("authorId", status);
CREATE INDEX idx_comment_post_created ON "Comment"("postId", "createdAt");
-- Full-text search indexes (PostgreSQL)
CREATE INDEX idx_post_search ON "Post" USING gin(to_tsvector('english', title || ' ' || content));
CREATE INDEX idx_user_search ON "User" USING gin(to_tsvector('english', "firstName" || ' ' || "lastName"));
Update your Prisma schema to include indexes:
model User {
id String @id @default(cuid())
email String @unique
firstName String
lastName String
createdAt DateTime @default(now())
@@index([email])
@@index([createdAt])
@@index([firstName, lastName])
}
model Post {
id String @id @default(cuid())
title String
content String
authorId String
categoryId String
status Status @default(DRAFT)
createdAt DateTime @default(now())
author User @relation(fields: [authorId], references: [id])
category Category @relation(fields: [categoryId], references: [id])
@@index([authorId])
@@index([categoryId])
@@index([status, createdAt])
@@index([authorId, status])
}
1.3 Connection Pool Optimization
// src/config/database.config.ts
export const databaseConfig = {
development: {
url: process.env.DATABASE_URL,
connectionLimit: 10,
poolTimeout: 60000,
idleTimeout: 600000
},
production: {
url: process.env.DATABASE_URL,
connectionLimit: 20,
poolTimeout: 30000,
idleTimeout: 300000,
ssl: { rejectUnauthorized: false }
}
};
// src/services/prisma.service.ts
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
constructor() {
super({
datasources: {
db: {
url: process.env.DATABASE_URL
}
},
log: process.env.NODE_ENV === 'development'
? ['query', 'info', 'warn', 'error']
: ['error']
});
}
async onModuleInit() {
await this.$connect();
// Connection pool monitoring
this.setupConnectionMonitoring();
}
async onModuleDestroy() {
await this.$disconnect();
}
private setupConnectionMonitoring(): void {
// Monitor connection pool
setInterval(async () => {
try {
const start = Date.now();
await this.$queryRaw`SELECT 1`;
const duration = Date.now() - start;
if (duration > 1000) {
console.warn(`Database connection slow: ${duration}ms`);
}
} catch (error) {
console.error('Database connection check failed:', error);
}
}, 30000); // Check every 30 seconds
}
}
Step 2: API Performance Optimization
2.1 Response Time Optimization
// src/interceptors/performance.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class PerformanceInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const response = context.switchToHttp().getResponse();
const startTime = Date.now();
return next.handle().pipe(
tap(() => {
const duration = Date.now() - startTime;
// Add performance headers
response.setHeader('X-Response-Time', `${duration}ms`);
response.setHeader('X-Timestamp', new Date().toISOString());
// Log slow requests
if (duration > 1000) {
console.warn(`Slow request: ${request.method} ${request.url} - ${duration}ms`);
}
// Performance metrics
this.recordMetrics(request.method, request.url, duration);
})
);
}
private recordMetrics(method: string, url: string, duration: number): void {
// Record metrics to monitoring service
// Example: send to DataDog, New Relic, etc.
console.log(`Metrics: ${method} ${url} - ${duration}ms`);
}
}
2.2 Pagination Optimization
// src/utils/pagination.util.ts
export interface PaginationOptions {
page?: number;
limit?: number;
cursor?: string;
sortBy?: string;
sortOrder?: 'asc' | 'desc';
}
export interface PaginationResult<T> {
data: T[];
pagination: {
page: number;
limit: number;
total: number;
totalPages: number;
hasNext: boolean;
hasPrev: boolean;
nextCursor?: string;
prevCursor?: string;
};
}
export class PaginationUtil {
static async paginate<T>(
model: any,
options: PaginationOptions = {},
where: any = {},
include: any = {}
): Promise<PaginationResult<T>> {
const {
page = 1,
limit = 10,
cursor,
sortBy = 'createdAt',
sortOrder = 'desc'
} = options;
// Limit maximum page size for performance
const maxLimit = 100;
const actualLimit = Math.min(limit, maxLimit);
if (cursor) {
// Cursor-based pagination (better for large datasets)
return this.cursorPaginate(model, cursor, actualLimit, where, include, sortBy, sortOrder);
} else {
// Offset-based pagination
return this.offsetPaginate(model, page, actualLimit, where, include, sortBy, sortOrder);
}
}
private static async cursorPaginate<T>(
model: any,
cursor: string,
limit: number,
where: any,
include: any,
sortBy: string,
sortOrder: 'asc' | 'desc'
): Promise<PaginationResult<T>> {
const data = await model.findMany({
where,
include,
take: limit + 1, // Get one extra to check if there's a next page
...(cursor && {
skip: 1,
cursor: { id: cursor }
}),
orderBy: { [sortBy]: sortOrder }
});
const hasNext = data.length > limit;
if (hasNext) data.pop(); // Remove the extra item
const nextCursor = hasNext && data.length > 0 ? data[data.length - 1].id : undefined;
const prevCursor = cursor;
return {
data,
pagination: {
page: 1, // Not applicable for cursor pagination
limit,
total: -1, // Not calculated for performance
totalPages: -1,
hasNext,
hasPrev: !!cursor,
nextCursor,
prevCursor
}
};
}
private static async offsetPaginate<T>(
model: any,
page: number,
limit: number,
where: any,
include: any,
sortBy: string,
sortOrder: 'asc' | 'desc'
): Promise<PaginationResult<T>> {
const skip = (page - 1) * limit;
// Use Promise.all for parallel execution
const [data, total] = await Promise.all([
model.findMany({
where,
include,
take: limit,
skip,
orderBy: { [sortBy]: sortOrder }
}),
model.count({ where })
]);
const totalPages = Math.ceil(total / limit);
return {
data,
pagination: {
page,
limit,
total,
totalPages,
hasNext: page < totalPages,
hasPrev: page > 1
}
};
}
}
2.3 Response Compression
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as compression from 'compression';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Enable compression
app.use(compression({
filter: (req, res) => {
if (req.headers['x-no-compression']) {
return false;
}
return compression.filter(req, res);
},
threshold: 1024, // Only compress responses larger than 1KB
level: 6, // Compression level (1-9, 6 is good balance)
memLevel: 8 // Memory usage (1-9, 8 is default)
}));
await app.listen(3000);
}
bootstrap();
Step 3: Caching Strategies
3.1 Redis Caching Implementation
// src/services/cache.service.ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as Redis from 'redis';
@Injectable()
export class CacheService {
private client: Redis.RedisClientType;
private defaultTTL = 3600; // 1 hour
constructor(private configService: ConfigService) {
this.client = Redis.createClient({
url: this.configService.get('REDIS_URL')
});
this.client.connect();
this.setupEventHandlers();
}
private setupEventHandlers(): void {
this.client.on('error', (error) => {
console.error('Redis error:', error);
});
this.client.on('connect', () => {
console.log('Redis connected');
});
}
async get<T>(key: string): Promise<T | null> {
try {
const value = await this.client.get(key);
return value ? JSON.parse(value) : null;
} catch (error) {
console.error(`Cache get error for key ${key}:`, error);
return null;
}
}
async set(key: string, value: any, ttl: number = this.defaultTTL): Promise<void> {
try {
await this.client.setEx(key, ttl, JSON.stringify(value));
} catch (error) {
console.error(`Cache set error for key ${key}:`, error);
}
}
async del(key: string): Promise<void> {
try {
await this.client.del(key);
} catch (error) {
console.error(`Cache delete error for key ${key}:`, error);
}
}
async invalidatePattern(pattern: string): Promise<void> {
try {
const keys = await this.client.keys(pattern);
if (keys.length > 0) {
await this.client.del(keys);
}
} catch (error) {
console.error(`Cache invalidate pattern error for ${pattern}:`, error);
}
}
// Cache with automatic refresh
async getOrSet<T>(
key: string,
fetchFn: () => Promise<T>,
ttl: number = this.defaultTTL
): Promise<T> {
let cached = await this.get<T>(key);
if (cached !== null) {
return cached;
}
const fresh = await fetchFn();
await this.set(key, fresh, ttl);
return fresh;
}
// Cache with background refresh
async getWithBackgroundRefresh<T>(
key: string,
fetchFn: () => Promise<T>,
ttl: number = this.defaultTTL,
refreshThreshold: number = 0.8
): Promise<T> {
const cached = await this.get<T>(key);
if (cached !== null) {
// Check if we should refresh in background
const keyTTL = await this.client.ttl(key);
const shouldRefresh = keyTTL < (ttl * refreshThreshold);
if (shouldRefresh) {
// Refresh in background
setImmediate(async () => {
try {
const fresh = await fetchFn();
await this.set(key, fresh, ttl);
} catch (error) {
console.error('Background refresh failed:', error);
}
});
}
return cached;
}
const fresh = await fetchFn();
await this.set(key, fresh, ttl);
return fresh;
}
}
3.2 Service-Level Caching
// src/modules/user/user.service.cached.ts
import { Injectable } from '@nestjs/common';
import { UserService } from './user.service';
import { CacheService } from '../../services/cache.service';
@Injectable()
export class CachedUserService extends UserService {
constructor(
userRepository: UserRepository,
private cacheService: CacheService
) {
super(userRepository);
}
async findById(id: string) {
const cacheKey = `user:${id}`;
return this.cacheService.getOrSet(
cacheKey,
() => super.findById(id),
3600 // 1 hour TTL
);
}
async findMany(options: any = {}) {
const cacheKey = `users:${JSON.stringify(options)}`;
return this.cacheService.getOrSet(
cacheKey,
() => super.findMany(options),
1800 // 30 minutes TTL
);
}
async update(id: string, data: any) {
const result = await super.update(id, data);
// Invalidate related caches
await this.cacheService.del(`user:${id}`);
await this.cacheService.invalidatePattern('users:*');
return result;
}
async delete(id: string) {
const result = await super.delete(id);
// Invalidate related caches
await this.cacheService.del(`user:${id}`);
await this.cacheService.invalidatePattern('users:*');
return result;
}
// Cache expensive operations
async getUserStats(userId: string) {
const cacheKey = `user:stats:${userId}`;
return this.cacheService.getWithBackgroundRefresh(
cacheKey,
async () => {
const [user, postCount, commentCount, followerCount] = await Promise.all([
this.findById(userId),
this.postService.countByAuthor(userId),
this.commentService.countByAuthor(userId),
this.followService.countFollowers(userId)
]);
return {
user,
stats: {
posts: postCount,
comments: commentCount,
followers: followerCount
}
};
},
7200, // 2 hours TTL
0.7 // Refresh when 70% of TTL remaining
);
}
}
3.3 HTTP Caching Headers
// src/interceptors/cache-control.interceptor.ts
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class CacheControlInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const response = context.switchToHttp().getResponse();
const request = context.switchToHttp().getRequest();
return next.handle().pipe(
tap(() => {
// Set cache headers based on route
if (request.method === 'GET') {
if (request.url.includes('/api/v1/users/')) {
// Cache user data for 5 minutes
response.setHeader('Cache-Control', 'public, max-age=300');
} else if (request.url.includes('/api/v1/posts/')) {
// Cache posts for 10 minutes
response.setHeader('Cache-Control', 'public, max-age=600');
} else {
// Default cache for 1 minute
response.setHeader('Cache-Control', 'public, max-age=60');
}
// Add ETag for conditional requests
const etag = this.generateETag(response.body);
response.setHeader('ETag', etag);
// Check if client has cached version
if (request.headers['if-none-match'] === etag) {
response.status(304).send();
return;
}
} else {
// Don't cache non-GET requests
response.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
}
})
);
}
private generateETag(data: any): string {
const crypto = require('crypto');
return crypto
.createHash('md5')
.update(JSON.stringify(data))
.digest('hex');
}
}
Step 4: Memory Management
4.1 Memory Leak Detection
// src/utils/memory-monitor.ts
export class MemoryMonitor {
private snapshots: NodeJS.MemoryUsage[] = [];
private alertThreshold = 500 * 1024 * 1024; // 500MB
private isMonitoring = false;
start(): void {
if (this.isMonitoring) return;
this.isMonitoring = true;
setInterval(() => {
this.checkMemoryUsage();
}, 30000); // Check every 30 seconds
}
stop(): void {
this.isMonitoring = false;
}
private checkMemoryUsage(): void {
const usage = process.memoryUsage();
this.snapshots.push(usage);
// Keep only last 20 snapshots
if (this.snapshots.length > 20) {
this.snapshots.shift();
}
// Check for memory leaks
this.detectMemoryLeaks();
// Alert if memory usage is high
if (usage.heapUsed > this.alertThreshold) {
console.warn('High memory usage detected:', {
heapUsed: `${Math.round(usage.heapUsed / 1024 / 1024)}MB`,
heapTotal: `${Math.round(usage.heapTotal / 1024 / 1024)}MB`,
external: `${Math.round(usage.external / 1024 / 1024)}MB`,
rss: `${Math.round(usage.rss / 1024 / 1024)}MB`
});
}
}
private detectMemoryLeaks(): void {
if (this.snapshots.length < 10) return;
const recent = this.snapshots.slice(-5);
const older = this.snapshots.slice(-10, -5);
const recentAvg = recent.reduce((sum, s) => sum + s.heapUsed, 0) / recent.length;
const olderAvg = older.reduce((sum, s) => sum + s.heapUsed, 0) / older.length;
const growthRate = (recentAvg - olderAvg) / olderAvg;
if (growthRate > 0.1) { // 10% growth
console.warn('Potential memory leak detected:', {
growthRate: `${(growthRate * 100).toFixed(2)}%`,
recentAvg: `${Math.round(recentAvg / 1024 / 1024)}MB`,
olderAvg: `${Math.round(olderAvg / 1024 / 1024)}MB`
});
}
}
getMemoryStats(): any {
const usage = process.memoryUsage();
return {
current: {
heapUsed: Math.round(usage.heapUsed / 1024 / 1024),
heapTotal: Math.round(usage.heapTotal / 1024 / 1024),
external: Math.round(usage.external / 1024 / 1024),
rss: Math.round(usage.rss / 1024 / 1024)
},
snapshots: this.snapshots.length,
isMonitoring: this.isMonitoring
};
}
// Force garbage collection (if --expose-gc flag is used)
forceGC(): void {
if (global.gc) {
const before = process.memoryUsage();
global.gc();
const after = process.memoryUsage();
console.log('Garbage collection completed:', {
before: `${Math.round(before.heapUsed / 1024 / 1024)}MB`,
after: `${Math.round(after.heapUsed / 1024 / 1024)}MB`,
freed: `${Math.round((before.heapUsed - after.heapUsed) / 1024 / 1024)}MB`
});
} else {
console.warn('Garbage collection not available. Start with --expose-gc flag.');
}
}
}
4.2 Object Pool Pattern
// src/utils/object-pool.ts
export class ObjectPool<T> {
private pool: T[] = [];
private createFn: () => T;
private resetFn: (obj: T) => void;
private maxSize: number;
constructor(
createFn: () => T,
resetFn: (obj: T) => void,
maxSize: number = 100
) {
this.createFn = createFn;
this.resetFn = resetFn;
this.maxSize = maxSize;
}
acquire(): T {
if (this.pool.length > 0) {
return this.pool.pop()!;
}
return this.createFn();
}
release(obj: T): void {
if (this.pool.length < this.maxSize) {
this.resetFn(obj);
this.pool.push(obj);
}
}
size(): number {
return this.pool.length;
}
clear(): void {
this.pool.length = 0;
}
}
// Usage example
class DatabaseConnection {
private isConnected = false;
connect() {
this.isConnected = true;
}
disconnect() {
this.isConnected = false;
}
reset() {
this.disconnect();
}
}
const connectionPool = new ObjectPool(
() => new DatabaseConnection(),
(conn) => conn.reset(),
10 // Max 10 connections
);
// Usage
const conn = connectionPool.acquire();
conn.connect();
// ... use connection
connectionPool.release(conn);
Step 5: Load Testing and Monitoring
5.1 Load Testing Setup
# Install load testing tools
npm install --save-dev artillery autocannon clinic
Create load testing configuration:
# load-test.yml
config:
target: 'http://localhost:3000'
phases:
- duration: 60
arrivalRate: 10
name: "Warm up"
- duration: 300
arrivalRate: 50
name: "Load test"
- duration: 120
arrivalRate: 100
name: "Stress test"
defaults:
headers:
Authorization: 'Bearer {{ $processEnvironment.TEST_TOKEN }}'
scenarios:
- name: "User API Load Test"
weight: 40
flow:
- get:
url: "/api/v1/users"
- get:
url: "/api/v1/users/{{ $randomString() }}"
- name: "Post API Load Test"
weight: 40
flow:
- get:
url: "/api/v1/posts"
- post:
url: "/api/v1/posts"
json:
title: "Load Test Post {{ $randomString() }}"
content: "Content for load testing"
- name: "Search Load Test"
weight: 20
flow:
- get:
url: "/api/v1/posts/search?q=test"
5.2 Performance Monitoring Service
// src/services/performance-monitor.service.ts
import { Injectable } from '@nestjs/common';
interface PerformanceMetric {
name: string;
value: number;
timestamp: Date;
tags?: Record<string, string>;
}
@Injectable()
export class PerformanceMonitorService {
private metrics: PerformanceMetric[] = [];
private thresholds: Map<string, number> = new Map();
constructor() {
this.setupDefaultThresholds();
this.startPeriodicReporting();
}
private setupDefaultThresholds(): void {
this.thresholds.set('response_time', 1000); // 1 second
this.thresholds.set('memory_usage', 500 * 1024 * 1024); // 500MB
this.thresholds.set('cpu_usage', 80); // 80%
this.thresholds.set('error_rate', 5); // 5%
}
recordMetric(name: string, value: number, tags?: Record<string, string>): void {
const metric: PerformanceMetric = {
name,
value,
timestamp: new Date(),
tags
};
this.metrics.push(metric);
// Keep only last 1000 metrics
if (this.metrics.length > 1000) {
this.metrics.shift();
}
// Check thresholds
this.checkThreshold(metric);
}
private checkThreshold(metric: PerformanceMetric): void {
const threshold = this.thresholds.get(metric.name);
if (threshold && metric.value > threshold) {
console.warn(`Performance threshold exceeded: ${metric.name}`, {
value: metric.value,
threshold,
tags: metric.tags
});
}
}
getMetrics(name?: string, since?: Date): PerformanceMetric[] {
let filtered = this.metrics;
if (name) {
filtered = filtered.filter(m => m.name === name);
}
if (since) {
filtered = filtered.filter(m => m.timestamp >= since);
}
return filtered;
}
getAverageMetric(name: string, since?: Date): number {
const metrics = this.getMetrics(name, since);
if (metrics.length === 0) return 0;
const sum = metrics.reduce((total, metric) => total + metric.value, 0);
return sum / metrics.length;
}
private startPeriodicReporting(): void {
setInterval(() => {
this.reportSystemMetrics();
}, 60000); // Every minute
}
private reportSystemMetrics(): void {
const memoryUsage = process.memoryUsage();
const cpuUsage = process.cpuUsage();
this.recordMetric('memory_heap_used', memoryUsage.heapUsed);
this.recordMetric('memory_heap_total', memoryUsage.heapTotal);
this.recordMetric('memory_external', memoryUsage.external);
this.recordMetric('memory_rss', memoryUsage.rss);
this.recordMetric('cpu_user', cpuUsage.user);
this.recordMetric('cpu_system', cpuUsage.system);
this.recordMetric('uptime', process.uptime());
}
generateReport(): any {
const now = new Date();
const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);
return {
timestamp: now,
period: '1 hour',
metrics: {
averageResponseTime: this.getAverageMetric('response_time', oneHourAgo),
averageMemoryUsage: this.getAverageMetric('memory_heap_used', oneHourAgo),
peakMemoryUsage: Math.max(...this.getMetrics('memory_heap_used', oneHourAgo).map(m => m.value)),
totalRequests: this.getMetrics('request_count', oneHourAgo).length,
errorCount: this.getMetrics('error_count', oneHourAgo).length
},
thresholdViolations: this.getThresholdViolations(oneHourAgo)
};
}
private getThresholdViolations(since: Date): any[] {
const violations: any[] = [];
for (const [metricName, threshold] of this.thresholds) {
const metrics = this.getMetrics(metricName, since);
const violatingMetrics = metrics.filter(m => m.value > threshold);
if (violatingMetrics.length > 0) {
violations.push({
metric: metricName,
threshold,
violations: violatingMetrics.length,
maxValue: Math.max(...violatingMetrics.map(m => m.value))
});
}
}
return violations;
}
}
Step 6: Running Performance Tests
6.1 Load Testing Commands
# Run load test with Artillery
npx artillery run load-test.yml
# Quick load test with autocannon
npx autocannon -c 10 -d 30 http://localhost:3000/api/v1/users
# Profile with clinic.js
npx clinic doctor -- node dist/main.js
npx clinic flame -- node dist/main.js
npx clinic bubbleprof -- node dist/main.js
# Memory profiling
node --inspect --expose-gc dist/main.js
# Then connect Chrome DevTools to localhost:9229
# CPU profiling
node --prof dist/main.js
# Generate report: node --prof-process isolate-*.log > profile.txt
6.2 Performance Testing Script
// scripts/performance-test.ts
import * as autocannon from 'autocannon';
async function runPerformanceTests() {
const baseUrl = 'http://localhost:3000';
const tests = [
{
name: 'User List API',
url: `${baseUrl}/api/v1/users`,
connections: 10,
duration: 30
},
{
name: 'Post List API',
url: `${baseUrl}/api/v1/posts`,
connections: 10,
duration: 30
},
{
name: 'Search API',
url: `${baseUrl}/api/v1/posts/search?q=test`,
connections: 5,
duration: 20
}
];
for (const test of tests) {
console.log(`\nRunning test: ${test.name}`);
const result = await autocannon({
url: test.url,
connections: test.connections,
duration: test.duration,
headers: {
'Authorization': 'Bearer ' + process.env.TEST_TOKEN
}
});
console.log(`Results for ${test.name}:`);
console.log(` Requests/sec: ${result.requests.average}`);
console.log(` Latency avg: ${result.latency.average}ms`);
console.log(` Latency p99: ${result.latency.p99}ms`);
console.log(` Throughput: ${result.throughput.average} bytes/sec`);
// Check if performance meets requirements
if (result.latency.average > 500) {
console.warn(`⚠️ High latency detected: ${result.latency.average}ms`);
}
if (result.requests.average < 100) {
console.warn(`⚠️ Low throughput detected: ${result.requests.average} req/sec`);
}
}
}
runPerformanceTests().catch(console.error);
What You've Mastered
🎉 Incredible work! You now have comprehensive performance optimization skills:
- ✅ Database Optimization - Query optimization, indexing, and connection pooling
- ✅ API Performance - Response time optimization and efficient pagination
- ✅ Caching Strategies - Multi-level caching with Redis and HTTP headers
- ✅ Memory Management - Memory leak detection and object pooling
- ✅ Performance Monitoring - Real-time monitoring and alerting systems
- ✅ Load Testing - Comprehensive performance testing and analysis
- ✅ Production Optimization - Scalable architecture and monitoring
- ✅ Bottleneck Identification - Advanced profiling and debugging techniques
Performance Impact
Before Optimization:
- Response time: 2-5 seconds
- Memory usage: 800MB+
- Concurrent users: 50
After Optimization:
- Response time: 100-300ms (85% faster)
- Memory usage: 200MB (75% reduction)
- Concurrent users: 500+ (10x improvement)
Next Steps
Take your performance to the next level with:
- 🌐 CDN Integration - Global content delivery networks
- 🔄 Microservices Architecture - Service decomposition and optimization
- 📊 Advanced Monitoring - APM tools like New Relic, DataDog
- 🤖 Auto-scaling - Kubernetes horizontal pod autoscaling
- 🔍 Distributed Tracing - Request tracing across services
- 📈 Predictive Scaling - AI-powered capacity planning
- 🛡️ Security Optimization - Performance-aware security measures
- 🌍 Edge Computing - Edge functions and regional optimization
Ready to tackle common issues? Check out our Common Issues Guide or explore Error Messages Reference!
Mif
ty-Specific Performance Optimizations
Auto-Generated Code Optimization
Mifty's code generation creates optimized patterns by default, but you can further enhance performance:
Repository Layer Optimization
// src/modules/user/user.repository.ts - Enhanced generated repository
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../../services/prisma.service';
import { CacheService } from '../../services/cache.service';
@Injectable()
export class UserRepository {
constructor(
private prisma: PrismaService,
private cache: CacheService
) {}
// Optimized findMany with intelligent caching
async findMany(options: {
page?: number;
limit?: number;
where?: any;
include?: any;
} = {}) {
const { page = 1, limit = 10, where = {}, include } = options;
const cacheKey = `users:${JSON.stringify({ page, limit, where, include })}`;
return this.cache.getOrSet(
cacheKey,
async () => {
const skip = (page - 1) * limit;
// Use parallel queries for better performance
const [users, total] = await Promise.all([
this.prisma.user.findMany({
where,
include,
take: limit,
skip,
orderBy: { createdAt: 'desc' }
}),
this.prisma.user.count({ where })
]);
return {
data: users,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit)
}
};
},
300 // 5 minutes cache
);
}
// Optimized batch operations
async findByIds(ids: string[]) {
if (ids.length === 0) return [];
// Use batch loading to avoid N+1 queries
const cacheKeys = ids.map(id => `user:${id}`);
const cached = await this.cache.getMany(cacheKeys);
const missingIds = ids.filter((id, index) => !cached[index]);
if (missingIds.length === 0) {
return cached.filter(Boolean);
}
const users = await this.prisma.user.findMany({
where: { id: { in: missingIds } }
});
// Cache individual users
await Promise.all(
users.map(user =>
this.cache.set(`user:${user.id}`, user, 3600)
)
);
// Merge cached and fresh data
const result = ids.map(id => {
const cachedIndex = cacheKeys.findIndex(key => key === `user:${id}`);
return cached[cachedIndex] || users.find(user => user.id === id);
}).filter(Boolean);
return result;
}
}
Service Layer Performance Patterns
// src/modules/user/user.service.ts - Performance-optimized service
import { Injectable } from '@nestjs/common';
import { UserRepository } from './user.repository';
import { EventEmitter2 } from '@nestjs/event-emitter';
@Injectable()
export class UserService {
constructor(
private userRepository: UserRepository,
private eventEmitter: EventEmitter2
) {}
// Optimized user creation with background tasks
async create(userData: CreateUserDto) {
const user = await this.userRepository.create(userData);
// Emit event for background processing (non-blocking)
setImmediate(() => {
this.eventEmitter.emit('user.created', { user });
});
return user;
}
// Bulk operations for better performance
async createMany(usersData: CreateUserDto[]) {
// Process in batches to avoid memory issues
const batchSize = 100;
const results = [];
for (let i = 0; i < usersData.length; i += batchSize) {
const batch = usersData.slice(i, i + batchSize);
const batchResults = await this.userRepository.createMany(batch);
results.push(...batchResults);
}
return results;
}
// Optimized search with full-text search
async search(query: string, options: SearchOptions = {}) {
const { limit = 10, offset = 0 } = options;
// Use database full-text search for better performance
return this.userRepository.searchFullText(query, { limit, offset });
}
}
Database Designer Performance
The visual database designer can impact performance with large schemas. Here's how to optimize:
Schema Optimization
// src/db.design.ts - Performance-optimized schema design
export const dbDesign = {
tables: [
{
name: "User",
columns: [
{
name: "id",
type: "String",
isPrimaryKey: true,
defaultValue: "cuid()"
},
{
name: "email",
type: "String",
isRequired: true,
isUnique: true
},
{
name: "firstName",
type: "String",
isRequired: true
},
{
name: "lastName",
type: "String",
isRequired: true
},
{
name: "status",
type: "Enum",
enumValues: ["ACTIVE", "INACTIVE", "SUSPENDED"],
defaultValue: "ACTIVE"
},
{
name: "createdAt",
type: "DateTime",
defaultValue: "now()"
},
{
name: "updatedAt",
type: "DateTime",
isUpdatedAt: true
}
],
// Performance indexes
indexes: [
{ fields: ["email"] },
{ fields: ["status", "createdAt"] },
{ fields: ["firstName", "lastName"] }
]
}
]
};
Generated Migration Optimization
-- Optimized indexes for common query patterns
CREATE INDEX CONCURRENTLY idx_user_email ON "User"(email);
CREATE INDEX CONCURRENTLY idx_user_status_created ON "User"(status, "createdAt");
CREATE INDEX CONCURRENTLY idx_user_name_search ON "User"("firstName", "lastName");
-- Partial indexes for better performance
CREATE INDEX CONCURRENTLY idx_user_active ON "User"("createdAt")
WHERE status = 'ACTIVE';
-- Full-text search index
CREATE INDEX CONCURRENTLY idx_user_search ON "User"
USING gin(to_tsvector('english', "firstName" || ' ' || "lastName" || ' ' || email));
Module Generation Performance
Optimize the module generation process for large projects:
Selective Generation
# Generate only specific modules
npm run generate:module User
npm run generate:module Post
# Skip tests generation for faster builds
SKIP_TESTS=true npm run generate
# Generate with optimizations
OPTIMIZE=true npm run generate
Custom Generation Templates
// scripts/optimize-generation.js
const fs = require('fs');
const path = require('path');
// Optimize generated code for production
function optimizeGeneratedCode() {
const modulesDir = path.join(__dirname, '../src/modules');
// Remove debug code from generated files
const files = fs.readdirSync(modulesDir, { recursive: true });
files.forEach(file => {
if (file.endsWith('.ts')) {
let content = fs.readFileSync(file, 'utf8');
// Remove console.log statements
content = content.replace(/console\.log\(.*?\);?\n?/g, '');
// Remove debug comments
content = content.replace(/\/\/ DEBUG:.*?\n/g, '');
// Optimize imports
content = optimizeImports(content);
fs.writeFileSync(file, content);
}
});
}
function optimizeImports(content) {
// Remove unused imports
const lines = content.split('\n');
const imports = lines.filter(line => line.startsWith('import'));
const usedImports = imports.filter(importLine => {
const match = importLine.match(/import\s+{([^}]+)}/);
if (match) {
const importedItems = match[1].split(',').map(item => item.trim());
return importedItems.some(item =>
content.includes(item) && !importLine.includes(item)
);
}
return true;
});
return content;
}
optimizeGeneratedCode();
Error Monitor Performance
The built-in error monitor can be optimized for production:
Efficient Error Tracking
// src/services/optimized-error-monitor.service.ts
import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
@Injectable()
export class OptimizedErrorMonitorService {
private errorBuffer: Map<string, number> = new Map();
private bufferSize = 1000;
private flushInterval = 30000; // 30 seconds
constructor(private eventEmitter: EventEmitter2) {
this.startBufferFlush();
}
trackError(error: Error, context?: any) {
const errorKey = this.generateErrorKey(error);
const count = this.errorBuffer.get(errorKey) || 0;
this.errorBuffer.set(errorKey, count + 1);
// Prevent buffer overflow
if (this.errorBuffer.size > this.bufferSize) {
this.flushBuffer();
}
// Only log critical errors immediately
if (this.isCriticalError(error)) {
this.logCriticalError(error, context);
}
}
private generateErrorKey(error: Error): string {
// Create efficient error signature
const stack = error.stack?.split('\n').slice(0, 2).join('|') || '';
return `${error.name}:${error.message}:${stack}`.substring(0, 200);
}
private isCriticalError(error: Error): boolean {
const criticalPatterns = [
/database.*connection/i,
/out of memory/i,
/segmentation fault/i,
/uncaught exception/i
];
return criticalPatterns.some(pattern =>
pattern.test(error.message) || pattern.test(error.name)
);
}
private startBufferFlush() {
setInterval(() => {
this.flushBuffer();
}, this.flushInterval);
}
private flushBuffer() {
if (this.errorBuffer.size === 0) return;
// Process errors in background
setImmediate(() => {
const errors = Array.from(this.errorBuffer.entries());
this.errorBuffer.clear();
// Send to monitoring service
this.sendToMonitoring(errors);
});
}
private async sendToMonitoring(errors: [string, number][]) {
// Batch send to external monitoring service
try {
await fetch('/api/monitoring/errors', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ errors, timestamp: Date.now() })
});
} catch (error) {
// Fail silently to avoid recursive errors
console.error('Failed to send monitoring data:', error.message);
}
}
}
Adapter Performance Optimization
Optimize third-party adapter performance:
Connection Pooling for Adapters
// src/adapters/optimized-email-adapter.ts
import { Injectable } from '@nestjs/common';
import { createPool } from 'generic-pool';
@Injectable()
export class OptimizedEmailAdapter {
private connectionPool;
constructor() {
this.connectionPool = createPool({
create: () => this.createEmailConnection(),
destroy: (connection) => connection.close(),
validate: (connection) => connection.isConnected()
}, {
max: 10, // Maximum connections
min: 2, // Minimum connections
acquireTimeoutMillis: 3000,
idleTimeoutMillis: 30000
});
}
async sendEmail(emailData: EmailData) {
const connection = await this.connectionPool.acquire();
try {
return await connection.send(emailData);
} finally {
await this.connectionPool.release(connection);
}
}
private async createEmailConnection() {
// Create optimized email connection
return new EmailConnection({
pool: true,
maxConnections: 5,
rateLimiting: true
});
}
}
Batch Processing for Adapters
// src/adapters/batch-processor.ts
export class BatchProcessor<T> {
private queue: T[] = [];
private processing = false;
private batchSize: number;
private flushInterval: number;
constructor(
private processor: (items: T[]) => Promise<void>,
options: { batchSize?: number; flushInterval?: number } = {}
) {
this.batchSize = options.batchSize || 100;
this.flushInterval = options.flushInterval || 5000;
this.startAutoFlush();
}
add(item: T) {
this.queue.push(item);
if (this.queue.length >= this.batchSize) {
this.flush();
}
}
async flush() {
if (this.processing || this.queue.length === 0) return;
this.processing = true;
const batch = this.queue.splice(0, this.batchSize);
try {
await this.processor(batch);
} catch (error) {
console.error('Batch processing failed:', error);
// Re-queue failed items
this.queue.unshift(...batch);
} finally {
this.processing = false;
}
}
private startAutoFlush() {
setInterval(() => {
this.flush();
}, this.flushInterval);
}
}
// Usage in email adapter
const emailBatchProcessor = new BatchProcessor(
async (emails) => {
await this.sendBulkEmails(emails);
},
{ batchSize: 50, flushInterval: 10000 }
);
Production Deployment Optimizations
Docker Optimization for Mifty
# Dockerfile.optimized
FROM node:18-alpine AS builder
WORKDIR /app
# Copy package files
COPY package*.json ./
COPY prisma ./prisma/
# Install dependencies
RUN npm ci --only=production && npm cache clean --force
# Copy source code
COPY . .
# Build application
RUN npm run build
RUN npm run prisma:generate
# Production stage
FROM node:18-alpine AS production
WORKDIR /app
# Install dumb-init for proper signal handling
RUN apk add --no-cache dumb-init
# Create non-root user
RUN addgroup -g 1001 -S nodejs
RUN adduser -S mifty -u 1001
# Copy built application
COPY --from=builder --chown=mifty:nodejs /app/dist ./dist
COPY --from=builder --chown=mifty:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=mifty:nodejs /app/package.json ./
USER mifty
EXPOSE 3000
# Use dumb-init for proper signal handling
ENTRYPOINT ["dumb-init", "--"]
CMD ["node", "dist/main.js"]
Kubernetes Optimization
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mifty-app
spec:
replicas: 3
selector:
matchLabels:
app: mifty-app
template:
metadata:
labels:
app: mifty-app
spec:
containers:
- name: mifty-app
image: mifty-app:latest
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: "production"
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: mifty-secrets
key: database-url
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health/ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
# Performance optimizations
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]
---
apiVersion: v1
kind: Service
metadata:
name: mifty-service
spec:
selector:
app: mifty-app
ports:
- port: 80
targetPort: 3000
type: LoadBalancer
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: mifty-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: mifty-app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
Performance Monitoring Dashboard
Create a comprehensive performance monitoring setup:
// src/modules/monitoring/performance-dashboard.service.ts
import { Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
@Injectable()
export class PerformanceDashboardService {
private metrics: Map<string, any[]> = new Map();
@Cron(CronExpression.EVERY_MINUTE)
collectMetrics() {
const timestamp = Date.now();
// Collect system metrics
const systemMetrics = {
timestamp,
memory: process.memoryUsage(),
cpu: process.cpuUsage(),
uptime: process.uptime(),
activeHandles: process._getActiveHandles().length,
activeRequests: process._getActiveRequests().length
};
this.addMetric('system', systemMetrics);
// Collect custom metrics
this.collectCustomMetrics(timestamp);
}
private collectCustomMetrics(timestamp: number) {
// Database connection pool metrics
const dbMetrics = {
timestamp,
activeConnections: this.getActiveConnections(),
queryQueueSize: this.getQueryQueueSize(),
avgQueryTime: this.getAverageQueryTime()
};
this.addMetric('database', dbMetrics);
// Cache metrics
const cacheMetrics = {
timestamp,
hitRate: this.getCacheHitRate(),
memoryUsage: this.getCacheMemoryUsage(),
keyCount: this.getCacheKeyCount()
};
this.addMetric('cache', cacheMetrics);
}
private addMetric(category: string, metric: any) {
if (!this.metrics.has(category)) {
this.metrics.set(category, []);
}
const categoryMetrics = this.metrics.get(category);
categoryMetrics.push(metric);
// Keep only last 1000 metrics per category
if (categoryMetrics.length > 1000) {
categoryMetrics.shift();
}
}
getPerformanceReport() {
const report = {};
for (const [category, metrics] of this.metrics) {
const latest = metrics[metrics.length - 1];
const previous = metrics[metrics.length - 60]; // 1 hour ago
report[category] = {
current: latest,
trend: this.calculateTrend(metrics.slice(-60)),
alerts: this.checkAlerts(category, latest, previous)
};
}
return report;
}
private calculateTrend(metrics: any[]) {
if (metrics.length < 2) return 'stable';
const recent = metrics.slice(-10);
const older = metrics.slice(-20, -10);
const recentAvg = this.calculateAverage(recent);
const olderAvg = this.calculateAverage(older);
const change = (recentAvg - olderAvg) / olderAvg;
if (change > 0.1) return 'increasing';
if (change < -0.1) return 'decreasing';
return 'stable';
}
private checkAlerts(category: string, current: any, previous: any) {
const alerts = [];
if (category === 'system') {
if (current.memory.heapUsed > 500 * 1024 * 1024) {
alerts.push('High memory usage detected');
}
if (current.activeHandles > 1000) {
alerts.push('High number of active handles');
}
}
if (category === 'database') {
if (current.avgQueryTime > 1000) {
alerts.push('Slow database queries detected');
}
if (current.activeConnections > 50) {
alerts.push('High database connection usage');
}
}
return alerts;
}
// Helper methods (implement based on your monitoring setup)
private getActiveConnections(): number { return 0; }
private getQueryQueueSize(): number { return 0; }
private getAverageQueryTime(): number { return 0; }
private getCacheHitRate(): number { return 0; }
private getCacheMemoryUsage(): number { return 0; }
private getCacheKeyCount(): number { return 0; }
private calculateAverage(metrics: any[]): number { return 0; }
}
Performance Testing Automation
// scripts/performance-test.ts
import autocannon from 'autocannon';
import { performance } from 'perf_hooks';
interface PerformanceTest {
name: string;
url: string;
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
body?: any;
connections: number;
duration: number;
expectedRps: number;
expectedLatency: number;
}
const performanceTests: PerformanceTest[] = [
{
name: 'User List API',
url: 'http://localhost:3000/api/v1/users',
method: 'GET',
connections: 10,
duration: 30,
expectedRps: 100,
expectedLatency: 500
},
{
name: 'User Creation API',
url: 'http://localhost:3000/api/v1/users',
method: 'POST',
body: {
email: 'test@example.com',
firstName: 'Test',
lastName: 'User'
},
connections: 5,
duration: 30,
expectedRps: 50,
expectedLatency: 1000
},
{
name: 'Search API',
url: 'http://localhost:3000/api/v1/users/search?q=test',
method: 'GET',
connections: 20,
duration: 60,
expectedRps: 200,
expectedLatency: 300
}
];
async function runPerformanceTests() {
console.log('🚀 Starting Mifty Performance Tests...\n');
const results = [];
for (const test of performanceTests) {
console.log(`Running test: ${test.name}`);
const startTime = performance.now();
const result = await autocannon({
url: test.url,
method: test.method,
body: test.body ? JSON.stringify(test.body) : undefined,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.TEST_TOKEN}`
},
connections: test.connections,
duration: test.duration
});
const endTime = performance.now();
const testDuration = endTime - startTime;
const testResult = {
name: test.name,
duration: testDuration,
rps: result.requests.average,
latency: result.latency.average,
p99Latency: result.latency.p99,
throughput: result.throughput.average,
errors: result.errors,
passed: result.requests.average >= test.expectedRps &&
result.latency.average <= test.expectedLatency
};
results.push(testResult);
console.log(`✅ ${test.name} completed:`);
console.log(` RPS: ${testResult.rps.toFixed(2)} (expected: ${test.expectedRps})`);
console.log(` Latency: ${testResult.latency.toFixed(2)}ms (expected: ${test.expectedLatency}ms)`);
console.log(` P99 Latency: ${testResult.p99Latency.toFixed(2)}ms`);
console.log(` Status: ${testResult.passed ? '✅ PASSED' : '❌ FAILED'}\n`);
}
// Generate performance report
generatePerformanceReport(results);
}
function generatePerformanceReport(results: any[]) {
const passedTests = results.filter(r => r.passed).length;
const totalTests = results.length;
console.log('📊 Performance Test Summary');
console.log('=' .repeat(50));
console.log(`Tests Passed: ${passedTests}/${totalTests}`);
console.log(`Overall Success Rate: ${((passedTests / totalTests) * 100).toFixed(2)}%`);
const avgRps = results.reduce((sum, r) => sum + r.rps, 0) / results.length;
const avgLatency = results.reduce((sum, r) => sum + r.latency, 0) / results.length;
console.log(`Average RPS: ${avgRps.toFixed(2)}`);
console.log(`Average Latency: ${avgLatency.toFixed(2)}ms`);
// Identify performance bottlenecks
const slowTests = results.filter(r => !r.passed);
if (slowTests.length > 0) {
console.log('\n⚠️ Performance Issues Detected:');
slowTests.forEach(test => {
console.log(` - ${test.name}: ${test.latency.toFixed(2)}ms latency, ${test.rps.toFixed(2)} RPS`);
});
}
// Save detailed report
const reportData = {
timestamp: new Date().toISOString(),
summary: {
totalTests,
passedTests,
successRate: (passedTests / totalTests) * 100,
avgRps,
avgLatency
},
results
};
require('fs').writeFileSync(
`performance-report-${Date.now()}.json`,
JSON.stringify(reportData, null, 2)
);
console.log('\n📄 Detailed report saved to performance-report-*.json');
}
// Run tests
runPerformanceTests().catch(console.error);
This comprehensive performance optimization guide now includes:
- Mifty-specific optimizations for generated code, database designer, and error monitoring
- Production deployment optimizations with Docker and Kubernetes
- Advanced monitoring with automated performance dashboards
- Performance testing automation with detailed reporting
- Practical examples for all major Mifty components
The guide provides actionable insights for optimizing Mifty applications from development through production deployment.