안녕하세요, 이중혁입니다

배우고 경험한 기술들을 하나씩 정리하는 공간

MongoDB

MongoDB 로그 서버 구조

요청 컨텍스트에 수집된 로그를 전용 로그 서버로 전달하고, 게임 코드별 MongoDB 커넥션으로 저장하는 구조

NestJSMongoDBInterceptorRepository

구성 요소

  • MongoLogProvider: 요청 컨텍스트에 컬렉션별 로그를 버퍼링하는 경량 유틸. 다수의 서비스/도메인에서 호출해도 한 요청 단위로 합쳐짐.
  • MongoLogInterceptor: 컨트롤러 수행 후 버퍼를 읽어 로그 서버에 비동기 전송. 실패하더라도 본 요청의 응답에는 영향이 없도록 설계.
  • Log Server (apps/log): 수신 DTO 검증 → 게임 코드별 리포지토리 선택 → 컬렉션별 batch insert. 파싱/검증 실패는 명확한 에러 코드로 응답.
  • Repository: Mongoose 커넥션을 직접 사용하여 컬렉션 핸들로 insertMany 실행. 스키마 고정 없이 유연한 로그 스키마를 수용.

요청 → 저장 시퀀스

  1. 애플리케이션 코드에서 MongoLogProvider.setMongoLog 호출로 컬렉션별 로그를 요청 컨텍스트에 누적
  2. 응답 직전 MongoLogInterceptor가 버퍼를 JSON으로 직렬화해 로그 서버에 전송(비동기)
  3. 로그 서버는 gameCd로 리포지토리를 선택하여 MongoDB에 insertMany로 배치 저장
환경 변수 / 연결
  • MONGO_LOG_DOCUMENT_URI: 로그 서버의 Mongo 연결 문자열. 예) mongodb://user:pass@host:27017/survive?authSource=survive
  • GAME_CODE: 호출 애플리케이션의 게임 코드. 리포지토리 맵의 키와 대문자 일치 필요(예: SURVIVE).
  • 로컬: Docker Compose MongoDB(localhost:13017)에 연결, 운영: Kubernetes의 Service DNS(portfolio-manager-mongodb...) 사용.
데이터 모델 / 저장 방식
  • 요청 1건에 여러 컬렉션을 동시에 적재 가능한 배치 저장 구조
  • 컬렉션은 자유 스키마. 서비스별로 로그 구조를 독립적으로 진화 가능
  • 응답으로는 삽입된 문서의 ObjectId 목록을 컬렉션별로 반환
@Controller('log/mongo')
@ApiTags('Mongo')
export class MongoLogController {
  constructor(private readonly mongoLogService: MongoLogService) {}

  @Post()
  @ApiResponseEntity({ summary: '공통 로그' })
  @UseGuards(NfTokenGuard)
  @ApiSecurity('nfToken')
  async setLog(
    @Body() createMongoLogInDto: CreateMongoLogInDto,
  ): Promise<ResponseEntity<unknown>> {
    await this.mongoLogService.setMongoLog(createMongoLogInDto);

    return ResponseEntity.ok().build();
  }

  @Post('search')
  @ApiResponseEntity({
    type: GetMongoLogsOutDto,
    isPagination: true,
    summary: '몽고 로그 조회',
  })
  @UseGuards(NfTokenGuard)
  @ApiSecurity('nfToken')
  async getMongoLogs(
    @Body() getMongoLogsInDto: GetMongoLogsInDto,
  ): Promise<ResponseEntity<GetMongoLogsOutDto>> {
    const getMongoLogsOutDto =
      await this.mongoLogService.getMongoLog(getMongoLogsInDto);

    return ResponseEntity.ok().body(getMongoLogsOutDto);
  }
}

MongoLogProvider - 요청 컨텍스트 로그 수집

요청 수명주기 동안 분산된 위치에서 수집되는 로그를 컨텍스트에 누적하고, 응답 직후 인터셉터가 전송할 수 있도록 버퍼링합니다.

ProviderContextInterceptor

핵심 개념

  • 요청 컨텍스트에 MongoLogs 구조로 컬렉션별 로그 누적
  • 단건/배열 모두 지원, 여러 서비스에서 호출해도 한 요청으로 병합
  • 오류는 애플리케이션 흐름에 영향 주지 않도록 내부 로깅 후 무시
@Injectable()
export class MongoLogProvider {
  static setMongoLog(
    collectionName: string,
    contents: any,
  ): void {
    try {
      const mongoLogs = ContextProvider.getMongoLogs() ?? ({ logs: {} } as MongoLogs);

      if (!mongoLogs.logs[collectionName]) mongoLogs.logs[collectionName] = [];

      if (Array.isArray(contents)) {
        mongoLogs.logs[collectionName] = [
          ...mongoLogs.logs[collectionName],
          ...contents,
        ];
      } else {
        mongoLogs.logs[collectionName].push(contents);
      }

      ContextProvider.setMongoLogs(mongoLogs);
    } catch (e) {
      Logger.error(collectionName, e);
    }
  }
}