همه نوشته‌ها

بهبود عملکرد Node.js با کش Redis

از پاسخ ۸۰۰ میلی‌ثانیه‌ای API تا ۱۲ میلی‌ثانیه؛ راهنمای عملی کش

تصویر جلد مقاله بهبود عملکرد Node.js با کش Redis

مسئله

یک 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}`);
}

نتایج

معیارقبلبعد
میانگین زمان پاسخ820ms12ms
زمان پاسخ P951,400ms18ms
کوئری پایگاه داده در دقیقه4,200380

کاهش ۹۸ درصدی زمان پاسخ، بار پایگاه داده را نیز به‌شکل محسوسی کم کرد؛ یک مزیت جانبی ارزشمند.

همه‌چیز را کش نکنید

فقط داده‌ای را کش کنید که قدیمی بودن آن قابل تحمل است. برای مثال داده مالی کاربر نباید بدون بررسی بسیار دقیق کش شود.

خطاهای بی‌اعتبارسازی کش می‌توانند پنهان و جدی باشند. از داده‌ای شروع کنید که واقعاً لازم نیست لحظه‌ای باشد.