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

+ Recent posts