feat: 실시간 알림(인앱+SSE, Redis pub/sub) + Redis 캐싱 (8단계)
알림(8-2): - Notification 모듈 + SSE 스트림(@Sse, @SkipTransform 으로 표준 래퍼 우회). - 12종 알림 적재(업무 지시/해제/승인요청/승인/수정요청/시작/마감변경/삭제/댓글·답글, 멤버 초대/역할변경/제거). 행위자 제외 + 중복 dedup. AppShell 종(미읽음 뱃지·드롭다운). - 실시간 fan-out: REDIS_HOST 시 ioredis pub/sub(다중 인스턴스), 미설정 시 인메모리 폴백. Redis 캐싱(8-3): - CacheModule → @keyv/redis(미설정 시 인메모리 폴백). 저장소 목록을 버전 네임스페이스로 캐시, 생성/수정/삭제·동기화·멤버 초대/제거 시 무효화(공유 버전 키라 다중 인스턴스 정합). - enableShutdownHooks 로 종료 시 Redis 연결 graceful close. 검증: 백엔드 build/lint 0·20 tests, 프론트 build/lint/type-check 0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { Logger, Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { ThrottlerGuard, ThrottlerModule } from '@nestjs/throttler';
|
||||
import { CacheModule } from '@nestjs/cache-manager';
|
||||
import { createKeyv } from '@keyv/redis';
|
||||
import type Keyv from 'keyv';
|
||||
import { APP_GUARD } from '@nestjs/core';
|
||||
|
||||
import { AppController } from './app.controller';
|
||||
@@ -14,6 +16,7 @@ import { AuthModule } from './modules/auth/auth.module';
|
||||
import { RepoModule } from './modules/repo/repo.module';
|
||||
import { TaskModule } from './modules/task/task.module';
|
||||
import { ActivityModule } from './modules/activity/activity.module';
|
||||
import { NotificationModule } from './modules/notification/notification.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -22,10 +25,34 @@ import { ActivityModule } from './modules/activity/activity.module';
|
||||
isGlobal: true,
|
||||
envFilePath: `.env.${process.env.NODE_ENV || 'development'}`,
|
||||
}),
|
||||
// 전역 캐시 매니저 — Redis 도입 시 store 옵션을 redisStore 로 변경
|
||||
CacheModule.register({
|
||||
// 전역 캐시 매니저 — REDIS_* 설정 시 Redis(keyv) 스토어, 없으면 인메모리 폴백.
|
||||
// docker-compose 가 REDIS_HOST/PORT/PASSWORD 를 주입한다(단독 실행/테스트는 미설정 → 인메모리).
|
||||
CacheModule.registerAsync({
|
||||
isGlobal: true,
|
||||
ttl: 60_000, // 60초 기본 TTL
|
||||
useFactory: (): { ttl: number; stores?: Keyv[] } => {
|
||||
const logger = new Logger('CacheModule');
|
||||
const options: { ttl: number; stores?: Keyv[] } = { ttl: 60_000 };
|
||||
const host = process.env.REDIS_HOST;
|
||||
if (!host) {
|
||||
// Redis 미설정 — 인메모리 폴백(LRU). 로컬/테스트에서 Redis 없이도 동작.
|
||||
// 주의: 다중 인스턴스를 인메모리 모드로 띄우면 인스턴스별 캐시·무효화가 분리됨(단일 인스턴스 전용)
|
||||
logger.log('캐시: 인메모리 모드(단일 인스턴스) — REDIS_HOST 미설정');
|
||||
return options;
|
||||
}
|
||||
const port = process.env.REDIS_PORT || '6379';
|
||||
const password = process.env.REDIS_PASSWORD;
|
||||
const auth = password ? `:${encodeURIComponent(password)}@` : '';
|
||||
const keyv = createKeyv(`redis://${auth}${host}:${port}`);
|
||||
// Redis 연결 오류가 unhandled 로 프로세스를 죽이지 않도록 흡수(캐시 장애는 DB 조회로 폴백)
|
||||
keyv.on('error', (err: unknown) => {
|
||||
logger.warn(
|
||||
`Redis 캐시 오류(무시, DB 폴백): ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
});
|
||||
options.stores = [keyv];
|
||||
logger.log('캐시: Redis 모드(다중 인스턴스 무효화 정합)');
|
||||
return options;
|
||||
},
|
||||
}),
|
||||
// 전역 Rate Limiter — main.ts 의 express-rate-limit 와 별개로 라우트 단위 제어 가능
|
||||
ThrottlerModule.forRoot([
|
||||
@@ -67,6 +94,7 @@ import { ActivityModule } from './modules/activity/activity.module';
|
||||
RepoModule,
|
||||
TaskModule,
|
||||
ActivityModule,
|
||||
NotificationModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [
|
||||
|
||||
Reference in New Issue
Block a user