Введение: зачем нужно кэширование в 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