Chapter 1 - The Basics
a. Databases
Redis도 database의 개념을 가지고 있다. 처음에는 database 0로 시작한다. 이 database를 다른 것으로 바꾸고 싶으면 select 명령어를 사용한다. select 1을 입력하면 1번 database로 옮겨갈 것이다. 돌아가고 싶으면 select 0를 입력하면 된다.
b. Commands, Keys and Values
Redis를 잘 사용하려면 key와 value의 개념을 파악하고 있는 것이 중요하다. key는 data를 구별해주는 것이고 value는 key와 관련있는 실제 data이다. Redis는 value를 byte array로 다루기 때문에 value가 어떤 값이던지 상관하지 않는다.
아래의 명령어를 입력해보자.
> set users:leto "{name: leto, planet: dune, likes: [spice]}"
user:leto를 key로, 2번째 파라메터를 value로 저장하라는 명령어이다. key에서의 ':'는 Redis의 입장에서는 아무런 의미도 없다. 우리가 봤을 때 leto라는 이름의 user라는 것을 짐작할 수 있게 해주는 key일 뿐이다. value를 가져오는 명령어는 아래와 같다.
> get user:leto
c. Querying
key로 value를 찾을 수 있지만 value를 가지고 무언가를 할 수는 없다. value를 가지고 문언가를 하는 것(예: dune이라는 planet에 살고 있는 유저 찾기)은 Redis의 용도가 아니다.
d. Memory and Persistence
Redis는 in-memory persistent store이다. 기본적으로 Redis는 적당한 수 이상으로 key가 변할 때(예: key가 9번보다 적게 변하면 15분마다 disk에 저장하기) disk에 database를 복제한다. 또는 append mode로 동작하여 key가 변할 때마다 append-only file에 업데이트를 할 수도 있다.
e. Putting It Together
query limitation과 data structure와 memory에 data 저장하기 때문에 Redis는 무척이나 빠르다. 따라서 DB를 쓸 때 db에 접속량을 줄이기 위해 고민하는 것 같은 것을 할 필요가 없다.
Redis는 만능이 아니다. 필요한 용도에 적합하게 사용해야 한다.
Chapter 2 - The Data Structures
a. Strings
위에서 String을 저장하는 set를 살펴봤다. 다른 명령어들도 있다.
> strlen users:leto
> getrange users:leto 27 40
> append users:leto " OVER 9000!!"
strlen은 key의 value의 길이(length)를 가져오고 getrange는 value의 지정한 영역(range)을 가져온다. append는 기존의 value에 새 value를 더한다. 물론 지금 설명한 명령어들은 string의 경우에 의미가 있을 것이다.
> incr stats:page:about
> incr stats:page:about
> incrby ratings:video:12333 5
> incrby ratings:video:12333 3
incr은 value의 값을 1 증가시키고 decr은 1 감소시킨다. incrby는 지정한 수만큼 증가시키고 decrby는 지정한 수만큼 감소시킨다. users:leto에 incr을 적용하면 에러가 발생할 것이다. 각자 setbit과 getbit도 한번 살펴보자.
b. Hashes
key와 value 사이에 field라는 것을 두어 field를 가지고 value를 처리한다.
> hset users:goku powerlevel 9000
> hget users:goku powerlevel
hset은 key로 field와 value를 설정한다. hget으로 key에서의 field의 value를 얻어온다.
>hmset users:goku race saiyan age 737
>hmget users:goku race powerlevel
>hgetall users:goku
>hkeys users:goku
>hdel users:goku age
hmset은 한번에 여러 field를 설정할 수 있도록 해주고 hmget은 한번에 여러 field를 가져올 수 있게 해준다. hgetall은 한번에 모든 field와 이의 value를 가져오고, hkeys는 모든 field를 list로 보여준다. hdel은 지정한 field를 지운다.
이처럼 hash를 통해 좀 더 세밀한 처리를 할 수 있게 된다.
c. Lists
List는 주어진 key로 value의 배열을 처리할 수 있게 해준다.
> lpush newusers goku
>ltrim newusers 0 50
lpush는 value를 list에 더하고 ltrim은 range 사이의 value만을 유지하게 한다. ltrim은 O(N) operation을 가지는 명령어(여기서 N은 제거하는 value의 수이다.)이기 때문에 위의 두 명령어를 항상 같이 실행 함으로서 O(1)로 유지할 수 있게 된다.
d. Sets
중복되지 않는 value를 저장하고 이와 관련한 명령어들을 제공한다.
> sadd friends:leto ghanima paul chani jessica
> sadd friends:duncan paul jessica alia
> sismember friends:leto jessica
> sismember friends:leto vladimir
value가 key의 멤버인지 아닌지 알려주는 sismember는 O(1) operation이므로 매우 효율적이다.
> sinter friends:leto friends:duncan
> sinterstore friends:leto_duncan friends:leto friends:duncan
sinter는 두 key가 같이 가지고 있는 value를 알려준다. sinterstore는 sinter의 결과를 바로 새 key에 저장한다.
중복되는 value가 없는 집합에서 value의 특정한 집합을 찾거나 지정하는데 Set은 유용하다.
e. Sorted Sets
score를 가지는 set이라 할 수 있다.
> zadd friends:duncan 70 ghanima 95 paul 95 chani 75 jessica 1 vladimir
score가 90 이상인 친구를 찾고 싶으면 아래와 같이 한다.
> zcount friends:duncan 90 100
chani의 rank를 알고 싶으면 아래와 같이 한다.
> zrevrank friends:duncan chani
Chapter 3 - Leveraging Data Structures
a. Big O Notation
다수의 Redis 명령어는 O(1)이다. zadd는 O(log(N))이다. ltrim은 O(N)이지만 N이 전체 item의 수가 아니라 제거할 아이템의 수이므로 앞에서 설명했듯이 사용하면 O(1)이 된다. zremrangebyscore는 O(log(N) + M)이다.(N은 set할 element의 수이고 M은 제거할 element의 수이다.) sort는 O(N + M*log(M))이다. 이러한 내용을 인지하고 가능한 한 느린 명령어를 사용하지 않는 방향으로 해야 한다.
b. Pseudo Multi Key Queries
email과 id로 유저의 정보를 찾기 위해 아래처럼 저장하는 것은 좋지 않다.
> set users:leto@dune.gov "{id: 9001, email: 'leto@dune.gov', ... }"
> set users:9001 "{id: 9001, email: 'leto@dune.gov', ... }"
hash를 사용하면 중복을 제거할 수 있다.
> set users:9001 "{id: 9001, email: 'leto@dune.gov', ... }"
> hset user:lookup:email leto@dune.gov 9001
c. References and Indexes
앞에서 한 value가 다른 value를 reference하는 것을 봤다. 만약에 reference하고 있는 것이 바뀌면 어떻하지? Redis는 이에 대한 간편한 해결책이 없다. 수동으로 관리를 해야한다.
> sadd friends:leto ghanima paul chani jessica
> sadd friends_of:chani leto paul
d. Round Trips and Pipelining
Redis는 mget이나 sadd 같은 여러 value를 한번에 전달하는 명령어를 통해 round trip를 줄일 수 있게 해준다.
보통은 클라이언트가 한 명령을 Redis에 보내고 나면 reply를 기다린다. 하지만, Redis는 여러 명령을 먼저 보내고 나중에 response를 받는 pipelining을 지원한다. Ruby같은 경우 pipelined 함수를 통해 pipelining을 할 수 있다.
e. Transactions
여러 명령어를 atomic하게 동작시키고 싶을 때가 있다. 아래와 같이 한다.
> multi
> hincrby groups:1percent balance -9000000000
> hincrby groups:99percent balance 9000000000
> exec
지정한 key를 watch하고 있다가 그 key가 변하면 transaction을 할 수 있다. 그냥 multi와 exec만을 쓰면 모든 명령어가 한번에 실행되 버리기 때문에 이와 같은 상황에 쓸 수 없다.
redis.watch('powerlevel')
current = redis.get('powerlevel')
redis.multi()
redis.set('powerlevel', current + 1)
redis.exec()
f. Keys Anti-Pattern
keys 명령어는 pattern을 인식한다. 하지만, 이 명령어는 매우 느리다.
> keys bug:1233:*
따라서 위처럼 1233에 관련된 value를 찾고 싶은 경우 위처럼 해서 모든 key를 뒤지게 하는 것보다는 아예 처음에 저장할 때 아래처럼 하는 것이 좋은 선택이다.
> hset bugs:1233 1 "{id:1, account:1233, subject: '...'}"
> hset bugs:1233 2 "{id:1, account:1233, subject: '...'}"
Chapter 4 - Beyond The Data Structures
a. Expiration
key에 expiration을 지정할 수 있다. expire할 앞으로의 시간(second)이나 날짜(timestamp since Jan 1, 1970)를 준다.
> expire pages:about 30
> expire pages:about 1356933600
첫번째는 30초 후 key를 지우고 두번째는 2012년 12월 31일 아침 12시에 key를 지운다. ttl은 남아있는 시간을 알려주고 persist는 expiration을 제거한다. setex는 value를 set하면서 바로 expiration time도 지정한다.
> setex pages:about 30 '<h1>about us</h1>'
b. Publication and Subscriptions
> subscribe warnings
> publish warnings "it's over 9000!"
subscribe를 통해 channel에 가입하고 publish를 통해 channel에 메시지를 보낼 수 있다.
c. Monitor and Slow Log
monitor 명령어를 통해 Redis에 전달되는 모든 명령어를 모니터할 수 있다. slowlog 명령어를 통해 지정한 시간 이상이 걸리는 명령어를 log할 수 있다.
모든 명령어를 log하려면 아래와 같이 한다.
> config set slowlog-log-slower-than 0
아래의 모든 log를 보고 최근 10개의 log만 보고 log의 개수를 보는 명령이다.
> slowlog get
> slowlog get 10
> slowlog len
d. Sort
list, set or sorted set을 sort한다.
> rpush users:leto:guesses 5 9 10 2 4 10 19 2
> sort users:leto:guesses
> sadd friends:ghanima leto paul chani jessica alia duncan
> sort friends:ghanima limit 0 3 desc alpha
위의 명령은 descending order로 lexicographically 소트한 후 지정한 위치의 element를 리턴하는 명령이다.
> sadd watch:leto 12339 1382 338 9338
> set severity:12339 3
> set severity:1382 2
> set severity:338 5
> set severity:9338 4
> sort watch:leto by severity:* desc
위의 명령에서 *은 pattern에 의해 watch:leto의 value로 치환 되고 이 친환된 key의 value에 따라 sort가 된다.
Chapter 5 - Lua Scripting
a. Why?
전형적인 stored procedure를 쓰는 것이나 Lua script를 쓰는 것이나 편하지 않은 것은 똑같다. 하지만 Lua script를 잘 쓰면 성능을 향상 시킬 수 있고 코드도 간단해진다.
b. Eval
eval 명령어는 Lua script를 string으로 하는 파라메터를 받는다.
c. Script Management
eval로 실행되는 script를 매번 보내는 것은 좋은 선택이 아니다. Redis는 script를 cache하는 script load 명령을 제공한다.
redis = Redis.new
script_key = redis.script(:load, "THE_SCRIPT") // script의 sha1 digest를 리턴
redis.evalsha(script_key, ['friends:leto'], ['m'])
아래와 같은 명령어도 있다.
script kill - 실행중인 script 중단시키기
script flush - cache되어 있는 모든 script 제거하기
script exists - script가 cache에 이미 있는지 확인하기
d. Libraries
다양한 유용한 라이브러리들이 있다. table.lib, string.lib, math.ib, cjson.lib등이 있다.
e. Atomic
Redis는 single-threaded이므로 Lua script는 중간에 인터럽트 되지 않는다.
f. Administration
lua-time-limit 명령으로 Lua script가 얼마나 오래 실행될 수 있는지를 지정할 수 있다. 기본은 5초이고 보통의 Lua script는 milisecond 단위로 실행되기 때문에 이것은 꽤 긴 시간이다.
Chapter 6 - Administration
a. Configuration
redis.conf 파일을 통해 설정을 지정할 수 있다.
> config get *log*
위의 명령으로 모든 log가 들어가는 명령을 확인 할 수 있다.
b. Authentication
redis.conf 파일에 requirepass 명령으로 password를 지정할 수 있다. 이 값이 지정되면 클라이언트는 auth password 명령으로 인증해야 한다.
심각한 위험을 초래할 수 있는 명령어는 rename 할 수도 있다.
> rename-command CONFIG 5ec4db16...
> rename-command FLUSHALL 10412850...
c. Size Limitations
Redis는 수십억개의 data를 저장할 수 있다.
d. Replication
master에 data를 저장하면 하나 이상의 slave에 master의 내용을 up-to-date 한다. slave로 띄우려면 configuration 파일에 slaveof를 지정하거나 slaveof 명령을 사용한다.
Redis는 automated failover를 지원하지 않는다.
e. Backups
기본적으로 Redis는 dump.rdb라는 파일에 snapshot을 저장한다. snapshot을 뜨거나 append-only file에 저장하는 것을 하지 않고 slave에 저장하는 것을 선택할 수도 있다. 이것은 master에 부하를 줄여주는 장점이 있다.
f. Scaling and Redis Cluster
몇몇의 명령어들은 부하가 심할 수 있으므로 slave를 통한 replication은 부하를 줄여 줄 수 있다. key들을 여러 Redis instance에 분산 배치하는 것도 좋은 방법이다(consistent-hashing algorithm).
Redis Cluster는 현재 작업중에 있다.
2015년 6월 10일 수요일
Redis command 분석 - 2
* INCR key
Time complexity O(1)
key에 저장된 value의 값을 1 증가시킨다.
- incrCommand in t_string.c
a. incrDecrCommand(c, 1);
b. 정수값의 경우 shared.integers[value]로 미리 만들어 놓은 object를 value로 사용
* DECR key
Time complexity O(1)
key에 저장된 value의 값을 1 감소시킨다.
- decrCommand in t_string.c
a. incrDecrCommand(c, -1);
* MGET key [key ...]
Time complexity O(N)
key에 맞는 값을 찾아서 리턴. 없으면 nil을 리턴. 다수의 key를 설정하므로 multi bulk리턴
* RPUSH key value [value ...]
Time complexity O(1)
기존에 저장된 value의 끝(tail)에 value를 추가. 기존에 저장된 value가 list가 아니면 에러 리턴. key가 없으면 ziplist를 새로 만듦.
ZIPLIST와 LINKEDLIST에 사용 가능
* LPUSH key value [value ...]
Time complexity O(1)
기존에 저장된 value의 처음(head)에 value를 추가. 기존에 저장된 value가 list가 아니면 에러 리턴. key가 없으면 ziplist를 새로 만듦.
ZIPLIST와 LINKEDLIST에 사용 가능
* RPUSHX key value
Time complexity O(1)
key가 있고 list인 경우에만 끝에 value를 추가.
* LPUSHX key value
Time complexity O(1)
key가 있고 list인 경우에만 처음에 value를 추가.
* LINSERT key BEFORE|AFTER pivot value
Time complexity O(N)
value를 pivot의 이전(BEFORE)이나 뒤(AFTER)에 추가.
pivot의 encoding값이 REDIS_ENCODING_RAW이어야 함. 기존에 저장된 value들이 ZIPLIST이고 새로 저장할 value의 크기가 list_max_ziplist_value를 넘으면 LINKEDLIST로 컨버전. pivot은 앞에서부터 뒤로 loop를 돌며 찾는다.
저장한 후 value가 ZIPLIST이고 리스트의 크기가 list_max_ziplist_entries보다 크면 LINKEDLIST로 컨버전한다.
* RPOP key
Time complexity O(1)
list에서 마지막 값을 제거한다.
* LPOP key
Time complexity O(1)
list에서 처음 값을 제거한다.
* BRPOP key [key ...] timeout
Time complexity O(1)
처음으로 비어있지 않은 list의 마지막 값을 제거한다.
주어진 모든 key가 비어 있으면 timeout만큼 block된다. 하지만, MULTI/EXEC안에서 이 명령어가 실행된 것이면 바로 "*-1\r\n"을 리턴한다.
block은 리턴을 하지 않아 버리는 것이다. 그럼으로서 client는 무한정 기다리게 된다. 서버는 serverCron에서 확인하여 지정한 timeout이 지나고 나면 리턴을 한다.
* BRPOPLPUSH source destination timeout
Time complexity O(1)
* BLPOP key [key ...] timeout
Time complexity O(1)
처음으로 비어있지 않은 list의 처음 값을 제거한다.
주어진 모든 key가 비어 있으면 timeout만큼 block된다. 하지만, MULTI/EXEC안에서 이 명령어가 실행된 것이면 바로 "*-1\r\n"을 리턴한다.
Time complexity O(1)
key에 저장된 value의 값을 1 증가시킨다.
- incrCommand in t_string.c
a. incrDecrCommand(c, 1);
b. 정수값의 경우 shared.integers[value]로 미리 만들어 놓은 object를 value로 사용
* DECR key
Time complexity O(1)
key에 저장된 value의 값을 1 감소시킨다.
- decrCommand in t_string.c
a. incrDecrCommand(c, -1);
* MGET key [key ...]
Time complexity O(N)
key에 맞는 값을 찾아서 리턴. 없으면 nil을 리턴. 다수의 key를 설정하므로 multi bulk리턴
* RPUSH key value [value ...]
Time complexity O(1)
기존에 저장된 value의 끝(tail)에 value를 추가. 기존에 저장된 value가 list가 아니면 에러 리턴. key가 없으면 ziplist를 새로 만듦.
ZIPLIST와 LINKEDLIST에 사용 가능
* LPUSH key value [value ...]
Time complexity O(1)
기존에 저장된 value의 처음(head)에 value를 추가. 기존에 저장된 value가 list가 아니면 에러 리턴. key가 없으면 ziplist를 새로 만듦.
ZIPLIST와 LINKEDLIST에 사용 가능
* RPUSHX key value
Time complexity O(1)
key가 있고 list인 경우에만 끝에 value를 추가.
* LPUSHX key value
Time complexity O(1)
key가 있고 list인 경우에만 처음에 value를 추가.
* LINSERT key BEFORE|AFTER pivot value
Time complexity O(N)
value를 pivot의 이전(BEFORE)이나 뒤(AFTER)에 추가.
pivot의 encoding값이 REDIS_ENCODING_RAW이어야 함. 기존에 저장된 value들이 ZIPLIST이고 새로 저장할 value의 크기가 list_max_ziplist_value를 넘으면 LINKEDLIST로 컨버전. pivot은 앞에서부터 뒤로 loop를 돌며 찾는다.
저장한 후 value가 ZIPLIST이고 리스트의 크기가 list_max_ziplist_entries보다 크면 LINKEDLIST로 컨버전한다.
* RPOP key
Time complexity O(1)
list에서 마지막 값을 제거한다.
* LPOP key
Time complexity O(1)
list에서 처음 값을 제거한다.
* BRPOP key [key ...] timeout
Time complexity O(1)
처음으로 비어있지 않은 list의 마지막 값을 제거한다.
주어진 모든 key가 비어 있으면 timeout만큼 block된다. 하지만, MULTI/EXEC안에서 이 명령어가 실행된 것이면 바로 "*-1\r\n"을 리턴한다.
block은 리턴을 하지 않아 버리는 것이다. 그럼으로서 client는 무한정 기다리게 된다. 서버는 serverCron에서 확인하여 지정한 timeout이 지나고 나면 리턴을 한다.
* BRPOPLPUSH source destination timeout
Time complexity O(1)
* BLPOP key [key ...] timeout
Time complexity O(1)
처음으로 비어있지 않은 list의 처음 값을 제거한다.
주어진 모든 key가 비어 있으면 timeout만큼 block된다. 하지만, MULTI/EXEC안에서 이 명령어가 실행된 것이면 바로 "*-1\r\n"을 리턴한다.
Redis command 분석 - 1
* GET key
Time complexity: O(1)
key의 값(value) 얻어오기. key가 없으면 nil을 리턴
GET은 string만을 다루기 때문에 key의 값이 string이 아니면 에러가 리턴됨
- getCommand in t_string.c
a. db로부터 key를 찾는다.
b. key가 있으면 access time을 업데이트
c. key가 없으면 "$-1\r\n"을 client에 보낸다.
d. 찾은 key의 type이 string이 아니면 "-ERR Operation against a key holding the wrong kind of value\r\n"을 client에 보낸다.
e. 리턴할 value의 크기를 prefix로 해주고(예: "$2344\r\n") 뒤에 value를 붙인 후 client에게 보낸다.(예: value가 Hello인 경우 - "$5\r\nHello\r\n")
- value의 크기가 32보다 작은 경우는 자주 사용되므로 미리 shared.bulkhdr[]에 보낼 값을 만들어 놓고 사용함
shared.bulkhdr[j] = createObject(REDIS_STRING, sdscatprintf(sdsempty(), "$%d\r\n", j));
* SET key value
Time complexity: O(1)
string인 value를 key로 하여 저장. key가 이미 있으면 그 값의 타입이 무었인지에 상관없이 덮어쓴다.
- setCommand in t_string.c
a. 공간 절약을 위해 string이 숫자이면 숫자로 변환한다.
b. db에 key/value를 무조건 설정한다.(없으면 add, 있으면 overwrite)
c. expire가 설정되어 있으면 delete
d. 이 key를 watch하고 있는 client가 있으면 이 client의 flags값에 REDIS_DIRTY_CAS를 더함으로서 그 client에서의 exec가 실패하도록 해준다.
e. 항상 "+OK\r\n"을 client에게 보낸다.(set은 실패하지 않음)
* SETNX key value
Time complexity: O(1)
key가 없을 경우만 key/value를 저장한다.
- setnxCommand in t_string.c
a. key가 없을 경우는 SET과 같다.
b. key가 있을 경우 ":0\\r\n"을 key가 없을 경우는 ":1\r\n"을 client에게 보낸다.
* SETEX key seconds value
Time complexity: O(1)
주어진 시간에 timeout하는 key/value 저장
아래의 두 명령을 MULTI/EXEC block 안에서 실행하는 것과 같은 명령
SET key value
EXPIRE key seconds
- setexCommand in t_string.c
위의 SET 명령어에 더하여 expire에 관련된 부분만 추가해줌
a. seconds를 milliseconds로 바꾼다.
b. db의 expires로부터 key를 찾는다.(없으면 추가)
c. key의 value를 milliseconds로 바꾸어준다.
* PSETEX key milliseconds value
Time complexity: O(1)
expire time을 millisecond로 설정한다는 것만 제외하고는 setex와 같은 명령어
* APPEND key value
Time complexity: O(1)
key가 이미 있고 string이라면 string의 끝에 value를 더한다. 없으면 key를 만들어서 value를 넣는다.
- appendCommand in t_string.c
a. key를 찾는다.
b. 없으면 key를 만들어 value를 저장한다.
c. key가 있으면 type을 확인하여 type이 string이 아니면 "-ERR Operation against a key holding the wrong kind of value\r\n"을 client에게 보낸다.
d. append할 경우 string의 size가 512MB를 넘으면 "string exceeds maximum allowed size (512MB)"를 client에게 보낸다.
e. object가 shared이거나 encoded이면 value를 copy한 후 key의 value에 덮어쓴다.
f. key의 value가 바뀌었으므로 watch하고 있는 client에 signal을 날린다.
* STRLEN key
Time complexity: O(1)
key에 저장된 value의 size를 리턴한다. key가 string이 아니면 에러를 리턴한다.
- strlenCommand in t_string.c
a. key가 없으면 ":0\r\n"을 client에게 보낸다.
b. key가 string이 아니면 "-ERR Operation against a key holding the wrong kind of value\r\n"을 client에게 보낸다.
c. value의 크기를 client에게 보낸다.
* DEL key [key ...]
Time complexity: O(1)
지정한 키를 지운다. key가 없으면 무시한다.
- delCommand in db.c
a. argument로 지정한 key들을 지운다.
b. 지정한 key를 watch하고 있는 client에게 알린다.
c. 지운 key의 수를 client에게 보낸다.
* EXISTS key
Time complexity: O(1)
key가 존재하는지 확인한다.
- existsCommand in db.c
a. key가 존재하는지 확인한다.
b. 있으면 ":1\r\n"을 client에게 보낸다.
c. 없으면 ":0\r\n"을 client에게 보낸다.
* SETBIT key offset value
Time complexity: O(1)
key에 저장된 value의 offset위치의 bit를 set(1) 또는 clear(0)한다.
- setbitCommand in bitops.c
a. offset의 값을 가져온다. 512MB이하여야 한다. 아닐경우 "-ERR bit offset is not an integer or out of range\r\n"를 리턴한다.
b. value의 값을 가져온다. LONG_MIN과 LONG_MAX사이여야 한다. 아닐 경우 "-ERR bit is not an integer or out of range\r\n"를 client에게 보낸다.
c. offset이 0이나 1이 아니면 "-ERR bit is not an integer or out of range\r\n"를 client에게 보낸다.
d. key를 찾는다.
e. key가 없으면 empty string을 새로 만든다.
f. key가 있으면 type이 string인지 확인한다. 그리고 나서 key가 shared 또는 encoded이면 copy를 하여 기존의 다른 값이 변하지 않도록 한다.
g. string의 크기가 offset만큼 크지 않으면 offset 설정이 가능하도록 string의 크기를 증가시킨다. 이때 추가되는 부분의 bit값은 0으로 한다.
h. offset 부분의 값을 value로 바꾼다.(key의 값이 바뀌므로 watch중인 client에 signal을 보낸다.)
i. 값을 바꾼 bit의 원래 값을 client에게 보낸다.
* GETBIT key offset
Time complexity: O(1)
key에 저장된 value의 offset 위치의 값을 리턴한다.
- getbitCommand in bitops.c
a. offset 값이 정상적인지 확인한다.
b. key가 없으면 ":0\r\n"을 client에게 보낸다. type이 string이 아니면 에러를 리턴한다.
c. key가 있으면 offset 위치의 값을 client에게 보낸다.(":1\\r\n" or ":0\r\n")
d. offset의 위치가 key의 string 크기를 벗어나면 0으로 가정하고 값을 리턴한다.
* SETRANGE key offset value
Time complexity: O(1)
string의 부분을 변경한다.
- setrangeCommand in t_string.c
a. offset값을 가져온다. 값이 0보다 작으면 "-ERR offset is out of range\r\n"을 client에게 보낸다.
b. key를 찾는다.
c. key가 없는 경우 다음의 순서로 진행한다.
c-1. value의 크기가 0이면 ":0\r\n"을 client에게 보낸다.
c-2. offset + value의 크기가 최대 크기를 넘으면 에러를 리턴한다.
c-3. key에 빈 스트링을 넣는다.
d. key가 있는 경우
d-1. key의 타입이 string인지 확인한다.
d-2. value의 크기가 0이면 저장되어 있는 key의 value의 크기를 리턴한다.
d-3. offset + value의 크기가 최대 크기를 넘으면 에러를 리턴한다.
d-4. object가 shared이거나 encoded이면 object를 복사한다.
e. value의 크기가 0보다 크면 key의 value의 offset위치의 값을 변경한다. 이때 offset위치가 저장되어 있는 value의 크기보다 크면 0(zero byte)을 붙여서 값 변경이 가능하도록 해준다.
f. 변경된 value의 크기를 리턴한다.
* GETRANGE key start end
Time complexity: O(N)
string의 지정한 부분의 값을 가져온다. 음수의 start와 end는 뒤에서 부터를 의미한다. 이전 버전에서는 SUBSTR이었다.(코드에 남아있으므로 사용은 가능하다.)
- getrangeCommand in t_string.c
a. key가 없으면 "$0\r\n\r\n"를 리턴한다.
b. start가 음수인 경우 뒤에서 부터 value에서의 실제 위치를 계산했을 때 value의 0 index보다 더 작게 가면 0으로 설정한다.
c. end가 음수인 경우 뒤에서 부터 value에서의 실제 위치를 계산했을 때 value의 0 index보다 작으면 0으로 설정한다.
d. end가 value의 크기보다 크면 값을 value의 크기로 조정한다.
c. end보다 start가 더 크면 "$0\r\n\r\n"를 리턴한다.
Time complexity: O(1)
key의 값(value) 얻어오기. key가 없으면 nil을 리턴
GET은 string만을 다루기 때문에 key의 값이 string이 아니면 에러가 리턴됨
- getCommand in t_string.c
a. db로부터 key를 찾는다.
b. key가 있으면 access time을 업데이트
c. key가 없으면 "$-1\r\n"을 client에 보낸다.
d. 찾은 key의 type이 string이 아니면 "-ERR Operation against a key holding the wrong kind of value\r\n"을 client에 보낸다.
e. 리턴할 value의 크기를 prefix로 해주고(예: "$2344\r\n") 뒤에 value를 붙인 후 client에게 보낸다.(예: value가 Hello인 경우 - "$5\r\nHello\r\n")
- value의 크기가 32보다 작은 경우는 자주 사용되므로 미리 shared.bulkhdr[]에 보낼 값을 만들어 놓고 사용함
shared.bulkhdr[j] = createObject(REDIS_STRING, sdscatprintf(sdsempty(), "$%d\r\n", j));
* SET key value
Time complexity: O(1)
string인 value를 key로 하여 저장. key가 이미 있으면 그 값의 타입이 무었인지에 상관없이 덮어쓴다.
- setCommand in t_string.c
a. 공간 절약을 위해 string이 숫자이면 숫자로 변환한다.
b. db에 key/value를 무조건 설정한다.(없으면 add, 있으면 overwrite)
c. expire가 설정되어 있으면 delete
d. 이 key를 watch하고 있는 client가 있으면 이 client의 flags값에 REDIS_DIRTY_CAS를 더함으로서 그 client에서의 exec가 실패하도록 해준다.
e. 항상 "+OK\r\n"을 client에게 보낸다.(set은 실패하지 않음)
* SETNX key value
Time complexity: O(1)
key가 없을 경우만 key/value를 저장한다.
- setnxCommand in t_string.c
a. key가 없을 경우는 SET과 같다.
b. key가 있을 경우 ":0\\r\n"을 key가 없을 경우는 ":1\r\n"을 client에게 보낸다.
* SETEX key seconds value
Time complexity: O(1)
주어진 시간에 timeout하는 key/value 저장
아래의 두 명령을 MULTI/EXEC block 안에서 실행하는 것과 같은 명령
SET key value
EXPIRE key seconds
- setexCommand in t_string.c
위의 SET 명령어에 더하여 expire에 관련된 부분만 추가해줌
a. seconds를 milliseconds로 바꾼다.
b. db의 expires로부터 key를 찾는다.(없으면 추가)
c. key의 value를 milliseconds로 바꾸어준다.
* PSETEX key milliseconds value
Time complexity: O(1)
expire time을 millisecond로 설정한다는 것만 제외하고는 setex와 같은 명령어
* APPEND key value
Time complexity: O(1)
key가 이미 있고 string이라면 string의 끝에 value를 더한다. 없으면 key를 만들어서 value를 넣는다.
- appendCommand in t_string.c
a. key를 찾는다.
b. 없으면 key를 만들어 value를 저장한다.
c. key가 있으면 type을 확인하여 type이 string이 아니면 "-ERR Operation against a key holding the wrong kind of value\r\n"을 client에게 보낸다.
d. append할 경우 string의 size가 512MB를 넘으면 "string exceeds maximum allowed size (512MB)"를 client에게 보낸다.
e. object가 shared이거나 encoded이면 value를 copy한 후 key의 value에 덮어쓴다.
f. key의 value가 바뀌었으므로 watch하고 있는 client에 signal을 날린다.
* STRLEN key
Time complexity: O(1)
key에 저장된 value의 size를 리턴한다. key가 string이 아니면 에러를 리턴한다.
- strlenCommand in t_string.c
a. key가 없으면 ":0\r\n"을 client에게 보낸다.
b. key가 string이 아니면 "-ERR Operation against a key holding the wrong kind of value\r\n"을 client에게 보낸다.
c. value의 크기를 client에게 보낸다.
* DEL key [key ...]
Time complexity: O(1)
지정한 키를 지운다. key가 없으면 무시한다.
- delCommand in db.c
a. argument로 지정한 key들을 지운다.
b. 지정한 key를 watch하고 있는 client에게 알린다.
c. 지운 key의 수를 client에게 보낸다.
* EXISTS key
Time complexity: O(1)
key가 존재하는지 확인한다.
- existsCommand in db.c
a. key가 존재하는지 확인한다.
b. 있으면 ":1\r\n"을 client에게 보낸다.
c. 없으면 ":0\r\n"을 client에게 보낸다.
* SETBIT key offset value
Time complexity: O(1)
key에 저장된 value의 offset위치의 bit를 set(1) 또는 clear(0)한다.
- setbitCommand in bitops.c
a. offset의 값을 가져온다. 512MB이하여야 한다. 아닐경우 "-ERR bit offset is not an integer or out of range\r\n"를 리턴한다.
b. value의 값을 가져온다. LONG_MIN과 LONG_MAX사이여야 한다. 아닐 경우 "-ERR bit is not an integer or out of range\r\n"를 client에게 보낸다.
c. offset이 0이나 1이 아니면 "-ERR bit is not an integer or out of range\r\n"를 client에게 보낸다.
d. key를 찾는다.
e. key가 없으면 empty string을 새로 만든다.
f. key가 있으면 type이 string인지 확인한다. 그리고 나서 key가 shared 또는 encoded이면 copy를 하여 기존의 다른 값이 변하지 않도록 한다.
g. string의 크기가 offset만큼 크지 않으면 offset 설정이 가능하도록 string의 크기를 증가시킨다. 이때 추가되는 부분의 bit값은 0으로 한다.
h. offset 부분의 값을 value로 바꾼다.(key의 값이 바뀌므로 watch중인 client에 signal을 보낸다.)
i. 값을 바꾼 bit의 원래 값을 client에게 보낸다.
* GETBIT key offset
Time complexity: O(1)
key에 저장된 value의 offset 위치의 값을 리턴한다.
- getbitCommand in bitops.c
a. offset 값이 정상적인지 확인한다.
b. key가 없으면 ":0\r\n"을 client에게 보낸다. type이 string이 아니면 에러를 리턴한다.
c. key가 있으면 offset 위치의 값을 client에게 보낸다.(":1\\r\n" or ":0\r\n")
d. offset의 위치가 key의 string 크기를 벗어나면 0으로 가정하고 값을 리턴한다.
* SETRANGE key offset value
Time complexity: O(1)
string의 부분을 변경한다.
- setrangeCommand in t_string.c
a. offset값을 가져온다. 값이 0보다 작으면 "-ERR offset is out of range\r\n"을 client에게 보낸다.
b. key를 찾는다.
c. key가 없는 경우 다음의 순서로 진행한다.
c-1. value의 크기가 0이면 ":0\r\n"을 client에게 보낸다.
c-2. offset + value의 크기가 최대 크기를 넘으면 에러를 리턴한다.
c-3. key에 빈 스트링을 넣는다.
d. key가 있는 경우
d-1. key의 타입이 string인지 확인한다.
d-2. value의 크기가 0이면 저장되어 있는 key의 value의 크기를 리턴한다.
d-3. offset + value의 크기가 최대 크기를 넘으면 에러를 리턴한다.
d-4. object가 shared이거나 encoded이면 object를 복사한다.
e. value의 크기가 0보다 크면 key의 value의 offset위치의 값을 변경한다. 이때 offset위치가 저장되어 있는 value의 크기보다 크면 0(zero byte)을 붙여서 값 변경이 가능하도록 해준다.
f. 변경된 value의 크기를 리턴한다.
* GETRANGE key start end
Time complexity: O(N)
string의 지정한 부분의 값을 가져온다. 음수의 start와 end는 뒤에서 부터를 의미한다. 이전 버전에서는 SUBSTR이었다.(코드에 남아있으므로 사용은 가능하다.)
- getrangeCommand in t_string.c
a. key가 없으면 "$0\r\n\r\n"를 리턴한다.
b. start가 음수인 경우 뒤에서 부터 value에서의 실제 위치를 계산했을 때 value의 0 index보다 더 작게 가면 0으로 설정한다.
c. end가 음수인 경우 뒤에서 부터 value에서의 실제 위치를 계산했을 때 value의 0 index보다 작으면 0으로 설정한다.
d. end가 value의 크기보다 크면 값을 value의 크기로 조정한다.
c. end보다 start가 더 크면 "$0\r\n\r\n"를 리턴한다.
2014년 1월 21일 화요일
Redis Cluster and limiting divergences
http://antirez.com/news/70
곧 Redis Cluster가 될텐데 여전히 많은 이들의 의문을 가지고 있는 Redis Cluster구현상의 논쟁점 중의 하나인 Redis Cluster가 어떻게 node들 사이에 data set의 불일치를 해결하려고 하는지에 대한 설.
불일치를 완변하게 해결하는 것이 아닌 최대한 제한하는 형태로 해결을 함
곧 Redis Cluster가 될텐데 여전히 많은 이들의 의문을 가지고 있는 Redis Cluster구현상의 논쟁점 중의 하나인 Redis Cluster가 어떻게 node들 사이에 data set의 불일치를 해결하려고 하는지에 대한 설.
불일치를 완변하게 해결하는 것이 아닌 최대한 제한하는 형태로 해결을 함
2013년 4월 8일 월요일
Redis Run ID 만들기
Redis는 각각의 Redis instance를 구별하기 위해 Run ID라는 것을 사용합니다. 이 값이 같으면 같은 instance로 확인하는 것이죠.
이 값의 생성 방법은 다음과 같습니다.
시스템의 '/dev/urandom' 디바이스 파일을 사용하여 난수를 발생시키는데요. 만약 디바이스 파일이 정상동작하지 않으면 pid와 현재 시간과 rand()함수를 가지고 난수를 발생시킵니다. 이렇게 생성된 난수 값의 character중 하위의 4bit만을 취한 Hex값으로 최종 Run ID를 만들게 됩니다.
Run ID를 만들어주는 함수는 getRandomHexChars() 이고 내부 동작은 아래와 같습니다.
1. 난수 발생을 위해 시스템의 /dev/urandom을 사용한다.
FILE *fp = fopen("/dev/urandom","r");
2. 이 파일을 REDIS_RUN_ID_SIZE(디폴트 값이 40으로 되어 있습니다.)만큼 읽는다.
if (fp == NULL || fread(p,len,1,fp) == 0) {
...
}
3. 읽은 값을 HEX값으로 변환한다. 이때 character의 하위 4비트만을 사용한다.
for (j = 0; j < len; j++)
p[j] = charset[p[j] & 0x0F];
하지만 시스템의 /dev/urandom이 정상적으로 동작하지 않는 경우는 아래의 방법을 사용하게 됩니다.
1. getpid()함수를 통해 Redis의 pid값을 얻어온다.
pid_t pid = getpid();
2. gettimeofday()함수를 통해 현재 시간을 얻어온다.
gettimeofday(&tv,NULL);
3. microsecond, second, pid의 순으로 버퍼에 저장한다.
if (l >= sizeof(tv.tv_usec)) {
memcpy(x,&tv.tv_usec,sizeof(tv.tv_usec));
l -= sizeof(tv.tv_usec);
x += sizeof(tv.tv_usec);
}
if (l >= sizeof(tv.tv_sec)) {
memcpy(x,&tv.tv_sec,sizeof(tv.tv_sec));
l -= sizeof(tv.tv_sec);
x += sizeof(tv.tv_sec);
}
if (l >= sizeof(pid)) {
memcpy(x,&pid,sizeof(pid));
l -= sizeof(pid);
x += sizeof(pid);
}
4. rand()함수를 사용하여 발생한 랜덤 값과 XOR을 한다.
for (j = 0; j < len; j++)
p[j] ^= rand();
5. 읽은 값을 HEX값으로 변환한다.
전체 코드
void getRandomHexChars(char *p, unsigned int len) {
// 디바이스 파일 열기
FILE *fp = fopen("/dev/urandom","r");
char *charset = "0123456789abcdef"; // hex 변환에 사용
unsigned int j;
/ 디바이스 파일로부터 난수 가져오기
if (fp == NULL || fread(p,len,1,fp) == 0) {
// 디바이스 파일을 읽을 수 없는 경우
/* If we can't read from /dev/urandom, do some reasonable effort
* in order to create some entropy, since this function is used to
* generate run_id and cluster instance IDs */
char *x = p;
unsigned int l = len;
struct timeval tv;
pid_t pid = getpid();
/* Use time and PID to fill the initial array. */
gettimeofday(&tv,NULL);
if (l >= sizeof(tv.tv_usec)) {
// microsecond의 값을 버퍼에 저장
memcpy(x,&tv.tv_usec,sizeof(tv.tv_usec));
l -= sizeof(tv.tv_usec);
x += sizeof(tv.tv_usec);
}
if (l >= sizeof(tv.tv_sec)) {
// second의 값을 버퍼에 저장
memcpy(x,&tv.tv_sec,sizeof(tv.tv_sec));
l -= sizeof(tv.tv_sec);
x += sizeof(tv.tv_sec);
}
if (l >= sizeof(pid)) {
// pid의 값을 버퍼에 저장
memcpy(x,&pid,sizeof(pid));
l -= sizeof(pid);
x += sizeof(pid);
}
/* Finally xor it with rand() output, that was already seeded with
* time() at startup. */
for (j = 0; j < len; j++)
p[j] ^= rand(); // 위에서 저장한 값을 rand()와 XOR
}
/* Turn it into hex digits taking just 4 bits out of 8 for every byte. */
// 위에서 저장한 값을 character단위로 하여 하위 4bit만 가지고 hex로 저장
for (j = 0; j < len; j++)
p[j] = charset[p[j] & 0x0F];
fclose(fp);
}
이 값의 생성 방법은 다음과 같습니다.
시스템의 '/dev/urandom' 디바이스 파일을 사용하여 난수를 발생시키는데요. 만약 디바이스 파일이 정상동작하지 않으면 pid와 현재 시간과 rand()함수를 가지고 난수를 발생시킵니다. 이렇게 생성된 난수 값의 character중 하위의 4bit만을 취한 Hex값으로 최종 Run ID를 만들게 됩니다.
Run ID를 만들어주는 함수는 getRandomHexChars() 이고 내부 동작은 아래와 같습니다.
1. 난수 발생을 위해 시스템의 /dev/urandom을 사용한다.
FILE *fp = fopen("/dev/urandom","r");
2. 이 파일을 REDIS_RUN_ID_SIZE(디폴트 값이 40으로 되어 있습니다.)만큼 읽는다.
if (fp == NULL || fread(p,len,1,fp) == 0) {
...
}
3. 읽은 값을 HEX값으로 변환한다. 이때 character의 하위 4비트만을 사용한다.
for (j = 0; j < len; j++)
p[j] = charset[p[j] & 0x0F];
하지만 시스템의 /dev/urandom이 정상적으로 동작하지 않는 경우는 아래의 방법을 사용하게 됩니다.
1. getpid()함수를 통해 Redis의 pid값을 얻어온다.
pid_t pid = getpid();
2. gettimeofday()함수를 통해 현재 시간을 얻어온다.
gettimeofday(&tv,NULL);
3. microsecond, second, pid의 순으로 버퍼에 저장한다.
if (l >= sizeof(tv.tv_usec)) {
memcpy(x,&tv.tv_usec,sizeof(tv.tv_usec));
l -= sizeof(tv.tv_usec);
x += sizeof(tv.tv_usec);
}
if (l >= sizeof(tv.tv_sec)) {
memcpy(x,&tv.tv_sec,sizeof(tv.tv_sec));
l -= sizeof(tv.tv_sec);
x += sizeof(tv.tv_sec);
}
if (l >= sizeof(pid)) {
memcpy(x,&pid,sizeof(pid));
l -= sizeof(pid);
x += sizeof(pid);
}
4. rand()함수를 사용하여 발생한 랜덤 값과 XOR을 한다.
for (j = 0; j < len; j++)
p[j] ^= rand();
5. 읽은 값을 HEX값으로 변환한다.
전체 코드
void getRandomHexChars(char *p, unsigned int len) {
// 디바이스 파일 열기
FILE *fp = fopen("/dev/urandom","r");
char *charset = "0123456789abcdef"; // hex 변환에 사용
unsigned int j;
/ 디바이스 파일로부터 난수 가져오기
if (fp == NULL || fread(p,len,1,fp) == 0) {
// 디바이스 파일을 읽을 수 없는 경우
/* If we can't read from /dev/urandom, do some reasonable effort
* in order to create some entropy, since this function is used to
* generate run_id and cluster instance IDs */
char *x = p;
unsigned int l = len;
struct timeval tv;
pid_t pid = getpid();
/* Use time and PID to fill the initial array. */
gettimeofday(&tv,NULL);
if (l >= sizeof(tv.tv_usec)) {
// microsecond의 값을 버퍼에 저장
memcpy(x,&tv.tv_usec,sizeof(tv.tv_usec));
l -= sizeof(tv.tv_usec);
x += sizeof(tv.tv_usec);
}
if (l >= sizeof(tv.tv_sec)) {
// second의 값을 버퍼에 저장
memcpy(x,&tv.tv_sec,sizeof(tv.tv_sec));
l -= sizeof(tv.tv_sec);
x += sizeof(tv.tv_sec);
}
if (l >= sizeof(pid)) {
// pid의 값을 버퍼에 저장
memcpy(x,&pid,sizeof(pid));
l -= sizeof(pid);
x += sizeof(pid);
}
/* Finally xor it with rand() output, that was already seeded with
* time() at startup. */
for (j = 0; j < len; j++)
p[j] ^= rand(); // 위에서 저장한 값을 rand()와 XOR
}
/* Turn it into hex digits taking just 4 bits out of 8 for every byte. */
// 위에서 저장한 값을 character단위로 하여 하위 4bit만 가지고 hex로 저장
for (j = 0; j < len; j++)
p[j] = charset[p[j] & 0x0F];
fclose(fp);
}
2013년 2월 12일 화요일
Redis hash command
* HDEL key field [ field ... ]
Time complexity: O(N) - N은 지우려는 field의 수
1. key를 찾는다.
2. key의 value인 hash object에서 field를 지운다.
3. hash object에서 모든 element를 지웠다면 key도 지운다.
- 리턴
1. key가 없으면 ":0\r\n"을 리턴
2. 실제로 지운 field의 개수를 리턴
* HEXISTS key field
Time complexity: O(1)
1. key를 찾는다.
2. field가 있는지 없는지 확인한다.
- 리턴
1. key가 없으면 ":0\r\n"을 리턴
2. field가 있으면 ":1\r\n"을, 없으면 ":0\r\n"을 리턴
* HGET key field
Time complexity: O(1)
1. key로부터 field를 찾는다.
2. field로부터 value를 찾는다.
- 리턴
1. key가 없거나 field가 없으면 "$-1\r\n"을 리턴
2. field에 해당하는 value를 리턴
* HGETALL key
Time complexity: O(N) - N은 hash의 size
1. key를 찾는다.
2. key의 value인 hash object에 있는 element의 수를 계산한다.
3. hash object를 처음부터 끝까지 순회하며 file와 value를 리턴할 버퍼에 넣는다.
- 리턴
1. key가 없으면 "*0\r\n"을 리턴
2. key에 속한 모든 field의 value를 리턴: 앞에는 element의 개수*2를 주고, 이후에 field, value의 순서로 표시한다.
* HINCRBY key field increment
Time complexity: O(1)
1. field의 값이 integer이면 increment만큼 증가시킨다.
2. increment는 양수/음수가 가능하다.
- 리턴
1. field로 저장된 값이 integer가 아니면 아무것도 리턴하지 않는다.
2. integer이면 변경된 값을 리턴
* HINCRBYFLOAT key field increment
Time complexity: O(1)
1. HINCRBY와 같으나 field의 value가 float라는 것만 다르다.
* HKEYS key
Time complexity: O(N) - N은 hash의 size
1. key를 찾는다.
2. key의 value인 hash object에 있는 element의 수를 계산한다.
3. hash object를 처음부터 끝까지 순회하며 field를 리턴할 버퍼에 넣는다.
- 리턴
1. key가 없으면 "*0\r\n"을 리턴
2. key에 속한 모든 field를 리턴: 앞에는 element의 개수를 주고, 이후에 field를 표시한다.
* HLEN key
Time complexity: O(1)
1. key의 value인 hash object에 있는 element의 수를 계산한다.
- 리턴
1. key가 없으면 ":0\r\n"을 리턴
2. key에 속한 field의 개수를 리턴 -> 예) 5개인 경우 - ":5\r\n"
* HMGET key field [ field ...]
Time complexity: O(N) - N은 가져오려는 field의 수
1. key를 찾는다.
2. key의 value인 hash object에서의 field로부터 value를 찾는다.
- 리턴
1. 앞에 가져온 field의 개수를 표시하고, 이후에 field에 맞는 value를 표시한다.
* HMSET key field value [ field value ...]
Time complexity: O(N) - N은 설정하려는 field의 수
1. key를 찾는다.
2. key의 value인 hash object에 field/value를 저장한다.(HSET참조)
- 리턴
1. field/value의 쌍이 맞지 않으면 에러 리턴
2. 정상적으로 설정했을 경우 "+OK\r\n"을 리턴
* HSET key field value
Time complexity: O(1)
1. key가 있는지 확인 후 없으면 새로 만든다. 이때 key에 대한 value는 zip list를 가지는 hash object이다.
2. field나 value의 크기가 zip list에 넣을 수 없을만큼 크면 zip list로 저장하고 있던 것을 hash table로 바꾼다.
3. hash table로 저장되어 있는 경우라면 string이 integer로 변환될 수 있는지 확인하여 가능하다면 변환한다. -> 공간 절약
4. key의 value인 hash object에 field와 value를 저장한다. -> field가 hash object의 key
5. field가 이미 있으면 덮어쓴다.
- 리턴
1. 기존에 저장되어 있는 key의 type이 REDIS_HASH가 아니면 wrong type error 를 리턴한다.
2. field가 새로 저장된 것이면 ":1\r\n"을 리턴
3. field가 이미 있어서 덮어 쓴 것이면 ":0\r\n"을 리턴
* HSETNX key field value
Time complexity: O(1)
1. key를 찾는다.
- 리턴
1. field가 있으면 아무것도 저장하지 않고 ":0\r\n"을 리턴
2. field가 없으면 hash object에 field/value를 저장하고 ":1\r\n"을 리턴
* HVALS key
Time complexity: O(N) - N은 hash의 size
1. key를 찾는다.
2. key의 value인 hash object에 있는 element의 수를 계산한다.
3. hash object를 처음부터 끝까지 순회하며 value를 리턴할 버퍼에 넣는다.
- 리턴
1. key가 없으면 "*0\r\n"을 리턴
2. key에 속한 모든 field의 value를 리턴: 앞에는 element의 개수를 주고, 이후에 value를 표시한다.
------------
1. lookupKey: redisDb로부터 key를 찾는다. 찾으면 value를 리턴하고 못찾으면 NULL을 리턴한다.
2. expireIfNeeded: expire 시간이 지났으면 key를 지운다.
3. propagateExpire: slave와 AOF 파일에도 key를 지운다.
4. lookupKeyWrite/lookupKeyRead: expireIfNeeded를 호출한 후 lookupKey를 호출한다. read의 경우는 통계정보를 저장해준다.
5. hashTypeLookupWriteOrCreate: key를 찾아보고 없으면 새로 만든다. 이미 있는 경우는 key의 type이 REDIS_HASH인지 확인한다.
6. hash object를 처음 만드는 경우 zip list로 만든다. zip list에는 제약(string의 크기, list의 개수 등...)이 있으므로 사용중 zip list로 저장할 수 없는 값이 생기면 hash table로 바꿔준다.
Time complexity: O(N) - N은 지우려는 field의 수
1. key를 찾는다.
2. key의 value인 hash object에서 field를 지운다.
3. hash object에서 모든 element를 지웠다면 key도 지운다.
- 리턴
1. key가 없으면 ":0\r\n"을 리턴
2. 실제로 지운 field의 개수를 리턴
* HEXISTS key field
Time complexity: O(1)
1. key를 찾는다.
2. field가 있는지 없는지 확인한다.
- 리턴
1. key가 없으면 ":0\r\n"을 리턴
2. field가 있으면 ":1\r\n"을, 없으면 ":0\r\n"을 리턴
* HGET key field
Time complexity: O(1)
1. key로부터 field를 찾는다.
2. field로부터 value를 찾는다.
- 리턴
1. key가 없거나 field가 없으면 "$-1\r\n"을 리턴
2. field에 해당하는 value를 리턴
* HGETALL key
Time complexity: O(N) - N은 hash의 size
1. key를 찾는다.
2. key의 value인 hash object에 있는 element의 수를 계산한다.
3. hash object를 처음부터 끝까지 순회하며 file와 value를 리턴할 버퍼에 넣는다.
- 리턴
1. key가 없으면 "*0\r\n"을 리턴
2. key에 속한 모든 field의 value를 리턴: 앞에는 element의 개수*2를 주고, 이후에 field, value의 순서로 표시한다.
* HINCRBY key field increment
Time complexity: O(1)
1. field의 값이 integer이면 increment만큼 증가시킨다.
2. increment는 양수/음수가 가능하다.
- 리턴
1. field로 저장된 값이 integer가 아니면 아무것도 리턴하지 않는다.
2. integer이면 변경된 값을 리턴
* HINCRBYFLOAT key field increment
Time complexity: O(1)
1. HINCRBY와 같으나 field의 value가 float라는 것만 다르다.
* HKEYS key
Time complexity: O(N) - N은 hash의 size
1. key를 찾는다.
2. key의 value인 hash object에 있는 element의 수를 계산한다.
3. hash object를 처음부터 끝까지 순회하며 field를 리턴할 버퍼에 넣는다.
- 리턴
1. key가 없으면 "*0\r\n"을 리턴
2. key에 속한 모든 field를 리턴: 앞에는 element의 개수를 주고, 이후에 field를 표시한다.
* HLEN key
Time complexity: O(1)
1. key의 value인 hash object에 있는 element의 수를 계산한다.
- 리턴
1. key가 없으면 ":0\r\n"을 리턴
2. key에 속한 field의 개수를 리턴 -> 예) 5개인 경우 - ":5\r\n"
* HMGET key field [ field ...]
Time complexity: O(N) - N은 가져오려는 field의 수
1. key를 찾는다.
2. key의 value인 hash object에서의 field로부터 value를 찾는다.
- 리턴
1. 앞에 가져온 field의 개수를 표시하고, 이후에 field에 맞는 value를 표시한다.
* HMSET key field value [ field value ...]
Time complexity: O(N) - N은 설정하려는 field의 수
1. key를 찾는다.
2. key의 value인 hash object에 field/value를 저장한다.(HSET참조)
- 리턴
1. field/value의 쌍이 맞지 않으면 에러 리턴
2. 정상적으로 설정했을 경우 "+OK\r\n"을 리턴
* HSET key field value
Time complexity: O(1)
1. key가 있는지 확인 후 없으면 새로 만든다. 이때 key에 대한 value는 zip list를 가지는 hash object이다.
2. field나 value의 크기가 zip list에 넣을 수 없을만큼 크면 zip list로 저장하고 있던 것을 hash table로 바꾼다.
3. hash table로 저장되어 있는 경우라면 string이 integer로 변환될 수 있는지 확인하여 가능하다면 변환한다. -> 공간 절약
4. key의 value인 hash object에 field와 value를 저장한다. -> field가 hash object의 key
5. field가 이미 있으면 덮어쓴다.
- 리턴
1. 기존에 저장되어 있는 key의 type이 REDIS_HASH가 아니면 wrong type error 를 리턴한다.
2. field가 새로 저장된 것이면 ":1\r\n"을 리턴
3. field가 이미 있어서 덮어 쓴 것이면 ":0\r\n"을 리턴
* HSETNX key field value
Time complexity: O(1)
1. key를 찾는다.
- 리턴
1. field가 있으면 아무것도 저장하지 않고 ":0\r\n"을 리턴
2. field가 없으면 hash object에 field/value를 저장하고 ":1\r\n"을 리턴
* HVALS key
Time complexity: O(N) - N은 hash의 size
1. key를 찾는다.
2. key의 value인 hash object에 있는 element의 수를 계산한다.
3. hash object를 처음부터 끝까지 순회하며 value를 리턴할 버퍼에 넣는다.
- 리턴
1. key가 없으면 "*0\r\n"을 리턴
2. key에 속한 모든 field의 value를 리턴: 앞에는 element의 개수를 주고, 이후에 value를 표시한다.
------------
1. lookupKey: redisDb로부터 key를 찾는다. 찾으면 value를 리턴하고 못찾으면 NULL을 리턴한다.
2. expireIfNeeded: expire 시간이 지났으면 key를 지운다.
3. propagateExpire: slave와 AOF 파일에도 key를 지운다.
4. lookupKeyWrite/lookupKeyRead: expireIfNeeded를 호출한 후 lookupKey를 호출한다. read의 경우는 통계정보를 저장해준다.
5. hashTypeLookupWriteOrCreate: key를 찾아보고 없으면 새로 만든다. 이미 있는 경우는 key의 type이 REDIS_HASH인지 확인한다.
6. hash object를 처음 만드는 경우 zip list로 만든다. zip list에는 제약(string의 크기, list의 개수 등...)이 있으므로 사용중 zip list로 저장할 수 없는 값이 생기면 hash table로 바꿔준다.
2013년 2월 6일 수요일
Redis Log
- 로그
1. 로그 레벨
conf에 아래의 로그레벨을 설정하여 이보다 작은 값의 경우는 로그하지 않도록 한다.
REDIS_DEBUG 0
REDIS_VERBOSE 1
REDIS_NOTICE 2
REDIS_WARNING 3
REDIS_LOG_RAW (1 << 10) // 혼자 사용되지 않고 위의 값과 함께 사용됨
2. 기본적으로 redisLog 호출 -> 내부에서 redisLogRaw를 호출함
3. redisLog
printf같은 형태로 로그에 출력할 메시지를 설정하게 해주는 함수
로그할 메시지의 내용을 만든 후 redisLogRaw를 호출함
4. redisLogRaw
* conf의 logfile에 파일 이름이 지정되어 있으면 그 파일에 로그 출력하고 없으면 stdout에 출력한다.
* REDIS_LOG_RAW가 로그레벨에 포함되어 있으면 메시지만을 출력하고 그렇지 않으면 'pid, tlrks, log level, 메시지'의 형태로 출력한다.
* conf에 syslog_enabled가 설정되어 있으면 syslog에도 출력한다.
redis의 log level에 대응되는 syslog의 priority는 다음과 같다.
LOG_DEBUG : REDIS_DEBUG
LOG_INFO : REDIS_DEBUG
LOG_NOTICE : REDIS_NOTICE
LOG_WARNING : REDIS_WARNING
1. 로그 레벨
conf에 아래의 로그레벨을 설정하여 이보다 작은 값의 경우는 로그하지 않도록 한다.
REDIS_DEBUG 0
REDIS_VERBOSE 1
REDIS_NOTICE 2
REDIS_WARNING 3
REDIS_LOG_RAW (1 << 10) // 혼자 사용되지 않고 위의 값과 함께 사용됨
2. 기본적으로 redisLog 호출 -> 내부에서 redisLogRaw를 호출함
3. redisLog
printf같은 형태로 로그에 출력할 메시지를 설정하게 해주는 함수
로그할 메시지의 내용을 만든 후 redisLogRaw를 호출함
4. redisLogRaw
* conf의 logfile에 파일 이름이 지정되어 있으면 그 파일에 로그 출력하고 없으면 stdout에 출력한다.
* REDIS_LOG_RAW가 로그레벨에 포함되어 있으면 메시지만을 출력하고 그렇지 않으면 'pid, tlrks, log level, 메시지'의 형태로 출력한다.
* conf에 syslog_enabled가 설정되어 있으면 syslog에도 출력한다.
redis의 log level에 대응되는 syslog의 priority는 다음과 같다.
LOG_DEBUG : REDIS_DEBUG
LOG_INFO : REDIS_DEBUG
LOG_NOTICE : REDIS_NOTICE
LOG_WARNING : REDIS_WARNING
Redis command - 1
* GET key
Time complexity: O(1)
key의 값(value) 얻어오기. key가 없으면 nil을 리턴
GET은 string만을 다루기 때문에 key의 값이 string이 아니면 에러가 리턴됨
- getCommand in t_string.c
a. db로부터 key를 찾는다.
b. key가 있으면 access time을 업데이트
c. key가 없으면 "$-1\r\n"을 client에 보낸다.
d. 찾은 key의 type이 string이 아니면 "-ERR Operation against a key holding the wrong kind of value\r\n"을 client에 보낸다.
e. 리턴할 value의 크기를 prefix로 해주고(예: "$2344\r\n") 뒤에 value를 붙인 후 client에게 보낸다.(예: value가 Hello인 경우 - "$5\r\nHello\r\n")
- value의 크기가 32보다 작은 경우는 자주 사용되므로 미리 shared.bulkhdr[]에 보낼 값을 만들어 놓고 사용함
shared.bulkhdr[j] = createObject(REDIS_STRING, sdscatprintf(sdsempty(), "$%d\r\n", j));
* SET key value
Time complexity: O(1)
string인 value를 key로 하여 저장. key가 이미 있으면 그 값의 타입이 무었인지에 상관없이 덮어쓴다.
- setCommand in t_string.c
a. 공간 절약을 위해 string이 숫자이면 숫자로 변환한다.
b. db에 key/value를 무조건 설정한다.(없으면 add, 있으면 overwrite)
c. expire가 설정되어 있으면 delete
d. 이 key를 watch하고 있는 client가 있으면 이 client의 flags값에 REDIS_DIRTY_CAS를 더함으로서 그 client에서의 exec가 실패하도록 해준다.
e. 항상 "+OK\r\n"을 client에게 보낸다.(set은 실패하지 않음)
* SETNX key value
Time complexity: O(1)
key가 없을 경우만 key/value를 저장한다.
- setnxCommand in t_string.c
a. key가 없을 경우는 SET과 같다.
b. key가 있을 경우 ":0\\r\n"을 key가 없을 경우는 ":1\r\n"을 client에게 보낸다.
* SETEX key seconds value
Time complexity: O(1)
주어진 시간에 timeout하는 key/value 저장
아래의 두 명령을 MULTI/EXEC block 안에서 실행하는 것과 같은 명령
SET key value
EXPIRE key seconds
- setexCommand in t_string.c
위의 SET 명령어에 더하여 expire에 관련된 부분만 추가해줌
a. seconds를 milliseconds로 바꾼다.
b. db의 expires로부터 key를 찾는다.(없으면 추가)
c. key의 value를 milliseconds로 바꾸어준다.
* PSETEX key milliseconds value
Time complexity: O(1)
expire time을 millisecond로 설정한다는 것만 제외하고는 setex와 같은 명령어
* APPEND key value
Time complexity: O(1)
key가 이미 있고 string이라면 string의 끝에 value를 더한다. 없으면 key를 만들어서 value를 넣는다.
- appendCommand in t_string.c
a. key를 찾는다.
b. 없으면 key를 만들어 value를 저장한다.
c. key가 있으면 type을 확인하여 type이 string이 아니면 "-ERR Operation against a key holding the wrong kind of value\r\n"을 client에게 보낸다.
d. append할 경우 string의 size가 512MB를 넘으면 "string exceeds maximum allowed size (512MB)"를 client에게 보낸다.
e. object가 shared이거나 encoded이면 value를 copy한 후 key의 value에 덮어쓴다.
f. key의 value가 바뀌었으므로 watch하고 있는 client에 signal을 날린다.
* STRLEN key
Time complexity: O(1)
key에 저장된 value의 size를 리턴한다. key가 string이 아니면 에러를 리턴한다.
- strlenCommand in t_string.c
a. key가 없으면 ":0\r\n"을 client에게 보낸다.
b. key가 string이 아니면 "-ERR Operation against a key holding the wrong kind of value\r\n"을 client에게 보낸다.
c. value의 크기를 client에게 보낸다.
* DEL key [key ...]
Time complexity: O(1)
지정한 키를 지운다. key가 없으면 무시한다.
- delCommand in db.c
a. argument로 지정한 key들을 지운다.
b. 지정한 key를 watch하고 있는 client에게 알린다.
c. 지운 key의 수를 client에게 보낸다.
* EXISTS key
Time complexity: O(1)
key가 존재하는지 확인한다.
- existsCommand in db.c
a. key가 존재하는지 확인한다.
b. 있으면 ":1\r\n"을 client에게 보낸다.
c. 없으면 ":0\r\n"을 client에게 보낸다.
* SETBIT key offset value
Time complexity: O(1)
key에 저장된 value의 offset위치의 bit를 set(1) 또는 clear(0)한다.
- setbitCommand in bitops.c
a. offset의 값을 가져온다. 512MB이하여야 한다. 아닐경우 "-ERR bit offset is not an integer or out of range\r\n"를 리턴한다.
b. value의 값을 가져온다. LONG_MIN과 LONG_MAX사이여야 한다. 아닐 경우 "-ERR bit is not an integer or out of range\r\n"를 client에게 보낸다.
c. offset이 0이나 1이 아니면 "-ERR bit is not an integer or out of range\r\n"를 client에게 보낸다.
d. key를 찾는다.
e. key가 없으면 empty string을 새로 만든다.
f. key가 있으면 type이 string인지 확인한다. 그리고 나서 key가 shared 또는 encoded이면 copy를 하여 기존의 다른 값이 변하지 않도록 한다.
g. string의 크기가 offset만큼 크지 않으면 offset 설정이 가능하도록 string의 크기를 증가시킨다. 이때 추가되는 부분의 bit값은 0으로 한다.
h. offset 부분의 값을 value로 바꾼다.(key의 값이 바뀌므로 watch중인 client에 signal을 보낸다.)
i. 값을 바꾼 bit의 원래 값을 client에게 보낸다.
* GETBIT key offset
Time complexity: O(1)
key에 저장된 value의 offset 위치의 값을 리턴한다.
- getbitCommand in bitops.c
a. offset 값이 정상적인지 확인한다.
b. key가 없으면 ":0\r\n"을 client에게 보낸다. type이 string이 아니면 에러를 리턴한다.
c. key가 있으면 offset 위치의 값을 client에게 보낸다.(":1\\r\n" or ":0\r\n")
d. offset의 위치가 key의 string 크기를 벗어나면 0으로 가정하고 값을 리턴한다.
* SETRANGE key offset value
Time complexity: O(1)
string의 부분을 변경한다.
- setrangeCommand in t_string.c
a. offset값을 가져온다. 값이 0보다 작으면 "-ERR offset is out of range\r\n"을 client에게 보낸다.
b. key를 찾는다.
c. key가 없는 경우 다음의 순서로 진행한다.
c-1. value의 크기가 0이면 ":0\r\n"을 client에게 보낸다.
c-2. offset + value의 크기가 최대 크기를 넘으면 에러를 리턴한다.
c-3. key에 빈 스트링을 넣는다.
d. key가 있는 경우
d-1. key의 타입이 string인지 확인한다.
d-2. value의 크기가 0이면 저장되어 있는 key의 value의 크기를 리턴한다.
d-3. offset + value의 크기가 최대 크기를 넘으면 에러를 리턴한다.
d-4. object가 shared이거나 encoded이면 object를 복사한다.
e. value의 크기가 0보다 크면 key의 value의 offset위치의 값을 변경한다. 이때 offset위치가 저장되어 있는 value의 크기보다 크면 0(zero byte)을 붙여서 값 변경이 가능하도록 해준다.
f. 변경된 value의 크기를 리턴한다.
* GETRANGE key start end
Time complexity: O(N)
string의 지정한 부분의 값을 가져온다. 음수의 start와 end는 뒤에서 부터를 의미한다. 이전 버전에서는 SUBSTR이었다.(코드에 남아있으므로 사용은 가능하다.)
- getrangeCommand in t_string.c
a. key가 없으면 "$0\r\n\r\n"를 리턴한다.
b. start가 음수인 경우 뒤에서 부터 value에서의 실제 위치를 계산했을 때 value의 0 index보다 더 작게 가면 0으로 설정한다.
c. end가 음수인 경우 뒤에서 부터 value에서의 실제 위치를 계산했을 때 value의 0 index보다 작으면 0으로 설정한다.
d. end가 value의 크기보다 크면 값을 value의 크기로 조정한다.
c. end보다 start가 더 크면 "$0\r\n\r\n"를 리턴한다.
피드 구독하기:
글 (Atom)
Generic interfaces 요점
https://go.dev/blog/generic-interfaces Generic interface를 정의할 때 최소한의 제약만을 정의하고 실제 구현체들이 자신만의 필요한 제약을 추가할 수 있도록 하는 것이 좋다. pointer receiver를...