환경 설정
개발환경(MacOS)
맥을 기준으로 사전 환경 설정을 설명합니다.
1. brew를 이용하여 redis를 설치해 줍니다.
brew install redis
2. redis를 작동시킵니다.
redis-server
redis-cli 간단 사용법
redis 서버에 접근 가능한 redis-cli에 대한 간단한 사용법입니다.
1. redis-cli 실행
redis-cli
2. db 선택
select 1
redis는 여러 개의 데이터베이스를 둘 수 있고, 저희는 1번으로 settings에 설정해 두었으므로 1번에 접근합니다.
3. keys 보기
keys *
현재 생성된 key를 확인합니다.
배포 환경(docker-compose)
docker-compose를 사용할 때 배포 환경 설정입니다.
version: "2"
services:
web:
~~~
depends_on:
- redis
redis:
image: "redis:alpine"
container_name: redis
ports:
- "6379:6379"
기존의 파이썬 서버등에 redis를 의존성으로 추가해 주고, redis:alipine 이미지를 기반으로 redis 서비스를 등록해 줍니다.
장고에서 설정
redis 사용을 위해 django-redis 라이브러리를 사용합니다.
pip install django-redis
설치가 무사히 완료되었으면, settings.py 파일에 캐시를 설정해 줍니다.
# app/settings.py
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1" if DEBUG else "redis://redis:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
},
}
}
저의 경우에는 위와 같이 DEBUG시에는 mac환경의 redis-server를, 배포 환경에서는 docker-compose로 연결된 redis를 사용하게 해 주었습니다.
코드에서 redis 사용
redis는 간단한 key:value 저장소로 간단하게 cache.set(), cache.get()등의 함수로 사용할 수 있습니다.
cache 사용
원래 대략적으로 짜본 코드는 다음과 같습니다.
class RestaurantDetail(generics.RetrieveAPIView):
queryset = Restaurant.objects.all()
serializer_class = serializers.RestaurantSerializer
lookup_field = "uuid"
def get(self, request, *args, **kwargs):
# Redis에서 캐시된 데이터를 가져옵니다.
cache_key = f"restaurant_detail_{kwargs['uuid']}"
cache_data = cache.get(cache_key)
if cache_data is not None:
# 캐시에서 데이터를 찾은 경우, 캐시된 데이터를 반환합니다.
return Response(cache_data)
else:
# 캐시에서 데이터를 찾지 못한 경우, DB에서 데이터를 가져옵니다.
response = super().get(request, *args, **kwargs)
# 가져온 데이터를 Redis에 캐시합니다.
cache.set(cache_key, response.data, timeout=3600) # 1시간 동안 캐시 유지
return response
redis에서 가져오는 것을 시도하고, 아직 등록되지 않았다면, 기존의 super().get()으로 데이터를 가져온 뒤, 해당 데이터를 캐시에 설정해 주고, response를 반환합니다.
다만 위의 코드는 redis 서버가 갑자기 꺼진다는 등의 문제가 생기면 오류가 발생할 수 있습니다.
redis 서버 종료 오류에 대비
class RestaurantDetail(generics.RetrieveAPIView):
queryset = Restaurant.objects.all()
serializer_class = serializers.RestaurantSerializer
lookup_field = "uuid"
def get(self, request, *args, **kwargs):
cache_key = f"restaurant_detail_{kwargs['uuid']}"
try:
# Redis에서 캐시된 데이터를 가져옵니다.
cache_data = cache.get(cache_key)
if cache_data is not None:
return response.Response(cache_data)
except (ConnectionError, TimeoutError, CacheKeyWarning) as e:
# Redis 연결 오류 처리
logging.error(f"Redis error: {e}")
# Redis에서 예외가 발생한 경우, 데이터베이스에서 데이터를 가져옵니다.
# 이 부분은 아래 super().get 호출로 처리됩니다.
# 캐시에서 데이터를 찾지 못한 경우, DB에서 데이터를 가져옵니다.
response_data = super().get(request, *args, **kwargs)
try:
# 가져온 데이터를 Redis에 캐시합니다.
cache.set(cache_key, response_data.data, timeout=3600) # 1시간 동안 캐시 유지
except (ConnectionError, TimeoutError, CacheKeyWarning) as e:
# Redis 연결 오류 처리
logging.error(f"Failed to set cache in Redis: {e}")
return response_data
각 발생가능한 오류별로 예외처리를 해줬습니다. 다만 코드가 길어지는 것 같아 더 개성방안을 강구해 봤습니다.
cache.get_or_set() 사용
cache.get()을 시도하고 존재하지 않는다면, set을 하는 get_or_set() 함수를 사용하고 데이터를 가져오는 부분을 조금 분리했습니다.
class RestaurantDetail(generics.RetrieveAPIView):
queryset = Restaurant.objects.all()
serializer_class = serializers.RestaurantSerializer
lookup_field = "uuid"
def get(self, request, *args, **kwargs):
def get_data():
instance = self.get_object()
serializer = self.get_serializer(instance)
return serializer.data
# Redis에서 캐시된 데이터를 가져옵니다.
cache_key = f"restaurant_detail_{kwargs['uuid']}"
try:
data = cache.get_or_set(
cache_key, get_data, timeout=3600
) # 1시간 동안 캐시 유지
except (ConnectionError, TimeoutError, CacheKeyWarning) as e:
logging.error(f"Redis 캐시 접근중 오류 발생: {e}")
data = get_data()
return Response(data)
더 개선이 필요한 부분
가능하면 decorator 등을 하나 붙이는 것으로 cache 적용이 가능하게 바꿀 수도 있을 것 같습니다.
db에서 정말 가끔 발생하는 오류로 만약 잘못된 정보나 오류코드가 반환되었는데, cache에서 해당 정보를 그냥 cache 해버리면 한 시간 동안 잘못된 데이터가 cache를 통해서 전달될 수 있을 것입니다. db에서 오류가 반환되는 일은 별로 없을 테지만, 이용량이 급격히 증가하거나 하면 문제가 생길 수 있을 듯합니다. 이를 위해 적절한 예외처리가 추가적으로 필요해 보입니다.
또한 음식점 데이터가 바뀌었을 때 cache를 조기삭제하고 업데이트하는 코드도 추가가 필요합니다.
레퍼런스
https://jupiny.com/2018/02/27/caching-using-redis-on-django/
https://nachwon.github.io/redis/
'서버(Server) > 장고 (Django)' 카테고리의 다른 글
Django 테스트 개선기(의존성 벗어나기) (0) | 2024.03.24 |
---|---|
장고 필드의 null, blank에 대한 정리 (0) | 2023.08.18 |
[Django Basic] 4. Template (0) | 2023.08.03 |
dj-rest-auth 소셜 로그인(OAuth)에 대하여 (0) | 2023.07.27 |
[Django Basic] 4. View, views.py (0) | 2023.07.27 |