Redis

  • Redis는 싱글 스레드다

    • 싱글 스레드 기반이므로 하나의 명령어가 오래걸린다면 이는 적합하지 않다.

      • 서버에서 keys 명령을 사용하지 말자

      • flushall, flushdb 명령을 주의하자

        • flushdb: Redis는 db란느 가상의 공간을 분리할 수 있는 개념을 제공. 이를 모두 지우는 행위

        • flushall: 모든 db를 지우는 행위

        • flushall 은 지우는데 keys 명령어와 같이 많이 시간이 걸린다. 실제로 모든 데이터를 지우므로 O(n) 이 걸린다.

  • Redis Persistent

    • Memcached와 달리 Redis는 디스크에 저장된 데이터를 기반으로 다시 복구하는게 가능하다. 이런 Persistent 기능은 데이터 스토어로서 장점이 있지만 이 기능 때문에 장애의 주 원인이 되기도 한다.

    • RDB, AOF 가능을 알아보로 장애를 피하자

      • RDB: RDBMS라는 오해를 많이 받지만, 단순히 메모리의 스냅샷을 파일 형태로 저장할 때 쓰는 파일의 확장자명이다

        • SAVE 방식은 모든 작업을 멈추로 RDB 파일을 생성. 50GB 메모리 저장한다면 7~8분 소요

        • BGSAVE 방식은 fork() 명령을 통해 자식 프로세스를 생성하고 현재 매모리의 상태가 복제된 상태에서 데이터를 저장

      • AOF(Append Only File): 데이터를 저장하기 전에 AOF 파일에 현재 수행할 명령어를 저장해놓고 장애가 발생하면 AOF를 기반으로 복구하는 것

    • BGSAVE 방식에서 fork() 를 사용하면 COW(Copy On Write) 기술을 이용해서 부모 프로세스의 메모리에서 실제로 변경이 필요한 부분을 복사한다. 이때 메모리를 두 배로 사용하는 경우가 발생할 수 있다.

메모리 정책

LRU

least recently used algorithm. 가장 오랫동안 참조되지 않는 페이지를 교체하는 방식

pub/sub

  • Redis는 Key-Value 기능 외에도 pub/sub을 지원한다.

    redis > SUBSCRIBE channel [channel ...]
    redis > PUBLISH channel message
  • pub/sub는 키 공간과 관련이 없으며 DB 번호를 포함하여 어떤 레벨에서도 방해받지 않도록 설계

  • 패턴매칭도 가능: PSUBSCRIBE news.*

  • 다른 메세지 브로커와는 다르게 메시지 지속성이 없음. 메세지 전송후 삭제.

클라이언트

자료구조

String

> set hello world
OK
> get hello
"world"
> exists hello
(integer) 1
> del hello
(integer) 1
> exists hello
(integer) 0
> set counter 100
OK
> get counter
"100"
> incr counter
(integer) 101
> incr counter
(integer) 102
> incrby counter 50
(integer) 152
> incr count
1
> getset count 0
"1"
> get count
"0"
> del count
(integer) 1
> get count
(nil)
  • SET 으로 값을 넣을 때 이미 값이 있으면 덮어씀 - doc

    If key already holds a value, it is overwritten, regardless of its type.

List

  • linked list

  • pub-sub 패턴으로 활용

Hash

> hmget user2 email country
1) "id@domain.com"
2) "Korea"

Set

> sadd partner:visa:merchants 3212 1231 (1)
(integer) 2
> smembers partner:visa:merchants (2)
1) "3212"
2) "1231"
> scard partner:visa:merchants (3)
(integer) 2
> sismember partner:visa:merchants 3212 (4)
(integer) 1
> srandmember partner:visa:merchants (5)
"1231"
> srandmember partner:visa:merchants 2 (6)
1) "1231"
2) "3212"
> srandmember partner:visa:merchants -2 (7)
1) "1231"
2) "1231"
> spop
> stem
> smove
> sinter
> sinterstore
> sdiff
> sdiffstore
> sunion
> sunionstore
1 set add
2 set members
3 set cardinality
4 if is member, return 1(true). otherwise, return 0(false)
5 set random member
6 multiple random
7 multiple random(duplicate)

The max number of members in a set is 232 - 1 (4294967295, more than 4 billion of members per set).

Sorted Set

key naming

object-type#id:data
partner:user#123:name
partner:merchant#123:bno
partner:merchant#123
merchant#121231:base-url
users (1)
1 `users`에 user 키를 모두 저장하는 list 혹은 set

batch

bulk insert cat data.txt | redis-cli --pipe using pipe mode

transaction

MULTI
INCR id:users
SET user:{id} '{"name": "yj","age": 30}'
SADD users {id}
EXEC

데이터 설계

  • 모든 데이터를 키에 저장할 수 있는가?

    • 키만 조회하여 업무를 처리할 수 있도록 구성

  • 자료구조로 구현이 가능한가

    • 여러개의 명령어를 사용해도 실행시간이 O(1)인지

    • 우리에겐 lua가 있다

  • 데이터 사용 성향에 따라 다른 데이터 구조 선택 필요

    • 빠른 쓰기가 필요한지 빠른 읽기가 필요한지

  • 단순한 데이터 조회 패턴을 가지는가?

    • where 절 없음

  • 숫자 데이터가 많은가?

    • 카운터와 같은 숫자 데이터 저장에 강함

  • lua 사용시 전체 시간 복잡도는 O(log n)을 초과하지 않도록 하라

lua 사용시 주의 사항

  • UNLINK

    • Redis 4.0에서 추가

    • DEL 과 다른 점은 비동기로 별도 스레드에서 백그라운드로 실행됨

    • 컬렉션에 데이터가 많은 DEL 보다 빠름

    • 키 삭제는 sync로 하고, 값 삭제를 별도 쓰레드에서 async로 처리.
      (맴버수가 64개 이하일 경우 DEL 과 같이 sync로 처리)

    • 메인 스레드는 백그라운드 스레드와 동기화를 해야하며 이것도 비용으로 볼 수 있음

  • DEL

    • 블록킹 모드에서 값을 제거함

    • 제거할 값이 클 경우(큰 리스트나 해시에 할당이 많을 경우) redis가 오랫동안 블락킹됨

    • 이를 해결하기 위해 redis는 non-blocking' delete 로 UNLINK 를 제공함

  • UPDATE:

    • Redis 6.0부터 신규 설정이 추가됨 → lazyfree-lazy-user-del

      • 해당 값을 true로 설정시 DELUNLINK 와 같이 실행함

version check

$ telnet <ip> <port>
Trying <ip>...
Connected to <ip> (<ip>).
Escape character is '^]'.
info
$3506
# Server
redis_version:5.0.5
...
# Cluster
cluster_enabled:1
...

  • 공유된 자원을 여러 스레드가 접근하는 것을 피하고자할 때 락을 사용함

  • 분산락: 데이터베이스 등 공통된 저장소를 이용하여 자원이 사용 중인지 체크하는 것. 전체 서버에서 동기화된 처리를 가능하게 함

  • 스핀락(spinlock): 임계 구역(critical section)에 진입이 불가능할 때 진입이 가능할 때까지 루프를 돌면서 재시도하는 방식으로 구현된 락

  • 락을 획득한다는 것. "(1) 락이 존재하는지 확인, (2) 존재하지 않으면 락 획득." 이것을 atomic하게 처리.

분산 락

  • setnx: 값이 존재하지 않으면 생성하는 연산자

    fun doProcess() {
        val lockKey = "lock"
    
        try {
            while (!tryLock(lockKey)) { (2)
                try {
                    Thread.sleep(50)
                } catch (e: InterruptedException) {
                    throw RuntimeException(e)
                }
            }
        } finally {
            unlock(lockKey)
        }
    }
    
    fun tryLock(String key): Boolean {
        return command.setnx(key, "1") (1)
    }
    
    fun unlock(key: String) {
        command.del(key)
    }
    1 락의 타임아웃이 지정되지 않음
    2 락을 획득하지 못하면 Excpetion이 발생하는데 무시됨
    • 스핀락을 사용하면 레디스에 부담이 큼

  • Redisson으로 락 사용하기?