Redis 캐싱했는데도 느렸던 이유, RTT가 숨은 범인이었다

2025. 4. 22. 23:34·트러블슈팅

 

TL;DR

거래대금 상위 50개 기업 조회 API가 임시 테이블 정렬로 인해 10초 이상 걸리자 redis에 캐싱해 3초까지 줄였지만 실시간 주가 50회 개별 조회가 RTT 70 ms씩 추가돼 병목이 남았다. redis 파이프라인을 활용한 벌크 조회로 redis 요청을 50→1회로 줄여 최종 응답을 200 ms 이하로 단축했고, 병목은 항상 ‘가장 느린 부분’을 찾아야 한다.

 

최근 신규 서비스를 준비하며 신규 API를 개발하는 일이 많았는데, 그중 API 성능을 높이기 위해 Redis 캐시를 도입하여 성능 개선을 시도했으나 기대한 만큼 개선 효과를 보지 못한 일이 발생하였습니다.

 

이번 글에서는 해당 성능 이슈의 원인을 알아보고, 병목 지점을 분석하여 API 성능을 개선한 해결방안을 정리해보려고 합니다.

 

문제 상황

특정 기간의 거래대금 상위 50개 기업을 조회하는 API의 응답 시간이 평균 10초 이상으로 매우 느린 문제가 있었습니다. 이는 당연히 서비스를 이용하는데 매우 큰 영향을 미치는 문제였습니다.

 

원인 분석

인덱스 활용 검토

처음에는 조회 쿼리가 문제가 있을것이라 판단하고 거래대금 상위 50개 기업을 조회하는 쿼리의 실행계획을 분석하였습니다.
특정 기간 동안 각 기업의 거래대금을 합산한 후, 거래대금 순으로 정렬하는 과정에서 성능 병목이 발생했는데 이 과정에서 데이터가 기업코드 기준으로 GROUP BY 된 후 임시 파일(Temporary table)에 기록되어 정렬되기 때문에, 인덱스를 활용하기 어렵다는 점이 원인이었습니다.

 

거래대금을 기간별로 GROUP BY → SUM() → ORDER BY → LIMIT 을 수행하는 쿼리에서 정렬 비용이 컸습니다.

 

 

실행계획 사진을 보면 정렬이 임시 디스크를 사용하기 때문에 인덱스르 활용할 수 없어, 캐시를 적용해야겠다고 판단했습니다.

 

redis 캐시 적용

DB 레벨에서 쿼리 개선이나 인덱스를 활용하여 성능 조회를 높일 수 없기 때문에, Redis에 캐싱하여 DB 부하 감소 및 API 조회 속도 개선을 진행했습니다.

 

캐시 설계는 다음과 같이 진행했습니다

 

캐시 키 구조

  • 환경 → 서비스 → 버전 → 메소드 파라미터 순으로, 변경 가능성이 낮은 순서로 키를 구성했습니다.
  • 예시: api-local:cache:top-trade-value::V1_KOSPI_1Y

캐시 업데이트 전략

  • 거래대금 순위 데이터는 장 마감 후에 업데이트가 필요하므로, 거래 데이터를 담고 있는 테이블 업데이트 시점에 캐시도 갱신하도록 설정했습니다.
  • 특히, 당일 주가 데이터 업데이트가 완료된 이후에만 캐시가 업데이트되도록, 최근 거래 날짜와 현재 날짜를 비교하는 조건을 추가했습니다.

결과적으로, API 조회 시점에는 @Cacheable 어노테이션을 사용해 Redis에서 캐시 데이터를 먼저 조회하고, 주기적 갱신에는 @CachePut을 이용하여 데이터를 최신으로 유지했습니다.

 

추가 병목 지점: 네트워크 왕복이 진짜 병목이었다

그런데 캐시를 적용한 후에도 API 응답 시간이 여전히 평균 3초 이상 소요되는 것을 확인했습니다.

 

추가적으로 분석한 결과, 거래대금 상위 기업을 조회하면서 각 기업의 실시간 주가를 Redis에서 조회하는 부분에서 병목이 발생하는 것을 발견했습니다.
장 중에 거래대금 상위 50개 기업을 조회하여 응답할 때, 해당 기업의 실시간 주가를 응답해주어야 하는데 이 실시간 값을 얻기 위해 또 한 번 redis에 접근하여 값을 조회해야 했습니다.


이 부분에서 병목이 발생하는 것을 발견했습니다.

 

왜 Redis가 느렸을까? 두 가지가 핵심이었습니다.

 

1. 거래대금 상위 50개 기업의 실시간 주가를 Redis에서 개별적으로 조회하는 방식으로 구현되어 있었습니다. (50개 키를 순차적으로 GET)
2. Redis에서 개별 조회 시 한 번 요청당 약 70ms가 소요되었습니다.

 

이로 인해 인해 총 50 × 70ms = 3,500ms 정도의 추가 지연이 발생한 것입니다.

 

일반적으로 redis는 단일 키 조회할 때 1ms 미만의 속도로 조회해오는데, 현재 한 개의 기업의 실시간 주가를 조회해 올 때 평균적으로 70ms 가 소요되는 문제가 있었습니다.

 

이 문제를 해결하면 개별 조회하여 50번 조회하여도 50ms 이기 때문에 1번은 큰 문제가 되지 않습니다.
하지만 redis 조회 시 값 응답 자체는 매우 빨랐기에 네트워크 통신 시 발생하는 문제라고 생각헀습니다. (실제 값이 차지하는 메모리도 10kb 이하)

Redis 지연 현상을 분석하기 위해, Redis 네트워크 응답 시간을 측정했습니다

redis-cli --latency
min: 70ms, max: 102ms, avg: 70ms

 

요청-응답 간의 네트워크 왕복 (RTT)이 큰 병목 요인이라는 사실을 알게 되었습니다. 50번의 개별 요청이 이루어지며 매번 RTT가 발생했기 때문에 병목이 심화되었습니다.

이를 해결하기 위해, 개별 조회 방식에서 Redis의 벌크 조회 방식(파이프라인 + HGETALL)으로 변경하여 한 번의 요청으로 50개의 기업 실시간 주가 데이터를 조회하도록 개선했습니다.


이러한 변경을 통해 Redis에 대한 네트워크 요청 횟수를 50회에서 1회로 줄였고, 결과적으로 Redis 조회 성능을 개선할 수 있었습니다.

 

정리

redis 벌크 조회 방식을 적용한 이후, API의 평균 응답 시간이 기존의 10초 이상 → 3초 → 200ms 이하로 단계적으로 감소하여, 사용자 경험을 크게 개선할 수 있었습니다.

이번 트러블 슈팅을 통해 성능 개선을 위해서는 단순히 캐시 적용뿐 아니라, 네트워크 비용도 고려하여 개선해야 한다는 점을 배웠습니다.

 

'트러블슈팅' 카테고리의 다른 글

아무리 스레드를 늘려도 성능 개선에 소용없던 이유 (MySQL CPU 사용률 99%)  (0) 2025.04.04
멀티 스레드로 병렬 처리하는것은 항상 성능에 이점이 있을까?  (0) 2025.01.29
LLM 기반 보고서 자동 요약 프롬프트 최적화 전략 (내용 누락 트러블 슈팅)  (0) 2024.11.27
주식 시황 피드의 종가 오류 해결기(데이터 일관성 개선)  (0) 2024.07.30
Confluent Schema Reference 관련 문제  (0) 2024.07.24
'트러블슈팅' 카테고리의 다른 글
  • 아무리 스레드를 늘려도 성능 개선에 소용없던 이유 (MySQL CPU 사용률 99%)
  • 멀티 스레드로 병렬 처리하는것은 항상 성능에 이점이 있을까?
  • LLM 기반 보고서 자동 요약 프롬프트 최적화 전략 (내용 누락 트러블 슈팅)
  • 주식 시황 피드의 종가 오류 해결기(데이터 일관성 개선)
seungjjun
seungjjun
  • seungjjun
    개발이야기
    seungjjun
  • 전체
    오늘
    어제
    • 분류 전체보기
      • 성장이야기
        • TIL
        • 주간회고
      • Java
        • Spring
        • Spring Security
      • 트러블슈팅
      • Kafka
      • OS
      • Network
      • 메가테라
      • Database
      • Algorithm
      • Git
      • HTML
      • CSS
      • 독서
      • 컴퓨터 이해하기
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    메가테라 주간회고
    개발일지
    메가테라
    redis
    Til
    주간회고
    이커머스 프로젝트
    항해99
    graphQL
    항해플러스
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
seungjjun
Redis 캐싱했는데도 느렸던 이유, RTT가 숨은 범인이었다
상단으로

티스토리툴바