Bulk Insert In Redis

Redis で初期データを大量投入する必要があったので、Redis でバルクインサートする方法を調べてみた。

ショートアンサーとしては、Redis 公式サイトの Documentation->Topics->Redis Mass Insertion を読むこと。
読みながら動かした備忘録が以下。

======

Redis はオンメモリデータベースなので各コマンドの処理時間は 非常に短い。
ボトルネックはネットワークの遅延ということで ping コマンドを1万回実行し、処理完了までの時間を以下の4通りで比較。

  1. コマンド送信のたびにTCP接続を確立
  2. TCP接続の確立を1回に減らす
  3. コマンド送信を1回に減らう
  4. 2.4.14/2.6で導入された –pipe を使う

1. コマンド送信のたびにtcp接続を確立

まずは、誰もやらないと思うけど、コマンド送信のたびに毎回 TCP 接続を確立する方法

#naive.sh
redis-cli -h 192.168.11.7 PING
redis-cli -h 192.168.11.7 PING
redis-cli -h 192.168.11.7 PING
...
redis-cli -h 192.168.11.7 PING

TCP 接続の確立と終了を毎回やるとさすがに無駄が多い

$ bash naive.sh
PONG
PONG
PONG
...
PONG

real    0m34.083s
user    0m1.236s
sys     0m1.628s

2. TCP接続の確立を減らす
毎回 TCP 接続を確立するのはさすがに無駄が大きいので1回で済ます。

require 'rubygems'
require 'redis'

r = Redis.new(:host => "192.168.11.7")
10000.times {
  r.ping
}

劇的に速くなった。とはいえ、リクエストのたびにレスポンスが来ないと次のコマンドを発行していないので、待ち時間が長い。また、コマンドの数だけラウンドトリップが発生している。

$ time ruby ping-redis.rb

real    0m6.343s
user    0m0.576s
sys     0m1.732s

3. ラウンドトリップを減らす

ラウンドトリップを減らすために、コマンドをまとめて送信する(パイプライン)

プログラムで unified request protocol のコマンドを生成し、netcat でまとめて送信する。

 コマンドの生成

# ping.rb
N = 10000
def gen_redis_proto(*cmd)
  proto = ""
  proto << "*"+cmd.length.to_s+"\r\n"
  cmd.each{|arg|
    proto << "$"+arg.length.to_s+"\r\n"
    proto << arg.to_s+"\r\n"
  }
  proto
end
N.times {
  STDOUT.write(gen_redis_proto('ping'))
}

netcat を使ってコマンドを実行

$ time (ruby ping.rb; sleep 5) | nc 192.168.11.7 6379

real    0m5.275s
user    0m0.076s
sys     0m0.036s

コマンドをまとめて1回数で送信することで、ラウンドトリップは減らせた。問題は netcat の仕様により、パイプでつないでいる stdin が閉じられるとレスポンスをすべて受け取る前であっても netcat が終了してしまう。

ubuntu bug#544935 : netcat-openbsd exits too soon

sleep を挟むことで netcat がすぐに終了しないようにできるが、レイテンシーが大きいのに sleep を短めに設定していたりすると、レスポンスを途中までしか受け取れない。sleep 時間の微妙なさじ加減で頭を悩ませたくない。

4. –pipe モード:Transfer raw Redis protocol from stdin to server
上記問題を解決するために、 Redis 2.4.14/2.6 以降の redis-lic では pipe モードが追加された。

処理の流れは以下

  1. プログラムがロープロトコルのコマンドを標準出力
  2. クライアントはコマンドを標準入力で受け取り、サーバに送信。
  3. クライアントは標準入力からのインプットがなくなったタイミングで送信終了の合図として、ランダムな20バイトを echo コマンドでサーバーに送信。
  4. クライアントはサーバーからのレスポンスを順次読み取る。送信したランダムな20バイトのエコーバックを受信を持って一括コマンドの処理が終了したとみなし、正常・異常の処理数のレポートとともに終了。
$ time ruby ping.rb | redis-cli -h 192.168.11.7 --pipe
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 10000

real    0m0.237s
user    0m0.076s
sys     0m0.020s

一括処理結果をすべて確認しつつ、sleep を挟んで無駄に処理完了が伸びることもなくなった。

なお、送信コマンドにエラーが含まれる場合は以下の様になる。

$ cat cmd
PING
PONG
PING
SET foo bar
$ cat cmd | redis-cli --pipe
All data transferred. Waiting for the last reply...
ERR unknown command 'PONG'
Last reply received from server.
errors: 1, replies: 4

======

データ送受信の違い
#1-#4 のサーバ⇔クライアント間のデータ送受信の違いを簡易的に図式化したのが以下

See Also

Advertisements
Tagged with: ,
Posted in database

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Archives
  • RT @__apf__: How to write a research paper: a guide for software engineers & practitioners. docs.google.com/presentation/d… /cc @inwyrd 6 months ago
  • RT @HayatoChiba: 昔、自然と対話しながら数学に打ち込んだら何かを悟れるのではと思いたち、専門書1つだけ持ってパワースポットで名高い奈良の山奥に1週間籠ったことがある。しかし泊まった民宿にドカベンが全巻揃っていたため、水島新司と対話しただけで1週間過ぎた。 それ… 6 months ago
  • RT @googlecloud: Ever wonder what underwater fiber optic internet cables look like? Look no further than this deep dive w/ @NatAndLo: https… 6 months ago
  • @ijin UTC+01:00 な時間帯で生活しています、、、 1 year ago
  • RT @mattcutts: Google's world-class Site Reliability Engineering team wrote a new book: amazon.com/Site-Reliabili… It's about managing produc… 1 year ago
%d bloggers like this: