← Назад к вопросам
Как реализовать кэширование в Node.js приложении?
2.2 Middle🔥 171 комментариев
#Node.js и JavaScript#Кэширование и производительность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Кэширование в Node.js приложении
Кэширование - это сохранение результатов дорогих операций для повторного использования. Правильное кэширование может снизить нагрузку на БД в 10-100 раз и уменьшить время ответа с сотен миллисекунд до единиц.
Уровни кэширования
Client Cache -> CDN -> Reverse Proxy -> App Cache -> DB Cache
(браузер) (CF) (nginx) (Redis) (pg buffer)
In-Memory кэш (в памяти процесса)
Самый быстрый, но не разделяется между инстансами:
import NodeCache from "node-cache";
const cache = new NodeCache({
stdTTL: 300,
checkperiod: 60,
maxKeys: 10000
});
async function getUser(id: string): Promise<User> {
const cacheKey = `user:${id}`;
const cached = cache.get<User>(cacheKey);
if (cached) return cached;
const user = await db.query("SELECT * FROM users WHERE id = $1", [id]);
cache.set(cacheKey, user, 600);
return user;
}
Redis кэш (распределенный)
Работает между инстансами, переживает перезапуски:
import Redis from "ioredis";
const redis = new Redis({
host: "localhost",
port: 6379,
maxRetriesPerRequest: 3
});
async function getUserCached(id: string): Promise<User> {
const cacheKey = `user:${id}`;
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);
const user = await userRepository.findById(id);
await redis.setex(cacheKey, 600, JSON.stringify(user));
return user;
}
Паттерн Cache-Aside (Lazy Loading)
Самый распространенный паттерн:
async function getCachedData<T>(
key: string,
fetcher: () => Promise<T>,
ttl: number = 300
): Promise<T> {
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
const data = await fetcher();
await redis.setex(key, ttl, JSON.stringify(data));
return data;
}
// Использование
const stats = await getCachedData(
"dashboard:stats",
() => analyticsService.calculateStats(),
60
);
Инвалидация кэша
1. TTL (Time-To-Live):
await redis.setex("key", 300, value); // автоудаление через 5 минут
2. Ручная инвалидация при изменении:
async function updateUser(id: string, data: UpdateUserDto) {
await userRepository.update(id, data);
await redis.del(`user:${id}`);
await redis.del("users:list");
}
3. Паттерн с тегами:
async function invalidateUserCache(userId: string) {
const keys = await redis.keys(`user:${userId}:*`);
if (keys.length > 0) {
await redis.del(...keys);
}
}
HTTP кэширование
app.get("/api/professions", async (req, res) => {
const professions = await getCachedData("professions", fetchProfessions, 3600);
res.set({
"Cache-Control": "public, max-age=3600",
"ETag": generateETag(professions)
});
res.json(professions);
});
// Conditional requests
app.get("/api/users/:id", async (req, res) => {
const user = await getUser(req.params.id);
const etag = generateETag(user);
if (req.headers["if-none-match"] === etag) {
return res.status(304).end();
}
res.set("ETag", etag);
res.json(user);
});
Защита от Cache Stampede
Когда кэш истекает и 1000 запросов одновременно идут в БД:
const locks = new Map<string, Promise<unknown>>();
async function getCachedWithLock<T>(
key: string,
fetcher: () => Promise<T>,
ttl: number
): Promise<T> {
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
if (!locks.has(key)) {
locks.set(key, fetcher().then(async (data) => {
await redis.setex(key, ttl, JSON.stringify(data));
locks.delete(key);
return data;
}));
}
return locks.get(key) as Promise<T>;
}
Когда НЕ кэшировать
- Данные, которые меняются при каждом запросе
- Персональные/конфиденциальные данные (без строгого контроля)
- Данные, где устаревание критично (финансовые операции)
- Очень маленькие и быстрые запросы (overhead кэша больше выгоды)