Table Of Contents

Previous topic

コマンドリファレンス

Next topic

データ型

This Page

リクエスト/レスポンス プロトコルと往復遅延時間

Redisは、典型的なTCPを使ったクライアント/サーバモデルです。リクエスト/レスポンスプロトコルと呼ばれます。

いくつかのステップでリクエストが成り立っています。

  • クライントは、クエリーを送る。 ソケットを読む、普段はブロックすること無しに、サーバからのレスポンス
  • サーバープロセスは、コマンドを受け取りそしてサーバにレスポンスを返します

インスタンスは、4つのコマンドシーケンスを送ると以下のように動作します。

  • Client: INCR X
  • Server: 1
  • Client: INCR X
  • Server: 2
  • Client: INCR X
  • Server: 3
  • Client: INCR X
  • Server: 4

クライアントとサーバはネットワーク越しに接続されます。ループバックの高速な接続でもインターネットを介した低速なネットワークでもどちらでもOKです。ネットワークのレイテンシとは、クライアントがパケットを送信してからサーバまでに届き、そして、サーバが応答してからクライアントに到達するまでの時間のことです。

この時間はRTT(Round Trip Time: 往復遅延時間)と呼ばれます。この値は、パフォーマンスに多大な影響を及ぼします。例えばRTTが250msかかる場合(この例はインターネット経由でのとても遅いパターンです)は、秒間100kリクエストが可能なサーバーでも秒間4リクエストが上限となります。

もしルークバックインターフェースが使用可能ならば、RTTは大幅に短縮が可能です(私の例だと0.044ms pinging 127.0.0.1)しかしもし大量に書き込みたい場合は、これほど早くは有りません。

幸いなことに、これらを改善する方法があります。

パイプライニング

リクエスト/レスポンスサーバーは、クライアントが過去のレスポンスを読み取る前にあtらしいリクエストを処理できる状態にあります。このやり方では、複数のコマンドをサーバーの応答を待たずに一括で送信しその後ひとつづつ受信処理を行ないます。

これをパイプライニング(pipelining)パイプライニングと呼ばれ数十年前から利用されています。例えば多くのPOP3プロトコル実装ではサーバから新しいメールをダウンロードするプロセスを大幅にスピードアップさせる為、この機能をサポートしています。 。

Redisでは、早い段階からパイプライニングをサポートしています(多分もちろんあなたがご使用のバージョンでも)あなたもパイプライニングが利用できます。以下の例では、netcatユーティリティーになります。

$ (echo -en "PING\r\nPING\r\nPING\r\n"; sleep 1) | nc localhost 6379
+PONG
+PONG
+PONG

今回は、RTTのコストを払っていません。一度に3つのコマンドを発行しています。

次の例はとてもわかりやすいパイプライニングの例です。

  • Client: INCR X
  • Client: INCR X
  • Client: INCR X
  • Client: INCR X
  • Server: 1
  • Server: 2
  • Server: 3
  • Server: 4

Important

パイプライニングを使用してクライアントからコマンドが送信されるとサーバは応答に強制的にメモリー使ったキューを利用します。もし超大量ののコマンドを送った場合は、それ相応(10kのコマンドを送ったとして約4倍)のメモリーが必要になります。

ベンチマーク

Redis 各種クライアントを用いたベンチマークサンプルです。パイプライニングをサポートしています。ベンチマークでは、パイプライニングの有り無しで計測を行っています。

Ruby Sample

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
require 'rubygems'
require 'redis'

def bench(descr)
    start = Time.now
    yield
    puts "#{descr} #{Time.now-start} seconds"
end

def without_pipelining
    r = Redis.new
    10000.times {
        r.ping
    }
end

def with_pipelining
    r = Redis.new
    r.pipelined {
        10000.times {
            r.ping
        }
    }
end

bench("without pipelining") {
    without_pipelining
}
bench("with pipelining") {
    with_pipelining
}

Running the above simple script will provide this figures in my Mac OS X system, running over the loopback interface, where pipelining will provide the smallest improvement as the RTT is already pretty low:

without pipelining 1.185238 seconds
with pipelining    0.250783 seconds

Python Sample

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/env python
# coding:utf-8

import redis
import time

def bench(descr, func, cnt):
  start = time.time()
  func(cnt)
  print descr, time.time() - start

def without_pipelining(cnt):
  r = redis.Redis()
  [r.ping() for x in range(cnt)]

def with_pipelining(cnt):
  r = redis.Redis().pipeline()
  [r.ping() for x in range(cnt)]
  r.execute()

if __name__ == '__main__':
  bench('without pipeline', without_pipelining, 10000)
  bench('   with pipeline', with_pipelining, 10000)

  

このサンプルは、MBP(i7 4GB OSX10.6.4)上でRedis-Server2.0.2,Python2.6.1上で計測されたものです。ネットワークはループバックインターフェース経由で接続されているのでRTTはプリティーLowです。

without pipeline 0.824854135513
   with pipeline 0.197869062424

ご覧のとおり、パイプラインを使用したほうが4倍以上高速化されています。

パイプライニング vs その他マルチコマンド

シングルパスで複数のオペレーションを行ないたい場合に使える便利コマンドは特に用意されていません。多くの場合あなたは大量の:com:SADD コマンドを発行する必要があります。

一方パイプライニングを使用すると、:com:MADD コマンドを使用した時と同じだけのグッドパフォーマンスを期待できます。しかし、redisは大量のコマンドを受け取ることになります。