서버의 응답 속도는 사용자 경험에 직결되는 중요한 요소이다. 성능을 개선하기 위한 방법 중 하나로 캐시(Caching)가 널리 사용된다. 캐시는 동일한 요청에 대한 반복적인 연산이나 I/O를 줄여 처리 속도를 향상시킨다. 그러나 캐시가 항상 최신 데이터를 제공하지 않을 수 있으므로, 캐시 무효화(Cache Invalidation) 전략이 필수적이다.
캐시는 자주 사용되는 데이터를 미리 저장하여 데이터 저장소(DB, API 등)에 직접 접근하지 않고 빠르게 응답하는 기술이다.
캐시는 보통 다음과 같은 저장소에서 사용된다:
캐싱에 적합한 데이터는 다음과 같은 특성을 가진다:
캐시의 핵심 과제는 일관성(consistency) 유지다. 원본 데이터가 변경되었는데 캐시가 갱신되지 않으면 잘못된 정보를 제공할 수 있다. 이를 방지하기 위해 다양한 캐시 무효화 전략이 사용된다.
캐시 항목에 유효 시간을 지정해 일정 시간이 지나면 자동으로 삭제되도록 설정하는 방식이다.
# Redis 예시
redis.set('key', 'value', ex=300) # 300초 후 만료
데이터 변경 시 애플리케이션 로직에서 캐시를 명시적으로 삭제하거나 갱신한다.
# Django cache 예시
cache.delete(f"user_profile:{user_id}")
DB와 캐시에 동시에 데이터를 쓰는 방식이다.
# Write-Through 방식 예시
db.save(user)
cache.set(f"user:{user.id}", user)
Write-Behind와 Write-Back은 쓰기 성능을 최적화하기 위해 비동기적 접근 방식을 사용하는 캐시 무효화 전략이다.
Write-Behind: 데이터를 즉시 캐시에 쓰고, 원본 데이터 소스(예: 데이터베이스)는 백그라운드에서 비동기적으로 나중에 업데이트한다.
Write-Back: 데이터를 캐시에만 쓰고, 원본 데이터 소스에 대한 업데이트는 특정 트리거(예: 캐시 제거, 명시적 플러시, 시스템 체크포인트)나 이벤트가 발생할 때까지 지연시킨다.
장점:
단점:
# Write-Behind 예시
cache.set(f"user:{user.id}", user)
async_task(db.save, user) # 비동기적으로 DB에 저장
# Write-Back 예시
cache.set(f"user:{user.id}", user) # 캐시에만 쓰기
if cache_full: # 특정 조건(예: 캐시 가득 참)에서 DB 업데이트
db.save(cache.get_all_modified())
데이터를 캐시에 쓰지 않고 바로 DB에 저장한다. 캐시는 읽기 전용으로 사용된다.
자주 사용되지 않는 데이터의 캐시 오염을 방지한다.
# Write-Around 방식 예시
db.save(user) # 캐시에는 저장하지 않고 DB에 직접 저장
주기적으로 캐시된 데이터를 백그라운드에서 갱신하는 방식이다.
데이터를 요청하는 시점에서 유효성 검사를 수행하고, 유효하지 않으면 그 때 데이터를 갱신하는 방식이다.
데이터 요청 시 캐시에 값이 없으면 DB에서 읽어오고, 이후 캐시에 저장하는 방식이다. Lazy Invalidation과 자주 함께 사용된다.
# Cache-aside 예시
data = cache.get(key)
if data is None:
data = db.get(key)
cache.set(key, data)
캐시는 원본 데이터와 달리 임시 저장된 데이터로, 원본 데이터가 변경시 캐시는 더 이상 최신 상태를 보장하지 못하는 Stale Data(오래된 데이터) 문제가 발생할 수 있다.
이 문제를 해결하기 위해서는 캐시의 유효성을 판단하고, 오래되었거나 변경된 경우 이를 무효화하거나 갱신하는 전략이 필요하다.
캐시 생성 시 유효 기간(Time-To-Live)을 설정하여 자동 만료시킨다.
캐시 데이터의 메타데이터(예: last_updated
)를 원본과 비교하여 유효성을 확인한다.
데이터 변경 시 캐시를 즉시 삭제 또는 갱신한다.
장점: 즉각적인 일관성 보장.
단점: 구현 복잡, 누락 위험.
if user_updated:
cache.delete(f"user:{user.id}")
백그라운드에서 주기적으로 캐시를 갱신한다.
데이터 조회 시점에 캐시 유효성을 검사하고 필요 시 무효화 및 갱신
캐시 키에 버전 정보를 포함하여 데이터 변경 시 자연스럽게 무효화한다.
전략 | 장점 | 단점 | 주요 사용처 |
---|---|---|---|
TTL 기반 | 단순 구현, 자동 만료 | 적절한 TTL 설정 어려움, stale data 가능성 | 변동이 적은 데이터 |
Freshness Verification | 최신성 보장 | 검증 오버헤드 발생 | 중요 데이터, 신선도 엄격 요구 시 |
Active Invalidation | 즉시 무효화로 일관성 보장 | 구현 복잡, 누락 위험 | 변경 이벤트가 명확한 환경 |
Cache Refresh | stale data 감소 | 불필요한 갱신으로 리소스 낭비 | 대량 데이터, 주기적 갱신 가능한 경우 |
Lazy Invalidation | 불필요 갱신 최소화 | 조회 시 지연 가능 | 응답 지연 허용 가능, 데이터 변동이 적은 경우 |
버전 관리 | 의존성 관리 용이 | 구현 복잡 | 복잡한 캐시 구조, 분산 환경 |
Write-Through | 즉시 일관성 보장 | 쓰기 성능 저하 가능 | 높은 일관성 요구 환경 |
Write-Back (Write-Behind) | 빠른 쓰기 성능, 배치 처리 가능 | 데이터 유실 위험, 지연에 따른 일관성 문제 | 쓰기 성능 최적화가 중요한 환경 |
Write-Around | 캐시 오염 방지, 메모리 효율적 사용 | 캐시 히트율 저하 가능 | 드물게 접근되는 데이터 |
다음과 같은 경우에는 캐시가 오히려 문제를 일으킬 수 있다:
캐시는 서버 성능 최적화에 유용하지만, 잘못된 전략은 데이터 불일치, 디버깅 어려움, 성능 저하를 초래할 수 있다. 캐시 무효화 전략은 시스템 일관성과 안정성을 유지하는 핵심이다. 데이터 특성과 요구사항에 따라 TTL, Write-Through, Write-Back, Write-Around, Cache-Aside 등을 적절히 혼용하여 최적의 성능과 일관성을 확보해야 한다.