"캐시 써보셨죠? 캐시 전략은요?" 면접관의 꼬리 질문에 대비하는 법

TeamGrit·2025년 12월 7일

Interview-Question

목록 보기
4/12
post-thumbnail

안녕하세요, 이력서 기반 면접 준비를 도와드리는 QueryDaily 팀입니다.

혹시 이런 경험 있으신가요?
"Redis 캐시 사용 경험 있으시네요. 캐시 전략은 어떤 걸 사용하셨나요?"
"음... 그냥 자주 조회하는 데이터를 캐시에 넣었는데요..."

캐시를 '사용'한 경험은 있지만, '왜 그 전략을 선택했는지', '다른 전략과의 차이점은 무엇인지' 설명하라고 하면 막막해지는 상황. 면접관은 바로 이 지점을 파고듭니다.

오늘은 면접에서 자주 등장하는 캐시 전략(Caching Strategy) 에 대해 깊이 있게 알아보겠습니다.


캐시(Cache)란?

캐시는 자주 사용되는 데이터를 빠르게 접근할 수 있는 임시 저장소에 보관하는 기술입니다.

[클라이언트] → [애플리케이션 서버] → [캐시(Redis 등)] → [데이터베이스]

왜 캐시를 사용할까요?

  1. 속도: 메모리 기반 캐시(Redis, Memcached)는 디스크 기반 DB보다 10~100배 빠릅니다
  2. 부하 분산: DB에 대한 반복적인 쿼리를 줄여 시스템 전체 부하를 낮춥니다
  3. 비용 절감: DB 스케일업 대신 캐시 레이어 추가로 비용 효율적인 성능 개선이 가능합니다

면접 팁: "캐시를 왜 사용하셨나요?"라는 질문에는 단순히 "빨라서요"가 아닌, 구체적인 수치와 상황을 들어 설명하세요. "상품 목록 조회 API의 평균 응답 시간이 500ms였는데, 캐시 적용 후 50ms로 개선되었습니다" 처럼요.


캐시 전략의 종류

캐시 전략은 크게 읽기 전략쓰기 전략으로 나눌 수 있습니다.

1️⃣ Cache-Aside (Lazy Loading)

가장 널리 사용되는 패턴으로, 애플리케이션이 직접 캐시를 관리합니다.

public Product getProduct(Long productId) {
    // 1. 캐시에서 먼저 조회
    Product cached = cache.get("product:" + productId);

    if (cached != null) {
        return cached;  // Cache Hit!
    }

    // 2. Cache Miss - DB에서 조회
    Product product = productRepository.findById(productId);

    // 3. 캐시에 저장
    cache.put("product:" + productId, product, Duration.ofMinutes(30));

    return product;
}

동작 방식:
1. 캐시에서 데이터 조회 시도
2. Cache Hit → 캐시 데이터 반환
3. Cache Miss → DB 조회 → 캐시에 저장 → 반환

장점:

  • 실제로 요청되는 데이터만 캐시하므로 메모리 효율적
  • 캐시 장애 시에도 DB에서 데이터 조회 가능 (높은 가용성)
  • 구현이 단순하고 직관적

단점:

  • 첫 번째 요청은 항상 Cache Miss (Cold Start 문제)
  • 캐시와 DB 간 데이터 불일치 가능성

2️⃣ Read-Through

Cache-Aside와 비슷하지만, 캐시 라이브러리가 DB 조회를 담당합니다.

// 캐시 설정 시 Loader 정의
Cache<Long, Product> cache = CacheBuilder.newBuilder()
    .expireAfterWrite(30, TimeUnit.MINUTES)
    .build(new CacheLoader<Long, Product>() {
        @Override
        public Product load(Long productId) {
            // Cache Miss 시 자동으로 호출됨
            return productRepository.findById(productId);
        }
    });

// 사용 - 캐시가 알아서 처리
public Product getProduct(Long productId) {
    return cache.get(productId);  // Hit이든 Miss든 동일한 코드
}

Cache-Aside vs Read-Through:

구분Cache-AsideRead-Through
DB 조회 주체애플리케이션캐시 라이브러리
코드 복잡도조건문 필요단순함
유연성높음낮음 (Loader에 의존)

3️⃣ Write-Through

데이터 쓰기 시 캐시와 DB에 동시에 저장하는 전략입니다.

public void updateProduct(Product product) {
    // 1. DB에 저장
    productRepository.save(product);

    // 2. 캐시에도 저장 (동기)
    cache.put("product:" + product.getId(), product);
}

장점:

  • 캐시와 DB의 데이터 일관성 보장
  • 읽기 시 항상 Cache Hit 가능

단점:

  • 쓰기 지연 시간 증가 (DB + 캐시 모두 완료되어야 함)
  • 자주 읽히지 않는 데이터도 캐시에 저장되어 메모리 낭비 가능

4️⃣ Write-Behind (Write-Back)

캐시에만 먼저 쓰고, DB에는 비동기로 나중에 저장하는 전략입니다.

public void updateProduct(Product product) {
    // 1. 캐시에만 먼저 저장 (빠름!)
    cache.put("product:" + product.getId(), product);

    // 2. 비동기로 DB 저장 (배치 처리)
    writeQueue.add(product);  // 별도 스레드가 주기적으로 DB에 flush
}

장점:

  • 쓰기 성능 극대화 (캐시 쓰기만 기다리면 됨)
  • 여러 쓰기 요청을 배치로 묶어 DB 부하 감소

단점:

  • 캐시 장애 시 데이터 유실 위험 ⚠️
  • 구현 복잡도 높음
  • 데이터 일관성 보장 어려움

주의: Write-Behind는 데이터 유실 가능성 때문에 결제, 주문 등 중요 데이터에는 사용하면 안 됩니다.

5️⃣ Write-Around

쓰기는 DB에만, 캐시는 읽기 시에만 갱신하는 전략입니다.

public void updateProduct(Product product) {
    // DB에만 저장
    productRepository.save(product);

    // 캐시는 삭제 (다음 읽기 시 갱신되도록)
    cache.delete("product:" + product.getId());
}

장점:

  • 자주 읽히지 않는 데이터의 캐시 메모리 낭비 방지
  • Write-Through보다 쓰기 성능 우수

단점:

  • 쓰기 직후 읽기 요청 시 Cache Miss 발생

실무에서 자주 마주치는 문제들

1. 캐시 일관성(Cache Consistency) 문제

DB와 캐시의 데이터가 달라지는 상황입니다.

시간순서:
1. [서버 A] 캐시 삭제
2. [서버 B] 캐시 조회 - Miss! - DB 조회 - 옛날 데이터를 캐시에 저장
3. [서버 A] DB 업데이트
→ 결과: 캐시에는 옛날 데이터, DB에는 새 데이터 😱

해결 방법:

  • TTL 설정: 캐시 만료 시간을 짧게 설정하여 불일치 시간 최소화
  • 캐시 무효화: 데이터 변경 시 관련 캐시 즉시 삭제
  • 버전 관리: 데이터에 버전을 부여하여 구버전 캐시 무시

2. 캐시 스탬피드(Cache Stampede)

캐시가 만료되는 순간, 수많은 요청이 동시에 DB로 몰리는 현상입니다.

[캐시 만료] → 1000개 요청 동시에 Cache Miss → DB에 1000개 쿼리 폭탄 💥

해결 방법:

// 1. 뮤텍스 락 사용
public Product getProductWithLock(Long productId) {
    Product cached = cache.get("product:" + productId);
    if (cached != null) return cached;

    // 락 획득 시도
    if (lock.tryLock("product_lock:" + productId)) {
        try {
            // 더블 체크
            cached = cache.get("product:" + productId);
            if (cached != null) return cached;

            Product product = productRepository.findById(productId);
            cache.put("product:" + productId, product);
            return product;
        } finally {
            lock.unlock("product_lock:" + productId);
        }
    }

    // 락 획득 실패 시 잠시 대기 후 재시도
    Thread.sleep(50);
    return getProductWithLock(productId);
}
// 2. 확률적 조기 갱신 (Probabilistic Early Expiration)
// TTL 만료 전에 일정 확률로 미리 갱신
if (remainingTTL < threshold && Math.random() < 0.1) {
    // 10% 확률로 미리 갱신
    refreshCache(productId);
}

3. 핫 키(Hot Key) 문제

특정 키에 트래픽이 집중되어 단일 캐시 노드에 부하가 몰리는 현상입니다.

해결 방법:

  • 로컬 캐시 활용: 핫 키는 각 서버의 로컬 캐시에도 저장
  • 복제본 분산: product:123:replica1, product:123:replica2 등으로 분산
  • 캐시 클러스터 확장: 노드 수 증가

면접 예상 질문

Q1. "Cache-Aside와 Read-Through의 차이점은?"

핵심 포인트: DB 조회 주체가 다릅니다.
Cache-Aside는 애플리케이션이, Read-Through는 캐시 라이브러리가 DB를 조회합니다.

Q2. "캐시 무효화(Invalidation) 전략에는 어떤 것들이 있나요?"

답변 포인트:

  • TTL 기반: 일정 시간 후 자동 만료
  • 이벤트 기반: 데이터 변경 시 즉시 삭제
  • 버전 기반: 데이터 버전 비교로 유효성 판단

Q3. "캐시를 도입했을 때 고려해야 할 트레이드오프는?"

답변 포인트:

  • 일관성 vs 성능
  • 메모리 비용 vs 응답 속도
  • 복잡도 증가 vs 확장성

Q4. "Write-Behind 전략은 언제 사용하면 안 되나요?"

핵심: 데이터 유실이 치명적인 경우 (결제, 주문, 재고 등). 캐시 장애 시 미처 DB에 저장되지 않은 데이터가 사라질 수 있습니다.


정리

전략읽기 성능쓰기 성능일관성적합한 상황
Cache-Aside⭐⭐⭐-보통범용적으로 사용
Read-Through⭐⭐⭐-보통캐시 라이브러리 활용 시
Write-Through⭐⭐⭐⭐⭐높음일관성이 중요한 경우
Write-Behind⭐⭐⭐⭐⭐⭐낮음쓰기 성능이 중요한 경우
Write-Around⭐⭐⭐⭐⭐보통자주 읽히지 않는 데이터

캐시 전략은 정답이 없습니다. 서비스의 특성, 데이터의 성격, 허용 가능한 불일치 시간 등을 종합적으로 고려해서 선택해야 합니다.
면접에서도 "이 전략이 무조건 좋다"가 아닌, "우리 상황에서는 이런 이유로 이 전략을 선택했다" 라고 말할 수 있어야 합니다.


오늘 살펴본 것처럼, 캐시는 단순히 "데이터를 저장하는 곳"이 아닙니다. 어떤 전략을 선택하느냐에 따라 시스템의 성능, 일관성, 복잡도가 크게 달라집니다. 이력서에 "Redis 캐시 적용"이라고 한 줄 적었다면, 그 뒤에 숨겨진 수많은 결정들에 대해 설명할 준비가 되어 있어야 합니다.
QueryDaily와 함께 여러분의 이력서에서 파생될 수 있는 꼬리 질문들을 미리 연습해 보세요.


👉 팀그릿 더 알아보기


#캐시 #Cache #Redis #백엔드면접 #QueryDaily

profile
우리는 당신의 가능성을 믿는 사람들입니다. '되는 사람'이 되는 방법을 이야기합니다.

0개의 댓글