MySQL같은 RDBMS는 데이터 스트럭처라는 개념이 없다. 데이터베이스를 이루는 여러 개의 테이블이 있으며, 이 테이블을 (join등으로 엮어서) 필요한 데이터를 추출할 뿐이다. RDBMS를 사용하는 서비스에서 데이터 스트럭처는 "애플리케이션 영역"에서 처리 한다.
반면 Redis는 List, Set, Sorted set, Hash, HyperLogLogs와 같은 애플리케이션 영역에서나 다룰만한 데이터 스트럭처를 제공한다. 애플리케이션에서 해야 할 일들을 Redis에 맡길 수 있음을 의미한다. Redis에서 key는 string이고, 데이터 스트럭처는 value에 저장한다. 아래 Redis가 지원하는 데이터 스트럭처를 정리했다. 이들 모든 데이터 스트럭처를 다룰 것이다.
Strings
Binary-safe strings
Lists
Sets
Sorted sets
Hashes
Bit arrays
HyperLogLogs
이들 자료구조 외에 GeoHash, PUB/SUB과 같은 기능을 제공하는데, 데이트 스트럭처라고 보기는 힘들어서 따로 다루려 한다.
자료구조외에 앞으로 자주 사용할 기본적인 관리 명령 몇 개도 살펴보겠다.
Redis Strings
Redis의 가장 기본이 되는 데이터스트럭처다. 기본적으로 Redis의 String은 연속된 바이트의 조합으로 python이나 ruby언어에서의 string이 아닌 C/C++ 언어에서의 char 배열과 비슷하다. 따라서 String 뿐만 아니라 바이트로 표현할 수 있는 정보, 예를 들어 JPEG 이미지나 serialized된 루비 객체들도 저장 할 수 있다. String value의 최대 크기는 512M 이다.
바이트를 저장하는 만큼 일반적인 string 뿐만 아니라 int나 float형도 저장을 할 수 있다. 명령도 다양해서 string과 관련해서 무려 24개의 명령을 제공한다. string 관련 지원 명령의 목록은 redis.io에서 확인 할 수 있다. 여기에서는 핵심적인 몇 개 명령어들을 살펴볼 것이다.
SET & GET
SET 명령어로 key에 string 값을 설정 할 수 있다. key에 문자열을 저장하는 느낌으로 가장 쉽게 사용 할 수 있는 명령이다. user:info:yundream key에 yundream@gmail.com 을 저장해 보자.
SET user:info:yundream yundream@gmail.com
GET 명령으로 key의 값을 읽을 수 있다.
GET user:info:yundream
"yundream@gmail.com"
Key는 데이터베이스에서 유일해야 한다. key가 이미 존재할 경우 value를 업데이트 한다.
SET user:info:yundream yundream@naver.com
OK
GET user:info:yundream
"yundream@naver.com"
이외에 몇 개의 유용한 옵션들이 있다.
EX seconds : key 만료시간을 초 단위로 설정 할 수 있다.
PX milliseconds : key 만료시간을 미리세컨트단위로 설정 할 수 있다.
NX : key가 없을 때만 값을 설정한다.
XX : key가 있을 때만 값을 설정한다. 이 경우 업데이트가 될거다.
이미 key가 있을 경우 value를 업데이트 한다고 했는데, 무시하고 싶을 때가 있다. NX 옵션을 설정하고 명령을 내리면, 동일한 key가 있을 때 value를 업데이트하지 않는다.
SET user:info:yundream yundream@yahoo.com NX
(nil)
EX와 PX는 만료시간이 있는 세션(session)유지에 유용하게 사용 할 수 있다. 유저가 사이트에 방문하면 세션을 만드는데, 적당한 시간에 세션을 정리해줘야 한다. EX와 PX 옵션을 이용해서 일정 시간 후에 key가 삭제되도록 할 수 있다. 60초의 만료시간을 설정했다.
SET user:info:yundream yundream@yahoo.com EX 60
OK
TTL key 명령으로 key의 남은 만료시간을 확인 할 수 있다.
TTL user:info:yundream
(integer) 52
String 명령어 리스트
Redis는 각 데이터 스트럭처를 관리하기 위한 명령어들을 제공한다. Redis는 string 관련 20개가 넘는 명령어들을 제공한다. 명령어들 모두를 정리했다. 이 중 자주 사용되는 명령어들만 자세히 설명하겠다.
명령
사용법
설명
DECR
key
값을 1씩 감소
DECRBY
key decrement
decrement 만큼 감소
DEL
key [key...]
key를 삭제
GET
key
key의 값을 조회
GETSET
key value
key를 조회하고 새로운 값을 저장
INCR
key
값을 1씩 증가
INCRBY
key increment
increment만큼 값을 증가
MGET
key [key...]
여러 key를 한번에 조회
SET
key value
key에 값을 저장
SETNX
key value
key가 없을 경우 값을 저장
MSET
key value [key value...]
여러 key를 저장
MSETNX
key value [key value...]
key가 없을 경우 저장
APPEND
key value
데이터를 추가(append) 한다.
SETEX
key seconds value
데이터 만료시간을 설정한다.
GETBIT
key offset
offset 이후의 비트 값 조회
SETBIT
key offset value
offset 이후에 비트 값 설정
STRLEN
key
데이터의 바이트 수를 반환한다.
GETRANGE
key start end
start end 범위의 데이터를 반환
BITCOUNT
key [start end]
비트 1을 센다.
BITOP
key operation destkey key [key...]
비트연산(AND, OR, XOR, NOT) 실행
INCRBYFLOAT
key increment
increment만큼 실수 증가
PSETEX
key milliseconds value
밀리초단위로 만료시간 설정
BITPOS
key bit
지정한 bit의 위치를 구한다
INCR 과 DECR을 이용한 Rate Limit 구현
INCR은 value를 1씩 증가한다. 인터넷 서비스에서 페이지와 아이템에 대한 방문 카운트를 저장할 때 유용하게 사용 할 수 있다. "item:x0001" 아이템에 대한 카운터를 해보자. key가 없다면, value가 0인 key를 만들고 여기에 1을 더한다.
INCR item:x0001
(integer) 1
INCR item:x0001
(integer) 2
GET item:x0001
DECR은 value를 1씩 감소한다. key가 없다면, value가 0인 key를 만들고 여기에서 1을 뺀다. -1이 될 것이다.
DECR item:x0002
(integer) -1
INCR은 카운팅외에, 유저별 API 호출 제한 등에 사용 할 수 있다. 보통 호출 제한은 두 가지 방식으로 이루어진다.
한달 동안 호출할 수 있는 총량
특정시간(예를 들어 1분)동안 호출 할 수 있는 API 제한. 한돌 동안의 호출량을 초과하지 않더라도 특정시간에 대량의 API가 호출 되면, Rate Limite를 걸어야 할 수 있다.
API counter는 일정 시간이 지나면 지워져야 한다. 전달 API 사용량은 Redis에서 조회하는 정책으로 하면, 2달 정도 유지하면 될 것이다.
API를 호출전에 INCR 명령어를 실행해서 카운트를 늘린다. INCR 명령어는 현재 value를 반환하는데, 이 값이 한달 사용량을 초과 하면, API 호출 실패 메시지(한달 사용량을 초과했습니다.)를 리턴한다.
실 서비스에서라면 Limit의 90%정도에 도달 했을 때, 경고메일을 전송하는 장치를 만들어야 할 것이다.
짧은시간 대량의 요청을 막기 위한 장치도 시간이 분단위로 조정된다는 것 말고는 특이 사항은 없다.
BIT
Redis는 string를 바이트로 다룬다고 했다. 이런 이유로 Redis string은 BitField와 XOR, AND, OR, NOT과 같은 비트연산도 지원한다. 비트연산을 어디에 써먹을 수 있을 것인지 느낌이 오지 않을 수 있을 것 같아서, Redis string에서의 비트 관련 명령어들에 설명과 이들 명령어를 어디에 응용할 수 있는지 살펴보기로 했다.
SETBIT로 key에 BIT를 설정 할 수 있다. 사용법은 아래와 같다.
SETBIT key offset value
offset 만큼 bit단위로 이동한 다음 value를 설정한다. offset은 0부터 시작한다. offset 0, 즉 0번째 위치에 1을 설정하고 값을 읽어보자.
SETBIT mykey 0 1
(integer) 0
GET mykey
"\x80"
16진수로 80이 출력됐다. 왜 이 값이 출력됐는지를 확인하기 위해서 현재 상태를 그림으로 묘사했다.
0번째 비트를 1로 설정했기 때문에, "10000000"이고 이걸 16진수로 출력하다보니 "\x80"이 된거다. 여기에서 7번째 비트를 1로 설정하면 어떤 값이 나올지 예상 할 수 있을 것이다.
SETBIT mykey 7 1
(integer) 0
GET mykey
"\x81"
GETBIT를 이용해서 offset 만큼위치의 bit 값을 읽을 수 있다.
GETBIT mykey 7
(integer) 1
BITCOUNT를 이용하면 몇개의 비트가 1로 설정됐는지를 확인 할 수 있다.
BITCOUNT key [start end]
start : 시작 바이트. 비트가 아닌 바이트다.
end : 종료바이트 위치
start와 end를 생략하면 key 전체에서 1로 설정된 비트를 계산한다.
BITCOUNT mykey 0 0
(integer) 2
start가 0 이고 end가 0이니, 처음 1바이트에 1로 세팅된 비트를 카운팅한다.
SETBIT를 이용하면 유니크방문자를 계산 할 수 있다. 유저식별번호가 int 형이라고 가정할 때, 유저가 방문을 하면 유저식별번호 만큼 offset한 위치를 1로 설정한다. 그리고 BITCOUNT를 하면 특정시간동안 방문한 유니크유저를 구할 수 있다. 아래와 같이 Key를 만들 수 있을 것이다. 2018년 8월 26일 12021, 10035 유저가 방문했다면, 아래와 같이 SETBIT을 한다.
이렇게 하면, 공간을 극도로 아낄 수 있다. 천만명 유저가 방문하는 사이트라면, 1.2메가바이트 정도의 공간으로 유니크한 일 방문자수를 계산할 수 있다.
Redis에서 저장 할 수 있는 string의 최대크기는 512메가바이트 이므로 2^32 만큼의 정보를 저장 할 수 있다.
BITFIELD
Bit field는 컴퓨터 프로그래밍에서 사용하는 데이터 구조다. 일련의 비트들을 저장하기 위해서 일정한 크기의 메모리 공간을 할당하고, 여기에 고정된 크기의 데이터를 차례로 저장을 한다.
Redis의 BITFIELD는 Bit field의 구현이다. 입력해야 하는 데이터의 크기가 고정이되며, 인접한 위치에 연속적으로 배치 할 수 있을 경우 사용 한다. 대표적인 예가 타임시리즈(Time series)데이터의 저장이다.
타임시리즈 데이터의 대표적인 예는 모니터링 정보의 저장이다. 예를들어 cpu 사용량을 모니터링 할 경우, 5분단위로 0~100 사이의 데이터를 연속해서 저장할 거다. 사용방법은 아래와 같다.
BITFIELD key [GET type offset] [GET type offset] ...
BITFIELD key [SET type offset value] [SET type offset value] ...
type는 i8(int8), i16(int16), u8(unsigned int8), u16(unsigned int16)등으로 정할 수 있다.
5분단위로 cpu 사용율 데이터를 저장한다고 가정해보자. cpu 사용율은 0~100일테니 u8(0~255)로 충분할거다. 2018년 8월 29일 00:00, 00:05 데이터를 저장했다.
BITFIELD cpu:usage:20180829 SET u8 #0 12 SET u8 #1 25
이런식으로 저장하면 된다. cpu의 하루치 모니터링 정보를 저장하기 위해서 사용한 메모리 공간은 288 바이트 정도다. 데이터를 꺼내보자.
BITFIELD cpu:usage:20180829 GET u8 #0 GET u8 #1
1) (integer) 12
2) (integer) 25
GETRANGE로도 데이터를 읽을 수 있다.
GETRANGE cpu:usage:20180829 0 1
"\x0c\x19"
16진수로 표현되기 때문에, 변환을 해야 겠지만 많은 데이터(이를테면 하루치 전부)를 가져올 때 편하게 사용 할 수 있다. STRLEN을 value의 길이를 계산해보자.
STRLEN cpu:usage:20180829
(integer) 2
DEL
key를 삭제 할 수 있다. key와 함께 value도 삭제된다.
DEL user:info:yundream
(integer) 1
FLUSHALL
FLUSHALL 명령으로 데이터베이스에 있는 모든 key들을 삭제할 수 있다.
KEYS
KYES 명령으로 데이터베이스에서 특정 패턴을 가지는 KEY를 가져올 수 있다. glob-style 패턴을 사용 할 수 있다.
KEYS pattern
h?llo 는 hello, hallo, hxllo와 일치한다.
h*llo는 hllo, heeeello와 일치한다.
h[ae]llo는 hello, hallo와 일치한다.
h[^e]llo는 hallo, hbllo와 일치한다. hello와는 일치하지 않는다.
h[a-c]llo는 hall, hbllo, hcllo와 일치한다.
*는 모든 key를 가져온다.
KEYS 명령의 시간복잡도는 O(N)으로 데이터베이스에 있는 모든 key들을 스캔해서 일치하는 key를 찾는다. Key가 많을 경우 많은 시간이 걸릴 수 있다. 또한 Redis의 one thread 운용이라는 특성 때문에, 이 시간동안 다른 작업이 블럭된다. 그러니 특별한 관리목적이 아닌 한 애플리케이션에 사용하면 된다. 대신 SCAN의 사용을 권장한다.
SCAN
패턴으로 일치하는 key를 찾기 위해서는 모든 key에 대해서 패턴비교연산을 수행하는 수 밖에 없다. 이런 작업에서 시간을 줄이기 위해서 사용하는 기술이 paging이다. 모든 데이터를 전부 가져오는 대신에, 일정 갯수만 가져오는 방식이다. 현재 어디까지 가져왔는지를 알고 있다면, 증분해서 반복을 하면서 데이터를 가져올 수 있다.
SCAN 명령을 이용해서 증분반복을 통해서 key를 가져올 수 있다.
SCAN cursor [MATCH pattern] [COUNT count]
cursor : SCAN은 cusrsor를 기반으로 반복을한다. 명령을 호출 할 때 마다, 다음 반복자(iterator)를 반환하는데, 이 값을 cursor로 이용해서 증분반복을 할 수 있다. 커서가 0이면 처음부터 반복을 시작한다.
count : 몇 개의 KEY를 SCAN 할지를 결정하는게 아니다. SCAN 명령을 내렸을 때 수행하는 작업의 양이라고 보면 된다. 일종의 힌트 같은 거라서, 몇 개의 key를 반환할지는 환경에 따라 다르다. 경험적으로 설정하는 수 밖에 없는데, 왠만해서는 굳이 설정할 필요는 없다.
테스트를 위해서 10개의 key를 입력했다. MSET을 이용하면 하나의 명령으로 여러 개의 키를 입력할 수 있다.
SCAN은 string에서 key를 찾기 위해서 사용하는 명령이고, 다른 데이터 스트럭처에 대해서도 그에 맞는 SCAN 변형이 존재한다.
SSCAN : SET
ZSCAN : Sorted SET
HSCAN : Hash
이들은 각 데이터 스트럭처를 다루면서 살펴보도록 하겠다.
Redis와 Persistent 스토어
앞서예제로 다루었던 Rate Limit 아이디어를 Redis로 만드는 건 어려운일이 아니다. 하지만 Redis는 persistent하지 않아서 persistent하게 데이터를 저장 할 수 있는 시스템을 준비해야 하기 때문에, 실제 구성에서는 신경써야 할 것들이 꽤 있다. 아래 그림은 이러한 구성 중 하나를 묘사하고 있다.
유저가 API를 호출하면, 호출로그를 Message Queue(Kafka같은)으로 보낸다. Message Queue에 연결돼 있는 메시지 처리기(보통 컨슈머 프로세스라고 부르는)가 메시지를 읽어서 REDIS 카운트를 하고, RDBMS에도 기록한다. 만약 REDIS에 문제가 생겨서 모든 데이터가 날라가더라도 RDBMS에 있는 데이터로 복원 할 수 있을 것이다.
구성은 애플리케이션마다 달라질 수 있겠으나, persistent한 스토어를 가지는 이 패턴은 거의 모든 경우에 들어가야 한다. 죽 Redis는 "보조" 역할이어야 하며, 그 자체가 "주"가 되어서는 안된다. 예를들어 요즘에는 대량의 유저 데이터를 "빅데이터 플랫폼"에서 분석 한 다음 서비스 할 수 있도록 데이터베이스에 올리는 등의 작업을 많이 하는데, 이 경우에도 우선은 RDBMS혹은 MongoDB, DynamoDB등으로 올리고 난 다음, Redis는 캐시 혹은 2차연산 결과를 서비스하기 위한 목적으로 사용 한다.
복습
웹 서비스에서 유저세션 정보를 저장해야 한다. 어떻게 저장할지 계획을 세우고 redis-cli로 테스트를 해보자.
Contents
Redis 데이터 스트럭처
Redis Strings
SET & GET
String 명령어 리스트
INCR 과 DECR을 이용한 Rate Limit 구현
BIT
BITFIELD
DEL
FLUSHALL
KEYS
SCAN
Redis와 Persistent 스토어
복습
Recent Posts
Archive Posts
Tags