Redis는 애초에 'High performance'를 목적에 두고 만들어졌기 때문에, 성능 개선을 위한 작업들을 많이 진행해 왔습니다. Pipelining이 그 중 하나고, 이번에는 Redis Scripting이라고도 불리는 eval을 알아 보겠습니다.

eval [script] [numkeys] [key ...] [arg ...]

EVAL은 Redis 버전 2.6.0부터 내장된 lua 인터프리터를 사용하여 스크립트를 평가하는 데 사용됩니다. 필수적으로 eval [script] [numkeys] 커맨드가 들어가 있어야 합니다.

  • script : 문자열 형태의 lua 스크립트입니다.
  • numkeys : 키의 갯수입니다. 이는 뒤에 추가적으로 붙을 선택 인자들 중 몇 개가 key인지를 lua가 알 수 있도록 합니다.

선택 인자 [key ...][arg ...]는 각각 lua에서 사용할 수 있도록 KEYSARGV 배열에 바인딩됩니다. Lua는 배열의 인덱스를 1부터 센다(one-based numbering)는 것에 주의해야 합니다.

아래 두 개의 lua 함수를 사용하여, lua script에서 redis 명령을 호출할 수 있습니다.

  • redis.call()
  • redis.pcall()

redis.call()은 redis.pcall()과 유사하지만, redis 명령 수행 중 오류가 발생하면 lua 오류를 발생시킵니다. 반대로 redis.pcall()은 오류를 trap하고 오류를 나타내는 lua 테이블을 반환합니다. 아래는 eval과 lua script를 사용한 간단한 set 명령입니다.

127.0.0.1:6379> eval "return redis.call('set', 1, 123)" 0
OK

script는 "return redis.call('set', 1, 123)", numkeys는 0입니다. 실제로 전달되는 명령은 set 1 123이 됩니다. eval의 컨벤션 상, 스크립트가 사용하는 모든 키는 KEYS 배열을 사용하여 전달되어야 하므로, 아래처럼 변경해야 합니다.

127.0.0.1:6379> eval "return redis.call('set', KEYS[1], 123)" 1 1
OK

어떤 이점이 있나?

복잡도만 늘어난 것으로 보이지만, Lua 스크립트는 다음과 같은 장점을 가집니다.

  • Pipelining처럼, 여러 명령을 한 번의 request/response만으로 수행할 수 있습니다.
  • 원하는 함수를 redis에서 지원하고 있지 않더라도 lua 스크립트로 대체 가능합니다.(반환되는 값 count, 반환되는 value 모두 더하기 등)
  • 아래와 같이, 스크립트를 재활용할 수도 있습니다.

스크립트 재활용하기

Lua script를 script load를 통해 실행하면, redis의 script cache라는 곳에 캐시됩니다. 그리고 그 실행 결과로 SHA 해시가 반환됩니다. 이는 각 lua script의 식별자로 활용되어, evalsha 명령어로 실행 가능합니다.

127.0.0.1:6379> script load "return {KEYS[1], KEYS[2]}"
"ee9729934e089eab4ec897f3c113f45c6a426b9d"
127.0.0.1:6379> evalsha ee9729934e089eab4ec897f3c113f45c6a426b9d 2 1 2
1) "1"
2) "2"

lua script의 길이가 길다면, 매 실행마다 script의 모든 코드를 전달하여 실행하는 것보다 미리 캐시된 script를 sha 값을 이용해 실행하는 것이 성능상 이점을 얻을 수 있습니다.

script exists로 특정 sha에 해당하는 script가 존재하는지 조회할 수 있고, script flush를 통해 script cache에 저장된 모든 script를 삭제할 수 있습니다.

127.0.0.1:6379> script exists ee9729934e089eab4ec897f3c113f45c6a426b9d
1) (integer) 1
127.0.0.1:6379> script flush
OK
127.0.0.1:6379> script exists ee9729934e089eab4ec897f3c113f45c6a426b9d
1) (integer) 0

eval은 러닝 커브로서 lua를 배워야 한다는 점이 있지만, 성능을 개선하기 위해 매우 강력한 무기이므로 배워 두면 정말 좋을 것 같습니다.

'데이터베이스 > Redis' 카테고리의 다른 글

[Redis] Pipelining  (0) 2018.09.06
[Redis] String(기본 조작)  (0) 2018.09.06
[Redis] List의 고급 커맨드들  (0) 2018.09.06
[Redis] List  (0) 2018.09.06
[Redis] select  (0) 2018.09.06

Redis는 고성능의 key-value store지만, TCP 기반의 클라이언트-서버 모델을 따르기에 아래와 같이 동작하고, 네트워크 IO에 대한 병목이 존재할 수밖에 없습니다.(물리적인 네트워크 지연, 3-way handshake 등의 이유로)

  • 클라이언트가 서버에게 전송한 쿼리는 소켓을 통해 읽혀지고, 보통 blocking 형태로 response됩니다.
  • 서버는 명령을 처리하고 응답을 다시 클라이언트로 보냅니다.

따라서, 아래의 예에서 수행하는 5번의 작업은 각각 1회씩 request와 response를 거칩니다. 총 5번의 request/response가 발생합니다.

127.0.0.1:6379> set a 1
OK
127.0.0.1:6379> incr a
(integer) 2
127.0.0.1:6379> incr a
(integer) 3
127.0.0.1:6379> incr a
(integer) 4
127.0.0.1:6379> incr a
(integer) 5

Redis가 로컬에서 동작하고 있다면 클라이언트와 서버 간의 네트워킹 링크가 굉장히 빠르겠지만, 두 호스트 간에 물리적으로 많은 홉이 사용되었다면 지연이 발생될 가능성이 큽니다. request/response 모델에 의해 클라이언트 - 서버 - 클라이언트 순서로 패킷이 흘러가야 하니까요.

이 시간을 RTT(Round Trip Time)라고 합니다. 만약 서버가 초당 100k(100,000)개의 요청을 처리할 수 있다고 하더라도, RTT가 250ms라면 초당 최대 4개의 요청만을 처리할 수 있게 됩니다. 로컬에서 동작하는 Redis의 경우엔 RTT가 훨씬 더 짧지만, 많은 read/write를 수행하기 위해서는 여전히 많은 양입니다. 제 경우엔 RTT가 0.12ms였는데, 처리량이 초당 100k라면 이에 훨씬 못 미치는 속도입니다.

이러한 병목을 개선하기 위해, Redis에서는 pipelining API를 제공합니다. 이를 통해 클라이언트는 복수 개의 작업을 쌓아 한 번에 전송(request)할 수 있게 됩니다. 따라서 응답을 전혀 기다리지 않고 여러 명령을 한꺼번에 서버로 보낼 수 있고, 응답을 한번에 읽을 수 있습니다.

커맨드에서 pipelining하기

넷캣(netcat)을 사용하겠습니다. Netcat(nc)은 TCP나 UDP를 사용하는 네트워크 연결에서 데이터를 읽고 쓰는 간단한 리눅스 유틸리티입니다. Unix의 cat이 파일을 읽거나 쓰듯이, nc는 네트워크에 읽고 씁니다. 조금 더 알아보니, 해킹에도 꽤 유용하게 쓰인다고 합니다.

$ printf "PING\r\nPING\r\nPING\r\n" | nc localhost 6379
+PONG
+PONG
+PONG

별 차이 없어 보이지만, 3번의 ping 요청에 대해 RTT 비용을 단 한 번만 사용하게 되었습니다.

코드로 확인하기

이제 코드로 확인해 봅시다. redis-py를 사용했습니다.

Pipelining은 패킷의 왕복 시간으로 인한 대기 시간 비용을 줄이기 위한 방법일 뿐만 아니라, Redis 서버에서 수행할 수 있는 초당 작업량이 엄청나게 증가나게 됩니다. 다만, 파이프라이닝을 하는 동안에는 클라이언트의 메모리가 소모되므로 파이프라이닝을 할 때에는 한 번의 파이프라이닝 대기열에 포함시킬 작업의 합리적인 수를 가장 먼저 산정해야 합니다.

'데이터베이스 > Redis' 카테고리의 다른 글

[Redis] Eval  (0) 2018.09.06
[Redis] String(기본 조작)  (0) 2018.09.06
[Redis] List의 고급 커맨드들  (0) 2018.09.06
[Redis] List  (0) 2018.09.06
[Redis] select  (0) 2018.09.06

이전에 redis-cliRedis 서버에 접속하는 것까지 해 봤습니다. Redis에는 많은 데이터 타입들이 있는데, 가장 기본은 문자열입니다. Redis에서 문자열은 Byte string, 정수, 실수를 저장하기 위해 사용됩니다. 가장 일반적인 형태의 key-value store로서 redis를 사용하기 위한 타입이며, 여기서는 string 타입을 위한 get, set 등의 기본 조작을 수행해 봅시다.

set [key] [value] [EX seconds] [PX milliseconds] [NX|XX]

set은 redis에 key-value 매핑을 설정하기 위해 사용합니다. 기본적인 커맨드는 set key value이며, EX나 PX 등을 통해 해당 매핑에 expire(유효 기한)을 설정할 수 있습니다.

127.0.0.1:6379> set a 1
OK

key가 "a", value가 1이며 유효 기한은 없습니다. 별도로 제거하지 않는 이상 계속해서 남아 있습니다.

127.0.0.1:6379> set b 2 EX 3
OK

key가 "b", value가 2이며 유효 기한은 3초입니다.

set 명령어는 기본적으로 insert를 수행하나, key가 이미 존재한다면 update를 수행합니다.

127.0.0.1:6379> set a 1
OK
127.0.0.1:6379> set a 3
OK
127.0.0.1:6379> set a 10
OK

get [key]

key에 해당하는 value를 반환합니다. 존재하지 않는 경우 (nil)을 반환합니다. Redis는 key들을 해시하므로, 시간복잡도는 O(1)으로 고정됩니다.

127.0.0.1:6379> get a
"10"
127.0.0.1:6379> get b
(nil)

incr [key], decr [key]

incrdecr 각각 key에 해당하는 value를 숫자로 평가 가능한지 판단한 이후, 1 증가시키거나, 감소시킵니다.

127.0.0.1:6379> set a 1
OK
127.0.0.1:6379> incr a
(integer) 2
127.0.0.1:6379> incr a
(integer) 3
127.0.0.1:6379> decr a
(integer) 2

value를 숫자로 평가할 수 없는 경우, 에러가 발생합니다.

127.0.0.1:6379> set a abc
OK
127.0.0.1:6379> incr a
(error) ERR value is not an integer or out of range

exists [key ...]

1개 이상의 key들을 받아 각각이 redis에 존재하는지를 검사하고, 존재하는 매핑의 수를 반환합니다.

127.0.0.1:6379> set a 1
OK
127.0.0.1:6379> set b 3
OK
127.0.0.1:6379> exists a
(integer) 1
127.0.0.1:6379> exists a b
(integer) 2
127.0.0.1:6379> exists a b abc cde q s
(integer) 2

del [key ...]

1개 이상의 key들을 받아 해당하는 매핑을 제거하고, 제거한 매핑의 수를 반환합니다.

127.0.0.1:6379> set a 1
OK
127.0.0.1:6379> set b 3
OK
127.0.0.1:6379> set c 5
OK
127.0.0.1:6379> del a
(integer) 1
127.0.0.1:6379> del b c d e
(integer) 2


'데이터베이스 > Redis' 카테고리의 다른 글

[Redis] Eval  (0) 2018.09.06
[Redis] Pipelining  (0) 2018.09.06
[Redis] List의 고급 커맨드들  (0) 2018.09.06
[Redis] List  (0) 2018.09.06
[Redis] select  (0) 2018.09.06

+ Recent posts