ہماری Performance & Scalability سیریز کا حصہ
مکمل گائیڈ پڑھیںویب ایپلیکیشنز کے لیے کیشنگ پیٹرنز کو ریڈیس
ویب ایپلیکیشنز میں ڈیٹا بیس کے سوالات سب سے عام کارکردگی کی رکاوٹ ہیں۔ ایک ناقص انڈیکس شدہ استفسار جس میں 200ms کا وقت لگتا ہے، جسے 1,000 ہم آہنگ صارفین کے لیے ہر صفحہ لوڈ پر کال کیا جاتا ہے، 200 سیکنڈ ڈیٹا بیس CPU فی سیکنڈ پیدا کرتا ہے - ایک موت کا سرپل۔ Redis ایک تریاق ہے: ایک ذیلی ملی سیکنڈ ان میموری اسٹور جو پڑھنے کے بوجھ کو جذب کرتا ہے جو بصورت دیگر ایک جیسے ڈیٹا کے لیے آپ کے ڈیٹا بیس کو بار بار مارے گا۔
لیکن کیشنگ صرف "چیزوں کو ریڈیس میں ڈالنا" نہیں ہے۔ غلط پیٹرن باسی ڈیٹا کیڑے، گرجنے والی ریوڑ کی آفات، یا یادداشت کی بے حد ترقی کا سبب بنتا ہے۔ اس گائیڈ میں چار کینونیکل کیشنگ پیٹرن، کیش کو غلط کرنے کی حکمت عملی، بھگدڑ کی روک تھام، اور پروڈکشن NestJS کے نفاذ کا احاطہ کیا گیا ہے تاکہ آپ شروع سے ہی صحیح طریقے سے کیش کر سکیں۔
اہم ٹیک ویز
- Cache-side (سست لوڈنگ) سب سے محفوظ ڈیفالٹ ہے — صرف وہی کیش کریں جس کی اصل میں درخواست کی گئی ہے
- رائٹ تھرو کیش اور ڈیٹا بیس کو مطابقت پذیر رکھتا ہے لیکن لکھنے میں تاخیر کا اضافہ کرتا ہے - اکثر پڑھے جانے والے، کبھی کبھار لکھے جانے والے ڈیٹا کے لیے استعمال کریں
- کیش سٹیمپیڈ (گرجتا ہوا ریوڑ) ڈیٹا بیس کو تباہ کر دیتا ہے جب ایک مقبول کلید کی میعاد ختم ہو جاتی ہے — امکانی ابتدائی میعاد ختم ہونے یا میوٹیکس تالے کا استعمال کریں
- TTL کو ہر کلید پر سیٹ کیا جانا چاہیے — بے حد Redis گروتھ ایک ایسا واقعہ ہے جو ہونے کا انتظار کر رہا ہے۔
- کیشے کی غلط کاری مشکل ہے: مکمل طور پر باطل کرنے کی کوشش کرنے پر مختصر TTLs اور ایونٹ پر مبنی باطل کو ترجیح دیں
- کیشے کیز میں استفسار کے تمام جہتیں شامل ہونی چاہئیں:
contacts:{orgId}:{page}:{limit}:{search}:{sortField}:{sortDir}- بڑی تعداد میں باطل کرنے کے لیے Redis نام کی جگہوں اور کلیدی نمونوں کا استعمال کریں۔
- کیش ہٹ ریٹ مانیٹر کریں؛ 80% سے کم کا مطلب ہے کہ آپ کے TTLs بہت چھوٹے ہیں یا آپ کی کلیدی ساخت غلط ہے۔
NestJS میں Redis سیٹ اپ
pnpm add ioredis @nestjs-modules/ioredis
// src/modules/cache/cache.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { InjectRedis } from '@nestjs-modules/ioredis';
import Redis from 'ioredis';
@Injectable()
export class CacheService {
private readonly logger = new Logger(CacheService.name);
constructor(@InjectRedis() private readonly redis: Redis) {}
async get<T>(key: string): Promise<T | null> {
const value = await this.redis.get(key);
if (!value) return null;
return JSON.parse(value) as T;
}
async set<T>(key: string, value: T, ttlSeconds = 300): Promise<void> {
await this.redis.set(key, JSON.stringify(value), 'EX', ttlSeconds);
}
async del(key: string): Promise<void> {
await this.redis.del(key);
}
// Use SCAN instead of KEYS to avoid blocking Redis on large key spaces
async invalidatePattern(pattern: string): Promise<void> {
let cursor = '0';
do {
const [nextCursor, keys] = await this.redis.scan(
cursor,
'MATCH', pattern,
'COUNT', 100
);
cursor = nextCursor;
if (keys.length > 0) {
await this.redis.del(...keys);
}
} while (cursor !== '0');
}
async remember<T>(
key: string,
ttlSeconds: number,
factory: () => Promise<T>
): Promise<T> {
const cached = await this.get<T>(key);
if (cached !== null) return cached;
const value = await factory();
await this.set(key, value, ttlSeconds);
return value;
}
}
// src/modules/cache/cache.module.ts
import { Global, Module } from '@nestjs/common';
import { RedisModule } from '@nestjs-modules/ioredis';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { CacheService } from './cache.service';
@Global()
@Module({
imports: [
RedisModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
type: 'single',
options: {
host: config.get('REDIS_HOST', 'localhost'),
port: config.get<number>('REDIS_PORT', 6379),
password: config.get('REDIS_PASSWORD'),
db: config.get<number>('REDIS_DB', 0),
keyPrefix: 'ecosire:',
},
}),
}),
],
providers: [CacheService],
exports: [CacheService],
})
export class AppCacheModule {}
پیٹرن 1: Cache-Side (Lazy Loading)
سب سے عام پیٹرن. کیشے سے پڑھیں؛ مس ہونے پر، ڈیٹا بیس سے پڑھیں اور کیشے کو پاپولٹ کریں۔
// contacts.service.ts
async findAll(orgId: string, page: number, limit: number, search?: string) {
const cacheKey = `contacts:${orgId}:${page}:${limit}:${search ?? 'all'}`;
return this.cache.remember(cacheKey, 300, async () => {
const results = await this.db.query.contacts.findMany({
where: and(
eq(contacts.organizationId, orgId),
search
? or(
ilike(contacts.name, `%${search}%`),
ilike(contacts.email, `%${search}%`)
)
: undefined
),
limit,
offset: (page - 1) * limit,
orderBy: desc(contacts.createdAt),
});
return results;
});
}
** پیشہ **: سادہ؛ cache میں صرف وہی ہوتا ہے جس کی اصل میں درخواست کی جاتی ہے۔ کونس: پہلی درخواست ہمیشہ سست ہوتی ہے (کیشے کی کمی)؛ TTL کی میعاد ختم ہونے تک ڈیٹا باسی ہو سکتا ہے۔
کب باطل کرنا ہے: تخلیق/اپ ڈیٹ/حذف کرنے کے بعد، contacts:{orgId}:* سے مماثل تمام کلیدوں کو حذف کر دیں۔
پیٹرن 2: لکھنا
ہر تحریر پر ڈیٹا بیس کے ساتھ بیک وقت کیشے کو اپ ڈیٹ کریں۔ ٹریڈز پڑھنے میں مستقل مزاجی کے لیے لیٹنسی لکھتے ہیں۔
async update(orgId: string, contactId: string, dto: UpdateContactDto) {
// 1. Update the database
const [updated] = await this.db
.update(contacts)
.set({ ...dto, updatedAt: new Date() })
.where(
and(
eq(contacts.id, contactId),
eq(contacts.organizationId, orgId)
)
)
.returning();
// 2. Update the individual contact cache immediately (write-through)
const singleKey = `contact:${orgId}:${contactId}`;
await this.cache.set(singleKey, updated, 3600);
// 3. Invalidate list caches (they now contain stale order/counts)
await this.cache.invalidatePattern(`contacts:${orgId}:*`);
return updated;
}
کب استعمال کرنا ہے: کثرت سے پڑھے جانے والے، کبھی کبھار لکھے جانے والے ریکارڈ (صارف کے پروفائلز، سیٹنگز، پروڈکٹ کیٹلاگ)۔
پیٹرن 3: کیشے سٹیمپیڈ کی روک تھام
جب ایک مقبول کیش کلید کی میعاد ختم ہو جاتی ہے، تو سینکڑوں ہم آہنگ درخواستیں بیک وقت کیشے سے محروم ہو جاتی ہیں اور سبھی ڈیٹابیس سے ٹکرا جاتی ہیں - "تھنڈرنگ ہرڈ۔" ڈیٹا بیس اچانک بوجھ کے نیچے گر جاتا ہے۔
Mutex لاک پیٹرن
صرف ایک درخواست کیشے کو دوبارہ بناتی ہے۔ دوسرے انتظار کریں اور دوبارہ کوشش کریں:
// cache.service.ts — mutex-protected cache fetch
async getWithLock<T>(
key: string,
ttlSeconds: number,
factory: () => Promise<T>
): Promise<T> {
// Try cache first
const cached = await this.get<T>(key);
if (cached !== null) return cached;
const lockKey = `lock:${key}`;
const lockOwner = `${process.pid}-${Date.now()}`;
// Try to acquire lock: NX = only set if not exists, EX = expire in 30s
const acquired = await this.redis.set(lockKey, lockOwner, 'NX', 'EX', 30);
if (acquired === 'OK') {
try {
const value = await factory();
await this.set(key, value, ttlSeconds);
return value;
} finally {
// Atomic check-and-delete: only release if we still own the lock
// Uses Lua defined with defineCommand for atomicity
const currentOwner = await this.redis.get(lockKey);
if (currentOwner === lockOwner) {
await this.redis.del(lockKey);
}
}
} else {
// Another process is rebuilding — wait 100ms and retry
await new Promise<void>((resolve) => {
const timer = setTimeout(resolve, 100);
// Store timer reference so Node.js does not block on it
timer.unref?.();
});
return this.getWithLock(key, ttlSeconds, factory);
}
}
امکانی ابتدائی معیاد (XFetch)
کیشے کی میعاد ختم ہونے سے پہلے ممکنہ طور پر دوبارہ بنانا شروع کریں، دوبارہ گنتی کے بوجھ کو پھیلاتے ہوئے:
async getWithEarlyExpiration<T>(
key: string,
ttlSeconds: number,
factory: () => Promise<T>,
beta = 1 // Higher = more aggressive early recomputation
): Promise<T> {
const metaKey = `${key}:meta`;
const meta = await this.get<{ createdAt: number; ttl: number }>(metaKey);
const value = await this.get<T>(key);
if (value !== null && meta) {
const elapsed = Date.now() / 1000 - meta.createdAt;
const remaining = meta.ttl - elapsed;
// XFetch: trigger early recomputation probabilistically
const shouldRecompute = -Math.log(Math.random()) * beta > remaining;
if (!shouldRecompute) return value;
}
const newValue = await factory();
const now = Date.now() / 1000;
await this.set(key, newValue, ttlSeconds);
await this.set(metaKey, { createdAt: now, ttl: ttlSeconds }, ttlSeconds + 60);
return newValue;
}
پیٹرن 4: ٹیگ پر مبنی کیشے کی غلط کاری
کیش کیز کو منطقی ٹیگز کے ذریعے گروپ کریں اور ایک ہی وقت میں پورے ٹیگ کو باطل کریں:
// Tag-based invalidation using Redis sets
async setWithTags(
key: string,
value: unknown,
ttlSeconds: number,
tags: string[]
): Promise<void> {
await this.set(key, value, ttlSeconds);
// Add key to each tag's member set using a pipeline
const pipeline = this.redis.pipeline();
for (const tag of tags) {
pipeline.sadd(`tag:${tag}`, key);
pipeline.expire(`tag:${tag}`, ttlSeconds + 60);
}
await pipeline.exec();
}
async invalidateByTag(tag: string): Promise<void> {
const keys = await this.redis.smembers(`tag:${tag}`);
if (keys.length > 0) {
const pipeline = this.redis.pipeline();
for (const cacheKey of keys) {
pipeline.del(cacheKey);
}
pipeline.del(`tag:${tag}`);
await pipeline.exec();
}
}
// Usage
await this.setWithTags(
`contacts:${orgId}:page:1`,
contacts,
300,
[`org:${orgId}`, 'contacts']
);
// After any contact mutation in org_123:
await this.invalidateByTag(`org:${orgId}`);
سیشن اور اوتھ کیشنگ
Redis auth سیشن اسٹوریج کے لیے مثالی ہے — ہر درخواست پر ڈیٹا بیس کی تلاش کو ختم کریں:
// Cache user session data (role, permissions, orgId) for JWT validation
async cacheUserSession(userId: string, session: UserSession): Promise<void> {
const key = `session:${userId}`;
await this.redis.hset(key,
'id', session.id,
'email', session.email,
'role', session.role,
'organizationId', session.organizationId,
'tokenVersion', String(session.tokenVersion)
);
await this.redis.expire(key, 900); // 15 minutes — matches access token TTL
}
async getUserSession(userId: string): Promise<UserSession | null> {
const data = await this.redis.hgetall(`session:${userId}`);
if (!data || Object.keys(data).length === 0) return null;
return {
...data,
tokenVersion: parseInt(data.tokenVersion, 10),
} as UserSession;
}
async invalidateUserSession(userId: string): Promise<void> {
await this.redis.del(`session:${userId}`);
}
ڈیٹا کی قسم کے لحاظ سے TTL حکمت عملی
| ڈیٹا کی قسم | تجویز کردہ TTL | باطل کرنے کی حکمت عملی |
|---|---|---|
| صارف پروفائل | 15 منٹ | پروفائل اپ ڈیٹ پر |
| تنظیم کی ترتیبات | 1 گھنٹہ | ترتیبات کی تبدیلی پر |
| مصنوعات کی فہرست | 24 گھنٹے | پروڈکٹ تخلیق / اپ ڈیٹ پر |
| بلاگ پوسٹ کی فہرست | 10 منٹ | پوسٹ شائع کرنے پر |
| API شرح کی حد کاؤنٹر | فی ونڈو (60s) | خودکار ختم |
| توثیق سیشن | 15 منٹ | لاگ آؤٹ یا ٹوکن منسوخ ہونے پر |
| تلاش کے نتائج | 5 منٹ | صرف TTL |
| مجموعی میٹرکس | 1 منٹ | صرف TTL |
| ایک بار کے کوڈز (تصویر) | 60 سیکنڈ | استعمال یا ختم ہونے کے بعد |
| صفحہ بندی کی فہرست کا سوال | 5 منٹ | org میں کسی بھی تبدیلی پر |
کیشے کی کارکردگی کی نگرانی کرنا
// Add hit/miss tracking to CacheService
async get<T>(key: string): Promise<T | null> {
const value = await this.redis.get(key);
if (value) {
await this.redis.incr('metrics:cache_hits');
return JSON.parse(value) as T;
}
await this.redis.incr('metrics:cache_misses');
return null;
}
// Report hit rate every 5 minutes
@Cron('*/5 * * * *')
async reportCacheMetrics(): Promise<void> {
const [hits, misses] = await this.redis.mget(
'metrics:cache_hits',
'metrics:cache_misses'
);
const h = parseInt(hits ?? '0', 10);
const m = parseInt(misses ?? '0', 10);
const total = h + m;
const hitRate = total === 0 ? 1 : h / total;
if (hitRate < 0.8) {
this.logger.warn(`Low cache hit rate: ${(hitRate * 100).toFixed(1)}%`);
}
// Reset counters
await this.redis.set('metrics:cache_hits', '0');
await this.redis.set('metrics:cache_misses', '0');
}
redis-cli INFO stats کے ذریعے مانیٹر کرنے کے لیے کلیدی ریڈیس میٹرکس:
keyspace_hits/keyspace_misses— عالمی ہٹ ریٹ (ہدف 80% سے زیادہ)used_memory— بے حد ترقی پر نگاہ رکھیںevicted_keys— maxmemory پالیسی کے ذریعے نکالی گئی چابیاں (کیشے کو ایک طرف رکھنے کے لیے 0 کے قریب ہونا چاہیے)connected_clients- کنکشن پول کا استعمالinstantaneous_ops_per_sec- موجودہ تھرو پٹ
اکثر پوچھے گئے سوالات
کیشے کے لیے مجھے کونسی maxmemory پالیسی استعمال کرنی چاہیے؟
allkeys-lru استعمال کریں (تمام کلیدوں سے کم از کم حال ہی میں استعمال شدہ چابیاں نکالیں) یا volatile-lru (LRU کیز کو بے دخل کریں جن میں TTL سیٹ ہے)۔ خالص کیشے کے کام کے بوجھ کے لیے، allkeys-lru معیاری ہے — میموری بھر جانے پر Redis خود بخود کولڈ کیز کو نکال دیتا ہے۔ کبھی بھی کیشے کے لیے noeviction استعمال نہ کریں — یہ پرانے ڈیٹا کو نکالنے کے بجائے میموری بھر جانے پر غلطیاں لوٹاتا ہے۔
میں Redis میں حساس ڈیٹا کو ذخیرہ کرنے سے کیسے بچ سکتا ہوں؟
کبھی بھی خام پاس ورڈز، پرائیویٹ کیز، پیمنٹ کارڈ ڈیٹا، یا SSNs کو کیش نہ کریں۔ توثیق کے سیشنز کے لیے، کم سے کم ڈیٹا کیش کریں: userId، رول، OrganizationId، token Version — مکمل JWT پے لوڈز یا API اسناد نہیں۔ پروڈکشن میں Redis AUTH اور TLS کو فعال کریں۔ اگر آپ کی Redis مثال سے سمجھوتہ کیا گیا ہے، تو صرف میٹا ڈیٹا سامنے آتا ہے جب آپ اس نظم و ضبط کی پیروی کرتے ہیں۔
SCAN اور KEYS میں پیٹرن کو حذف کرنے کے لیے کیا فرق ہے؟
KEYS pattern ایک بلاک کرنے والا O(n) آپریشن ہے جو اسکیننگ کے دوران تمام Redis کمانڈز کو روکتا ہے — یہ بڑی کلیدی جگہوں پر سیکنڈوں کے ڈاؤن ٹائم کا سبب بن سکتا ہے۔ SCAN غیر مسدود ہے، کرسر کے ساتھ چھوٹے ٹکڑوں میں اعادہ کرتا ہے۔ پروڈکشن پیٹرن کو حذف کرنے کے لیے ہمیشہ SCAN استعمال کریں۔ ٹریڈ آف یہ ہے کہ SCAN تمام چابیاں واپس نہیں کر سکتا ہے اگر وہ سکین کے دوران شامل یا حذف کر دی جائیں — کیشے کی غلط کاری کے لیے قابل قبول ہے۔
کیا مجھے کیشنگ بمقابلہ ریٹ محدود بمقابلہ سیشنز کے لیے علیحدہ Redis مثال استعمال کرنی چاہیے؟
زیادہ تر ایپلی کیشنز کے لیے، مختلف کلیدی سابقوں کے ساتھ ایک Redis مثال ٹھیک ہے۔ الگ الگ مثالیں اس وقت معنی رکھتی ہیں جب: کیشے کو ریٹ کی حد اسٹور (کیشے: allkeys-lru، شرح کی حد: noeviction) سے مختلف maxmemory-policy کی ضرورت ہو، یا جب آپ کو آزاد اسکیلنگ اور فیل اوور کی ضرورت ہو۔ پیمانے پر، کام کے بوجھ کی قسم کے لیے الگ الگ کلسٹرز کے ساتھ Redis کلسٹر استعمال کریں۔
میں صفحہ بندی کی فہرست کے سوالات کے لیے کیشے کی غلط کاری کو کیسے ہینڈل کروں؟
صفحہ بندی کی فہرست کے کیشز مشکل ہیں - صفحہ 1 پر رابطہ شامل کرنا صفحہ 2+ پر موجود تمام رابطوں کو شفٹ کر دیتا ہے۔ عملی حل: مختصر TTLs (2-5 منٹ) استعمال کریں اور پیٹرن کی غلط کاری (contacts:{orgId}:*) کا استعمال کرتے ہوئے کسی بھی تحریر پر تنظیم کے لیے تمام صفحات کو باطل کریں۔ بھاری تحریری حجم والی بڑی تنظیموں کے لیے، کیشنگ صفحہ بندی کو مکمل طور پر چھوڑ دیں اور اس کے بجائے ڈیٹا بیس کی سطح کی اصلاح (مناسب اشاریہ جات، کورنگ اشاریہ جات) پر انحصار کریں۔
میں Vitest میں کیشے کے رویے کی جانچ کیسے کروں؟
vi.fn() کے ساتھ یونٹ ٹیسٹ میں CacheService کا مذاق اڑائیں ہٹ/مس منظرناموں کے لیے مناسب قدریں لوٹائیں۔ انضمام کے ٹیسٹ کے لیے، ایک حقیقی Redis مثال (Docker) استعمال کریں اور صاف شروع کرنے کے لیے beforeEach میں redis.flushdb() استعمال کریں۔ کیش ہٹ پاتھ (کوئی ڈیٹا بیس کال نہیں) اور کیش مس پاتھ (ڈیٹا بیس جسے + کیش پاپولڈ کہا جاتا ہے) دونوں کو واضح طور پر جانچیں۔
اگلے اقدامات
Redis کیشنگ ویب ایپلیکیشن میں اعلی ترین ROI کارکردگی کی سرمایہ کاری میں سے ایک ہے۔ 200ms ڈیٹا بیس استفسار اور 2ms Redis ہٹ کے درمیان فرق 100x ہے — پیمانے پر، جو براہ راست بنیادی ڈھانچے کی لاگت کی بچت اور صارف کے تجربے میں بہتری کا ترجمہ کرتا ہے۔
ECOSIRE ہر NestJS پروجیکٹ پر کیش-سائیڈ، رائٹ تھرو انیلیڈیشن، سٹیمپیڈ سے بچاؤ، اور کیش ہٹ ریٹ مانیٹرنگ کے ساتھ Redis کیشنگ کو لاگو کرتا ہے۔ ہماری بیک اینڈ انجینئرنگ سروسز کو دریافت کریں یہ جاننے کے لیے کہ ہم کس طرح پرفارمنٹ، قابل توسیع APIs کو ڈیزائن کرتے ہیں۔
تحریر
ECOSIRE Research and Development Team
ECOSIRE میں انٹرپرائز گریڈ ڈیجیٹل مصنوعات بنانا۔ Odoo انٹیگریشنز، ای کامرس آٹومیشن، اور AI سے چلنے والے کاروباری حل پر بصیرت شیئر کرنا۔
متعلقہ مضامین
API Rate Limiting: Patterns and Best Practices
Master API rate limiting with token bucket, sliding window, and fixed counter patterns. Protect your backend with NestJS throttler, Redis, and real-world configuration examples.
k6 Load Testing: Stress-Test Your APIs Before Launch
Master k6 load testing for Node.js APIs. Covers virtual user ramp-ups, thresholds, scenarios, HTTP/2, WebSocket testing, Grafana dashboards, and CI integration patterns.
NestJS 11 Enterprise API Patterns
Master NestJS 11 enterprise patterns: guards, interceptors, pipes, multi-tenancy, and production-ready API design for scalable backend systems.
Performance & Scalability سے مزید
k6 Load Testing: Stress-Test Your APIs Before Launch
Master k6 load testing for Node.js APIs. Covers virtual user ramp-ups, thresholds, scenarios, HTTP/2, WebSocket testing, Grafana dashboards, and CI integration patterns.
Nginx Production Configuration: SSL, Caching, and Security
Nginx production configuration guide: SSL termination, HTTP/2, caching headers, security headers, rate limiting, reverse proxy setup, and Cloudflare integration patterns.
Odoo Performance Tuning: PostgreSQL and Server Optimization
Expert guide to Odoo 19 performance tuning. Covers PostgreSQL configuration, indexing, query optimization, Nginx caching, and server sizing for enterprise deployments.
Odoo vs Acumatica: Cloud ERP for Growing Businesses
Odoo vs Acumatica compared for 2026: unique pricing models, scalability, manufacturing depth, and which cloud ERP fits your growth trajectory.
Testing and Monitoring AI Agents in Production
A complete guide to testing and monitoring AI agents in production environments. Covers evaluation frameworks, observability, drift detection, and incident response for OpenClaw deployments.
Compliance Monitoring Agents with OpenClaw
Deploy OpenClaw AI agents for continuous compliance monitoring. Automate regulatory checks, policy enforcement, audit trail generation, and compliance reporting.