모각코 - 캐시에 대한 정리

김필모·2024년 3월 15일

정의

데이터나 값을 미리 복사해 놓는 임시 장소
캐시에 접근하는 시간 << 원래 데이터에 접근 or 값을 계산하는 시간 인 경우 사용함.
그렇지 않다면 굳이 캐시를 쓸 이유가 없다.

캐시의 종류

  • 컴퓨터 구조
    T(cpu) <<< T(메인 메모리)
    cpu core << L1 Cache << L2 Cache << L3 Cache 순의 multi-level 구성이 되어있다.
  • Disk Cache
    T(Main memory) << T(Disk) 이므로 os는 디스크 접근을 줄이기 위해 페이지를 미리 로딩하는 등의 일종의 캐시 정책을 적용
    T(SSD) << T(HDD) 을 이용해 disck cache를 구성하기도 하는데 RAID 시스템같은 대용량 시스템에서 활용

  • 알고리즘: Memoization
    Factorial과 같은 동일 계산이 반복되는 계산에 사용
    계산 시간 비용 -> 공간 비용(Cache)로 전이

느린 장치를 빠른 장치에 옮겨 놓는 것도 캐시지만 오래 걸리는 작업을 미리 만들어 놓고 재사용할 수 있다면 캐시 방법으로 해결할 수 있는 것이다.
여러개의 프로세스에서 공통된 캐시 접근이 필요로 한다면 공유캐시가 필요한데 만약 프로세스가 동일하다면 공유 메모리를 사용할 것이고 다르다면 Redis같은 솔루션을 이용할 것이다.

캐시의 동작

  • Read Cache
    데이터 읽기 성능을 개선, 원 데이터를 미리 읽어두거나 계산 값 재활용
  • Write Cache
    쓰기 속도를 극복, 어플리케이션 입장에서 쓰기를 완료하고 싶은데 장치에 쓰는게 너무 느리다면? 빠른 캐시에 쓰고 완료 처리하고 나중에 원 데이터 반영(Write back)
    하지만 이런 플로우로 쓰기 완료했는데 만약 에러로 반영 전에 휘발되는 위험이 있으므로 주의가 필요

공유 캐시

여러 개의 프로세스가 공유하는 캐시를 말한다. 캐시의 크기에 따라 고려할 것이 있다.

작은 공유 캐시

  • 같은 host + 작은 크기 공유
  • 공유 메모리, MMAP를 사용

큰 공유 캐시

  • 캐시 프로세스 분리
    프로세스와 캐시 프로세스간의 통신이 필요함
    T(cache) = T(read/write) + T(comm)

더더더더더더 큰 공유 캐시가 필요하면

  • 캐시 프로세스가 여러 개
    호스트가 분리될 수 있고 여러 프로세스에 저장될 데이터의 구분이 필요해짐
    내가 원하는 데이터가 어떤 캐시 프로세스에 있는 지 알거나 적어도 여기에는 없다는 정보를 알아야 함

Redis

  • In-memory Data structure Store
    문자열 뿐 아니라 집합, 정렬집합, 해쉬, 리스트 같은 자료구조를 지원
  • 오픈소스임
  • 싱글 스레드 기반
    싱글 스레드 기반이다 보니 응답속도가 느리다면 캐시의 효과가 줄어들 수 있음
    큰 연산을 피하는게 좋다.

그 외 기능으로 다음과 같은 것이 있다.

  • 영구적 보관 기능
  • 메시지 브로커 기능
  • 데이터 스트림 전송 기능
  • 확률적 자료구조

Redis 사용

Key: Value 구조로 데이터를 저장하는데 key는 식별자 value는 값으로 문자열 등의 자료구조를 저장

rediscli로 클라이언트 쉘을 얻을 수 있는데 데이터를 조작하는 명령어를 TEXT로 형태로 제공된다.

Redis 서버 실행

docker run -d --name redisd redis

-d 옵션은 daemonize

docker run -d -p <port>:6379 --name redisd redis

-p option은 호스트의 TCP port를 컨테이너의 6379번 포트에 포워딩

docker exec -it redisd redis-cli

Redis collections

redis에서 value를 변경하는 것은 atomic한데 이를 자료구조에서도 보장한다.

String key/value

  • 단일 키로 저장
    set [key][value]
    get [key]
    del [key]
redis:~~> set k:1234 hello
redis:~~> get k:1234 hello
  • 다중 키로 저장
    복수의 키와 값을 저장
    mset [k1][v1] [k2][v2]
    mget [k1][k2]
redis:~~> mset k:1 홍길동 k:2 성춘향
redis:~~> get k:1 k:2

갖고 오고 싶은 것이 n개라면 매번 get하기 보단 mget을 사용
atomic하므로 홍길동만 저장되는 경우는 없음

List

  • key 이름을 갖는 리스트
    하나의 키에 복수의 값이 저장된다.
    리스트의 어느 곳에 저장시키냐에 따라 LPUSH, RPUSH가 있음
redis:~~> lpush l:name 홍길동
redis:~~> lpush l:name 이몽룡 성춘향

--> 성춘향, 이몽룡, 홍길동

redis:~~> rpush l:name 고길동
redis:~~> rpush l:name 둘리 또치

--> 성춘향, 이몽룡, 홍길동, 고길동, 둘리, 또치

꺼내는 방법도 마찬가지로 LPOP, RPOP이 있음

redis:~~> lpop l:name
--> 성춘향
redis:~~> rpop l:name
--> 또치

list 확인하기 위해선 LRANGE 사용
lrange [key][s-idx][e-idx][s-idx] ~~ [e-idx] 1 2 라면 결과 값이 2개, [e-idx]이 -1이면 끝을 의미

데이터가 없으면 pop 했을 때 (nli)을 얻음
BLPOP <키> <타임아웃> 비어있을 경우 찰 때까지 기다림

Set

리스트 처럼 하나의 키에 여러개의 값을 저장, 하지만 중복 X

redis:~~> sadd s:number 5 10 15
redis:~~> sadd s:number 10 
-> 변화 없음
redis:~~> smembers s:number
-> 1) 5
-> 2) 10
-> 3) 15
redis:~~> sismember s:number 10
-> 1 (있다는 뜻)
redis:~~> sismember s:number 321231
-> 0

Sorted sets

zadd key score value
이때 score가 낮을수록 우선순위 높음
zrange key s-idx e-idx
zrevrange key s-idx e-idx
zrangebyscore key min max

Hash

HMSET k sk1 v1 sk2 v2
k에 sk1=v1, sk2=v2를 저장

HGETALL k
k에 저장된 모든 key/value 가져오기

HGET k sk1
k에 저장된 해쉬 중 키가 sk1인 value 가져오기

HMGET k sk1 sk2
k에 저장된 해쉬에서 여러개의 값을 가져오기

주의사항

하나의 컬렉션에 너무 많은 아이템 -> 성능 저하
경험상 10,000개 이하로 유지하는 게 좋음

Redis에는 저장기한을 지정할 수 있는데 multi-value에 대한 expire는 지정할 수 없음
즉, collection 전체에 대한 expire가 지정된다.

소감

캐시와 레디스에 대해서 글로 정리하는 시간을 가지니 다시 한번 머릿속에서 퍼즐이 맞춰지는 것 같아 좋았다. 다음 모각코 시간에는 cpython 관련 글을 작성해보고 싶다.

0개의 댓글