• 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) 기술을 이용해서 부모 프로세스의 메모리에서 실제로 변경이 필요한 부분을 복사한다. 이때 메모리를 두 배로 사용하는 경우가 발생할 수 있다.

메모리 정책

Note
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으로 락 사용하기?