Кэширование в TypeScript: Redis, node-cache и другие инструменты

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

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

Начать курс

Кэширование в TypeScript: Redis и другие инструменты

Кэширование — одна из ключевых стратегий оптимизации производительности веб-приложений. В экосистеме TypeScript и Node.js существует несколько мощных библиотек, позволяющих эффективно управлять кэшированием данных. В этой статье мы подробно разберём популярные инструменты, их применение и лучшие практики.

Зачем нужно кэширование?

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

Основные инструменты для кэширования в TypeScript

1. Redis через библиотеку ioredis

Redis — это высокопроизводительное in-memory хранилище данных, поддерживающее различные структуры данных. Для TypeScript лучшим выбором является ioredis — клиент с полной поддержкой типов и промисов.

import Redis from 'ioredis';

// Создаём клиент
const redis = new Redis({
  host: 'localhost',
  port: 6379,
});

// Функция для кэширования с TTL
async function cacheGet(key: string): Promise<string | null> {
  const data = await redis.get(key);
  return data;
}

async function cacheSet(key: string, value: string, ttl: number = 3600): Promise<void> {
  await redis.setex(key, ttl, value);
}

// Пример использования
async function getUserData(userId: string) {
  const cacheKey = `user:${userId}`;
  const cached = await cacheGet(cacheKey);
  if (cached) {
    return JSON.parse(cached);
  }
  // Эмуляция дорогого запроса
  const userData = { id: userId, name: 'John Doe' };
  await cacheSet(cacheKey, JSON.stringify(userData), 3600);
  return userData;
}

2. node-cache — in-memory кэш для небольших проектов

node-cache — простая in-memory библиотека, не требующая внешних зависимостей. Идеальна для прототипов и микросервисов с небольшим объёмом данных.

import NodeCache from 'node-cache';

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

// Сохраняем данные
myCache.set('key', { data: 'value' });

// Получаем данные
const value = myCache.get('key');
if (value) {
  console.log('Из кэша:', value);
} else {
  console.log('Кэш пуст, запрашиваем данные...');
}

// Удаление по ключу
myCache.del('key');

// Очистка всего кэша
myCache.flushAll();

3. async-cache-dedupe — для предотвращения дублирующих запросов

Эта библиотека решает проблему "cache stampede" (когда множество запросов одновременно пытаются заполнить кэш). Она автоматически дедуплицирует параллельные запросы к одному и тому же ключу.

import { createCache } from 'async-cache-dedupe';

const cache = createCache({
  ttl: 60, // время жизни в секундах
  stale: 10, // время, когда данные считаются устаревшими, но ещё могут быть отданы
});

// Оборачиваем асинхронную функцию
const getData = cache.define('getData', async (id: number) => {
  // Дорогой запрос
  const result = await fetch(`https://api.example.com/data/${id}`);
  return result.json();
});

// Используем
const result = await getData(42);

Стратегии инвалидации кэша

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

  • TTL (Time-To-Live) — данные автоматически удаляются через заданное время. Простой и надёжный метод.
  • Ленивая инвалидация — проверяем актуальность данных при каждом запросе (например, по версии).
  • Event-driven инвалидация — при изменении данных в БД отправляем событие для очистки соответствующего кэша.
  • Write-through — обновляем кэш одновременно с записью в основное хранилище.

Продвинутые паттерны с Redis

Кэширование с сериализацией и компрессией

import { compress, decompress } from 'lz-string';
import Redis from 'ioredis';

const redis = new Redis();

async function setCompressed(key: string, data: object, ttl: number) {
  const json = JSON.stringify(data);
  const compressed = compress(json);
  await redis.setex(key, ttl, compressed);
}

async function getCompressed(key: string): Promise<object | null> {
  const compressed = await redis.get(key);
  if (!compressed) return null;                    

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