1. 들어가며
이전 글에서는 어떠한 데이터를 캐싱할 것인지, 그리고 캐시된 데이터를 언제 만료시킬 것인지에 대한 이론적 배경을 살펴보았다. 이번 글에서는 실제 백엔드 서버에서 데이터를 어떠한 방식으로 캐시에 저장하고 불러오는지, 즉 데이터 캐싱 패턴(Caching Patterns)의 구체적인 메커니즘을 상세히 분석한다.
2. 데이터 읽기 전략 (Read Strategies)
캐싱 전략은 크게 데이터의 읽기(Read)와 쓰기(Write) 동작에 따라 구분된다. 먼저 조회 시 캐시를 어떻게 활용할 것인가에 대한 읽기 전략을 알아본다.
2.1. Cache Aside (Lazy Loading)

Cache Aside 패턴은 읽기 전략 중 가장 널리 사용되는 방식이다. 데이터가 필요한 시점에만 캐시에 적재한다는 특징 때문에 'Lazy Loading'이라고도 불린다.
- 동작 방식:
- 애플리케이션이 먼저 캐시에서 데이터를 조회한다.
- Cache Hit: 데이터가 있다면 즉시 반환한다.
- Cache Miss: 데이터가 없다면 DB에서 데이터를 조회한 후, 이를 캐시에 저장하고 반환한다


2.2 Read Through

Read Through는 캐시 미스 발생 시 애플리케이션이 직접 DB에 접근하는 대신, 캐시 시스템(미들웨어)이 자동으로 DB에서 데이터를 조회하여 캐시에 적재하는 방식이다.
- 동작 방식:
- 애플리케이션이 캐시에서 데이터를 조회한다.
- 캐시에 데이터가 없다면 캐시 시스템이 직접 DB에서 데이터를 가져와 캐시에 저장한 후 애플리케이션에 반환한다.
특징: 애플리케이션 코드에서 DB 조회 로직을 분리할 수 있어 코드가 단순해지지만, 캐시 시스템에 대한 의존도가 높아진다.


2.3. Cache Aside vs. Read Through
| 비교 항목 | Cache Aside | Read Through |
| 캐시 미스 처리 | 애플리케이션이 직접 DB 조회 및 캐시 저장 | 캐시 시스템이 DB 조회 및 캐시 저장 수행 |
| 장점 | 커스터마이징 용이, 흐름 제어 가능 | 코드 단순화, 미들웨어에 책임 위임 |
| 단점 | 캐시 로직을 직접 작성해야 함 | 캐시 시스템에 로직 위임으로 유연성 부족 |
실무에서 Cache Aside를 더 선호하는 이유는 흐름의 제어권 때문이다. 아래와 같은 복잡한 조건부 캐싱(특정 데이터 제외, 시간 개별 설정 등)을 애플리케이션 레벨에서 세밀하게 조정하기에 훨씬 유리하다.
1) "이건 절대 캐시하면 안 됨"
2) "이건 5분, 이건 10초만 캐싱"
3) "조회 로그도 남겨야 함"
3. 데이터 쓰기 전략 (Write Strategies)
데이터를 저장하거나 수정할 때 캐시와 DB의 일관성을 어떻게 유지할 것인지에 대한 전략이다.
3.1. Write Around

가장 기본적인 패턴으로, 모든 쓰기 요청은 DB에만 반영하고 캐시는 건드리지 않는다. 데이터가 조회될 때(Cache Aside 등) 비로소 캐시에 기록된다.

3.2. Write Through

데이터를 저장할 때 캐시와 DB에 동시에 저장하는 방식이다.
- 장점: 캐시가 항상 최신 데이터를 유지하여 데이터 일관성이 높다.
- 단점: 매 쓰기마다 두 번의 저장 작업이 발생하여 성능 오버헤드가 있다.

3.3. Write Back (Write Behind)

쓰기 요청을 캐시에 먼저 반영하고, DB에는 일정 주기나 특정 조건에 따라 비동기적으로 저장하는 방식이다.
- 장점: 쓰기 성능이 매우 뛰어나다 (대량의 로그, 센서 데이터 등).
- 단점: 캐시 시스템 장애 시 DB에 기록되지 않은 데이터가 유실될 위험이 있다.

4. 데이터 캐싱 패턴의 전략적 조합
실제 서비스 환경에서 모든 요구사항을 완벽하게 충족하는 단일 캐싱 전략은 존재하지 않는다. 각 전략은 고유의 설계 목적과 장단점이 뚜렷하기 때문이다. 따라서 백엔드 시스템에서는 각 전략의 장점을 극대화하고 단점을 보완하기 위해 여러 패턴을 조합하여 사용한다. 어떤 조합을 선택할지 결정할 때는 다음과 같은 비즈니스 및 기술적 요건을 우선적으로 체크해야 한다.
데이터 조회 및 변경 빈도: 읽기 위주인가, 쓰기 위주인가?
변경 시점의 예측 가능성: 서버가 데이터가 언제 바뀌는지 알 수 있는가?
데이터 일관성 요구 수준: 약간의 지연(Stale data)을 허용할 수 있는가?
응답 시간 민감도: 성능 향상이 정합성보다 우선되는가?
4.1. 읽기 위주 + 변경이 드문 데이터의 조합
전략 조합: Cache Aside (읽기) + Write Around (쓰기) + TTL
예시: 국가별 공휴일 목록
동작 메커니즘:
- 읽기: 캐시를 우선 조회하고, 미스 발생 시 DB에서 가져와 캐시에 저장한다.
- 쓰기: DB에만 데이터를 반영하고 캐시는 건드리지 않는다.
- 만료: 설정된 TTL에 의해 일정 시간 후 캐시가 자동 삭제된다.

한계점: 데이터 불일치(Stale Data) 이 조합은 구현이 단순하고 효율적이나, TTL이 만료되기 전까지는 오래된 데이터가 반환되는 치명적인 한계가 있다. DB의 값이 바뀌었음에도 캐시에는 과거의 값이 남아있기 때문이다.

4.2. 읽기 위주 + 변경이 드문 데이터 + 실시간 변경 반영이 중요 (⭐⭐)
전략 조합: Cache Aside (읽기) + Write Around (쓰기) + TTL + 명시적 무효화
예시: 이벤트 상세 정보
개선된 동작 메커니즘:
- 쓰기 발생 시 DB에 먼저 저장한 후, 해당 키를 캐시에서 즉시 삭제한다.
- 이후 첫 읽기 요청 시 캐시 미스가 발생하여 DB로부터 최신 데이터를 다시 로드하게 된다.

4.3. 동시성 상황에서의 일관성 파괴 시나리오
명시적 무효화를 도입하더라도 찰나의 순간에 발생하는 동시성 이슈를 완벽히 해결할 수는 없다. 다음은 대표적인 두 가지 훼손 사례이다.
사례 1: 무효화 지연으로 인한 이전 값 반환
변경 요청 직후 무효화가 완료되기 전, 아주 짧은 찰나에 조회 요청이 들어오면 유저는 여전히 Old 데이터를 받게 된다.

사례 2: 캐시 오염 (Cache Pollution)
조회 작업과 무효화 작업의 순서가 뒤바뀌어 DB에는 최신 값이, 캐시에는 다시 과거의 값이 저장되는 현상이다.

4.4. 결론 및 요약
조합을 통해 성능과 일관성을 조율할 수는 있지만, 분산 환경에서의 동시성 상황은 언제나 예측 불가능한 순서로 요청을 처리한다.
- 단순 조합의 한계: TTL과 명시적 무효화는 일관성을 향상시키지만 완벽히 보장하지는 못한다.
- 동시성 제어의 필요성: 이러한 미세한 불일치조차 허용할 수 없는 서비스라면, 단순 전략 조합을 넘어 락(Lock)과 같은 물리적인 동시성 제어 메커니즘을 병행하여 순차적 처리를 강제해야 한다.
결국 기술적 의사결정은 "어느 정도의 일관성을 위해 어느 정도의 성능 비용을 감수할 것인가"라는 질문에 대한 답을 찾아가는 과정이다.
5. 동시성 제어의 최종 병기: 락(Lock)
여러 전략을 조합하더라도 분산 환경에서의 미세한 타이밍 차이로 인한 데이터 불일치는 피하기 어렵다. 만약 서비스의 요구사항이 극도로 엄격하여 이러한 찰나의 불일치조차 허용할 수 없다면, 가장 확실한 해결책인 락(Lock)을 도입해야 한다.
5.1. 락(Lock)을 통한 일관성 보장 원리
동시성 제어 관점에서 락은 가장 직관적이면서도 강력한 도구이다. 락을 사용하면 특정 데이터에 대해 동시에 접근하려는 여러 흐름을 순차적(Serialized)으로 제어할 수 있다. 이를 통해 데이터가 변경되는 도중에 잘못된 상태가 조회되거나, 캐시가 이전 데이터로 오염되는 문제를 원천적으로 방지한다. 앞서 살펴본 Cache Aside(읽기) + Write Around(쓰기) + TTL + 명시적 무효화 조합에 락을 추가했을 때의 동작 과정을 살펴보자.
5.2. 락 기반 동시성 제어 메커니즘 분석
다음은 락을 통해 데이터 변경과 조회의 순서를 강제하는 과정을 나타낸 다이어그램이다.

상세 단계별 동작:
- 유저 A가 데이터 수정을 위해 락을 획득하고 DB의 값을 변경한다.
- 유저 B가 동시에 조회를 시도하지만, 유저 A가 락을 쥐고 있으므로 대기 상태(Blocking)가 된다.
- 유저 A가 캐시를 무효화한 후 락을 해제한다.
- 이제 유저 B가 락을 획득하고 캐시를 조회한다. 캐시는 비어 있으므로 미스가 발생한다.
- 유저 B가 DB에서 최신값(New)을 읽어온다.
- 유저 B가 캐시에 최신값을 저장하고 락을 해제한다.
이 과정을 거치면 데이터 변경 중간에 조회가 끼어들어 발생하는 '캐시 오염' 현상을 완벽하게 차단할 수 있다.
5.3. 성능과 일관성의 끝없는 줄타기 (Trade-off)
락은 일관성을 보장하는 가장 강력한 수단이지만, 공짜는 아니다. 락의 도입은 필연적으로 성능 저하라는 비용을 요구한다.
- 성능적 손실: 캐시를 사용하는 핵심 목적은 빠른 응답 속도와 높은 처리량이다. 그러나 락을 사용하면 동시 요청이 많아질수록 락 경쟁(Lock Contention)이 심화되어 요청 대기 시간이 길어지고 시스템 전체의 처리량이 떨어진다.
- 복잡성 증가: 분산 환경에서는 분산 락(Distributed Lock) 시스템을 별도로 운영해야 하며, 데드락(Deadlock) 같은 문제에 대한 세심한 관리가 필요하다.
주의 사항: 모든 캐시에 락을 거는 것은 캐시를 사용하는 이유 자체를 무색하게 만들 수 있다. 따라서 락 적용 여부는 단순히 "일관성이 필요한가?"가 아니라, "어느 수준의 일관성을 위해 어느 정도의 성능 손실을 감수할 것인가?"를 기준으로 판단해야 한다.
'Database > Redis' 카테고리의 다른 글
| [Optimization-5] Remote 캐싱의 필요성 (0) | 2025.12.28 |
|---|---|
| [Optimization-4] 로컬 캐싱(Local Caching)의 이해와 전략 (0) | 2025.12.28 |
| [Optimization-2] 캐싱 개념 (2) (0) | 2025.12.28 |
| [Optimization-1] 캐싱 개념(1) (0) | 2025.12.28 |
| [Basic-4] 세션 관리 (0) | 2025.07.03 |
