こんにちは。Bypass開発チームの川住と申します。
今回はRedisにおけるデータの大量挿入手法について紹介します。
Redisとは?
まず、Redisについて少し紹介します。
Redisとは、メモリ上に Key-Value Store(KVS)を構築できるソフトウェアです(http://redis.io/)。
同系統のソフトウェアにはmemcached (http://memcached.org/) などがあります。
memcachedと比較すると、
- シングルスレッドでクエリを処理する
- データを永続化できる
- ハッシュやリストなど、様々なデータ型を利用できる
といった特徴があります。
これらのソフトウェアを用いると、
メモリ上にデータを保持しており、かつ処理がシンプルであるため、
高速にデータの取得(格納)可能なKVSを構築できます。
データの大量挿入
本題に移ります。
Redisのデータの格納はとても高速ですが、シングルスレッドでクエリを処理するため、
データ数に比例して処理に時間がかかってしまいます。
そこで、大量のデータを挿入する時には、
「1件あたりのデータの挿入にかかる時間」をできるだけ短くする必要があります。
弊社では、Perlスクリプトでデータを加工し、Redisに大量のデータを格納しています。
この時、PerlのRedisモジュールを用いて1件ずつデータを格納してしまうと、
- Redisとのデータの送受信で発生するRTT (Round-Trip Time)
- Redisからのレスポンスをパースし、Perlのデータ型に加工する時間
が問題になります。そこで、
- 複数のコマンドの一括送信
- pipiline機能の利用 (http://redis.io/topics/pipelining)
- Redisからのレスポンスの破棄
- socketやnc (netcat) でコマンドを送信し、レスポンスを/dev/null等に捨てる
を行い、処理時間を削減します。
処理時間の計測
下記の手法で10万件のデータの格納にかかった時間をそれぞれ計測しました。
- PerlのRedisモジュールを使う(手法1)
1 2 3 4 5 6 7 8 9 10 11 |
#!/usr/bin/perl use strict; use warnings; use Redis; `echo "flushdb" | redis-cli`; my $r = Redis->new( server => '127.0.0.1:6379' ); my %data = ("foo" => "1", "bar" => "1"); for(my $i = 0;$i < 100000;$i++){ $r->hmset("Test::$i" , %data); $r->expire("Test::$i", 86400); } |
- socketを用いてデータを流し込む(手法2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#!/usr/bin/perl use strict; use warnings; use IO::Socket::INET; `echo "flushdb" | redis-cli`; my $sock = IO::Socket::INET->new( PeerAddr => "localhost", PeerPort => 6379, Proto => "tcp" ) or die $!; for(my $i = 0;$i < 100000;$i++){ $sock->print("hmset Test::$i foo 1 bar 1\r\nexpire Test::$i 86400\r\n"); } close($sock); |
- ncを用いてデータを流し込む(手法3)
1 2 3 4 5 6 7 8 9 10 |
# !/usr/bin/perl use strict; use warnings; `echo "flushdb" | redis-cli`; my $fh; open($fh, "| nc 127.0.0.1 6379 > /dev/null") or die $!; for(my $i = 0;$i < 100000;$i++){ print $fh "hmset Test::$i foo 1 bar 1\r\nexpire Test::$i 86400\r\n"; } close($fh); |
計測結果
同端末で各手法5回ずつ試行し、その平均を処理時間としました。
手法 | 処理時間 (sec) | ロス率 |
---|---|---|
手法1 (module) | 59.710145 | 0% |
手法2 (socket) | 2.978576 | 0% |
手法3 (netcat) | 2.626164 | 18% |
複数のデータの格納コマンドを一括で送信し、
レスポンスのパースを行わないことによって処理時間を大幅に削減できます。
socketを用いる場合は送信(受信)バッファの管理を行わないと、
バッファ溢れによってデータロスが発生してしまいます。
まとめ
今回はRedisにおけるデータの大量insert手法を紹介しました。
目的に合わせて最適な手法を選択することでより効率的にデータを処理できるようになります。
(参考URL: http://redis.io/topics/mass-insert)