Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BE] feat#219 redis 메모리 관리 구현 #236

Merged
48 changes: 48 additions & 0 deletions BE/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions BE/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/platform-socket.io": "^10.4.6",
"@nestjs/schedule": "^4.1.1",
"@nestjs/swagger": "^8.0.5",
"@nestjs/typeorm": "^10.0.2",
"@nestjs/websockets": "^10.4.7",
Expand Down
128 changes: 70 additions & 58 deletions BE/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,70 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { GameModule } from './game/game.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { RedisModule } from '@nestjs-modules/ioredis';
import { ConfigModule } from '@nestjs/config';
import { QuizSetModel } from './quiz-set/entities/quiz-set.entity';
import { QuizModel } from './quiz-set/entities/quiz.entity';
import { QuizChoiceModel } from './quiz-set/entities/quiz-choice.entity';
import { UserModel } from './user/entities/user.entity';
import { UserQuizArchiveModel } from './user/entities/user-quiz-archive.entity';
import { InitDBModule } from './InitDB/InitDB.module';
import { UserModule } from './user/user.module';
import { QuizSetModule } from './quiz-set/quiz-set.module';
import { WaitingRoomModule } from './waiting-room/waiting-room.module';
import { TimeController } from './time/time.controller';
import { TimeModule } from './time/time.module';
import { AuthModule } from './auth/auth.module';

@Module({
imports: [
ConfigModule.forRoot({
envFilePath: '../.env',
isGlobal: true
}),
GameModule,
TypeOrmModule.forRoot({
type: 'mysql',
host: process.env.DB_HOST || 'localhost',
port: +process.env.DB_PORT || 3306,
username: process.env.DB_USER || 'root',
password: process.env.DB_PASSWD || 'test',
database: process.env.DB_NAME || 'test_db',
entities: [QuizSetModel, QuizModel, QuizChoiceModel, UserModel, UserQuizArchiveModel],
synchronize: process.env.DEV ? true : false, // 개발 모드에서만 활성화
logging: true, // 모든 쿼리 로깅
logger: 'advanced-console'
// extra: {
// // 글로벌 batch size 설정
// maxBatchSize: 100
// }
}),
RedisModule.forRoot({
type: 'single',
url: process.env.REDIS_URL || 'redis://localhost:6379'
}),
QuizSetModule,
UserModule,
InitDBModule,
WaitingRoomModule,
TimeModule
AuthModule
],
controllers: [AppController, TimeController],
providers: [AppService]
})
export class AppModule {}
import { ClassSerializerInterceptor, Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { GameModule } from './game/game.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { RedisModule } from '@nestjs-modules/ioredis';
import { ConfigModule } from '@nestjs/config';
import { QuizSetModel } from './quiz-set/entities/quiz-set.entity';
import { QuizModel } from './quiz-set/entities/quiz.entity';
import { QuizChoiceModel } from './quiz-set/entities/quiz-choice.entity';
import { UserModel } from './user/entities/user.entity';
import { UserQuizArchiveModel } from './user/entities/user-quiz-archive.entity';
import { InitDBModule } from './InitDB/InitDB.module';
import { UserModule } from './user/user.module';
import { QuizSetModule } from './quiz-set/quiz-set.module';
import { WaitingRoomModule } from './waiting-room/waiting-room.module';
import { TimeController } from './time/time.controller';
import { TimeModule } from './time/time.module';
import { AuthModule } from './auth/auth.module';
import { ScheduleModule } from '@nestjs/schedule';
import { GameRedisMemoryService } from './game/redis/game-redis-memory.service';
import { APP_INTERCEPTOR } from '@nestjs/core';

@Module({
imports: [
ConfigModule.forRoot({
envFilePath: '../.env',
isGlobal: true
}),
GameModule,
// 스케줄러 모듈 추가 (Redis 메모리 관리 서비스의 @Cron 데코레이터 사용을 위해)
ScheduleModule.forRoot(),
TypeOrmModule.forRoot({
type: 'mysql',
host: process.env.DB_HOST || 'localhost',
port: +process.env.DB_PORT || 3306,
username: process.env.DB_USER || 'root',
password: process.env.DB_PASSWD || 'test',
database: process.env.DB_NAME || 'test_db',
entities: [QuizSetModel, QuizModel, QuizChoiceModel, UserModel, UserQuizArchiveModel],
synchronize: process.env.DEV ? true : false, // 개발 모드에서만 활성화
logging: true, // 모든 쿼리 로깅
logger: 'advanced-console'
// extra: {
// // 글로벌 batch size 설정
// maxBatchSize: 100
// }
}),
RedisModule.forRoot({
type: 'single',
url: process.env.REDIS_URL || 'redis://localhost:6379'
}),
QuizSetModule,
UserModule,
InitDBModule,
WaitingRoomModule,
TimeModule,
AuthModule
],
controllers: [AppController, TimeController],
providers: [
AppService,
GameRedisMemoryService,
{
provide: APP_INTERCEPTOR,
useClass: ClassSerializerInterceptor
}
]
})
export class AppModule {}
9 changes: 6 additions & 3 deletions BE/src/game/game.gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
WebSocketServer
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { Logger, UseFilters, UsePipes } from '@nestjs/common';
import { Logger, UseFilters, UseInterceptors, UsePipes } from '@nestjs/common';
import { WsExceptionFilter } from '../common/filters/ws-exception.filter';
import SocketEvents from '../common/constants/socket-events';
import { CreateGameDto } from './dto/create-game.dto';
Expand All @@ -20,7 +20,9 @@ import { UpdateRoomOptionDto } from './dto/update-room-option.dto';
import { UpdateRoomQuizsetDto } from './dto/update-room-quizset.dto';
import { GameChatService } from './service/game.chat.service';
import { GameRoomService } from './service/game.room.service';
import { GameActivityInterceptor } from './interceptor/gameActivity.interceptor';

@UseInterceptors(GameActivityInterceptor)
@UseFilters(new WsExceptionFilter())
@WebSocketGateway({
cors: {
Expand Down Expand Up @@ -120,9 +122,10 @@ export class GameGateway {
this.logger.verbose(`클라이언트가 연결되었어요!: ${client.id}`);
}

handleDisconnect(client: Socket) {
async handleDisconnect(client: Socket) {
this.logger.verbose(`클라이언트가 연결 해제되었어요!: ${client.id}`);

this.gameService.disconnect(client.id);
await this.gameService.disconnect(client.id);
await this.gameRoomService.handlePlayerExit(client.id);
}
}
14 changes: 8 additions & 6 deletions BE/src/game/game.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import { GameRoomService } from './service/game.room.service';
import { QuizCacheService } from './service/quiz.cache.service';
import { QuizSetModule } from '../quiz-set/quiz-set.module';
import { QuizSetService } from '../quiz-set/service/quiz-set.service';
import { ScoringSubscriber } from '../common/redis/subscribers/scoring.subscriber';
import { TimerSubscriber } from '../common/redis/subscribers/timer.subscriber';
import { RoomSubscriber } from '../common/redis/subscribers/room.subscriber';
import { PlayerSubscriber } from '../common/redis/subscribers/player.subscriber';
import { RedisSubscriberService } from '../common/redis/redis-subscriber.service';
import { RedisSubscriberService } from './redis/redis-subscriber.service';
import { ScoringSubscriber } from './redis/subscribers/scoring.subscriber';
import { TimerSubscriber } from './redis/subscribers/timer.subscriber';
import { RoomSubscriber } from './redis/subscribers/room.subscriber';
import { PlayerSubscriber } from './redis/subscribers/player.subscriber';
import { RoomCleanupSubscriber } from './redis/subscribers/room.cleanup.subscriber';

@Module({
imports: [RedisModule, QuizSetModule],
Expand All @@ -28,7 +29,8 @@ import { RedisSubscriberService } from '../common/redis/redis-subscriber.service
ScoringSubscriber,
TimerSubscriber,
RoomSubscriber,
PlayerSubscriber
PlayerSubscriber,
RoomCleanupSubscriber
],
exports: [GameService]
})
Expand Down
28 changes: 28 additions & 0 deletions BE/src/game/interceptor/gameActivity.interceptor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// 활동 시간 업데이트는 비즈니스 로직과 분리
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍👍 좋습니다!

import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from '@nestjs/common';
import { of } from 'rxjs';
import { GameRoomService } from '../service/game.room.service';

@Injectable()
export class GameActivityInterceptor implements NestInterceptor {
private readonly logger = new Logger(GameActivityInterceptor.name);

constructor(private readonly gameRoomService: GameRoomService) {}

async intercept(context: ExecutionContext, next: CallHandler) {
// 핵심 로직 실행 전
const before = Date.now();

// 핵심 로직 실행
const result = await next.handle().toPromise();

// 활동 시간 업데이트 (부가 기능)
const data = context.switchToWs().getData();
if (data.gameId) {
await this.gameRoomService.updateRoomActivity(data.gameId);
this.logger.debug(`Activity updated for room ${data.gameId} after ${Date.now() - before}ms`);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: 지금 로그가 한글로 찍혀 있는 부분이 많아서 한글로 통일하면 좋을 것 같습니다!

}

return of(result);
}
}
Loading