Skip to content

[BE] Ticketing Server. 공연장 좌석 조회 API.#63

Merged
shininghyunho merged 42 commits intodevfrom
backend-5
Jan 21, 2026
Merged

[BE] Ticketing Server. 공연장 좌석 조회 API.#63
shininghyunho merged 42 commits intodevfrom
backend-5

Conversation

@shininghyunho
Copy link
Copy Markdown
Collaborator

@shininghyunho shininghyunho commented Jan 18, 2026

🧭 Summary

  • 공연장 좌석 조회 API를 구현합니다.
  • 공연 예매 API 구현. (POST)
  • 공연 좌석 조회 API 구현 (GET)

🔗 Linked Issue

🛠 개발 기능(작업 내용)

모듈화 진행.

  • image

공연 예매 API. (POST)

  • image
  • Active Token 과 관련된 부분이 빠져있음. (인증 및 비활성화 로직 미포함)
  • Parrot 작업물을 반영해야함.

공연 좌석 조회 API (GET)

  • image
  • true : 예매 완료, false : 빈자리.
  • return 값은 FE와 논의가 필요.

🧩 주요 고민과 해결 방법

고민. 공연 정보 조회에도 Active Token 검증을 해야하나?

공연 예매 API (POST) 는 당연히 토큰 검증을 해야한다.
근데 단순 조회도 해야하나?

논리적으로 조회는 토큰 검증이 불필요하다.
문제는 조회가 10만건씩 오면 티켓팅 서버에 부하가 없냐는것이다.
악의적인 사용자가 계속 GET을 보내면? 계속 redis hit가 발생해 부하 발생.
⇒ 조회도 검증 필요.

추가적인 의문은 GET, POST 요청이 오고 guard에서 쳐냈을때
서버의 부하가 없는가?
그럼 현재 토큰 검증만으로는 티켓팅 서버의 부하를 못 막는거 아닌가?

토큰 검증은 가드에서 진행.
그래서 비지니스 로직까지는 들어오지 못함.
문제는 네트워크 부하임. NestJS 서버까지는 도달했으니까.
TCP 연결, SSL 핸드쉐이크, HTTP 파싱 등 여러 비용이 계속 발생.

그렇다면 토큰 검증을 티켓 서버에서 하면 안됨!
그 앞단에서 해결.
Nginx, API Gateway 를 거치면 좋음.

고민. Node는 싱글 스레드. 그러면 여러 요청이 온다면?

이때 요청을 1개씩 처리하기위해 큐에 작업을 저장해놓는가?
그렇다면 대기열하고 역할이 똑같네?

NodeJS의 이벤트 큐가 있긴함. 이를 하나씩 이벤트 루프가 처리.
이벤트 큐는 용량이 작고 넘치면 OOM 출력.

Jerry 와 나눈 대화.

Jerry 질문이 있는데요.
저희 Queue WAS가 NestJS로 구현되어있잖아요.

그래서 10만명 트래픽이 발생하면일단 NodeJS가 이벤트 루프로 1개씩 처리할텐데
이때 이벤트 루프가 사용하는 이벤트 큐는 안터지나요?
(그러면 대기열에 진입하기전에 서버가 터질거같아서요.)

애초에 순수한 NestJS 만으로  10만명을 받을 수 있는가?
앞단에 Nginx 같은걸 둬서 대기열로 들어오는 네트워크 자체 용량을 줄여야하는건 아닌가?
라는 고민이 드네요!
image

🔍 리뷰 포인트

  • 리뷰 시 어떤 부분에 집중해야 할지 명시해주세요.

@shininghyunho shininghyunho self-assigned this Jan 18, 2026
@shininghyunho shininghyunho marked this pull request as draft January 18, 2026 14:58
- axios, schedule, swagger, ioredis, class-transfer, class-validator.
- redis에 get, setNx 가 되도록 구현.
- 테스트 코드도 같이 추가.
- 매 5분마다 티켓팅이 진행될 수 있도록 변경.
- setup 과정은 4분, 9분, 14분, 19분 처럼 5분되기 1분전마다 진행.
- setup이 진행되면 1분뒤 티켓팅 시작.
- 티켓팅 시작 후 3분뒤 티켓팅 마감.
- setup 이외에 과정은 setTimeout 사용.
- 환경 변수 가이드를 제공하는 파일.
- .gitignore에 .env가 존재함.
- 그러나 .env.sample은 제외시킴.
- 최상단 모듈로서 생명주기를 담당.
- 부모 모듈 : TicketScheduler.
- 자식 모듈 : PerformanceApiModule, RedisModule.
- 부모 모듈 : TicketScheduler.
- 자식 모듈 : RedisModuel.
- await 문구를 통해 오류를 확실히 catch.
- 좌석 조회 API 추가.
- Active Token 인증은 Guard에서 처리.
- 일단 Auth 모듈 모킹하여 true로 반환.
- 일단 연결만 진행.
- 커다란 기능을 여러개의 관심사로 분리.
@shininghyunho shininghyunho marked this pull request as ready for review January 21, 2026 08:39
Copy link
Copy Markdown
Collaborator

@ParkTjgus ParkTjgus left a comment

Choose a reason for hiding this comment

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

너무 수고하셨습니다! 바쁘셨을텐데 test까지 너무 수고하셨어요🥹 CI 오류만 해결해주세요! 수고하셨습니다 :D

node_modules
dist
env*
!.env.example
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

env.example 좋네요!!

Comment on lines +9 to +16
# Ticketing Cycle
# Setup 간격 (Cron format, 매 5분되기 1분 전. 4분, 9분, 14분 19분, ...)
# 매 5분마다 티켓팅이 진행되기 위해 1분전에 setup 과정을 진행.
SETUP_INTERVAL="0 4/5 * * * *"
# Setup 이 끝나고 티켓팅 시작 딜레이 (ms, 디폴트 1분.)
TICKETING_OPEN_DELAY=60000
# 티켓팅이 진행되는 시간 (ms, 디폴트 3분.)
TICKETING_DURATION=180000 No newline at end of file
Copy link
Copy Markdown
Collaborator

@flowersayo flowersayo Jan 21, 2026

Choose a reason for hiding this comment

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

혹시 이거는 packages/shared-constant 로 공유하면 어떨까요?

Comment on lines +55 to +73
async handleCycle() {
try {
this.logger.log('Starting ticketing cycle...');

await this.setupService.setup();

await this.delay(this.openDelay);
await this.setupService.openTicketing();

await this.delay(this.duration);
await this.setupService.tearDown();

this.logger.log('Ticketing cycle completed successfully.');
} catch (e) {
const err = e as Error;
this.logger.error(`Ticketing cycle failed: ${err.message}`, err.stack);
await this.setupService.tearDown();
}
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

궁금증) 저는 Cron 이 짧은 작업을 주기적으로 실행하는데 도움을 주는 도구라고 알고 있었는데 CronJob 안에서 long-running 작업을 실행해도 괜찮은가요?

Copy link
Copy Markdown
Collaborator

@flowersayo flowersayo Jan 21, 2026

Choose a reason for hiding this comment

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

이 코드는 바로 해석하기가 어려워서 AI 랑 같이 코드 해석 + 리뷰 피드백을 진행해봤는데요!

개발 환경에서는 돌아가긴 하지만, 전반적으로 실제 운영 환경에서 스케쥴링이 꼬여서 위험한 포인트가 몇가지 있을 것 같다고 생각이 들어서 코멘트 남겨보았습니다 🙇‍♀️ ( 이번 PR 은 바로 머지하시고 나중에 추가적으로 작업해주셔도 괜찮습니다! )

  1. 중복 실행 방어 로직이 없음
    위 코드는 아래 상황을 고려하지 않음.
  • 서버 재시작
  • 배포 직후
  • 크론 간격 < duration + delay

위치: ticket-scheduler.service.ts:37-39

this.job = new CronJob(this.interval, () => {
  void this.handleCycle();
});

문제:

  • 이전 사이클이 끝나기 전에 다음 크론이 실행되면?
  • handleCycle()이 동시에 2번 실행될 수 있음
  • ticketing open/teardown 중복 → 데이터 정합성 파괴

실제 시나리오:

00:00 - Cron 트리거 → handleCycle() 시작
00:01 - setup 완료
00:02 - 60초 대기 중...
00:05 - 다음 Cron 트리거 → handleCycle() 또 시작 💥

필수 추가 사항:

private isRunning = false;

async handleCycle() {
  if (this.isRunning) {
    this.logger.warn('Previous cycle still running, skipping...');
    return;
  }

  this.isRunning = true;
  try {
    // ...
  } finally {
    this.isRunning = false;
  }
}

  1. 프로세스 메모리에 의존한 시간 기반 상태 전이 ⚠️ CRITICAL

위치: ticket-scheduler.service.ts:61-65

await this.delay(this.openDelay);    // 60초 대기
await this.setupService.openTicketing();

await this.delay(this.duration);     // 180초 대기
await this.setupService.tearDown();

전제:

"이 프로세스는 절대 죽지 않는다"

현실에서 일어날 일:

  • PM2 재시작
  • Docker 컨테이너 재배포
  • OOM Kill
  • K8s Pod Eviction

결과:

11:00:00 - setup() 완료
11:00:01 - openTicketing() 완료 → 티켓팅 오픈 ✅
11:00:30 - 서버 재시작 💥
11:01:00 - 새 프로세스 시작
         - tearDown() 실행 안 됨
         - 티켓팅 영구 오픈 상태 고착 🔥

이 설계가 위험한 이유:

  1. 복구 불가능: 프로세스 죽으면 상태 전이 체인이 끊김
  2. 관찰 불가능: 현재 어느 단계인지 외부에서 알 수 없음
  3. 재시작 시 일관성 보장 불가: 메모리 상태는 휘발됨

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

그렇네요. 제가 5분으로 강제 시간텀을 둬서 cron job 이 겹치진 않겠지만, 이후에 시간을 변경하면 job 시간이 겹칠 수 있겠네요.
그리고 시스템이 중간에 죽으면 상태가 멈춰야하는데 계속 진행이 되네요. (방어 코드가 더 필요할거같습니다.)
좋은 피드백 감사해요! 덕분에 더 다양하게 생각해볼수있었어요!

Copy link
Copy Markdown
Collaborator

@viixix viixix left a comment

Choose a reason for hiding this comment

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

린트 통과하셨군요 수고하셨습니다!

@flowersayo
Copy link
Copy Markdown
Collaborator

flowersayo commented Jan 21, 2026

고생하셨습니다~! 역시 공연 스케쥴링 + 좌석 예매 쪽이 혹시 로직이 가장 복잡하고 어려운데 깔끔하게 잘 구현해주신 것 같아요👍

  • reservation 에 사용한 DTO 는 프론트엔드에서도 공유할 수 있을 것 같아서
    shared-packages 쪽으로 빼두면 타입 재활용해볼 수 있을 것 같습니다 :)

@shininghyunho shininghyunho merged commit 91c9fe3 into dev Jan 21, 2026
6 checks passed
@shininghyunho
Copy link
Copy Markdown
Collaborator Author

고생하셨습니다~! 역시 공연 스케쥴링 + 좌석 예매 쪽이 혹시 로직이 가장 복잡하고 어려운데 깔끔하게 잘 구현해주신 것 같아요👍

  • reservation 에 사용한 DTO 는 프론트엔드에서도 공유할 수 있을 것 같아서
    shared-packages 쪽으로 빼두면 타입 재활용해볼 수 있을 것 같습니다 :)

일단 merge 충돌난게 많아서 merge 를 시켜버렸습니다.
이 내용은 이후 dev에서 다시 branch 따와서 작업해도 될까요?

@viixix viixix deleted the backend-5 branch January 23, 2026 01:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants