概要
先日CodeCraftersというサービスで、RedisをGolangで開発しました。
その中で、複数のクライアントからのアクセスを受け付ける必要があり、
解決策としてシングルスレッドのイベントループまたはマルチスレッドの並列処理が選択肢として挙げられました。
単純に考えると並列処理の方が多数のアクセスを捌くのに有利そうですが、Redis本家はシングルスレッドで動作しています。
実際に、1秒あたり100万リクエストに対応できるようです。
平均的なLinuxシステムで実行されているパイプラインRedisを使うと、1秒あたり100万リクエストを配信できる
http://mogile.web.fc2.com/redis/redis608/topics/faq.html
ここで、なぜRedisがシングルスレッドを採用しており、高速に動作するのか気になったので調査してみました。
Redisとは
インメモリのkey-valueデータストアです。
Redisサーバーがクライアントからのリクエストを受け付け、処理して、応答を返すという仕組みであるclient/serverモデルを採用しており、複数クライアントからのリクエストに対応できます。
Redisがシングルスレッドを採用している理由
RedisのボトルネックとなりうるのはネットワークI/Oであり、CPUではありません。
そのためマルチスレッドにすることにより期待できるパフォーマンス向上よりも、
- スレッドの生成と破棄によるオーバーヘッド
- スレッドのスケジューリングに関するオーバーヘッド
を抑えることのメリットが大きいためだと考えられます。
Redisはなぜシングルスレッドで高速に動作するのか
以下の3つの理由が挙げられます。
1. インメモリである
RedisはRAM(Random Access Memory)を使用します。
RAMはデータへのランダムアクセスを得意としており、必要なデータを素早く見つけることが可能です。
一方で、ディスクへのI/Oではシーケンシャルアクセスが用いられることが多いです。
シーケンシャルアクセスでは、ディスク全体を順番に読み込んでいくので必要なデータに到達するまでに時間がかかってしまいます。
特にHDDでは物理的な移動(ディスクの回転など)が必要なので、さらに時間を要します。
2. データ構造が最適化されている
RedisはList、Set、Hashなどのデータ型をサポートしていますが、それらにアクセスする操作の多くはO(1)であり、効率的です。
これは、入力サイズに関係なく常に一定の処理時間であるということを示しています。
例えば、RedisのlistはLinked Listsによって実装されています。これによって、listに多くの要素がある場合でも
listの先頭・末尾に要素を追加する速度は同じとなっています。
data typesにそれぞれのデータ構造に関する説明が記載されています
3. ノンブロッキングI/O
Redisはシングルスレッドで動作するため、複数リクエストに対応するためにノンブロックキングI/Oを採用しています。
ノンブロックキングI/Oとは、I/O処理が完了する前に他のリクエストの処理を進めることができるものです。
Redisで使用されているイベントループと組み合わせることで、C10K問題を回避することが可能です。
まとめ
Redisは
- インメモリであるためデータのI/Oが高速である
- データ構造が最適化されており多くの処理の計算量はO(1)である
- ノンブロッキングI/Oを採用しており、シングルスレッドで複数リクエストに対応している
という理由でシングルスレッドで高速に動作すると言えます。