Redisのパフォーマンスを考える:「遅くしない」という発想が鍵

シンプルなキャッシュ用途からテラバイト級の大規模構成まで、さまざまなユースケースでRedisを活用する多くの方々や企業と対話する機会に恵まれています。その中で、最もよく質問されるトピックが「パフォーマンス」です。Redisのパフォーマンスへの向き合い方は、他のシステムとは大きく異なります。多くのデータベースサーバーでは「パフォーマンスを向上させる」ことを目指しますが、Redisの場合、目標は「遅くしないこと」です。これは根本的に異なるアプローチであり、それを活かすには別のマインドセットが必要です。
パフォーマンス指標――レイテンシこそが王様
Redisを運用するうえで主に関心を寄せるべきパフォーマンス指標は、実質的に2つあります。1つは「1秒あたりにどれだけのコマンド(またはトランザクション)を実行できるか」、もう1つは「それぞれのコマンドにどれだけの時間がかかるか」です。これを分解してみると、前者は後者の結果として導かれる二次的な指標であることがわかります。Redisはシングルスレッドで動作するため、処理可能なops/secはコマンドの実行時間に完全に紐づいています。つまり、最終的に重要なのは、私が「コマンドレイテンシ」と呼ぶものなのです。
私がRedisの大きな利点だと考えているのは、そのシンプルさです。ユーザーはクエリではなく「コマンド」を発行します。コマンドはデータ取得のためのはるかに単純な経路であり、このシンプルさがコマンドの高速性にも反映されています。また、開発者はさまざまなクエリを最適化する代わりに、最適化されたコマンドを提供できます。このエレガントなシンプルさは、Redisを利用するプログラマーにももたらされます。多くの場合、セットのフィルタリングのような「クエリ」的な操作は、サーバーソフトウェアではなくクライアント側で実行することになります。
こうした種類のクエリはサーバー側で一貫した方法で処理すべきだと感じる方もいますが、現時点での私の見解は、それが望ましい道ではなくなりつつあるということです。理由は、私たちが「スケーラビリティ」と呼ぶ厄介な問題です。たとえば「水平スケーラブル」なWebサービスやサイトを運営し始めると、初期段階ではこのパターンで十分うまく機能します。しかし、「異常な」レベルのトラフィックを扱い始めると、データベースが重大なボトルネックになっていることにすぐ気づきます。その直後に、MySQLのようなSQLストアであるそのデータベースは「水平スケーラブル」ではない、つまり単純に台数を増やせないことを学ぶのです。
コマンドレイテンシを守る設計
もちろん、Redisも例外ではありません。しかし重要な違いは、フィルタリングロジックやソートなど、1つのRedisコマンド(せいぜい数個のコマンド)で実行できないものをクライアント側に保持することで、DBにロジック=「処理すべきもの」を負荷させないようにできる点です。Redisは従来型の「データベースサーバー」ではなく「データストア」として使うべきです。これこそが「Redisを遅くしない」という原則の第一歩として理解すべき点です。
言うまでもなく、Luaスクリプティングの道を進み始めると、結果としてパフォーマンスが低下するリスクを負うことになります。トラフィックが少〜中程度の開発段階では気づかないかもしれません。しかし、大規模になった瞬間に顕在化します。そしてその頃には、ロジックはすでに組み込まれてしまい、アプリケーションコードへ移行するには多大な技術的負債となっていることが少なくありません。技術的負債の解消にどれほどの優先度が割かれるかは、誰もが知るとおりです。
だからといって、Luaスクリプティングに存在価値がないわけではありません。ただし、厳しく精査すべきだというだけです。Luaスクリプトが純粋なパフォーマンス損失になるかどうかを見極める良い目安は、そのロジックをクライアント側で処理した場合に発生する追加のラウンドトリップのコストと比較することです。ただし、クライアントコードの実行速度ではありません。こう考えてください。ラウンドトリップのコストを2ms削減できても、スクリプト実行時間として3ms追加するなら、それは逆方向へ進んでいるのです。
ここで、シングルスレッドサーバーの本質を鋭く意識する必要があります。その3msのスクリプトは、実行中に潜在的に何百(場合によっては何千)ものコマンドをブロックします。これらを(取得)→(ロジック)→(取得)というシーケンスに分割すれば、サーバーは「(ロジック)」のフェーズ中に他のリクエストを処理できます。ロジックをクライアントコードに保持することで、マルチクライアントシステムに固有の同時実行性が保たれるのです。トランザクションやLuaスクリプティングが必要ならもちろん使うべきです。しかし、コードが「楽になりそうだから」という理由だけで使ってはいけません。常に同時実行性能への影響を意識し、計測し、意識的な選択を行いましょう。
ここから「Redisを遅くしない」ための第2のルールが導かれます。それは、スクリプトによるサーバーロジックを避け、同時実行性を維持することです。副次的なメリットとして、複数キーを操作するLuaスクリプトを書き換えたり廃棄したりすることなく、Redis Cluster構成へ移行できることも挙げられます。
Redisを遅くさせるその他の要因
Redisサーバーの稼働には、Redisを遅くしうるシステムレベル・運用レベルの側面がいくつかあります。I/Oを使用するあらゆるサーバーと同様、リソース制限はRedisを遅くします。たとえば8GBのネットワーク帯域幅が必要なのに1GBしかなければ、体感的に「遅い」状態になります。また、デーモンプロセスに必要な同時接続数より少ないソケット数の制限がかかっていると、コマンドを実行する代わりに接続が閉じられるのを待つ時間が発生します。
Redisを遅くさせる原因となる運用上の選択として、おそらく最も一般的なのは次の2つです。1つ目は仮想マシン上に配置すること(特にXenハイパーバイザー ベースのVM)、2つ目は重いディスク永続化です。
1つ目については、標準的なRedisドキュメントでかなり明確に述べられています。XenハイパーバイザーのVM上に置くな、ということです。2つ目の永続化についても言及されていますが、私の見解では、まだ十分に踏み込んでいません。
もちろん、定番の推奨事項もあります。ローカルの高速ディスクを使うこと、永続化時のCoW(コピーオンライト)に耐えられる十分なメモリを確保すること、さらには永続化をスレーブでのみ実行することなどです。これらは確かにコマンドレイテンシに波及効果をもたらしえます。しかし欠けているのは、それが実際に影響しているかどうかを適切に判断する方法です。
これを検証する標準的な方法の1つが、内蔵のレイテンシテスト機能の活用です。その技術的詳細に入る前に、より大きな「どうやって」の疑問に触れておきたいと思います。ここでの主要な論点は、いつこれらのテストを実行すべきかです。まず、データセットが小さい場合、このテストはほぼ無意味になりえます。どのくらい小さいのか? データをディスクへダンプするのにかかる時間を測ってみてください。1〜2秒、あるいは10秒程度であれば、有意義なデータは得られにくいでしょう。もちろん、これはすべてBGSAVE(SAVEではなく)を実行していることが前提です。
永続化レイテンシとの向き合い方
ここで、私が「永続化レイテンシ」と呼ぶ概念の最初の原則にたどり着きます。それは、コマンドがサーバーに到達してから結果がなんらかの形で永続化されるまでの時間のことであり、原則は「導入できるレイテンシが最小の永続化オプションを見つけること」です。ネットワークとディスクの状況によりますが、AOFまたはスレーブサーバーがレイテンシ増加の最小な永続化オプションとなり、RDBは最下位になります。
RDBが最下位になる主な理由は、「T秒間にN回の変更」という組み込みの遅延があるからです。つまり、その最小期間(デフォルトの最小ウィンドウは60秒)内に十分な変更があると仮定すると、永続化の遅延はインターバルT+RDBをディスクに書き込む時間となります。メモリのダンプに30秒かかり、デフォルトの60秒インターバルであれば、「永続化レイテンシ」は(60+30)で90秒になります。
しかし、この永続化レイテンシが本当に問題なのかという疑問が生じます。この問いには2つの側面があります。1つ目は「ビジネス要件に対して十分か?」、2つ目は「Redisを遅くしているか?」です。
1つ目の問いに対して、全員に当てはまる汎用的な答えは提示できません。ただ、永続化レイテンシの要件が上記の式が示すものより厳しいのであれば、答えは「RDBではなくスレーブおよび/またはAOFを使う」方向に傾く可能性が高いと言えます。これは、Redisを稼働させるプラットフォームで明白なミスをしていないことが前提です。そこで2つ目の側面、つまり「Redisを遅くしているか?」という話につながります。
一部の人にとっては、「RDBファイルの保存にどれくらいかかるか」が至上命題になります。私の見解では、これは誤りです。保存にかかる時間は本当に重要なのでしょうか? この問いの答えは「その保存がRedisを遅くしているかどうか」です。遅くしていないのであれば、1秒だろうと1時間だろうと気にする必要はありません。たとえば、RDBの保存に1000秒かかるサーバーAを考えてみましょう。保存していないときの本来のコマンドレイテンシ範囲は30〜100マイクロ秒です。保存中も、このレイテンシは依然として30〜100マイクロ秒の範囲にあります。この場合、「Redisのパフォーマンスが悪い」という思い込みのもとでRDBの保存時間短縮に取り組むのは、早すぎる最適化(不必要な最適化)です。
一方、サーバーBはわずか10秒で済むものの、コマンドレイテンシが30〜100マイクロ秒から130〜250マイクロ秒へ跳ね上がるとします。今度こそ、RDBファイルの生成にかかる時間を懸念する正当な理由があります。保存がRedisを遅くしており、その影響を最小化したいからです。それが高速ディスクの導入を意味するなら、ようやく投資を正当化する根拠が得られたことになります。ただし、約100〜150マイクロ秒の増加がアプリケーションのパフォーマンス不安を引き起こす場合に限ります。そうでなければ、また早すぎる最適化に戻ってしまうのです。
レイテンシの具体的な測定方法については、この記事がすでにかなり長くなっているため、続編のパート2で詳しく解説します。
-
Redisのハッシュ徹底解説!基本コマンドから実践的な活用例まで
Redisのハッシュ(Hash)は、1つのキーの下に複数の「フィールドと値」のペアを関連付けて格納できるデータ構造です。フィールドも値も文字列として扱われ、データ構造全体だけでなく、構造内の個々のフィールド単位でも柔軟に変更できるのが大きな特徴です。この特性により、アプリケーション内のオブジェクトを保存するバックエンドストアとして、非常に高速かつ理想的な選択肢となります。CLIで学ぶハッシュの基本操作まずは、2つのフィールドを持つハッシュを作成してみましょう。127.0.0.1:6379> HMSET my_hash key1 foo key2 bar OKハッシュに関連付けられたすべて
-
Redisを正しく使いこなすための10の実践テクニック
Redisは現在、技術コミュニティで非常に高い注目を集めています。Salvatore Sanfilippo(Antirez)氏の個人的な小さなプロジェクトとして始まったRedisは、今やインメモリデータストレージの業界標準へと成長しました。普及に伴い、Redisを適切に運用するためのベストプラクティスも確立されつつあります。本記事では、Redisを正しく活用するための10のヒントをわかりやすく解説します。 1. KEYS * の使用をやめる 冒頭から強い口調で申し訳ありませんが、これはおそらく最も重要なポイントです。Redisインスタンスのcommandstatsを確認すると、KEYSコマン