Кэширование в TypeScript: Redis, Memcached и встроенные решения — полное руководство

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: зачем нужно кэширование в TypeScript?

Современные веб-приложения требуют высокой скорости отклика и минимальной нагрузки на базы данных. Кэширование — один из самых эффективных способов добиться этого. В экосистеме TypeScript (и Node.js) разработчику доступны три основных подхода: распределённые системы (Redis, Memcached) и встроенные решения (in-memory cache). В этой статье мы разберём каждый из них, приведём практические примеры на TypeScript и поможем выбрать оптимальный вариант для вашего проекта.

Кэширование позволяет хранить часто запрашиваемые данные в быстром хранилище, избегая повторных дорогостоящих операций (запросы к БД, вычисления, вызовы внешних API). Правильная стратегия кэширования может сократить время ответа сервера в 10-100 раз.

1. Встроенное кэширование в TypeScript (In-Memory)

Самый простой способ — хранить данные прямо в памяти процесса Node.js. Для этого не требуется внешних сервисов, что идеально для небольших приложений или прототипов.

1.1. Использование Map и WeakMap

Встроенные коллекции JavaScript — отличная основа для простого кэша. Пример реализации с TTL (Time To Live):

type CacheEntry<T> = {
  value: T;
  expiresAt: number;
};

class SimpleCache<T> {
  private store = new Map<string, CacheEntry<T>>();

  set(key: string, value: T, ttlMs: number = 60000): void {
    this.store.set(key, {
      value,
      expiresAt: Date.now() + ttlMs,
    });
  }

  get(key: string): T | undefined {
    const entry = this.store.get(key);
    if (!entry) return undefined;
    if (Date.now() > entry.expiresAt) {
      this.store.delete(key);
      return undefined;
    }
    return entry.value;
  }

  delete(key: string): boolean {
    return this.store.delete(key);
  }

  clear(): void {
    this.store.clear();
  }
}

// Использование
const userCache = new SimpleCache<object>();
userCache.set('user:1', { name: 'Алексей' }, 30000); // TTL 30 сек
console.log(userCache.get('user:1'));

1.2. Библиотека node-cache

Для production-решений лучше использовать проверенную библиотеку node-cache. Она предоставляет продвинутые возможности: автоматическую очистку, статистику и события.

import NodeCache from 'node-cache';

const myCache = new NodeCache({ stdTTL: 100, checkperiod: 120 });

// Сохраняем объект
const success = myCache.set('myKey', { data: 'value' });
if (success) console.log('Кэш сохранён');

// Читаем
const value = myCache.get('myKey');
console.log(value); // { data: 'value' }

// Получаем статистику
const stats = myCache.getStats();
console.log(`Хиты: ${stats.hits}, Промахи: ${stats.misses}`);

Плюсы: простота, нулевая задержка сети, не требует установки сервисов.
Минусы: кэш теряется при перезапуске приложения, не подходит для распределённых систем.

2. Кэширование с Redis в TypeScript

Redis — это самое популярное решение для кэширования в продакшене. Он работает как отдельный сервер, поддерживает сложные структуры данных и устойчив к перезапускам приложения.

2.1. Подключение ioredis

Библиотека ioredis — стандарт де-факто для работы с Redis из TypeScript. Она поддерживает кластеризацию, Pub/Sub и Pipeline.

import Redis from 'ioredis';

const redis = new Redis({
  host: 'localhost',
  port: 6379,
  retryStrategy: (times) => Math.min(times * 50, 2000),
});

// Проверка соединения
redis.on('connect', () => console.log('Подключено к Redis'));
redis.on('error', (err) => console.error('Ошибка Redis:', err));

2.2. Пример кэширования с TTL

async function getCachedData(key: string): Promise<string | null> {
  const cached = await redis.get(key);
  if (cached) {
    console.log('Данные из кэша');
    return cached;
  }

  // Симуляция дорогой операции (запрос к БД)
  const freshData = await fetchFromDatabase(key);
  await redis.setex(key, 3600, freshData); // TTL 1 час
  console.log('Данные сохранены в кэш');
  return freshData;
}

async function fetchFromDatabase(key: string): Promise<string> {
  // Имитация задержки БД
  return new Promise((resolve) =>
    setTimeout(() => resolve(`Данные для ключа ${key}`), 1000)
  );
}

// Использование
(async () => {
  const data = await getCachedData('user:profile:42');
  console.log(data);
})();

2.3. Инвалидация кэша

Одна из главных проблем кэширования — устаревшие данные. В Redis можно удалять ключи по шаблону:

// Удаление всех кэшей пользователя
async function invalidateUserCache(userId: string): Promise<void> {
  const pattern = `user:${userId}:*`;
  const stream = redis.scanStream({ match: pattern });

  stream.on('data', (keys: string[]) => {
    if (keys.length) {
      redis.del(...keys);
    }
  });

  stream.on('end', () => console.log('Кэш пользователя очищен'));
}

Плюсы Redis: высокая производительность, поддержка сложных типов (списки, множества, хэши), персистентность, репликация.
Минусы: требуется отдельный сервер, дополнительное сетевое взаимодействие.

3. Memcached в TypeScript

Memcached — это распределённая система кэширования, ориентированная исключительно на производительность. Она проще Redis, но не поддерживает сложные структуры данных и персистентность.

3.1. Библиотека memcached

import Memcached from 'memcached';

const memcached = new Memcached('localhost:11211', {
  retries: 10,
  retry: 10000,
  remove: true,
  failOverServers: ['localhost:11212'],
});

// Сохранение с TTL (в секундах)
memcached.set('user:1', { name: 'Мария', role: 'admin' }, 3600, (err) => {
  if (err) console.error('Ошибка сохранения:', err                

Блоги

Рекомендаций по книгам