Python 데코레이터로 API 응답 캐싱 구현하기

문제 상황

재택근무로 전환되면서 사내 대시보드 사용량이 급증했다. 특히 외부 API를 호출하는 통계 페이지에서 병목이 발생했다. 같은 파라미터로 반복 호출되는 케이스가 많았지만, 매번 API를 호출하고 있었다.

해결 방법

데코레이터를 활용해 함수 레벨에서 캐싱을 적용하기로 했다. Redis를 캐시 저장소로 사용했다.

import json
import hashlib
from functools import wraps
import redis

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

def cache_response(expire_seconds=300):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # 함수명과 인자로 캐시 키 생성
            key_data = f"{func.__name__}:{args}:{kwargs}"
            cache_key = hashlib.md5(key_data.encode()).hexdigest()
            
            # 캐시 확인
            cached = redis_client.get(cache_key)
            if cached:
                return json.loads(cached)
            
            # 캐시 미스 시 실제 함수 실행
            result = func(*args, **kwargs)
            redis_client.setex(
                cache_key,
                expire_seconds,
                json.dumps(result)
            )
            return result
        return wrapper
    return decorator

사용 예시:

@cache_response(expire_seconds=600)
def fetch_user_stats(user_id, date_from, date_to):
    response = external_api.get_stats(
        user_id=user_id,
        from_date=date_from,
        to_date=date_to
    )
    return response.json()

결과

  • 평균 응답 시간: 1.2초 → 0.35초
  • 외부 API 호출량 67% 감소
  • 캐시 히트율 약 73%

추가 고려사항

초기에는 functools.lru_cache를 고려했으나, 멀티 프로세스 환경에서 캐시 공유가 안 되는 문제가 있었다. Redis를 사용하면서 여러 워커 간 캐시 공유가 가능해졌다.

다만 Redis 장애 시 fallback 로직이 없어서, 다음 스프린트에 에러 핸들링을 추가할 예정이다.

Python 데코레이터로 API 응답 캐싱 구현하기