django에서 redis 사용하기 1편 (캐시 사용: 데이터 / 세션)
상위 사이트의 내용을 학습한 내용을 정리함
django, flask 관계없이 파이썬이라면 적용 가능한 방법으로 'Caching in Django With Redis' 의 내용을 추천한다.
프로젝트 생성을 하지 않고 코드로만 작성하기에
나는 django의 shell_plus 의 주피터 노트북으로 테스트 했다.
해당 프로젝트는 다음 저장소를 통해 사용할 수 있다.
https://github.com/bluebamus/real-python-redis-series
쓸만한 내용으로 단순한 get set을 사용하는건 편의성으로 볼 수 있고
데이터의 내용이 길거나 dict 타입의 데이터를 저장해야 한다면 해시로 저장하고 보내는것이 맞다.
* json으로 만들어 한번에 저장도 가능하지만 검색 및 redis 내부 함수를 이용한 다양한 사용 확장이 불가능하다.
학습 사이트에서 hmset의 내용이 있는데 해당 명령어는 제거되어 반영되지 않고 있으며
hset의 파라메터값이 확장되어 이를 대신한다. "mapping=" 변수를 사용하면 된다.
원형은 다음과 같다 : pipeline.hset(name, key=None, value=None, mapping=None)
예시는 다음과 같다.
my_dictionary = {
'key1': 'value1',
'key2': 'value2',
}
r.hset(key, mapping=my_dictionary)
공식 문서를 확인해도 되지만 누군가 질문을 올리고 답변을 한 다음 github 이슈 페이지를 참고해도 되겠다.
https://github.com/redis/redis-py/issues/1396
* 만약 동작을 안한다면 redis-server의 버전을 바꾸면 된다.
redis의 여러 동작을 실행시키고자 한다면 다음과 같은 방법을 사용하면 된다.
- redis cmd의 multi 명령어에 의한 트랜잭션 동작과 같다.
from redis import StrictRedis
r2 = StrictRedis(db=2)
with r2.pipeline() as pipe:
for h_id, hat in hats.items():
print('h_id : ',h_id)
print('hat : ',hat)
r2.hset(h_id, mapping=hat)
pipe.execute()
만약 트랜잭션 도중 수정해야 하는 값이 변경된다면 큰 문제가 발생된다
이런 문제를 막을 방법에 대해 알아보자
redis에는watch라는 명령어가 있다.
이 명령어는 unwatch를 호출하거나 exec를 수행하면 해지가 되고
그 전에는 값의 변경 유무를 확인한다.
용어적으로 lock이라는 표현을 많이 사용하지만 내가 사용하는 시간 동안
다른 사용자의 접근을 막는게 아니라
내 작업이 수행할 시기에 변경사항이 발견 된다면,
현재 수행하고자 하는 명령어를 전부 버리는 작업을 하는 것이다.
이 부분을 명확히 이해하면 쉽게 접근할 수 있다.
코드를 확인하면 다음과 같다. (에러 없음)
import logging
import redis
logging.basicConfig()
class OutOfStockError(Exception):
"""Raised when PyHats.com is all out of today's hottest hat"""
def buyitem(r: redis.Redis, itemid: int) -> None:
with r.pipeline() as pipe:
error_count = 0
while True:
try:
# Get available inventory, watching for changes
# related to this itemid before the transaction
pipe.watch(itemid)
nleft: bytes = r.hget(itemid, "quantity")
if nleft > b"0":
pipe.multi()
pipe.hincrby(itemid, "quantity", -1)
pipe.hincrby(itemid, "purchased", 1)
pipe.execute()
break
else:
# Stop watching the itemid and raise to break out
pipe.unwatch()
raise OutOfStockError(
f"Sorry, {itemid} is out of stock!"
)
except redis.WatchError:
# Log total num. of errors by this user to buy this item,
# then try the same process again of WATCH/HGET/MULTI/EXEC
error_count += 1
logging.warning(
"WatchError #%d: %s; retrying",
error_count, itemid
)
return None
쇼핑몰 등에서 사용될만한 코드로 상품 수량과 구매자의 변동사항을 저장하는 것이다.
많은 사용자들의 동시 접속으로 값이 수시로 변동 될 때, 상위 코드는 정확한 증감 계산을 수행할 것이다.
에러를 발생하고자 한다면, watch후 multi 명령어 전, set 명령어로 데이터를 수정하면 결과를 확인할 수 있다.
이 외에
- ttl의 변경
- redis의 list 타입을 통해 이상 접근하는 사용자 ip를 접근제어 하는
간단한 방법 (실 사용은 검증된 라이브러리나 상용 서비스를 사용해야 함)
- 전송 용량을 줄일 수 있는 압축 전송
- redis가 외부에 노출될 경우에 대비한 암호화 방법
등을 배울 수 있다.
redis의 기본 정보를 좀 더 이해하는데 도움이 될 사이트의 링크
https://sabarada.tistory.com/177
'Caching in Django With Redis' 에서는 다음 세가지 경우에 대해 테스를 해볼 수 있게 해준다.
1. redis를 사용하지 않는 경우
2. redis 캐시를 사용한 경우 (get/set)
3. redis 캐시를 사용한 경우 (@cache_page(CACHE_TTL) / 데코레이션)
loadtest를 설치했지만, 윈도우에서 잘 작동을 하지 않았기 때문에
locust를 사용했다. 해당 실행 코드는 locustfile.py로 만들어져 프로젝트에 포함되어 있다.
결과는 다음과 같았다.
목표 : 3초 이내의 로딩 시간을 유지하는 사용자 수 & 변경없이 평균을 유지하는 요청 수 를 찾기
확인 해야 하는 변수 : 90% 상위 로딩시간, 접속 사용자 수, request 수
환경 : amd-1700x / 16gb ram
서버 : runserver (django 자체 서버 / 테스트 용으로 성능 최하)
1번 결과 : 90~100명까지 유지
2번 결과 : 110명까지 유지
3번 결과 : 120~130명까지 유지
테스트 결과가 아주 드라마틱하지는 않지만, 상용서버에서 튜닝이 된 web/app에서 동작이라면
이보다 더 차이가 날거라 생각된다.
* 해당 학습에는 session을 redis로 전환하는 방법은 적혀있지 않았다.
settings.py에서 변수를 선언해주면 되는 것으로 기본은 db에 저장하게 되어있다.
기본 설정 : SESSION_ENGINE = 'django.contrib.sessions.backends.db'
캐시 설정 :
- SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
- SESSION_CACHE_ALIAS = 'default'
둘 다 사용 : SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
파일 기반 : SESSION_ENGINE = 'django.contrib.sessions.backends.file'
상위 내용도 저장소에 반영이 되어 있다.
debug_toolbar를 이용하여 sql 항목을 보면 session을 위한 동작도 함께 확인할 수 있다.
* 캐시 된 세션 사용과 관련한 공식 문서
영문 : https://django.readthedocs.io/en/stable/topics/http/sessions.html
한글 번역문 : https://runebook.dev/ko/docs/django/topics/http/sessions
참고 자료 : https://forward.nhn.com/2021/sessions/16
'Study > django' 카테고리의 다른 글
django MQ 시리즈 2편 - task queue (2) :Learn Django Celery with RabbitMQ (0) | 2023.03.08 |
---|---|
django MQ 시리즈 1편 - task queue (1) : Asynchronous Tasks With Django and Celery (1) | 2023.03.04 |
i18n 다국어 기능 적용하는 방법 (1) | 2023.01.11 |
django admin - raw_id_fields, search_fields problem (0) | 2022.12.19 |
devspoon-portfolio-blog 오픈소스에 admin 기능 구현하기 (0) | 2022.12.11 |
댓글