Redis와 함께하는 캐싱: 애플리케이션을 강력하게 가속화하기

발행: (2026년 1월 2일 오후 12:53 GMT+9)
10 min read
원문: Dev.to

Source: Dev.to

TechBlogs

캐싱이란?

Redis에 대해 구체적으로 살펴보기 전에, 캐싱의 기본 원리를 이해하는 것이 중요합니다. 캐싱은 본질적으로 데이터의 일부를 원본보다 더 빠르고 접근하기 쉬운 위치에 저장하는 기술입니다. 목표는 해당 데이터에 대한 이후 요청을 캐시에서 직접 제공함으로써 지연 시간을 크게 줄이고 데이터베이스나 API와 같은 백엔드 시스템에 대한 부하를 감소시키는 것입니다.

인기 상품 목록을 자주 표시하는 웹 애플리케이션을 생각해 보세요. 이 목록을 매번 데이터베이스에 질의하는 대신, 캐싱 메커니즘이 인기 상품 목록을 메모리에 저장할 수 있습니다. 다음에 요청이 들어오면 애플리케이션은 데이터베이스 질의보다 훨씬 빠른 캐시에서 직접 데이터를 가져옵니다.

왜 캐싱에 Redis를 사용할까?

  • 인‑메모리 작동: 번개처럼 빠른 읽기 및 쓰기 작업.
  • 데이터 구조 풍부함: 문자열, 리스트, 집합, 정렬된 집합, 해시 등 다양한 구조를 통해 정교한 캐싱 전략을 구현할 수 있습니다.
  • 영속성 옵션: 선택 가능한 RDB 스냅샷과 AOF 로깅으로 데이터 손실을 방지합니다.
  • 고가용성 및 확장성: Sentinel과 Cluster가 고가용성 및 수평 확장을 제공합니다.
  • 원자성 연산: 경쟁 조건을 방지하고 데이터 무결성을 보장합니다.
  • Pub/Sub 메시징: 캐시 무효화 전략에 유용합니다.

Source:

Redis를 이용한 일반적인 캐싱 전략

1. 캐시 어사이드 패턴

Cache‑Aside(또는 Lazy Loading) 패턴은 널리 사용됩니다. 애플리케이션은 캐시와 기본 데이터 소스 모두와 상호 작용합니다.

작동 방식

  1. 읽기 요청: 애플리케이션이 먼저 캐시를 확인합니다.
  2. 캐시 히트: 데이터를 바로 반환합니다.
  3. 캐시 미스: 기본 소스(예: 데이터베이스)에서 데이터를 가져옵니다.
  4. 캐시 채우기: 가져온 데이터를 향후 요청을 위해 캐시에 저장합니다.
  5. 데이터 반환: 호출자에게 데이터를 전달합니다.

예시 (Python with redis-py)

import redis

r = redis.Redis(host='localhost', port=6379, db=0)

def get_user_data(user_id):
    cache_key = f"user:{user_id}"
    cached_data = r.get(cache_key)

    if cached_data:
        print(f"Cache hit for user {user_id}")
        return cached_data.decode('utf-8')   # Assuming data is stored as a string

    print(f"Cache miss for user {user_id}")
    # Simulate fetching from a database
    user_data_from_db = fetch_user_from_database(user_id)

    if user_data_from_db:
        # Store in Redis with an expiration time (e.g., 3600 s = 1 hour)
        r.set(cache_key, user_data_from_db, ex=3600)
        return user_data_from_db
    return None

def fetch_user_from_database(user_id):
    # In a real application, this would be a database query
    print(f"Fetching user {user_id} from database...")
    return f"User Data for {user_id}"

2. 쓰기‑스루 패턴

쓰기‑스루 패턴에서는 데이터가 캐시와 기본 데이터 소스에 동시에 기록되어 캐시가 항상 최신 상태를 유지합니다.

작동 방식

  1. 쓰기 요청: 애플리케이션이 데이터를 캐시에 기록합니다.
  2. 데이터 소스로 동기식 쓰기: 바로 뒤에 동일한 데이터를 기본 소스에 기록합니다.
  3. 확인: 두 기록이 모두 성공해야 작업이 완료됩니다.

장점

  • 캐시와 데이터 소스 간의 데이터 일관성을 보장합니다.

단점

  • 두 번의 기록이 모두 성공해야 하므로 쓰기 지연 시간이 증가할 수 있습니다.

예시 (Python)

import redis

r = redis.Redis(host='localhost', port=6379, db=0)

def update_user_data(user_id, new_data):
    cache_key = f"user:{user_id}"

    # Write to cache first
    r.set(cache_key, new_data)

    # Then write to primary data source
    update_user_in_database(user_id, new_data)

    print(f"User {user_id} data updated in cache and database.")

def update_user_in_database(user_id, new_data):
    print(f"Updating user {user_id} in database with: {new_data}")

쓰기‑비하인드(쓰기‑백) 패턴

쓰기‑비하인드 패턴은 기본 데이터 소스로의 기록을 연기함으로써 쓰기 성능을 향상시킵니다.

작동 방식

  • 쓰기 요청: 애플리케이션이 데이터를 즉시 캐시에 기록합니다.
  • 비동기식 데이터 소스 쓰기: 캐시가 기록을 큐에 넣고 백그라운드에서, 종종 배치 형태로 기본 저장소에 영구 저장합니다.

장점

  • 쓰기 집중 워크로드에서 쓰기 지연 시간을 크게 감소시킵니다.

단점

  • 백그라운드 기록이 완료되기 전에 캐시 서버가 실패하면 데이터 손실 위험이 높아집니다.
  • 일반 목적 캐싱에서는 덜 일반적입니다.

캐시 무효화

캐시 무효화는 기본 소스가 변경될 때 캐시 내 오래된 데이터를 제거하거나 업데이트합니다.

일반적인 무효화 기법

  • TTL(Time‑To‑Live): 캐시 항목에 만료 시간을 설정합니다.
  • 명시적 무효화: 기본 데이터가 업데이트될 때 캐시 항목을 삭제합니다.
  • 쓰기‑스루/쓰기‑비하인드: 이러한 패턴은 본질적으로 일관성을 유지합니다.
  • 이벤트 기반 무효화: Redis Pub/Sub를 사용해 무효화 메시지를 브로드캐스트합니다.

명시적 무효화 예시 (Python)

def update_and_invalidate_user(user_id, updated_data):
    cache_key = f"user:{user_id}"

    # Update in primary data source
    update_user_in_database(user_id, updated_data)

    # Explicitly invalidate the cache entry
    r.delete(cache_key)
    print(f"User {user_id} data")
 updated and cache invalidated.")

고급 Redis 캐싱 사용 사례

  • 세션 관리: 분산 서버 간에 빠른 검색을 위해 사용자 세션 데이터를 저장합니다.
  • 속도 제한: 만료가 있는 카운터를 사용하여 사용자/IP당 요청을 제한합니다.
  • 큐: 비동기 백그라운드 작업을 위한 작업 큐를 구현합니다.
  • 리더보드: 실시간 순위 시스템을 위해 정렬된 집합을 활용합니다.
  • 전체 페이지 캐싱: 전체 HTML 페이지를 캐시하여 서버 측 렌더링 없이 제공합니다.

Redis 캐싱 모범 사례

  • 올바른 데이터 구조 선택: 객체에는 해시, 큐에는 리스트, 순위표에는 정렬된 집합 등을 사용합니다.
  • 적절한 TTL 적용: 데이터 변동성 및 허용 가능한 오래됨 정도에 맞춰 만료 시간을 설정합니다.
  • 캐시 성능 모니터링: 히트 비율, 지연 시간, 메모리 사용량, 그리고 eviction 정책을 추적합니다.
  • 캐시 미스에 우아하게 대응: 필요 시 기본 데이터 소스로 정상적으로 폴백하도록 합니다.
  • 데이터 직렬화 고려: 복합 타입에는 JSON이나 MessagePack과 같은 효율적인 포맷을 사용합니다.
  • Eviction 정책 설정: 메모리 압박을 관리하기 위해 LRU, LFU, volatile‑lru 등 정책을 이해하고 적용합니다.
  • 네트워크 지연 최소화: 가능하면 Redis를 애플리케이션 서버와 같은 위치에 배치합니다.

결론

Redis는 고성능 캐싱 레이어를 구축하기 위한 견고하고 유연한 기반을 제공합니다. Cache‑Aside 및 Write‑Through와 같은 패턴을 이해하고 적용함으로써 지연 시간을 크게 줄이고, 데이터베이스 부하를 낮추며, 전반적인 응답성을 향상시킬 수 있습니다. 사용 사례에 가장 적합한 전략을 실험해 보고, Redis가 여러분의 소프트웨어를 슈퍼차지하도록 하세요.

Back to Blog

관련 글

더 보기 »