13.[DRF] Cache 캐시의 개념과 사용법

캐싱

  • 복잡한 계산의 결과를 저장해둬서 다음에 반복하지 않게끔
  • 저장 장소는 데이터베이스, 파일 시스템, 메모리 - 각각 성능이 다름
  • 설정에서 지정 - BACKEND와 LOCATION

메모리 저장 캐시

Memcached

  • 데이터베이스 접근 횟수를 줄임
  • 메모리에 저장됨
  • 서버 무너지면 데이터도 유실됨
  • 캐싱이나 메모리는 어차피 영구적인 데이터 저장목적은 아님
  • 여러 서버에서 하나의 캐시로 공유 가능

레디스

  • 레디스 서버가 로컬/리모트에서 돌아가야함
  • 메모리에 저장됨
  • 레디스 서버 여러개 돌릴 수 있음
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.redis.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379',
    }
}

데이터베이스 캐시

  • 빠르고 인덱싱이된 데이터베이스가 있으면 유리함
  • 만료된 캐시는 내가 함수로 호출해야함 - 자동이 아님
  • 커맨드로 DB 캐시테이블 생성 가능
  • 커맨드는 1개의 테이블만 생성
python manage.py createcachetable
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.db.DatabaseCache',
        'LOCATION': 'my_cache_table',
    }
}

DB가 여러개인 경우

  • DB 캐시 테이블들을 위한 라우팅이 필요

파일시스템 캐시

  • 위치에 실제로 폴더가 존재해야하고 읽고 쓰기가 가능해야함
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.filebased.FileBasedCache',
        'LOCATION': 'c:/foo/bar',
    }
}

로컬 메모리 캐시

  • 디폴트 캐싱
  • 메모리캐싱의 빠른 속도를 원하지만 Memcached같은 걸 돌릴 능력이 없을 때 씀
  • Location 생략가능
  • 로컬 메모리 캐싱이 여러개면 적어도 한개는 이름 지정해서 분리해줘야함
  • LRU(Least Recently Used) 컬링(골라모으는) 방법 사용
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'unique-snowflake',
    }
}

더미 캐시

  • 프로덕션쪽에는 무거운 캐싱 의무가 있지만 개발/테스트쪽에선 안 할 때
CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
    }
}

캐시 변수

  • TIMEOUT - 캐시 만료 (초), None 이면 만료안됨
  • OPTIONS - 서드파티들이 옵션을 보냄
    • MAX_ENTRIES
    • CULL_FREQUENCY
  • KEY_PREFIX - 모든 캐시 앞에 특정 문자열 붙일 수 있음
  • VERSION - 캐시 키 버전 넘버
  • KEY_FUNCTION

사이트 전체 캐싱

MIDDLEWARE = [
    'django.middleware.cache.UpdateCacheMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.FetchFromCacheMiddleware',
]

뷰 캐싱

  • URL path에다가 cache_page 래핑하기
  • view에다가 데코레이터 달아서 하는 방법도 있지만 비추천
from django.views.decorators.cache import cache_page

urlpatterns = [
    path('foo/<int:code>/', cache_page(60 * 15)(my_view)),
]

캐시 접근하는 방법(공통)

  • 딕셔너리방식
  • 없으면 InvalidCacheBackendError
fromdjango.core.cacheimport caches
cache1 = caches['myalias']
cache2 = caches['myalias']
cache1is cache2
True

디폴트 캐시 가져오기

from django.core.cache import cache #caches (복수) 아니고 cache (단수)

사용법

  • 키는 문자열이여야하고, 밸류는 아무 객체나 가능
cache.set('my_key', 'hello, world!', 30)
                            키, 밸류, 타임아웃, 버전
cache.get('my_key')
# 'hello, world!'
# 30초 후에 만료됨

sentinel

  • 해당 캐시에 값이 만료가 됐는지 확인할 때 + None을 저장했을 때
**sentinel** = object()

cache.get('my_key', **sentinel**) is sentinel
False

# Wait 30 seconds for 'my_key' to expire...

cache.get('my_key', sentinel)is sentinel
True

‘has expired’

  • 캐시 안에 값이 있는지 확인
cache.get('my_key', **'has expired'**)
'has expired'

cache.add

  • 키가 없을 때 추가
  • 키가 이미 있다면 업데이트 하지 않음
cache.set('add_key', 'Initial value')
cache.**add**('add_key', 'New value')
cache.get('add_key')
'Initial value'

cache.get_orset

  • 있으면 가져오고 아니면 저장하기
cache.get('my_new_key')# returns None
cache.get_or_set('my_new_key', 'my new value', 100)
'my new value'

cache.get_many

  • 딕셔너리로 모든 키 가져옴
cache.set('a', 1)
cache.set('b', 2)
cache.set('c', 3)
cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}

cache.set_many

  • 딕셔너리로 키 여러개 저장
cache.set_many({'a': 1, 'b': 2, 'c': 3})
cache.get_many(['a', 'b', 'c'])
{'a': 1, 'b': 2, 'c': 3}

cache.delete

cache.delete('a')
# True

cache.delete_many

  • 지정한 키 여러개 삭제
cache.delete_many(['a', 'b', 'c'])

cache.clear

  • 키 모두 삭제
cache.clear()

cache.touch

  • 만료시간 수정
  • 수정 성공 시 True, 실패 시 False
cache.touch('a', 10)
# True

cache.incr

cache.decr

  • 값이 숫자인 경우, 값을 1 증가/감소 시킨다
cache.set('num', 1)
cache.incr('num')
2
cache.incr('num', 10)
12
cache.decr('num')
11
cache.decr('num', 5)
6

cache.close

  • 캐시 연결 닫기
cache.close()

비동기 캐시

  • 새로나옴
  • 기본 함수들 다 쓸 수 있음
  • 기본 함수 앞에 a를 붙임
await cache.aset('num', 1)
await cache.ahas_key('num')
True

캐시 컨트롤

  • 2가지 캐시
    • 내 브라우저 캐시 (프라이빗 캐시)
    • 다른 제공자의 캐시 (퍼블릭 캐시)
      • 여러사람이 사용하고 컨트롤해서 보안에 취약함

cache_control

  • 프라이빗 캐시와 퍼블릭 캐시를 구분해준다
from django.views.decorators.cache import cache_control

**@cache_control(private=True)**
def my_view(request):
    ...

patch_cache_control

fromdjango.views.decorators.cacheimport patch_cache_control
fromdjango.views.decorators.varyimport vary_on_cookie

@vary_on_cookie
def list_blog_entries_view(request):
if request.user.is_anonymous:
        response = render_only_public_entries()
        patch_cache_control(response, public=True)
else:
        response = render_private_and_public_entries(request.user)
        patch_cache_control(response, private=True)

return response

Elasticache

  • redis의 주소를 받으려면 AWS의 elasticache를 생성해서 엔드포인트를 받아야함

참고

https://www.hides.kr/1011

https://wookkl.tistory.com/38

Did you find this article valuable?

Support Christy Choi by becoming a sponsor. Any amount is appreciated!