본문 바로가기
개발/스터디

WIL - 배치 기반 주간·월간 랭킹 설계: 핵심 정리

by 글쓰는 개발자 2026. 4. 17.

WIL - 배치 기반 주간·월간 랭킹 설계: 핵심 정리

이번 랭킹 설계를 통해 배운 점은 단순히 "배치를 돌려 랭킹을 만든다"는 것을 넘어, 데이터를 어떻게 구조화하고 운영할지에 대한 깊은 고민이 필요하다는 것이다. 아래 5가지 핵심 개념으로 정리해 보았다.


1. 운영 데이터와 조회용 데이터는 분리하여 생각해야 한다

실제 서비스에서 원본 데이터(판매량, 주문 등)와 조회 최적화용 데이터(미리 가공된 랭킹 결과)를 동일하게 다루면 복잡성이 증가한다.

  • 원본 데이터: product_metrics는 일 단위 집계 원본에 가깝다.
  • 조회용 데이터: 주간/월간 랭킹 테이블은 API 응답 속도를 높이기 위해 미리 계산해 둔 결과 저장소이다.

핵심: 주간/월간 랭킹은 "그때그때 계산하는 값"이 아니라, 미리 계산된 결과를 빠르게 읽기 위한 구조로 이해해야 한다.

2. Materialized View(MV)는 설계 개념으로 접근한다

MySQL 8.0에는 공식 MV 기능이 없지만, 실무에서는 "집계 결과를 저장하는 일반 테이블"을 MV처럼 활용한다. 중요한 것은 기능의 이름이 아닌, 그 역할이다.

  • 원본 데이터를 매번 SUM/GROUP BY 하지 않고, 배치 시점에 미리 집계한다.
  • API는 미리 집계된 결과만 조회하여 읽기 성능을 최적화한다.

핵심: MV는 사전 계산된 결과를 저장하여 읽기 성능을 높이는 전략이며, DB의 공식 기능 여부와는 별개의 설계 개념이다.

3. 랭킹은 정합성보다 운영 안정성 및 조회 성능이 중요할 수 있다

주문, 결제와 같은 데이터는 강한 정합성이 필수적이지만, 랭킹은 다르다. 주간 인기 상품 Top 100의 경우 1~2건의 미세한 오차보다 다음 요소들이 더 중요할 수 있다.

  • 빠른 API 응답 속도
  • 쉬운 배치 재실행
  • 간단한 장애 복구
  • 예측 가능한 랭킹 결과

따라서 이번 설계에서 중복 처리 전략을 "누적"이 아닌 덮어쓰기(OVERWRITE)로 결정한 것은 랭킹 도메인의 특성을 고려한 실용적인 선택이다.

4. 배치 Job은 "비즈니스 단위"와 "실패 격리 단위"로 나누어야 한다

배치 Job을 나누는 기준은 단순히 기능 개수가 아니다. 이번 설계에서 Job을 분리한 주된 이유는 다음과 같다.

  • 다른 실행 주기: 각 Job의 실행 주기가 다르다.
  • 실패 격리: 한 Job의 실패가 다른 Job에 영향을 미치지 않도록 방지한다.

핵심: Job 분리는 코드 구조의 문제라기보다는 운영 중 발생할 수 있는 실패와 재실행 상황에 어떻게 대응할지에 대한 전략이다.

5. 랭킹 API 확장은 파라미터 추가를 넘어선 "읽기 모델 선택" 문제이다

period=daily|weekly|monthly 파라미터 추가는 겉으로는 단순한 확장 같지만, 내부적으로는 조회 대상이 완전히 달라지는 큰 변화를 의미한다.

  • daily 선택 시: Redis 조회
  • weekly 선택 시: 주간 MV 테이블 조회
  • monthly 선택 시: 월간 MV 테이블 조회

즉, API 파라미터 하나가 단순한 필터 조건이 아니라, 다른 저장소와 집계 규칙, 조회 경로를 선택하게 된다. RankingPeriod enum을 사용하여 Facade에서 이 분기 처리를 구현한 것은 여러 읽기 모델을 하나의 진입점으로 통합하는 적절한 방법이었다.


결론

결론적으로 이번 랭킹 설계는 다음과 같은 철학을 담고 있다.

  • 실시간 vs. 배치: 실시간 시스템(일간 랭킹 - Redis)과 배치 시스템(주간/월간 랭킹 - MySQL)을 분리하여 각 특성에 맞는 저장소와 처리 방식을 적용했다.
  • 미리 계산 vs. 실시간 계산: 주간/월간 랭킹은 매 요청마다 계산하는 대신, 배치 시점에 미리 계산하여 "계산 비용"을 선불로 지불하고 "조회 비용"을 최소화했다.
  • 재처리 가능성 확보: product_metrics와 같은 중간 집계 계층을 두어, 장애 발생 시 원본 데이터를 재처리하기 쉽게 설계했다.
  • 배치 재실행 전략: 재실행 시 기존 값을 덮어쓰는 전략으로 운영 난이도를 낮추고, "여러 번 돌려도 최종 결과가 수렴하는 작업"을 지향했다.
  • 책임 분리: Chunk-Oriented Processing을 통해 Reader, Processor, Writer의 역할을 명확히 하고, 집계 로직의 책임(애플리케이션 vs. DB)을 적절히 분배했다.
  • 기준 정의의 중요성: 랭킹을 "어떻게 계산할까"보다 "무엇을 같은 기간으로 볼 것인가"와 같은 기준 정의가 더 중요함을 깨달았다.

이 설계를 통해 실시간 쓰기 부담을 줄이고, 읽기 성능을 높이며, 운영 복잡도를 효과적으로 통제하는 법을 배울 수 있었다

반응형