Налаштування Redis для кешування веб-застосунку

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Налаштування Redis для кешування веб-застосунку
Середня
від 1 робочого дня до 3 робочих днів
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Налаштування Redis для кешування веб-додатку

Redis — це не просто кеш. Це структури даних у пам'яті: рядки, хеші, списки, множини, відсортовані множини, потоки, pub/sub. Правильний вибір структури часто дає чистіше рішення, ніж спроба зробити те ж саме в реляційній базі.

Встановлення Redis 7.x

# Ubuntu 22.04+
apt install -y lsb-release curl gpg
curl -fsSL https://packages.redis.io/gpg | gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" > /etc/apt/sources.list.d/redis.list
apt update && apt install -y redis

/etc/redis/redis.conf — ключові параметри

# Мережа
bind 127.0.0.1 ::1
port 6379
requirepass your_strong_password_here

# Пам'ять
maxmemory 2gb
maxmemory-policy allkeys-lru    # витіснити найменш часто використовувані ключі
maxmemory-samples 10

# Персистентність для кешу — лише AOF з мінімальним fsync
save ""                          # вимкнути RDB снімки
appendonly yes
appendfsync everysec             # fsync щосекунди
no-appendfsync-on-rewrite yes

# Продуктивність
tcp-keepalive 300
hz 20
lazyfree-lazy-eviction yes       # асинхронне витіснення
lazyfree-lazy-expire yes
io-threads 4                     # для Redis 6+ на багатоядерних серверах
io-threads-do-reads yes

Шаблони кешування

Cache-aside — найпоширеніший:

// cache.service.ts
import { Redis } from 'ioredis'

export class CacheService {
  constructor(private redis: Redis) {}

  async getOrSet<T>(
    key: string,
    ttlSeconds: number,
    factory: () => Promise<T>
  ): Promise<T> {
    const cached = await this.redis.get(key)
    if (cached !== null) {
      return JSON.parse(cached) as T
    }
    const value = await factory()
    await this.redis.setex(key, ttlSeconds, JSON.stringify(value))
    return value
  }

  async invalidate(pattern: string): Promise<void> {
    // Використовуємо SCAN, не KEYS — не блокує Redis
    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.unlink(...keys)  // асинхронне видалення
      }
    } while (cursor !== '0')
  }
}

// Використання
const product = await cache.getOrSet(
  `product:${id}`,
  300,  // 5 хвилин
  () => db.products.findOne({ id })
)

// Після оновлення
await cache.invalidate(`product:${id}`)
await cache.invalidate(`products:category:${product.categoryId}:*`)

Write-through — запис у кеш і БД синхронно:

async updateProduct(id: string, data: UpdateProductDto) {
  const updated = await db.products.update(id, data)
  await redis.setex(`product:${id}`, 600, JSON.stringify(updated))
  return updated
}

Сесії користувачів

// session.service.ts
const SESSION_TTL = 86400 * 7  // 7 днів

export class SessionService {
  constructor(private redis: Redis) {}

  async create(userId: string, metadata: SessionMeta): Promise<string> {
    const sessionId = crypto.randomUUID()
    const key = `session:${sessionId}`

    await this.redis.hset(key, {
      userId,
      createdAt: Date.now(),
      ip: metadata.ip,
      userAgent: metadata.userAgent
    })
    await this.redis.expire(key, SESSION_TTL)

    // Додати в список сесій користувача
    await this.redis.zadd(`user:${userId}:sessions`, Date.now(), sessionId)
    await this.redis.expire(`user:${userId}:sessions`, SESSION_TTL)

    return sessionId
  }

  async get(sessionId: string): Promise<SessionData | null> {
    const data = await this.redis.hgetall(`session:${sessionId}`)
    if (!Object.keys(data).length) return null
    await this.redis.expire(`session:${sessionId}`, SESSION_TTL)  // скользящее вікно
    return data as SessionData
  }

  async destroyAll(userId: string): Promise<void> {
    const sessionIds = await this.redis.zrange(`user:${userId}:sessions`, 0, -1)
    if (sessionIds.length) {
      const keys = sessionIds.map(id => `session:${id}`)
      await this.redis.unlink(...keys, `user:${userId}:sessions`)
    }
  }
}

Rate limiting з ковзаючим вікном

async function rateLimit(
  redis: Redis,
  key: string,
  limit: number,
  windowMs: number
): Promise<{ allowed: boolean; remaining: number; resetAt: number }> {
  const now = Date.now()
  const windowStart = now - windowMs

  const pipeline = redis.pipeline()
  pipeline.zremrangebyscore(key, '-inf', windowStart)
  pipeline.zadd(key, now, `${now}-${Math.random()}`)
  pipeline.zcard(key)
  pipeline.pexpire(key, windowMs)

  const results = await pipeline.exec()
  const count = results![2][1] as number

  return {
    allowed: count <= limit,
    remaining: Math.max(0, limit - count),
    resetAt: now + windowMs
  }
}

Pub/Sub для подій

// publisher.ts
async function publishOrderEvent(event: OrderEvent) {
  await redis.publish('orders:events', JSON.stringify(event))
}

// subscriber.ts
const subscriber = redis.duplicate()
await subscriber.subscribe('orders:events')

subscriber.on('message', (channel, message) => {
  const event = JSON.parse(message) as OrderEvent
  eventBus.emit(event.type, event.payload)
})

Моніторинг

# Поточне навантаження
redis-cli --latency-history -i 1
redis-cli info stats | grep -E "instantaneous_ops|rejected_connections|evicted_keys"

# Використання пам'яті
redis-cli info memory | grep -E "used_memory_human|mem_fragmentation_ratio"

# Повільні команди
redis-cli config set slowlog-log-slower-than 10000  # 10ms
redis-cli slowlog get 10

Sentinel для високої доступності

# sentinel.conf
sentinel monitor mymaster 10.0.0.1 6379 2
sentinel auth-pass mymaster your_password
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 10000
sentinel parallel-syncs mymaster 1

Терміни

Базове налаштування Redis з кешуванням та конфігурацією сесій для додатку: 1 день. Налаштування Sentinel з автоматичною відмовостійкістю: 1–2 дні. Redis Cluster для горизонтального масштабування з шардуванням: 2–3 дні.