-
Redis로 Cache Server를 만들어 보자! (feat. django-cacheops)💻 프로그래밍/Django 2021. 2. 18. 07:28
안녕하세요! 운동하는 개발자 Jay입니다 😄
오늘은 이름하여 c.a.c.h.e 를 Django에서 사용해보려고 합니다.
지금 진행 중인 토이 프로젝트가 있는데, 이 프로젝트에서 캐시를 써보려고 해요! 캐시란 무엇이고 왜 사용하는지를 하나씩 알아가 보겠습니다!
1. What is Cache? 💰 (Cache is Cash.... 는 드립)
캐시(Cache)란 자주사용하는 데이터를 미리 복사해 놓은 저장소를 말합니다. 쉽게 설명하자면 내 키(key와 키를 이용해 뒤에서 설명까지 이어지기 위한 라임 this is 언어유희) 를 알고 싶은데, 매번 키를 재는 게 아닌 처음 재논 키의 정보를 저장해놔서 원할 때마다 그 정보를 보는 게 캐시입니다.
좀 더 Django와 연관 지어서 설명해보겠습니다. Django에서 ORM을 사용하게 되면 DB에 query가 나가게 됩니다. 사용자가 많아지고 그만큼 query가 많아지면 DB에 부하가 많아지고 서비스가 느려질 수 있습니다.
이럴 때 캐시를 사용하면 동일한 ORM을 호출할 때 이미 캐시에 저장되어 있는 경우 query를 하지 않고 캐시에서 ORM의 결과를 가져오게 됩니다. 그럼 DB부하가 줄어들겠죠?!
이런 캐시 시스템(?), 서비스 들 중 Memcached, Redis 등이 있습니다. 캐시는 하드웨어적, 애플리케이션 등에 따라 다양한 종류가 있는 것 같습니다. But 제가 실제로 사용해본 캐시 DB는 Redis(Remote Dictionary Server)라서 이번 포스팅에서는 Redis를 기준으로 캐시를 적용하는 방법에 대해서 알아보겠습니다.
2. What is Redis? 🎈
Redis는 "key-value" 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈소스 기반의 DBMS입니다. 여기서 중요한 건 "key-value" 구조라는 것이다. key값으로 value를 꺼내올 수 있다는 것이다 (dictionary 같은?)
Redis에 대해서 자세한 설명은 여기서 확인하면 좋을 것 같고, 이번 포스팅에서는 개인적으로 중요하다고 생각하는 Redis의 LRU(Least recently used) Cache에 대해서 집중 설명하겠습니다.
LRU Cache는 오래된 캐시를 어떤 정책과 방식으로 삭제할지에 대한 옵션입니다.
1. maxmemory
redis에서 사용할 수 있는 최대 메모리를 설정해줍니다. 0인 경우 제한 없음입니다.
ex) maxmemory 100mb
2. maxmemory-policy최대 메모리를 넘어가는 경우 어떻게 처리할지에 대한 정책입니다.
- noeviction : 메모리 제한에 도달했을 때, 클라이언트에서 그 이상의 메모리를 사용하려고 하면 오류를 반환합니다. (대부분 쓰기 명령이지만 DEL과 몇 예외가 있음)
- allkey-lru : 새로 추가된 데이터의 메모리 확보를 위해서 최근에 덜 사용된 (LRU) key를 제거합니다.
- volatile-lru : erpire_set(만료시간)이 있는 key 중에서 최근에 덜 사용된(LRU) key를 제거합니다.
- allkeys-random : 메모리 확보를 위해서 랜덤으로 key를 제거합니다.
- volatile-random : expire_set이 있는 key 중에서 랜덤으로 제거합니다.
- volatile-ttl : erpire_set이 있는 key를 제거합니다. 다만, TTL(Time To Live)이 짧은 key를 제거합니다.
일단은 maxmemory, maxmemory-policy만 알아도 기본적인 튜닝은 끝입니다! (아마두?!)
3. Redis 설치해보자! ⚙️
Ubuntu 서버에 Redis-server를 설치해 보겠습니다.
apt-get으로 설치
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install -y redis-serverserver 메모리 확인 후 메모리 할당 (vmstat -s)
일단 Total은 4 Gbyte이고, free memory만 봤을 때 0.19 Gbyte...?
아니 아무것도 안 하는데? htop으로 봐도 그렇게 사용 안 하는데... 흠 이건 찾아보니 리눅스는 사용하지 않은 메모리도 캐시, 버퍼로 사용한다고 하더라고요!!
buff/cache가 3.1 Gbyte라....! 여기서 봐야 할 곳은 avaliable인데 avaliable은 swapping 없이 새로운 애플리케이션을 실행 가능한 가용 메모리의 크기라고 하네요.
뭐... 여하튼 전 대략 avaliable이 3 Gbyte 되니까, Redis maxmemory를 1 Gbyte로 잡겠습니다.
메모리 관련해서 자세한 설명은 여기에서 참고해주세요!! (봐도 봐도 헷갈리네요 ㅋㅋ)
sudo vi /etc/redis/redis.conf로 cofig를 수정합니다.
maxmemory = 1G
maxmemory-policy = volatile-lru이렇게 설정합니다. maxmemory-policy = volatile-lru로 설정한 이유는 Cacheops 설명을 하면서 추가로 설명하겠습니다.
설정 완료 후 마지막으로 redis를 재실행, 서버가 시작되면 자동 시작되도록 설정합니다.
sudo systemctl restart redis-server.service
sudo systemctl enable redis-server.serviceRedis가 동작하는지 한번 확인해보겠습니다.
redis-cli
를 입력하면 redis-server에 접속해서 캐시를 get, set 할 수 있습니다. 저는 접속 확인만 하고 따로 명령어를 통해 캐시를 만들지 않겠습니다. (뒤에서 직접 api 호출해서 확인할 예정) 자세한 명령어를 알고 싶으신 분들은 공식문서에서 확인해보세요!
4. Django에 Redis Cache 적용해보기 🎉
이제 Django ORM에 Cache를 적용해보겠습니다. 일반적인 캐시 설정도 있지만 이번 포스팅에서는 ORM 캐시를 할 수 있는 라이브러리를 사용해보겠습니다. Django ORM 캐시 라이브러리는 Cachealot과 Cacheops가 있습니다. 저는 이 두 가지만 써봐서... 아마 이 두 가지가 제일 유명하지 않을까 생각되네요!
저희는 Cacheops를 사용할 겁니다. 그 이유는 Cacheops는 row단위로 캐시가 가능해서 효율이 더 높다고 알고 있습니다.
뭐, 이런저런 특징을 비교해놓은 게 있는데 cachealot에서 쓴 거라서 믿음은 그렇게 가지 않네요 ㅋㅋㅋ 무튼 회사에서도 cachealot을 사용하다가 cacheops로 넘어왔는데, 그 과정에서 있었던 트러블 슈팅도 함께 설명드리겠습니다.
pip install django-cacheops
명령어로 라이브러리를 설치해줍니다.
공식문서를 보면 매우 친절하게 settings 방법에 대해서 설명해주고 있습니다.
INSTALLED_APPS += ['cacheops']CACHEOPS_LRU = True # maxmemory-policy: volatile-lru 설정# (직접 redis config에서 수정하긴 했는데, 그렇게 안해도 되는지는 잘 모르겠네요)CACHEOPS_REDIS = "redis://127.0.0.1:6379/1" # local redisCACHEOPS_DEFAULTS = {'timeout': 60 * 60 * 1, # 1시간'ops': 'all', # get, fetch ... 모든 동작 ex) 'ops': 'get' 이러면 get 할 때만 캐시'cache_on_save': False # save()할때 캐시 할건지 (굳이 필요없을 것 같아서 False 로함)}CACHEOPS = {'*.*': {}, # 모든 앱에대해서 캐시적용}cs 기본적인 세팅은 이렇습니다!! (주석과 공식문서 참고하시길 바라요)
이제 api를 직접 호출해 보겠습니다.
첨부된 이미지에서 확인할 수 있듯이 처음 api를 호출했을 때 select query들이 실행되고, 그다음부터는 query실행 없이 api에서 data를 전달해 줍니다. redis server에서 캐싱된 데이터를 확인할 수 있습니다.
❗️ 여기서 잠깐!!!
이제 위에서 잠시 언급했던, maxmemory-policy = volatile-lru로 설정한 이유에 대해서 말씀드리겠습니다.
우리가 걱정해야 할 부분은 redis maxmemory가 가득 찬 경우, 클라이언트에서 그 이상의 메모리를 캐싱하려고 할 때입니다.
몇 가지 시나리오에 대해서 말하자면 maxmemory-policy 설정 중 allkeys-random, volatile-random이 있습니다. 이 경우 문제가 되는 부분은 말 그대로 random(무작위)으로 key를 지울 때입니다.
이렇게 무작위로 key를 지우게 되면 cacheops에서 query를 캐싱할 때 몇 가지 단계에 걸쳐 캐싱을 하게 되는데, 쉽게 a-b-c 이런 단계에서 무작위로 삭제되는 key 중 b가 삭제되게 되면 a, c만 남은 상황에서 정상적인 캐시 동작을 보장할 수 없습니다.
update를 해도 캐시 동기화가 안된다던지, 이상한 데이터로 보인다던지 하는 이슈가 생길 수 있습니다. 그렇기 때문에 voliate-lru 혹은 volatile-ttl로 maxmemory-policy을maxmemory 이상의 동작에서 expired_set 이 설정된 key를 삭제하고 expired 시간을 짧게 세팅해서 이슈를 (나름, 잘?) 해결하라는 말입니다.
제가 알기로는 cachealot은 a-b-c (schemes, confj, q) 이렇게 캐싱을 하는 게 아닌 값을 통째로 하나 캐싱하는 걸로 알 고있습니다. 그래서 아무거나 지워도 전체 캐싱된 데이터가 날아가서 큰 문제가 없었던 걸로(?) 기억하네요 ㅎㅎ 캐시 key가 삭제되면 다시 DB에 query 나가면 되니 데이터는 정상적으로 보입니다.
여하튼, 요점은 ORM Cache 라이브러리, 서비스, server memory 특성들에 따라 적절하게 Redis를 튜닝해야 한다는 점입니다!! 처음에 이 부분을 찾지 못해서 CS지옥에 빠졌던 기억이 새록새록 피어나네요! ㅋㅋㅋㅋ (그땐 악몽)
5. 마치며
개인적으로 redis, nginx, gunicorn 같은 것들의 튜닝은 회사에서 쉽게 건들기 힘든 부분이라 토이 프로젝트를 하면서 많이 건드려 보는 게 경험도 쌓을 수 있고 좋은 것 같습니다.
그리고 default로만 사용하는 것보다 왜 이런 옵션을 사용했고 왜 이렇게 동작하는지에 대해 알고 사용한다면, 그만큼 실무에서도 효율적인 퍼포먼스와 개인 능력치로 나타날 것이라고 생각되네요!!
그럼 오늘 하루도 즐거운 개발 하시길 바래요~👋
- 참고 문서, 블로그
redis.io/documentation'💻 프로그래밍 > Django' 카테고리의 다른 글
DRF Renderer에 따라 다르게 동작되는 서버 Response (0) 2022.07.10 Session과 JWT에 대해 알아보자잇! (feat.인증, 인가) (2) 2022.06.20 Django + Rest Framework 에서 Service Layer를 구성하는 방법 (2) 2021.02.13 Django 비즈니스 로직을 분리하는 4가지 방법 (1) 2021.02.13 Django, Nginx CORS 설정 (2) 2021.01.17