본문으로 이동
캐시를 적용하기 까지의 험난한 길 (TPS 1만 안정적으로 서비스하기)
문서

캐시를 적용하기 까지의 험난한 길 (TPS 1만 안정적으로 서비스하기)

김경윤 · 토스 · 토스 기술 블로그

원본 보기

소개

TPS가 평균 1만, 최대 2만까지 늘어난 약관(terms) 서버에 안정적인 서비스 제공을 위해 캐시를 적용한 이야기를 들려드리려고 해요.

AI 핵심 요약

새 서비스 배포로 약관 서버의 조회 트래픽이 급증해 DB 부하가 커지자, 개인정보 이용 동의의 정확성을 지키면서 부하를 낮추기 위해 Redis와 Look-aside 캐시를 도입했다. Replica의 복제 지연을 피하고, Spring의 `@TransactionalEventListener(AFTER_COMMIT)`로 커밋 후 캐시를 무효화했으며, 무효화 실패 시 Circuit Breaker를 열어 DB 조회로 우회하도록 했다. 커밋 직후 캐시 무효화 전 Kafka 이벤트가 소비되는 경합은 `TransactionSynchronizationManager`와 실행 순서 지정으로 해결하고, 남은 짧은 구간은 API 처리가 완료된 뒤 다음 요청부터 최신 값이 보장된다는 정책으로 정리했다. 그 결과 최대 2만 TPS를 DB에 큰 부하 없이 처리하게 되었으며, 완벽한 코드를 추구하기보다 정책과 모니터링을 함께 활용하는 것이 효과적이라는 교훈을 얻었다.

  • 약관 동의처럼 정확성이 중요한 데이터는 복제 지연이 있는 Replica보다 Strong Consistency를 보장할 수 있는 경로를 우선 고려해야 한다.
  • Look-aside 캐시에서는 DB 변경 후 커밋이 완료된 뒤 캐시를 무효화해야 커밋 전 데이터가 다시 적재되는 상황을 막을 수 있다.
  • 커밋 후 캐시 무효화에 실패하면 Circuit Breaker를 열고 DB 조회로 우회해 오래된 캐시 응답을 차단할 수 있다.
  • 커밋 후 실행되는 작업이 여러 개라면 순서를 명시해 캐시 무효화가 Kafka 이벤트 발행보다 먼저 이뤄지도록 해야 한다.
  • 모든 경합 상황을 코드로 없애기보다 API 처리 완료 시점을 기준으로 정책을 조정하면 더 단순한 해결책을 찾을 수 있다.