4. 읽기-통과 캐시의 캐시 스탬피드(thundering herd)

난이도 상 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C#
// ============================================================================
// [코드리뷰 문제] C# - 읽기-통과(read-through) 캐시의 TTL 만료와 동시 미스
// ----------------------------------------------------------------------------
// 시나리오 (영속화/캐시 일관성):
//   - 길드 랭킹은 계산이 비싸(여러 테이블 집계) 결과를 분산 캐시(Redis)에 TTL 30초로 둔다.
//   - 요청이 오면 캐시를 보고, 없으면 DB 에서 계산해 캐시에 채운 뒤 돌려준다.
//   - 인기 길드의 랭킹 키는 초당 수천 건씩 조회된다.
//
// 과제:
//   이 코드의 잠재적 문제를 모두 찾고, 인기 키의 TTL 이 만료되는 순간 동시에 수천 요청이
//   들어올 때 DB 와 응답 지연이 어떻게 되는지 설명하라. 또 존재하지 않는 키가 반복 조회될
//   때의 동작도 논하라. 수정안과 더 나은 설계를 제시하라.
//   (먼저 직접 리뷰를 적은 뒤 answer.md 와 대조할 것)
// ============================================================================

using System;
using System.Threading.Tasks;

public class Ranking { /* ... 집계 결과 ... */ }

public interface IDistributedCache
{
    Task<byte[]> GetAsync(string key);
    Task SetAsync(string key, byte[] value, TimeSpan ttl);
}

public interface IRankingDb
{
    Task<Ranking> ComputeRankingAsync(string key);   // 비싼 집계 쿼리
}

public class RankingCache
{
    private static readonly TimeSpan Ttl = TimeSpan.FromSeconds(30);
    private readonly IDistributedCache _cache;
    private readonly IRankingDb        _db;

    public RankingCache(IDistributedCache cache, IRankingDb db) { _cache = cache; _db = db; }

    public async Task<Ranking> GetAsync(string key)
    {
        var cached = await _cache.GetAsync(key);          // (A) 캐시 조회
        if (cached != null)
            return Deserialize(cached);

        var data = await _db.ComputeRankingAsync(key);    // (B) 비싼 재계산(DB)
        await _cache.SetAsync(key, Serialize(data), Ttl); // (C) 캐시 채움 (고정 TTL)
        return data;
    }

    private static byte[]  Serialize(Ranking r)  => Array.Empty<byte>();
    private static Ranking Deserialize(byte[] b) => new Ranking();
}
내 리뷰 · C#
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.