pile·
백엔드·여기어때 (GC컴퍼니)여기어때 (GC컴퍼니)·

더 빠르게, 그리고 무너지지 않게 — 전시 아키텍처 개선기 (1/3)

여기어때가 피크 타임에 Redis CPU 100% + P95 응답시간 4초 이상을 기록하던 전시(숙소 목록 조회) 서비스 병목을 해결한 3부작 1편이다. MongoDB 컬렉션 파편화, 순차 처리, Redis 단일 의존이라는 세 가지 병목을 동시에 공략하여 Redis CPU를 90% 줄이고 TPS를 3배 높였다. CDC+Kafka 기반 메타 캐시와 3단계 방어선(Redis→로컬 캐시→MongoDB)으로 고가용성까지 확보했다.

핵심 포인트
  • 기존 병목 3가지: MongoDB 15개 이상 컬렉션 분산 조회, 제휴점별 순차 처리, Redis 단일 의존으로 네트워크 대역폭 초과 시 패킷 드랍.
  • 컬렉션 통합으로 10여 개 조인 쿼리를 3~4개 단순 쿼리로 줄이고 Java Virtual Thread로 IO를 병렬 처리해 MongoDB 조회 비용을 대폭 낮췄다.
  • CDC 기반 메타 캐시 재설계: MongoDB 변경 감지 → Kafka → Virtual Thread 병렬 업데이트. Redis에는 메타 정보만 캐싱하고 가격은 실시간 계산으로 전환했다.
  • 3단계 방어선: Redis 정상 → Redis 서킷 OPEN 시 로컬 캐시 → 최후 보루 MongoDB 직접 조회. 실제 Redis 연결 끊김 시 사용자 영향 없이 투명하게 전환됐다.
  • 결과: Redis CPU 100% → 10% 이하(90% 감소), TPS 211 → 666(3배), P95 4.13초 → 1초대(75%+ 단축).
상세 정리
  • 문제 규모: 성수기 피크 타임에 Redis CPU 100%, 응답시간 정상 대비 3~5배 증가, 일부 요청 타임아웃 오류 발생.
  • 병목 1 — MongoDB 파편화: PLP 조회 시 15개 이상 컬렉션에 접근, 복잡한 조인 쿼리가 응답 지연의 주원인이었다.
  • 병목 2 — 순차 처리: 제휴점별 모델 생성을 순차 처리해 전체 응답시간이 각 처리 시간의 합으로 누적됐다.
  • 병목 3 — Redis 단일 의존: 모든 캐시 조회가 Redis로 집중되어 네트워크 대역폭이 초과되고 패킷 드랍이 발생했다.
  • Phase 1 — 컬렉션 통합: MongoDB 컬렉션을 재설계해 10여 개 조인 쿼리를 3~4개 단순 쿼리로 줄였다.
  • Phase 1 — Virtual Thread: Java Virtual Thread로 제휴점별 모델 생성을 병렬화, 수십 건의 IO를 동시에 처리해 순차 누적을 해소했다.
  • 메타 캐시 재설계: CDC로 MongoDB 변경을 감지 → Kafka 메시지 발행 → Virtual Thread 병렬 처리로 Redis 메타 캐시를 갱신. 가격 정보는 TTL 캐시 대신 실시간 계산으로 전환했다.
  • 3단계 방어선 설계: Level 1(Redis, 평시), Level 2(로컬 캐시, Redis 서킷 OPEN), Level 3(MongoDB 직접 조회, 최후 보루). 서킷 브레이커 민감도를 티어별로 다르게 설정했다.
  • 실전 검증: 운영 중 Redis 연결 끊김 시 로컬 캐시로 설계한 대로 투명하게 전환, 사용자는 아무것도 느끼지 못했다.
  • 성과: Redis CPU 100% → 10% 이하, MongoDB CPU 20~30% → 10% 이하, TPS 211 → 666, P95 4.13초 → 1초대.
왜 읽나Redis 과부하와 MongoDB 쿼리 병목을 동시에 해결하면서 장애 전파 방지 다단계 방어선 설계까지 다룬 백엔드 개선 실전 사례로, 트래픽이 몰리는 여행·커머스 플랫폼 백엔드를 운영하는 엔지니어에게 구체적인 개선 경로를 제시한다.
여기어때 (GC컴퍼니)
여기어때 (GC컴퍼니) 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. 백엔드·여기어때 (GC컴퍼니)여기어때 (GC컴퍼니)·

    트랜잭션 스크립트에서 숙소 메타 + 가격 계산 모듈로 — 전시 아키텍처 개선기 (2/3)

    여기어때 전시개발팀이 숙소 상세(PDP) API를 해부한 결과, 코드상으로는 DB 호출 3번처럼 보이던 요청이 실제로는 MongoDB $lookup 체인으로 컬렉션을 19회 접근하는 구조였다. 이 트랜잭션 스크립트 방식의 핵심 문제는 "aggregation이 I/O를 가린다"는 점으로, 독립적인 쿼리 10개가 단일 파이프라인에 직렬화되어 병렬화 기회를 잃고, 가격 때문에 거의 안 바뀌는 이미지까지 매 요청마다 읽어야 하는 읽기 증폭이 발생했다. V3에서는 "조회 시점 조립"을 "쓰기 시점 사전 조립"으로 전환하고, 화면별로 복제되던 가격 계산 로직을 goodsprice 단일 모듈로 수렴했다. 4개 API(PLP/PDP/RDP/ILP)의 반복 마이그레이션은 Claude Code skill로 절차를 고정하고 쉐도잉 + 동일성 검증으로 안전망을 마련하는 방식으로 진행됐다.

    #architecture#migration#caching+2