بهبود عملکرد Node.js با کش Redis
از پاسخ ۸۰۰ میلیثانیهای API تا ۱۲ میلیثانیه؛ راهنمای عملی کش
مسئله
یک endpoint که تحلیلهای کاربران را تجمیع میکرد، بهطور میانگین حدود ۸۰۰ میلیثانیه زمان میبرد. نه آنقدر کند که وضعیت بحرانی باشد، اما بهاندازهای کند که کاربران متوجه شوند. پس از پروفایلگیری، علت روشن شد: کوئریهای تکراری پایگاه داده برای اطلاعاتی که بهندرت تغییر میکردند.
چرا Redis
Redis یک مخزن داده در حافظه است؛ خواندن از آن در مقیاس میکروثانیه انجام میشود، در حالی که کوئری پایگاه داده معمولاً میلیثانیه زمان میبرد. برای دادهای که:
- بهندرت تغییر میکند (کمتر از یکبار در دقیقه)
- محاسبه آن پرهزینه است (چندین join در پایگاه داده)
- کاربران زیادی آن را درخواست میکنند
کش Redis تقریباً همیشه پاسخ مناسبی است.
پیادهسازی
import { createClient } from 'redis';
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();
async function getAnalytics(userId: string) {
const cacheKey = `analytics:${userId}`;
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
const data = await db.query(`
SELECT ... FROM analytics
WHERE user_id = $1
GROUP BY ...
`, [userId]);
await redis.setEx(cacheKey, 300, JSON.stringify(data));
return data;
}
بیاعتبارسازی کش
نقلقول معروف فیل کارلتون میگوید در علوم کامپیوتر تنها دو کار دشوار وجود دارد: بیاعتبارسازی کش و نامگذاری.
او اشتباه نمیکرد. در این کاربرد، بهجای بیاعتبارسازی صریح، انقضای مبتنی بر TTL پنجدقیقهای را انتخاب کردم. بدهبستان این دو راه:
- انقضای TTL: ساده و خودترمیم است، اما کاربر ممکن است تا پنج دقیقه داده قدیمی ببیند.
- بیاعتبارسازی صریح: داده قدیمی نداریم، اما پیچیدهتر است و باید پس از هر نوشتن کش را پاک کنیم.
async function updateAnalyticsEvent(userId: string, event: Event) {
await db.insert(event);
await redis.del(`analytics:${userId}`);
}
نتایج
| معیار | قبل | بعد |
|---|---|---|
| میانگین زمان پاسخ | 820ms | 12ms |
| زمان پاسخ P95 | 1,400ms | 18ms |
| کوئری پایگاه داده در دقیقه | 4,200 | 380 |
کاهش ۹۸ درصدی زمان پاسخ، بار پایگاه داده را نیز بهشکل محسوسی کم کرد؛ یک مزیت جانبی ارزشمند.
همهچیز را کش نکنید
فقط دادهای را کش کنید که قدیمی بودن آن قابل تحمل است. برای مثال داده مالی کاربر نباید بدون بررسی بسیار دقیق کش شود.
خطاهای بیاعتبارسازی کش میتوانند پنهان و جدی باشند. از دادهای شروع کنید که واقعاً لازم نیست لحظهای باشد.