프로그래밍/Python

🔌 Django 캐시 설정 문제: Redis/Memcached 연결이 안 될 때

Tiboong 2025. 12. 23. 13:45

실제 상황: 갑자기 느려진 쇼핑몰

금요일 오후 6시, 쇼핑몰 사이트가 갑자기 엄청나게 느려졌습니다. 상품 목록을 불러오는데 3초씩 걸리고, 사용자들의 불만이 쏟아지기 시작합니다.

로그를 확인해보니 이런 에러가 반복되고 있었습니다:

ConnectionError: Error 111 connecting to redis:6379. Connection refused.
django.core.cache.backends.base.CacheKeyWarning: Cache key contains characters that will cause errors

개발 환경에서는 멀쩡히 돌아가던 캐시가 운영 서버에서는 연결이 안 되고 있었던 거죠. 캐시가 작동하지 않으니 모든 데이터를 데이터베이스에서 직접 가져와야 했고, 그 결과 응답 속도가 극적으로 느려진 상황입니다.

캐시가 뭐길래?

캐시를 편의점에 비유해볼까요?

  • 데이터베이스 = 대형 창고: 모든 상품이 있지만, 가져오는 데 시간이 오래 걸림
  • 캐시 = 편의점: 자주 찾는 상품만 보관하지만, 즉시 가져갈 수 있음
  • Redis/Memcached = 편의점 체인: 여러 서버가 같은 캐시를 공유할 수 있게 해주는 중앙 시스템

집 앞 편의점(캐시)에서 우유를 사면 1분이면 되는데, 대형 마트 창고(DB)까지 가려면 30분이 걸리는 것과 같은 이치입니다. 그런데 만약 편의점이 문을 닫았다면? 모든 사람들이 창고로 몰려가야 하고, 시스템 전체가 느려지게 됩니다.

왜 이런 일이 생길까?

1. 연결 정보가 틀렸을 때

가장 흔한 원인은 호스트, 포트, 비밀번호가 잘못 설정된 경우입니다.

개발 환경에서는 localhost:6379를 쓰다가, 운영 환경에서 redis-server.internal:6379로 바뀌었는데 설정을 안 바꾼 경우가 많습니다. 마치 이사 간 친구 집에 예전 주소로 찾아가는 것과 같죠.

2. 네트워크 문제

Docker나 Kubernetes를 사용할 때, 캐시 서버가 다른 네트워크에 있어서 접근이 안 되는 경우도 있습니다.

편의점이 분명히 있는데, 우리 아파트 단지에서는 갈 수 없는 위치에 있는 상황입니다. 방화벽 설정이나 보안 그룹 규칙 때문에 막혀있을 수 있습니다.

3. 타임아웃 설정

캐시 서버 응답이 너무 느려서 Django가 먼저 포기하는 경우도 있습니다.

타임아웃을 1초로 설정했는데, 네트워크가 느려서 2초가 걸린다면? 매번 연결 실패로 처리됩니다. 편의점 직원이 물건을 찾는 데 시간이 오래 걸려서, 기다리다 지쳐서 그냥 가버리는 거죠.

4. 캐시 서버가 실제로 죽었을 때

Redis나 Memcached 서버 자체가 메모리 부족으로 다운되거나, 재시작 중일 수도 있습니다.

진짜로 편의점 문을 닫은 상황입니다. 이때는 캐시 설정 문제가 아니라 서버 문제를 먼저 해결해야 합니다.

해결 방법들

방법 1: 환경별 설정 분리 (기본)

핵심 아이디어: 개발/스테이징/운영 환경마다 다른 캐시 설정 사용

# settings/base.py
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.redis.RedisCache',
        'LOCATION': os.environ.get('REDIS_URL', 'redis://localhost:6379/1'),
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
        },
        'KEY_PREFIX': 'myproject',
        'TIMEOUT': 300,
    }
}

 

장점:

  • 환경변수로 쉽게 변경 가능
  • 코드 수정 없이 배포 환경에서 설정 변경
  • 민감 정보(비밀번호) 노출 방지

단점:

  • 환경변수 설정을 빼먹으면 기본값으로 작동 (의도치 않은 동작)
  • 여러 개발자가 각자 다른 환경변수를 쓰면 혼란 가능

언제 쓸까: 소규모 프로젝트나 팀에서, 설정이 복잡하지 않을 때

방법 2: Fallback 설정 (안전성 우선)

핵심 아이디어: 캐시 연결 실패 시 로컬 메모리나 더미 캐시로 자동 전환

# settings.py
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': os.environ.get('REDIS_URL', 'redis://localhost:6379/1'),
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
            'CONNECTION_POOL_KWARGS': {
                'max_connections': 50,
                'retry_on_timeout': True,
            },
            'SOCKET_CONNECT_TIMEOUT': 5,
            'SOCKET_TIMEOUT': 5,
            'IGNORE_EXCEPTIONS': True,  # 핵심: 에러 무시하고 계속 진행
        }
    }
}

또는 완전히 별도의 Fallback 캐시 설정:

try:
    # Redis 연결 테스트
    from django.core.cache import cache
    cache.set('test_key', 'test_value', 1)
    cache.get('test_key')
    
    # 성공하면 Redis 사용
    CACHES = {
        'default': {
            'BACKEND': 'django_redis.cache.RedisCache',
            'LOCATION': REDIS_URL,
        }
    }
except Exception:
    # 실패하면 로컬 메모리 캐시로 전환
    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
            'LOCATION': 'unique-snowflake',
        }
    }

 

장점:

  • 캐시 서버 장애 시에도 서비스는 계속 운영 가능
  • 점진적 성능 저하(graceful degradation)
  • 장애 시 자동 복구

단점:

  • 캐시가 제대로 안 되고 있는 걸 눈치채기 어려움
  • 성능 문제가 숨겨질 수 있음
  • 멀티 서버 환경에서 각 서버가 다른 캐시를 가지게 됨 (데이터 불일치)

언제 쓸까: 캐시가 없어도 서비스가 돌아가야 하는 경우, 높은 가용성이 중요한 서비스

방법 3: 연결 풀 최적화 (성능 우선)

핵심 아이디어: 연결 재사용과 타임아웃 설정으로 안정성 확보

CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': REDIS_URL,
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
            'CONNECTION_POOL_KWARGS': {
                'max_connections': 50,  # 최대 연결 수
                'retry_on_timeout': True,  # 타임아웃 시 재시도
                'retry_on_error': [ConnectionError, TimeoutError],  # 특정 에러 시 재시도
                'health_check_interval': 30,  # 30초마다 연결 상태 확인
            },
            'SOCKET_CONNECT_TIMEOUT': 5,  # 연결 타임아웃
            'SOCKET_TIMEOUT': 5,  # 응답 타임아웃
            'COMPRESSOR': 'django_redis.compressors.zlib.ZlibCompressor',  # 데이터 압축
            'PICKLE_VERSION': -1,  # 최신 pickle 프로토콜 사용
        },
        'KEY_PREFIX': 'myproject',
        'VERSION': 1,
    }
}

 

장점:

  • 연결 재사용으로 성능 향상
  • 자동 재연결로 일시적 네트워크 문제 해결
  • 세밀한 타임아웃 조정 가능

단점:

  • 설정이 복잡함
  • 잘못된 설정 시 오히려 성능 저하 가능
  • 연결 풀 관리에 메모리 사용

언제 쓸까: 트래픽이 많은 운영 환경, 캐시 성능이 중요한 서비스

방법 4: Sentinel/Cluster 구성 (고가용성)

핵심 아이디어: Redis 서버 장애 시 자동으로 다른 서버로 전환

CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': [
            'redis://redis-master:6379/1',
            'redis://redis-replica-1:6379/1',
            'redis://redis-replica-2:6379/1',
        ],
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.SentinelClient',
            'SENTINELS': [
                ('sentinel-1', 26379),
                ('sentinel-2', 26379),
                ('sentinel-3', 26379),
            ],
            'SENTINEL_KWARGS': {
                'password': os.environ.get('REDIS_PASSWORD'),
            },
            'CONNECTION_POOL_CLASS': 'redis.sentinel.SentinelConnectionPool',
            'CONNECTION_POOL_KWARGS': {
                'service_name': 'mymaster',
            },
        }
    }
}

 

장점:

  • 단일 장애점(Single Point of Failure) 제거
  • 자동 failover로 서비스 중단 최소화
  • 대규모 트래픽 처리 가능

단점:

  • 인프라 구성이 복잡하고 비용 증가
  • 설정과 관리가 어려움
  • 작은 프로젝트에는 과한 투자

언제 쓸까: 대규모 서비스, 다운타임이 허용되지 않는 중요 서비스

실전 디버깅 가이드

1단계: 연결 확인

먼저 Django 쉘에서 직접 연결을 테스트해보세요:

python manage.py shell

>>> from django.core.cache import cache
>>> cache.set('test', 'hello', 60)
>>> cache.get('test')
'hello'  # 이게 나오면 정상

 

에러가 나면 에러 메시지를 꼼꼼히 읽어보세요:

  • Connection refused: 캐시 서버가 안 떠있거나 포트가 틀림
  • Timeout: 네트워크가 느리거나 방화벽 문제
  • Authentication failed: 비밀번호가 틀림

2단계: 캐시 서버 상태 확인

Redis라면:

# Redis 서버에 직접 연결 시도
redis-cli -h redis-host -p 6379 ping
# PONG이 나오면 서버는 살아있음

# Redis 서버 정보 확인
redis-cli -h redis-host -p 6379 info
# 메모리 사용량, 연결 수 등 확인

Docker를 쓴다면:

# Redis 컨테이너가 떠있는지 확인
docker ps | grep redis

# 컨테이너 로그 확인
docker logs redis-container-name

3단계: 네트워크 연결 확인

# 호스트에서 포트가 열려있는지 확인
telnet redis-host 6379

# 또는
nc -zv redis-host 6379

# 방화벽 규칙 확인 (AWS 예시)
aws ec2 describe-security-groups --group-ids sg-xxxxx

4단계: Django 설정 검증

# settings.py에서
import os
print(f"REDIS_URL: {os.environ.get('REDIS_URL')}")
print(f"CACHES config: {CACHES}")

환경변수가 제대로 설정되어 있는지, 실제 사용되는 값이 맞는지 확인하세요.

자주 하는 실수들

실수 1: 환경변수를 안 넣음

# ❌ 나쁜 예
CACHES = {
    'default': {
        'LOCATION': os.environ['REDIS_URL'],  # 없으면 에러!
    }
}

# ✅ 좋은 예
CACHES = {
    'default': {
        'LOCATION': os.environ.get('REDIS_URL', 'redis://localhost:6379/1'),
    }
}

실수 2: 개발 환경 설정을 운영에 그대로 씀

# ❌ 나쁜 예
CACHES = {
    'default': {
        'LOCATION': 'redis://localhost:6379/1',  # 운영 서버에서는 localhost가 아님!
    }
}

실수 3: 타임아웃을 너무 짧게 설정

# ❌ 나쁜 예
'SOCKET_TIMEOUT': 0.1,  # 100ms는 너무 짧음, 네트워크 지연만 있어도 실패

# ✅ 좋은 예
'SOCKET_TIMEOUT': 5,  # 5초면 대부분의 경우 충분

실수 4: 에러를 조용히 무시

# ❌ 나쁜 예
try:
    cache.set('key', 'value')
except:
    pass  # 에러를 먹어버리면 나중에 디버깅 불가능

# ✅ 좋은 예
try:
    cache.set('key', 'value')
except Exception as e:
    logger.error(f"Cache error: {e}")
    # 모니터링 시스템에 알림
    sentry_sdk.capture_exception(e)

체크리스트

배포 전에 확인하세요:

설정 확인

  • [ ] 환경별로 올바른 Redis/Memcached 호스트 설정했나요?
  • [ ] 비밀번호가 필요하다면 환경변수로 관리하고 있나요?
  • [ ] 타임아웃 설정이 네트워크 상황에 맞나요?

연결 테스트

  • [ ] Django 쉘에서 캐시 읽기/쓰기가 되나요?
  • [ ] 캐시 서버에 직접 연결이 되나요? (redis-cli, telnet 등)
  • [ ] 방화벽이나 보안 그룹이 포트를 막고 있지 않나요?

장애 대응

  • [ ] 캐시 서버 장애 시에도 서비스가 돌아가나요?
  • [ ] 에러 로깅과 모니터링이 되어 있나요?
  • [ ] 캐시 miss 시 DB 부하가 감당 가능한가요?

성능

  • [ ] 연결 풀 크기가 적절하게 설정되어 있나요?
  • [ ] 불필요한 캐시 미스가 없나요?
  • [ ] 캐시 키 네이밍이 중복되지 않나요?

마무리: 캐시는 선택이 아닌 필수

캐시는 현대 웹 애플리케이션에서 선택이 아니라 필수입니다. DB 부하를 줄이고, 응답 속도를 개선하고, 더 많은 사용자를 감당할 수 있게 해주죠.

하지만 캐시 설정 문제는 개발할 때는 안 보이다가 운영에서 터지는 대표적인 문제입니다. 개발 환경에서는 localhost로 잘 되다가, 운영 환경에서 네트워크 설정 하나 잘못돼서 전체 서비스가 느려지는 걸 저도 여러 번 봤습니다.

핵심은 환경별로 다른 설정을 명확히 관리하고, 장애 상황을 대비하는 것입니다. 캐시가 없어도 서비스가 돌아가게 하되, 캐시가 있을 때 최대 성능을 낼 수 있도록 설계하세요.


💬 Django 캐시 설정, 혼자 해결하기 어려우신가요?

이런 고민 있으시다면 상담해보세요:

  • ✅ Redis/Memcached 설정이 맞는지 확인하고 싶다
  • ✅ 캐시 성능을 최적화하고 싶다
  • ✅ 고가용성 캐시 인프라를 구축하고 싶다
  • ✅ 캐시 전략을 처음부터 다시 설계하고 싶다

실무 경험을 바탕으로 해결해드립니다:

  • 🔧 현재 캐시 설정 진단 및 개선안 제시
  • 🚀 성능 테스트와 병목 지점 분석
  • 📊 모니터링 및 알림 시스템 구축
  • 💡 프로젝트에 맞는 캐시 전략 컨설팅

👉 [크몽에서 Django 컨설팅 신청하기]

 

캐시 하나 제대로 설정하는 것만으로도 서비스 응답 속도가 10배 빨라질 수 있습니다. 지금 바로 시작하세요!