Django

Redis

zhelddustmq 2024. 8. 30. 18:06

00.Redis 

01. Redis가 필요한 경우들

02. Redis 기본 구조

03. 캐시 읽기 전략 (Read Cache Strategy)

04. Django With Redis

 

 

00.Redis (Remote Dictionary Server): 데이터를 빠르게 저장하고 가져오는데 탁월한 성능을 발휘하는 도구

  • 인-메모리(In-memory) 데이터베이스로 메모리를 저장소로 사용
  • 한 마디로 아주 빠름 (Disk에 비해 약 1,000배 이상)
  • value에 String, Set, Hash 등 다양한 종류의 타입을 지원
  • 복잡한 쿼리가 필요 없음
  • 그런데 잘 잊어버림 (인메모리라 휘발성, 근데? 보완책 있음)
  • 한 번에 하나의 일만 처리할 수 있음 (어차피 빠르고, 순서를 보장해서 꼬일일이 없음. 장점!!)

 

 

01. Redis가 필요한 경우들

 

Ex01) 조회수, 방문자수 등을 우리가 보여줘야한다면?

  • DB에 직접 저장할 수 있겠지만 1초에 너무 많은 업데이트가 필요한 경우, 많은 DB 리소스를 소모함. (때에 따라 처리가 안되거나 누락되기도 함.)

→ 빠르게 응답이 가능한 Redis로 처리. 모든 처리를 Redis를 이용하고, 영속적인 저장이 필요할 때 데이터를 DB로 옮김.

 

Ex02) 빈번하게 업데이트 되는 데이터라면?

  •  좋아요/팔로우 등의 기능은 버튼을 누를때마다 DB에 읽기/쓰기 연산이 이루어짐
  • 다량의 insert/update는 RDB의 성능을 크게 저하시킴
  • 실제로 좋아요/팔로우를 DB 사용으로 구현해두고 2-3명이 동시에 버튼을 여러번 눌러보면 생각대로 동작하지 않는 것을 볼 수 있음.
  • 이벤트를 하게되어 실시간 랭킹이 필요하다면?
    • 이 역시 RDB로는 한계 

→ Redis를 이용해서 처리하면 초당 16만건 이상을 처리할 수 있음.또한 내부적으로 key-value 형태를 이용하여 각 사용자당 하나의 좋아요/팔로우만 할 수 있게 하는등의 처리가 용이

 

Ex03) 세션 / 실시간 검색 …

  • 실시간으로 로그인 해있는 유저를 파악하는 것이 필요하다면?
  • 모든 유저가 검색하는 것에 대한 실시간 파악이 필요하다면?

→ 즉, RDB로 처리하기에는 너무나 많은 쿼리가 발생할 것 같을때

  RDB에 바로 저장해야할 만큼 중요하지 않은 데이터일때

  너무 데이터의 크기가 크지 않을때(메모리 쓰기때문에 귀중한 자원임)

Redis 사용을 생각해 볼 수 있음.

 

Ex04) 서버가 여러개로 많아질 때...

 

 

캐싱(Caching)

 우리가 많이 사용하는 Relational Database는 빠르지만, 또한 빨라졌지만 아직 느림.

  • 고객의 수가 많아질수록, 요청이 증가할수록 우리의 DB는 점점 부하가 걸림.

예시) 우리가 도서 관련 서비스를 하는데 항상 유저에게 보여줘야하는 도서 데이터가 있다면? (월간 베스트 셀러)

  • 모든 유저에게 매번 데이터베이스에서 읽어서 보여주는게 아니라, 미리 불러오기(Load)해서 Redis에 저장을 해두고 사용하자 !→ 캐싱 Caching
  • → 데이터베이스를 거치지 않고 Redis에서 바로 가져와서 응답이 가능

단점

  • Redis는 메모리를 사용하기 때문에 빠른 것
  • 하지만 💰이 정말 많다면 가능한 이야기일수도...?(인스타그램 등)

 

02. Redis 기본 구조

1️⃣ (선택) DB에서 필요한 데이터를 미리 Caching (Cache warming)

2️⃣ 요청이 들어오면 Caching 되어 있는지 확인 (cache-key 이용)

2-1. Caching 되어 있다면 바로 조회해서 처리 (Cache Hit)

2-2. Caching 되어 있지않다면 (Cache Miss)

(1) DB에서 데이터를 조회해서

(2) Caching 하고

(3) 요청을 처리

 

  • Redis는 결국 또 다른 데이터베이스를 사용하는 것.
  • 이는 데이터를 관리하는 곳이 두 곳으로 늘어난다는 것.
  • 두 데이터베이스 사이에 정합성이 깨지지 않게 관리가 필요하게 된다는 것을 의미

캐싱을 고려하려면? 🤔

  • 데이터가 한 번 작성된 이후 여러번 읽혀지는 경우인가?
  • 반복적으로 조회되는 데이터가 고유한 데이터인가?
  • 데이터의 크기가 크지는 않은가?

03. 캐시 읽기 전략 (Read Cache Strategy)과 캐시 쓰기 전략 (Write Cache Strategy)

  • 캐시 읽기 전략 (Read Cache Strategy)

1️⃣ Look Aside(Cache Aside) 패턴

  1. 요청이 들어와서 데이터를 찾을때 캐시를 먼저 찾아봄
  2. 만약 캐싱되지 않았다면 DB를 조회
  • 가장 기본이 되는 캐시 전략
  • 캐시와 DB가 분산되어 운용되므로 redis가 죽어도 서비스에 문제가 없음
  • 하지만 redis가 죽었을때 요청이 한번에 DB로 몰리면 서비스 장애 가능성 있음
  • 최초에 캐시로 데이터를 넣어주는 작업 필요 (Cache Warming)

2️⃣ Read Through 패턴

  • Look Aside와 비슷하지만 캐시만 바라보고 데이터를 조회하는 것
  • 캐싱하는 로직은 직접 처리하지 않고 다른 라이브러리에게 위임 즉, 자동으로 DB와의 데이터가 동기화가 이루어지도록 하는 방식
  • 캐싱을 적극적으로 이용할 수 있으나, Redis가 다운될 경우 서비스가 중지됨 (라이브러리가 캐시 데이터를 DB에 저장하기 전에 다운되면 데이터도 유실됨)

 

  • 캐시 쓰기 전략 (Write Cache Strategy)

1️⃣ Write Back(Write Behind) 패턴

  • 데이터를 저장할 때 바로 DB에 저장하는 것이 아닌 캐시에 모아 두었다가 한 번에 저장
  • (N개의 데이터를 하나씩 저장하는 것보다, bulk create 하는것이 더 빠르기 때문)
  • 캐시에서 장애가 발생할 시 데이터 누락의 가능성이 있음

2️⃣ Write Through 패턴

  • 데이터를 캐시에도 저장하고, DB에도 저장하는 방식
  • 캐시를 이용해서 DB를 동기화
  • 캐시의 데이터가 항상 최신 데이터로 유지됨
  • 두번의 저장이 이루어지기 때문에 데이터 유실에 민감한 로직에서 사용

 3️⃣ Write Around 패턴

  • 모든 데이터는 DB에 바로 저장
  • cache miss가 발생했을때만 캐시와 DB에 저장
  • 캐시와 DB간 데이터 불일치 가능성
    • cache miss가 발생하기 전까지 DB에서 내용이 수정되었다면 서비스에 반영되지 않음 (cache가 만료될 때까지 유지됨)
    • cache의 만료시간(TTL)을 짧게 잡고 사용

일반적으로 Look Aside - Write Around 전략을 씀.

 

 

03. Redis 가볍게 사용하기

  • Redis도 결국 하나의 프로그램
  • 배포 환경에서는 Redis만 설치되어있는 컴퓨터를 따로 사용 (DB Server처럼, Redis Server를 따로 둠)
  • 하지만 우리는 배우는 단계이므로, 내 컴퓨터에 Redis를 설치하고 띄우는 것

Redis 설치(Window. Mac은 다른 글 보세요.... ㅎㅎ)

링크: https://github.com/microsoftarchive/redis/releases접속 후 .msi 다운

다른 옵션을 변경하지 않고 모두 Next로 진행후 설치

redis-cli 사용

  • C / Program Fiels / Redis / redis-cli.exe 를 실행하면 redis-cli를 볼 수 있다.
  • redis-cli는 redis에 명령을 입력할 수 있는 도구 !

ping 을 입력후 PONG 이라는 응답이오면 redis가 잘 동작하고 있는것 !

Redis 환경 변수 등록하기 (optional)

  • 항상 redis를 클릭해서 접속하는 것이 귀찮다면 환경 변수에 등록

  • 이제 명령 프롬프트 (not GitBash) 에서 redis-cli 만 입력해도 실행이 가능
  • ping 을 입력해서 PONG 이라는 답이오면 redis가 잘 실행되고 있는것 !
keys * # 모든 key 조회

 

  • keys * 명령어는 redis가 가지고 있는 모든 key를 조회하는 큰 부하가 들어가는 명령어
  • redis는 한 번에 하나의 일을 처리하기에 keys * 같은 명령어는 정말 조심 !
  • (※특히 프로덕션 환경에서는 결코 사용해서는 안됨 ※ )

아마 처음 명령어를 입력하면 아무 값도 없기때문에 (empty list or set) 이렇게 뜸

 

아래와 같이 key값으로 조회해서 하나의 명령만 시켜야 부하가 안걸림

get key # Key에 해당하는 value 조회

Create

set key value # key value 저장

setex key seconds value # seconds초 뒤에 삭제되는 key value를 저장

인증번호 같은 로직에서 유저에게 보낸후 만료시킬때 사용하면 좋겠죠?

Update

rename key newkey # key 이름 변경

expire key seconds # 해당 Key의 데이터 만료시간을 seconds초로 설정

 

Delete

del key1 [key2 ...] # key 삭제

04. Django With Redis

  • Django는 기본적으로 caching 기능을 내장

준비하기

seed를 이용해서 1만개의 product 데이터 만들기

python manage.py seed products --number=10000

모든 product를 조회하는 API를 만들기

 

api_pjt/urls.py

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    ...
    path("api/v1/products/", include("products.urls")),
]
...

 

products/urls.py 

from django.urls import path
from . import views

urlpatterns = [
    path("", views.product_list),
]

 

products/serializers.py

from rest_framework import serializers
from .models import Product


class ProductSerializer(serializers.ModelSerializer):
    class Meta:
        model = Product
        fields = "__all__"

 

products/views.py

from django.shortcuts import render
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import Product
from .serializers import ProductSerializer


# Create your views here.
@api_view(["GET"])
def product_list(request):
    products = Product.objects.all()
    serializer = ProductSerializer(products, many=True)
    return Response(serializer.data)

 

 

그냥 조회해보기

 

캐싱 사용하기

from django.core.cache import cache
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import Product
from .serializers import ProductSerializer


@api_view(["GET"])
def product_list(request):
    cache_key = "product_list"

    if not cache.get(cache_key):
        print("cache miss")
        products = Product.objects.all()
        serializer = ProductSerializer(products, many=True)
        json_data = serializer.data
        cache.set(cache_key, json_data, 10)

    response_data = cache.get(cache_key)
    return Response(response_data)

10초후 만료되는 데이터 캐싱

 

Cache Miss가 발생했을경우
Cache Hit 일 경우

→ 엇? 그런데 Redis에 내가 저장한 key가 없다 ?? 👀

 

Redis를 Cache로 사용하기

 

Local Cache

  • 서버 컴퓨터의 자원을 사용해서 각 서버에 캐시를 저장. 즉, 서버가 여러대로 늘어난다면 서버마다 개별 캐시가 있음
  • 서버 내에서 동작하기 때문에 속도가 빠름
  • 서버 수가 늘어날수록 모든 서버간 캐시 동기화를 위해 추가 리소스 소모

Global Cache

  • 별도의 캐시 서버를 두고 운용하는 방법으로 서버 간 데이터 공유가 쉬움
  • 데이터를 분산해서 저장할 수 있음. 자세한건 Replication과 Sharding을 키워드로 검색
  • 캐시 서버를 공유하고 있으므로 캐시 동기화에 추가적인 리소스x. 서버 수가 늘어나는 경우 효과적인 방법

 

 

 

Settings | Django documentation

The web framework for perfectionists with deadlines.

docs.djangoproject.com

 

  • Redis를 캐싱으로 사용하기 위해서는 설정이 필요.
    → 우리가 Redis를 사용해도 각 서버에서 별도의 Redis를 운용하면 Local Cache

  • Django와 Redis 사이를 연결해주는 미들웨어 설치
    • django-redis는 django가 기본으로 가지고 있는 cache 기능을 활용해서 편하게 캐싱을 구현할 수 있도록 지원
    • django-redis를 설치하지 않아도 redis 사용가능 (Django 문서 참고)
    • https://github.com/jazzband/django-redis
 

GitHub - jazzband/django-redis: Full featured redis cache backend for Django.

Full featured redis cache backend for Django. Contribute to jazzband/django-redis development by creating an account on GitHub.

github.com

 

pip install django-redis
...
CACHES = {  
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}
...

이제 redis에 캐싱

그런데 왜 product_list 가 아니라 :1: 이 붙는거지? 👀

https://docs.djangoproject.com/en/4.2/topics/cache/#cache-key-transformation

 

Django’s cache framework | Django documentation

The web framework for perfectionists with deadlines.

docs.djangoproject.com

 

key_prefix 와 version 이 : 으로 구분되어 붙음

 

  • 포트폴리오에 들어가야 하는것은 Redis를 써봤다! 가 아니라 아래의 고민이 들어가야 함.

    내가 어떤 상황에서 어떤 문제를 발견했고, 해당 문제를 해결할 방법들을 고민하던 중 이러한 해결방안이 필요했고, 그 결과 Redis를 선택했다.
    Redis를 선택한 이유는 무엇이며, Redis를 이용해 캐싱을 적용한 결과 어떠한 향상을 얻을 수 있었다.




  • 물론, 프로젝트를 하면서 Redis를 써야할만큼의 부하가 발생하지 않을 수 있기에, 부하를 만듦
    1. 데이터베이스에 아주 많은(몇십만건) 데이터를 넣어두고
    2. API 요청도 Postman으로 한두개 누르는게 아닌, 가짜 요청을 엄청 많이 만들 수 있음
      • 이런걸 ‘부하테스트’라고 함 
      • 더 자세한건 Locust 를 검색

 

'Django' 카테고리의 다른 글

Django 서버 배포하기  (4) 2024.09.03
외부 API 연동하기  (1) 2024.09.01
Django ORM 최적화  (0) 2024.08.30
Django ORM (심화)  (0) 2024.08.30
JSON Web Token, JWT  (0) 2024.08.30