Skip to content

[Performance] Cache 기능을 통해 읽기 전용(Read-Only) 데이터 조회 성능 최적화 #10

@crossbell8368

Description

@crossbell8368

1. 문제상황

콘텐츠 데이터(Wish, Todo)가 증가할수록, 서비스의 핵심인 홈 화면과 목록 조회 API에서 지연 현상이 확인됨

- 데이터 조회 흐름

  • Wishes(N) → Todos(M)

- 원인

  • 연관 API들은 '자주 변경되지는 않지만(Write-Light), 매우 빈번하게 조회되는(Read-Heavy)' 읽기 전용 데이터를 처리함
  • 기능 완성에 따라, 실제 데이터(10개의 카테고리, 카테고리별 10개의 Wish, Wish별 최소 3개의 Todo) 주입
  • 매 요청마다 디스크 기반의 DB(PostgreSQL)에 접근하는 I/O 비용 자체를 원인으로 판단됨

2. 해결과정

읽기 전용 데이터임을 감안하여 디스크보다 월등히 빠른 인메모리(In-Memory) 캐싱을 도입하기로 결정
이미 사용자 인증 토큰 관리를 위해 Redis를 사용하고 있었기에, 별도의 인프라 구축 없이 기존 Redis를 캐시 저장소로 확장

- Caching 전략

  • 서비스 시작 시, 자주 사용되는 핵심 데이터(Category, Wish 목록 등)를 미리 DB에서 조회하여 Redis에 업로드
  • API 요청 시에는, 먼저 Redis를 조회하고 데이터가 없을 경우(Cache Miss)에만 DB에 접근하여 데이터를 가져온 뒤, Cache 적용

3. 관련 코드 및 Commit

  1. 요청 시 먼저 Cache를 확인 (Cache Hit 시 즉시 반환)
  2. (Cache Miss 시) Lock을 획득하여 다수의 Cache 및 DB 접근 요청을 제한
  3. 이후 Cache를 다시 한번 확인하여 불필요한 DB 조회를 방지
  4. Cache가 여전히 비어있을 경우에만 DB에서 데이터를 조회하고, 그 결과를 Cache에 저장
@Transactional(readOnly = true)
    public List<HomeChallengeData> getHomeRanking(String userId) {

        // 1. fetch data from cache: Cache에서 먼저 데이터를 조회
        List<HomeChallengeData> challengeListFromCache = getChallengeDataFromCache(userId);
        if (!challengeListFromCache.isEmpty()) {
            return challengeListFromCache;
        }

        // 2. fetch data from db(cache miss): Cache에 데이터가 없을 경우, DB에서 데이터를 조회
        // Cache 업데이트를 시 데이터 정합성을 위해 Cache 접근 제한 및 응답 대기 상태로 전환
        cacheLock.lock();
        try {

            // 2-1. check cache again: Cache 업데이트 전, 데이터 재확인
            challengeListFromCache = getChallengeDataFromCache(userId);
            if (!challengeListFromCache.isEmpty()) {
                return challengeListFromCache;
            }

            // 3. get data from db: DB에서 조회한 데이터를 반환 (Cache 업데이트 포함)
            logger.warn("[HomeTrans][{}] Challenge at cache is still empty after acquiring lock. Fetching from DB...", userId);
            return getChallengeDataFromDB(userId);

        } finally {
            // Cache 업데이트가 완료된 이후, Cache 접근을 다시 허용
            cacheLock.unlock();
        }
    }

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions