Redis — ioredis, cache, TTL

~20 daq · Express · Ma'lumotlar Bazasi

Muammodan boshlaylik

Sizning API ingizda eng ko'p so'raladigan endpoint — GET /products. Har so'rovda ma'lumotlar bazasiga boriladi, 500 ms kutiladi, natija qaytariladi.

Sekundiga 1000 so'rov kelsa — ma'lumotlar bazasiga sekundiga 1000 ta so'rov. Ma'lumotlar bazasi zo'riqadi, javob sekinlashadi.

Lekin mahsulotlar ro'yxati har 5 daqiqada bir marta o'zgaradi. Nima uchun har so'rovda ma'lumotlar bazasiga borish kerak? Natijani bir joyda saqlab, keyingi so'rovlarda o'sha joydan olsa bo'lmaydimi?

Bu — caching (keshlash). Redis — eng mashhur cache tizimi.


Redis nima?

Redis — xotirada ishlaydigan ma'lumotlar ombori. Ma'lumotlar diskda emas, RAM da saqlanadi — shuning uchun juda tez. O'qish va yozish — mikrosekundlarda.

Hayotiy o'xshatma: siz kutubxonadan kitob oldingiz. Har safar kutubxonaga borish o'rniga — kitobni stolga qo'yasiz. Kerak bo'lganda stoldan olasiz — tez va qulay. Ish tugagach qaytarasiz. Redis — o'sha stol. Ma'lumotlar bazasi — kutubxona.

Redis faqat cache emas — session saqlash, real-time leaderboard, rate limiting uchun ham ishlatiladi. Lekin eng ko'p ishlatilishi — cache.

npm install ioredis

ioredis — eng yaxshi Node.js Redis kutubxonasi. Rasmiy redis paketidan tezroq va imkoniyatlari ko'proq.


Ulanish

// redis.js
const Redis = require('ioredis');

const redis = new Redis({
  host:     process.env.REDIS_HOST || 'localhost',
  port:     process.env.REDIS_PORT || 6379,
  password: process.env.REDIS_PASSWORD || undefined,
});

redis.on('connect', () => console.log('Redis ga ulandi'));
redis.on('error',   (err) => console.error('Redis xatosi:', err.message));

module.exports = redis;

Asosiy operatsiyalar — set va get

Redis — kalit-qiymat ombori. Xuddi JavaScript ob'ekti kabi: kalit bilan saqlanadi, kalit bilan olinadi. Lekin farqi — Redis alohida jarayon, server qayta ishga tushsa ham ma'lumotlar saqlanadi.

const redis = require('./redis');

// Saqlash:
await redis.set('user:1', JSON.stringify({ id: 1, name: 'Ali' }));

// Olish:
const data = await redis.get('user:1');
const user = JSON.parse(data); // { id: 1, name: 'Ali' }

// O'chirish:
await redis.del('user:1');

Redis faqat string saqlaydi — ob'ektlarni JSON.stringify bilan stringga, o'qiganda JSON.parse bilan ob'ektga aylantirish kerak.


TTL — vaqt cheklash

Cache ning eng muhim xususiyati — ma'lumot eskiradi. Mahsulotlar ro'yxati 5 daqiqadan keyin yangilanishi mumkin — cache ham 5 daqiqadan keyin o'chirilishi kerak.

TTL (Time To Live) — ma'lumot qancha vaqt saqlanishi. TTL o'tgach Redis uni o'zi o'chiradi. Siz o'chirishni o'ylamasangiz ham bo'ladi.

// 5 daqiqa (300 soniya) saqlash:
await redis.set('products', JSON.stringify(products), 'EX', 300);

TTL qo'ymaslik — keng tarqalgan xato. Qo'yilmasa ma'lumot abadiy saqlanadi, ma'lumotlar bazasida o'zgarsa ham cache eski qoladi.


Cache pattern — qanday ishlaydi

Cache ning ishlash mantiqini bir marta tushunib olsangiz — hamma joyda ishlatish oson. "Cache-aside" pattern — eng keng tarqalgan:

So'rov keldi
    ↓
Cache da bormi?
    ↓ ha              ↓ yo'q
Cache dan qaytarish   Ma'lumotlar bazasidan ol
(1-2 ms)                  ↓
                      Cache ga saqlash (TTL bilan)
                          ↓
                      Qaytarish (500 ms)
const redis = require('../redis');
const pool  = require('../db');

router.get('/products', async (req, res, next) => {
  try {
    const CACHE_KEY = 'products:all';

    // 1. Cache da bormi?
    const cached = await redis.get(CACHE_KEY);
    if (cached) {
      return res.json(JSON.parse(cached));
    }

    // 2. Yo'q — ma'lumotlar bazasidan ol
    const result   = await pool.query('SELECT * FROM products');
    const products = result.rows;

    // 3. Cache ga saqlash — 5 daqiqa
    await redis.set(CACHE_KEY, JSON.stringify(products), 'EX', 300);

    res.json(products);
  } catch (err) {
    next(err);
  }
});

Birinchi so'rov — ma'lumotlar bazasiga boradi, ~500 ms. Keyingi so'rovlar — Redis dan, ~1 ms. 500 baravar tez.


Cache invalidation — eskirgan cache ni o'chirish

Mahsulot qo'shildi yoki o'zgartirildi — cache eskirdi. Uni o'chirish kerak, aks holda foydalanuvchilar eski ma'lumot ko'radi.

Bu — cache invalidation. Dasturlashdagi eng qiyin muammolardan biri deyiladi — qachon cache ni o'chirish kerakligini bilish.

router.post('/products', async (req, res, next) => {
  try {
    const result = await pool.query(
      'INSERT INTO products (name, price) VALUES ($1, $2) RETURNING *',
      [req.body.name, req.body.price]
    );

    await redis.del('products:all'); // cache o'chirildi — eskirdi

    res.status(201).json(result.rows[0]);
  } catch (err) {
    next(err);
  }
});

Keyingi GET /products so'rovida cache yo'q — ma'lumotlar bazasidan yangi ma'lumot olinadi va cache yangilanadi.


Kalit nomlash qoidasi

Redis da kalit nomlash muhim — loyiha o'sganda tartib bo'lishi uchun. Keng tarqalgan qoida: resurs:id yoki resurs:filter.

'products:all'               ← barcha mahsulotlar
'products:1'                 ← 1-mahsulot
'users:1:orders'             ← 1-foydalanuvchining buyurtmalari
'products:category:phones'   ← telefon mahsulotlari

Tez-tez uchraydigan xatolar

JSON.parse(null) xatosi:

redis.get kalit yo'q bo'lsa null qaytaradi. JSON.parse(null) esa null qaytaradi — xato emas, lekin null.name kabi ishlatilsa xato bo'ladi. Avval tekshirish kerak:

const cached = await redis.get('key');
if (cached) return res.json(JSON.parse(cached));
// cached null bo'lsa — bu blok o'tkazib yuboriladi

TTL qo'ymaslik:

await redis.set('products', JSON.stringify(data));
// TTL yo'q — abadiy saqlanadi, ma'lumot yangilansa ham eski cache qoladi

Redis ishlamasa server ham ishlamay qolishi:

Redis — qo'shimcha tezlashtiruvchi. U ishlamasa ham asosiy funksionallik ishlashi kerak. Redis xatosini ushlab, ma'lumotlar bazasidan olish kerak:

let products;

try {
  const cached = await redis.get('products:all');
  if (cached) return res.json(JSON.parse(cached));
} catch {
  // Redis ishlamasa — davom etamiz, ma'lumotlar bazasidan olamiz
}

const result = await pool.query('SELECT * FROM products');
products = result.rows;

try {
  await redis.set('products:all', JSON.stringify(products), 'EX', 300);
} catch {
  // Cache ga yozib bo'lmasa ham — javob yuboramiz
}

res.json(products);

Xulosa

Redis — xotirada ishlaydigan tez cache tizimi. Ma'lumotlar bazasiga har so'rovda borish o'rniga — natija Redis da saqlanadi, keyingi so'rovlarda u yerdan olinadi.

TTL — ma'lumot qancha vaqt saqlanishi, o'tgach Redis o'zi o'chiradi. Cache invalidation — ma'lumot o'zgarganda cache o'chiriladi.

Cache-aside pattern: cache da bor → cache dan qaytarish. Yo'q → ma'lumotlar bazasidan olish → cache ga saqlash → qaytarish.


Keyingi dars

18-dars: Database Transaction — nima, qachon kerak, qanday yoziladi