본문 바로가기
Redis

[OSSCA Redis / 빅데이터 저장 및 분석을 위한 NoSQL & Redis] Part3

by 개복취 2023. 11. 1.

 

  1. 데이터 입력/수정/삭제/조회 (String)
  2. Redis 에서 제공하는 자료구조
    • Hash
    • List
    • Set (Sorted Set)
    • Stream
    • Hyperloglog
    • Geospatial index

데이터 입력/수정/삭제/조회 (String)

key / value 의 구조를 가지고 O(1)의 get/set/mget/mset command 를 제공한다.

https://redis.com/redis-enterprise/data-structures/

 

#명령어

종류 내용
set 데이터 저장할 때 (key, value)
get 저장된 데이터 검색할 때
rename 저장된 데이터 값을 변경할 때
randomkey 저장된 key 중 하나의 key를 랜덤하게 검색할 때
keys 저장된 모든 key를 검색할 때
exits 검색 대상 key가 존재하는지 여부를 확인할 때
mset / mget 여러 개의 key와 value를 한번 저장하고 검색할 때
  • mset/mget 은 O(1) * N 이기 때문에 mget/mset의 경우 많은 item을 요청하면 전체 응답이 느려진다.
    • mset/mget은 하나의 transaction으로 묶이기 때문에 실행하는 도중에 다른게 들어와서 실행하지 못한다.
  • 특히, cluster에서는 mget은 여러 서버의 응답을 모아야 하므로 더 느려지는 경향을 보인다.
  • Redis 내부에 데이터를 저장할 때에는 set [key] [value] 형태로 저장한다.
  • key를 통해 value 값을 가지고 올 때에는 get [key] 형태로 가지고 온다.

set / get

  • Redis 는 사용할수록 점점 사용하는 메모리를 아끼는게 중요하다.
    • 같은 데이터 사이즈를 유지하는게 나중에 메모리 파편화를 줄이는데 도움이 된다.
  • 메모리를 아끼기 위한 노력으로 value 를 Json serialize 형태로 집어넣어서 파싱하여 사용하는 경우도 많다.
    • 초반에 데이터를 직접 볼 수 있다는 장점때문에 사용한다.
    • 메모리를 아끼기 위해 Protobuf(직렬화 프로토콜) 등의 바이너리 인코딩을 할 수 있다. 
    • 자바 구조체를 Serialize 해서 사용하면 자바계열(JVM) 위에서만 읽을 수 있어서 피하는게 좋다.
    • 그러나 Json Serialize 사용하면 Race Condition 이 발생할 가능성이 생긴다.
    • 개별 데이터의 변경이 필요하면 hash 자료구조를 사용하는 것이 유리하다.

 

 

Redis 에서 제공하는 자료구조

Redis에서 제공하는 자료구조는 효율적으로 데이터를 저장/관리 할 수 있도록 다양한 타입의 데이터 속성을 제공하고 있다.
기본적으로 Redis의 자료구조는 Atomic하기 때문에 RaceCondition을 피할 수 있다.

 

Hash

Redis에서 기본 자료구조로 Hash Table은 Key-Value 쌍으로 이루어져 있다. Redis 4.x버전부터는 'SipHash'를 지원한다.

인코딩 타입은 OBJ_ENCODING_HT 이다.

Hash Table

  • Redis 에서 데이터를 표현 할 때 기본 타입은, 하나의 key - (하나 이상의 Field/Element) value 값으로 저장된다.
  • key에는 아스키 값을 저장하고, value에는 string 데이터 및 redis에서 제공하는 컨테이너 타입의 데이터를 저장한다.
    (기본적으로 key값은 String 구조로 저장된다. / value값은 set, sorted set, list..)
  • hash 타입의 데이터를 처리할 때는 hmset, hget, hgetall, hkey, hlen 등의 명령어를 사용한다.

 

#Siphash?

  • Redis dict에서 hash로 사용된다. Redis 4.x부터 도입되었으며 참고로 Python도 Siphash를 3.x 부터 도입하였다.
  • Siphash를 쓰는 이유?
    • 일반 Hash는 언제나 같은 key에 대해서 동일한 결과를 준다.
    • 어떤 서버에서 동작하든 간에 같은 결과를 가지므로 데이터 DDos 의 원인이 될 수 있다.
      (특정 버킷으로만 키가 들어가도록 호출하도록 하는 방법)
  • Siphash는 서버가 시작하는 시점마다 시드값을 사용해서 서로다른 결과가 나오도록 한다.
    • Hash알고리즘은 같고 해당 프로세서 내에서는 매번 같은 hash결과를 주지만, 다른 서버나 재시작시에는 다른 hash값을 주도록 하여 추측하기 어렵도록 한다.

#Redis 에서 해시충돌을 줄이기 위한 방법

  • Redis에서는 선형리스트 갯수를 줄이는 방식을 채택한다. linked list 테이블에 두배의 버킷을 할당하고 key를 복사한다.

  • 기존 key들은 버킷 단위로 새 ht_table로 이동
  • 새로운 key들은 새 ht_table에만 저장한다.
  • 양 쪽에 키가 존재하지는 않는다.

Redis Dict struct / Redis dictType

  • Redis에서 RB Tree 사용하지 않고 선형리스트 주로 쓰는 이유?
    • 복잡하지 않아서 주로 사용된다. 많이 들어갈 때에만 중요하고 bucket을 빨리 늘리는 구조로 만들어서 구현하는게 더 효율적
    • RB Tree를 사용하는게 우아하지만 복잡성이 증대한다.
  • 참고로 Java 에서의 HashMap 자료구조는 Java7 버전에서 Java8 버전으로 넘어가면서 서로 다른 구현방식을 채택하고 있다.
    • 해시충돌이 Java8 에서는 Linked List가 아닌 Red/Black Tree를 사용한다. (O(N) 에서 O(logN))

# Redis에서의 Key

Redis에서는 Key는 string으로 고정이지만, value에는 여러가지 자료구조로 구현된다.

 

List

Redis 에서의 list는 Array 형태가 아니라 Linked list로 구현되어 있다.

https://redis.com/redis-enterprise/data-structures/

  • list 타입은 기존 관계형 테이블에서는 존재하지 않는 데이터 유형이다. (제 1정규화를 위반하게 되므로)
  • Redis에서의 list는 Array 형태가 아닌 linked list 로 구현되어 있어서 list의 head 또는 tail 에 데이터를 삽입을 상수시간에 처리 할 수 있다. Redis 내부 자료구조가 linked list 형태로 구현한 이유는 DB에서의 데이터를 고속으로 넣는것이 더 중요하기 때문이다.
  • Redis에서는 nested 한 자료구조를 지양하기 때문에 트리구조를 지원하지 않는다. 따라서, 트리구조를 나타내기 위해 Hash / list 를  parent-child 관계를 가지는 형태로 구현한다.
  • 내부의 Task Queue, Prioirty Queue 등 Queue의 구현이 List 형태로 되어있다.
  • string타입의 경우 배열에 저장할 수 있는 데이터 크기의 제한은 512MB 이다.
  • list 타입의 데이터를 처리할 때 lpush, lrange, rpush, rpop, llen, index 명령어를 사용한다.

 

Set , Sorted Set (Unique 집합)

유일한 데이터를 저장하는 자료구조이다. 인코딩 타입에 따라서 데이터 저장 타입이 달라진다.

  • Set에서의 OBJ_ENCODING_HT 인코딩 타입
    • 내부적으로 hash table이 존재한다(dict 가 하나 더 존재함)
  • Set에서의 OBJ_ENCODING_INTSET 인코딩 타입
    • hash table은 메모리 사용량이 크므로 정수 배열을 사용하여 메모리 사용량을 낮출 수 있다.
    • 정수 타입만 들어있을 경우는 정수의 배열을 만들어서 사용한다.
    • 2byte, 4byte, 8byte 크기 값에 따라 다르게 구성된다.
    • 찾을때는 bisect로 확인한다.
  • Sorted Set 에서의 OBJ_ENCODING_SKIPLIST 인코딩 타입
    • 메모리를 상당히 많이 사용한다.
    • 내부적으로 skiplist 라는 자료구조를 사용한다.
    • 'zscore' 명령을 위해서 dict 자료구조도 사용한다.
  • Sorted Set 에서의 OBJ_ENCODING_LISTPACK 인코딩 타입
    • 메모리 사용량을 아끼기 위해 선형 메모리에 저장하는 형태이다.
  • Score 은 Integer 가 아니라 Double 형태로 저장된다.
    • Double과 Long의 부동소수점 정밀도가 서로 다르기 때문에 여기서 버그가 생길 수 있는 여지가 존재한다.
  • 특정 데이터가 존재하는지를 체크하는 용도로 사용하기 쉽다는 장점이 있다.

 

https://redis.com/redis-enterprise/data-structures/

  • list 타입은 하나의 필드에 여러개의 배열을 저장할 수 있었지만, set타입은 여러개의 원소(element)로 데이터를 표현할 수 있다.
    (중복되는 값이 존재하지 않는다.)
  • set 타입의 데이터를 처리할 때 sadd, smembers, scard, sdiff, sunion 명령어를 사용한다. 
  • sorted set 타입은 set과 동일하지만 정렬된 상태이면 sorted set이라고 한다.
  • sorted set 타입의 데이터를 처리할 때 zadd, zrange, zcard, zcount, zrank, zrevrank 명령어를 사용한다.

 

#skiplist

  • O(LogN)의 속도를 가지는 자료구조 이다. Tree구조를 사용해서 나오는 시간 복잡도와 동일한 효율을 보여준다.
    (비유를 하자면 일반선과 급행선이 있는 9호선과 동일하다.)
  • Redis에서는 Sorted Set을 저장하기 위한 자료구조로 사용된다.
  • skiplist의 높이는 Random 하게 결정된다.

https://transitinto.com/?p=3807

아래와 같은 skiplist가 만들어졌고 '30' 의 값을 찾기위한 절차는 다음과 같다.

  1. Layer1 에서 시작하여 5까지 간다음, 다음 연결리스트와 목표지점을 비교하고 다음 Layer으로 내려간다.
  2. Layer2 로 내려와서 15까지 간다음, 다음 연결리스트와 목표지점을 비교하고 다음 Layer으로 내려간다.
  3. Layer3 에서 목표지점인 30 까지 다다를 수 있기 때문에 값을 찾을 수 있다.

결론적으로, skiplist로 연결리스트를 사용하는 단점중 하나인 인덱싱에 걸리는 시간을 대폭 낮춰주는 역할을 해준다.

 

HyperLogLogs

  • Hyperloglogs 타입은 관계형 DB의 테이블 구조에서 Check 제약조건과 유사한 개념의 데이터 구조이다.
  • 관계형 DB에서 Check 제약조건을 사용하는 이유는 해당 컬럼에 반드시 저장되어야 할 특정 데이터 값만 저장되도록 제한을 가하는 것이다.
  • Redis에서도 저장되어야 할 데이터 값을 미리 생성하여 저장한 후 필요에 따라 연결하여 사용할 수 있는 데이터 타입이다.
  • Hyperloglogs 타입의 데이터를 처리할 때 pfadd, pfcount, pfmerge 명령어를 사용한다.

 

Geospatial

  • 위치정보를 저장하는 데이터를 저장, 관리하는 타입이다.
  • 내부적으로는 Sorted Set을 사용한다.
  • Geospatial 타입의 데이터를 처리할 때 geoadd, geopos, georadius, geohash 명령어를 사용한다.

 

Streams

  •  로그를 처리하기 위해 최적화된 데이터 타입입니다. 차별화된 다양한 장점이 있지만, 가장 큰 특징은 소비자(Consumer)그룹을 지정할 수 있다.

 


출처:

https://redis.io/docs/data-types/

https://redis.com/redis-enterprise/data-structures/

https://www.slideshare.net/charsyam2/redis-196314086