Redis кэширование в TypeORM: Полное руководство
Узнайте, как настроить Redis как драйвер кэширования для TypeORM. Повышайте производительность приложений, уменьшая нагрузку на базу данных. Пошаговое руководство с примерами кода.
Как использовать Redis как драйвер для кэширования запросов в TypeORM?
Для использования Redis в качестве драйвера кэширования запросов в TypeORM необходимо установить соответствующие пакеты, настроить соединение с Redis и включить кэширование в конфигурации TypeORM. Redis значительно повышает производительность приложений, кэшируя результаты запросов к базе данных и уменьшая нагрузку на сервер.
Содержание
- Настройка окружения и установка зависимостей
- Конфигурация Redis соединения
- Настройка TypeORM с Redis кэшированием
- Примеры использования кэширования
- Оптимизация производительности
- Отладка и мониторинг
- Заключение
Настройка окружения и установка зависимостей
Для начала работы с Redis кэшированием в TypeORM необходимо установить несколько ключевых пакетов:
npm install redis typeorm reflect-metadata class-validator
# или для yarn
yarn add redis typeorm reflect-metadata class-validator
Основные зависимости включают:
- redis - клиент для работы с Redis сервером
- typeorm - основной ORM фреймворк
- reflect-metadata - для декораторов TypeScript
- class-validator - для валидации данных (опционально)
После установки зависимостей убедитесь, что у вас установлен Redis сервер. Для разработки можно использовать Docker:
docker run -d -p 6379:6379 redis:alpine
Проверьте, что Redis работает, выполнив команду в терминале:
redis-cli ping
Должен вернуться ответ “PONG”.
Конфигурация Redis соединения
Создайте файл конфигурации для Redis соединения. Обычно это делается в отдельном файле конфигурации (например, redis.config.ts):
// src/config/redis.config.ts
import { RedisOptions } from 'ioredis';
export const redisOptions: RedisOptions = {
host: process.env.REDIS_HOST || 'localhost',
port: parseInt(process.env.REDIS_PORT || '6379'),
password: process.env.REDIS_PASSWORD || undefined,
db: parseInt(process.env.REDIS_DB || '0'),
retryDelayOnFailover: 100,
maxRetriesPerRequest: 3,
lazyConnect: true,
enableReadyCheck: true,
keepAlive: true,
reconnectOnError: (err) => {
const targetError = 'READONLY';
if (err.message.includes(targetError)) {
return true;
}
return false;
},
};
Для работы с TypeScript создайте интерфейс конфигурации:
// src/types/redis.types.ts
export interface RedisCacheOptions {
host: string;
port: number;
password?: string;
db?: number;
keyPrefix?: string;
ttl?: number;
isGlobal?: boolean;
}
Настройка TypeORM с Redis кэшированием
Для настройки TypeORM с Redis кэшированием необходимо создать специальный кэш-провайдер. Создайте файл redis-cache.ts:
// src/cache/redis-cache.ts
import { Cache, CacheOptions } from 'typeorm';
import Redis from 'ioredis';
import { redisOptions } from '../config/redis.config';
export class RedisCache implements Cache {
private redis: Redis;
private options: CacheOptions;
constructor(options: CacheOptions) {
this.options = options;
this.redis = new Redis(redisOptions);
}
async get(key: string): Promise<any> {
const value = await this.redis.get(key);
return value ? JSON.parse(value) : null;
}
async set(key: string, value: any, seconds?: number): Promise<void> {
const ttl = seconds || this.options.ttl || 3600;
await this.redis.setex(key, ttl, JSON.stringify(value));
}
async remove(key: string): Promise<void> {
await this.redis.del(key);
}
async clear(): Promise<void> {
await this.redis.flushdb();
}
async disconnect(): Promise<void> {
await this.redis.quit();
}
}
Теперь настройте TypeORM для использования Redis кэша:
// src/config/typeorm.config.ts
import { DataSource } from 'typeorm';
import { RedisCache } from '../cache/redis-cache';
export const AppDataSource = new DataSource({
type: 'postgres', // или другой тип вашей базы данных
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '5432'),
username: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'password',
database: process.env.DB_NAME || 'mydb',
entities: [__dirname + '/../entities/**/*.entity{.ts,.js}'],
migrations: [__dirname + '/../migrations/**/*{.ts,.js}'],
subscribers: [__dirname + '/../subscribers/**/*{.ts,.js}'],
cache: {
type: 'redis',
options: {
cache: new RedisCache({}),
// дополнительные опции
duration: 60000, // время жизни кэша в миллисекундах
alwaysEnabled: true,
}
},
synchronize: process.env.NODE_ENV !== 'production',
logging: process.env.NODE_ENV === 'development',
});
Для использования в приложении:
// src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppDataSource } from './config/typeorm.config';
import { RedisCache } from './cache/redis-cache';
@Module({
imports: [
TypeOrmModule.forRoot({
...AppDataSource.options,
cache: {
type: 'redis',
options: {
cache: new RedisCache({}),
duration: 60000,
}
}
}),
],
})
export class AppModule {}
Примеры использования кэширования
Базовое кэширование запросов
Включите кэширование для конкретной сущности:
// src/entities/user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity('users')
@Cache({ type: 'redis', duration: 300000 }) // 5 минут
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column({ unique: true })
email: string;
@Column()
createdAt: Date;
}
Используйте кэширование в репозитории:
// src/services/user.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from '../entities/user.entity';
import { Cache } from 'typeorm';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
) {}
@Cache({ type: 'redis', key: 'user::#{id}' })
async findById(id: number): Promise<User> {
return this.userRepository.findOne({ where: { id } });
}
@Cache({ type: 'redis', key: 'users::#{page}-#{limit}' })
async findAll(page: number = 1, limit: number = 10): Promise<[User[], number]> {
return this.userRepository.findAndCount({
skip: (page - 1) * limit,
take: limit,
});
}
// Метод без кэширования
async create(userData: Partial<User>): Promise<User> {
const user = this.userRepository.create(userData);
return this.userRepository.save(user);
}
}
Кэширование с пользовательскими ключами
Для более сложных сценариев создайте кастомный декоратор кэширования:
// src/decorators/cache.decorator.ts
import { SetMetadata } from '@nestjs/common';
export const CACHE_KEY = 'cache_key';
export const CACHE_OPTIONS = 'cache_options';
export const CacheWithKey = (key: string, options?: any) =>
(target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
SetMetadata(CACHE_KEY, key)(target, propertyKey, descriptor);
SetMetadata(CACHE_OPTIONS, options)(target, propertyKey, descriptor);
};
Используйте в сервисе:
// src/services/product.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Product } from '../entities/product.entity';
import { CacheWithKey } from '../decorators/cache.decorator';
@Injectable()
export class ProductService {
constructor(
@InjectRepository(Product)
private productRepository: Repository<Product>,
) {}
@CacheWithKey('product::category::#{categoryId}', { type: 'redis', duration: 600000 })
async findByCategory(categoryId: number): Promise<Product[]> {
return this.productRepository.find({
where: { categoryId },
order: { createdAt: 'DESC' }
});
}
}
Очистка кэша
Для очистки кэша при изменении данных:
// src/services/user.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from '../entities/user.entity';
import { Cache } from 'typeorm';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
) {}
async updateUser(id: number, userData: Partial<User>): Promise<User> {
const user = await this.findById(id);
Object.assign(user, userData);
const updatedUser = await this.userRepository.save(user);
// Очистка кэша для обновленного пользователя
await this.userRepository.manager.connection.cache.remove(
`user::${id}`
);
return updatedUser;
}
}
Оптимизация производительности
Настройка TTL (Time To Live)
Для различных типов данных используйте разное время жизни кэша:
// src/config/cache.config.ts
export const CACHE_TTL = {
USER_PROFILE: 3600, // 1 час
USER_LIST: 1800, // 30 минут
PRODUCT_DETAILS: 7200, // 2 часа
SEARCH_RESULTS: 900, // 15 минут
STATISTICS: 300, // 5 минут
};
Пакетная обработка данных
Для улучшения производительности используйте пакетную вставку данных с кэшированием:
// src/services/batch.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { BatchItem } from '../entities/batch-item.entity';
import { Cache } from 'typeorm';
@Injectable()
export class BatchService {
constructor(
@InjectRepository(BatchItem)
private batchItemRepository: Repository<BatchItem>,
) {}
@Cache({ type: 'redis', key: 'batch::status::#{batchId}' })
async getBatchStatus(batchId: string): Promise<any> {
const items = await this.batchItemRepository.find({
where: { batchId },
select: ['id', 'status', 'processedAt']
});
return {
batchId,
totalItems: items.length,
processedItems: items.filter(i => i.status === 'processed').length,
status: items.length > 0 ? items[0].status : 'pending'
};
}
async processBatch(batchId: string, items: any[]): Promise<void> {
const batchEntities = items.map(item =>
this.batchItemRepository.create({
batchId,
...item,
status: 'pending'
})
);
await this.batchItemRepository.save(batchEntities);
// Очистка кэша после добавления новых данных
await this.batchItemRepository.manager.connection.cache.clear();
}
}
Мониторинг производительности
Добавьте мониторинг для отслеживания эффективности кэширования:
// src/monitoring/cache.monitor.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CacheMetrics } from '../entities/cache-metrics.entity';
@Injectable()
export class CacheMonitorService {
constructor(
@InjectRepository(CacheMetrics)
private cacheMetricsRepository: Repository<CacheMetrics>,
) {}
async recordCacheHit(operation: string, duration: number): Promise<void> {
await this.cacheMetricsRepository.save({
operation,
type: 'hit',
duration,
timestamp: new Date()
});
}
async recordCacheMiss(operation: string, duration: number): Promise<void> {
await this.cacheMetricsRepository.save({
operation,
type: 'miss',
duration,
timestamp: new Date()
});
}
async getCacheStats(operation?: string): Promise<any> {
const conditions = operation ? { operation } : {};
const hits = await this.cacheMetricsRepository.count({
where: { ...conditions, type: 'hit' }
});
const misses = await this.cacheMetricsRepository.count({
where: { ...conditions, type: 'miss' }
});
const total = hits + misses;
const hitRate = total > 0 ? (hits / total) * 100 : 0;
return {
hits,
misses,
total,
hitRate: hitRate.toFixed(2) + '%'
};
}
}
Отладка и мониторинг
Включение логирования Redis
Для отладки включите подробное логирование Redis:
// src/config/redis-debug.config.ts
import { RedisOptions } from 'ioredis';
export const redisDebugOptions: RedisOptions = {
...redisOptions,
retryDelayOnFailover: 100,
maxRetriesPerRequest: 3,
lazyConnect: true,
enableReadyCheck: true,
keepAlive: true,
reconnectOnError: (err) => {
const targetError = 'READONLY';
if (err.message.includes(targetError)) {
return true;
}
return false;
},
// Включаем логирование
showFriendlyErrorStack: true,
enableReadyCheck: false,
connectTimeout: 10000,
commandTimeout: 5000,
};
// Создаем расширенный Redis клиент с логированием
export class DebugRedisCache extends RedisCache {
constructor(options: CacheOptions) {
super(options);
this.redis.on('error', (err) => {
console.error('Redis Error:', err);
});
this.redis.on('connect', () => {
console.log('Redis connected successfully');
});
this.redis.on('ready', () => {
console.log('Redis is ready');
});
this.redis.on('reconnecting', () => {
console.log('Redis reconnecting...');
});
this.redis.on('close', () => {
console.log('Redis connection closed');
});
}
}
Инструменты мониторинга
Используйте следующие инструменты для мониторинга Redis кэша:
- Redis CLI - для базовой проверки:
redis-cli info memory
redis-cli info stats
redis-cli keys "*typeorm*"
-
RedisInsight - визуальный инструмент для мониторинга Redis
-
Prometheus + Grafana - для комплексного мониторинга:
# prometheus.yml
scrape_configs:
- job_name: 'redis'
static_configs:
- targets: ['redis:6379']
metrics_path: '/metrics'
scrape_interval: 15s
Профилирование производительности
Добавьте профилирование для измерения производительности кэширования:
// src/utils/performance.utils.ts
import { performance } from 'perf_hooks';
export class PerformanceProfiler {
private static timers = new Map<string, number>();
static start(key: string): void {
this.timers.set(key, performance.now());
}
static end(key: string): number {
const startTime = this.timers.get(key);
if (!startTime) return 0;
const duration = performance.now() - startTime;
this.timers.delete(key);
return duration;
}
static async measureAsync<T>(key: string, fn: () => Promise<T>): Promise<{ result: T; duration: number }> {
this.start(key);
const result = await fn();
const duration = this.end(key);
return { result, duration };
}
}
// Использование в сервисе
async getUserWithProfiling(id: number) {
return PerformanceProfiler.measureAsync(
`getUser_${id}`,
() => this.findById(id)
);
}
Заключение
Использование Redis в качестве драйвера для кэширования запросов в TypeORM предоставляет значительные преимущества для производительности приложений. Вот ключевые выводы:
-
Повышение производительности - Redis кэширование сокращает время отклика за счет хранения результатов запросов в оперативной памяти, что особенно важно для часто запрашиваемых данных.
-
Гибкая конфигурация - TypeORM позволяет настраивать различные параметры кэширования, включая TTL, префиксы ключей и стратегии инвалидации, что позволяет адаптировать кэширование под конкретные нужды приложения.
-
Масштабируемость - Redis кластеры обеспечивают горизонтальное масштабирование кэша, что позволяет обрабатывать большие объемы данных и высокую нагрузку.
-
Надежность - Встроенные механизмы восстановления Redis и возможность репликации данных обеспечивают высокую доступность кэша.
-
Простота интеграции - Интеграция с TypeORM осуществляется через стандартный интерфейс кэширования, что упрощает внедрение и поддержку.
Для оптимальной работы с Redis кэшированием рекомендуется:
- Регулярно анализировать метрики производительности
- Настраивать подходящие значения TTL для разных типов данных
- Внедрать мониторинг и алерты для оперативного реагирования на проблемы
- Проводить нагрузочное тестирование для определения оптимальной конфигурации
Правильная настройка Redis кэширования в TypeORM может значительно снизить нагрузку на базу данных и улучшить общую производительность вашего приложения.