프로젝트 개요
2022.05 — 2022.07
들어가며
단 하나의 API 응답 속도가 시스템 전체의 경험을 좌우할 수 있습니다. 이 글은 트렌비 리뷰 서비스 운영 중 발생한 DB 커넥션 풀 고갈과 API 타임아웃 문제를 5단계에 걸쳐 구조적으로 개선한 과정을 SBI(Situation → Behavior → Impact) 구조로 정리한 기록입니다.
Situation — 두 가지 알림이 반복되었다
트렌비 리뷰 서비스(review-service) 런칭 이후 사용자와 트래픽이 증가하면서 두 가지 시스템 알림이 지속적으로 발생했습니다. 첫째, 대량 트래픽 구간에서 "Could not open JPA EntityManager" 에러가 반복되는 DB 커넥션 풀 고갈이었습니다. 둘째, 외부 서비스 의존으로 인한 연쇄 지연, 즉 API 타임아웃이었습니다.
p90 API 응답 속도는 약 200ms~250ms 수준으로, 사용자가 '즉각적인 응답'으로 느끼는 기준인 100ms 이내를 크게 초과하고 있었습니다.
"엔지니어는 로그를 보고 있었고, 사용자는 버벅이는 리뷰 목록을 보고 있었다."
Behavior — 원인 분석 후 5단계 순차 개선
1단계: n번 API 호출 → Bulk API 1번 호출
주문 서비스(order-service)를 루프로 n번 호출하던 로직을 Bulk 형태의 API로 교체했습니다. 네트워크 왕복 횟수를 n에서 1로 줄이는 것만으로 p90 응답 속도를 200ms에서 100ms 수준으로 단축했습니다.
2단계: 외부 서비스 의존 제거
주문 서비스에서 가져오던 '구매 옵션' 데이터를 리뷰 저장 시 자체적으로 함께 저장하도록 변경하여 외부 의존을 완전히 끊었습니다. 이 한 가지 변경으로 응답 속도가 100ms에서 30~40ms로 급격히 개선되었습니다.
3단계: 트랜잭션 분리로 DB 커넥션 풀 고갈 해소
@Transactional 메소드 내부에서 외부 API 호출 후 DB I/O를 수행하는 구조가 커넥션 점유를 장기화시키고 있었습니다. 외부 I/O와 DB I/O 구간을
분리하고, DB 로직에만 TransactionTemplate을 명시적으로 적용하여 트랜잭션이 보유하는 커넥션 시간을 최소화했습니다.
4단계: Redis 캐시 레이어 도입
상품 시스템의 외부 API가 간헐적으로 타임아웃을 일으켜 장애가 전파됐습니다. 리뷰 도메인이 필요로 하는 상품 정보는 변동성이 낮다는 점에 착안하여 Redis 캐시 레이어를 앞단에 적용했습니다. 외부 API 타임아웃이 유발하던 스파이크성 에러가 눈에 띄게 줄었습니다.
5단계: 인덱스 누락 발견 및 추가 (Pinpoint APM 활용)
APM 도구 Pinpoint로 특정 조회 API가 약 600ms를 소요하는 것을 발견했습니다. 원인을 추적하니 중복 리뷰 방지용 단순 조회
쿼리(SELECT * FROM review WHERE order_item_id = ?)에서 order_item_id 인덱스가 누락되어 풀 스캔이
발생하고 있었습니다. 인덱스 추가 하나로 해당 API의 응답 속도를 즉각 개선했습니다.
Impact — p90 응답 속도 80% 단축, 알림 소멸
5단계 개선을 통해 p90 응답 속도를 약 80% 단축했습니다. DB 커넥션 풀 고갈 알림과 API 타임아웃 알림이 더 이상 발생하지 않게 되었으며, 각 단계가 단일 수정이 아닌 구조적 원인을 제거하는 방식으로 이루어졌기 때문에 재발 방지 효과도 확보했습니다.
핵심 교훈
- N+1 쿼리는 트래픽이 높아질 때 비로소 드러납니다. n번 API 호출의 문제는 초기에는 눈에 띄지 않다가 사용자가 늘어나는 시점에 급격히 나타났습니다.
- 트랜잭션 범위와 외부 I/O는 분리해야 합니다. 트랜잭션 안에서 외부 호출을 하면 커넥션이 그 시간 동안 점유되어 풀 고갈로 이어질 수 있습니다.
- APM 없이는 병목을 찾기 어렵습니다. 인덱스 누락 문제는 Pinpoint 없이는 발견하기 어려웠을 패턴이었습니다.
정리하며
이 경험은 성능 문제가 단일 원인이 아닌 여러 구조적 결함의 복합적 결과임을 보여줬습니다. 하나를 고치면 다음 병목이 드러나는 과정을 반복하면서, 시스템을 관찰하고 원인을 정확히 파악하는 것이 빠른 수정보다 중요하다는 것을 다시 한번 확인했습니다.