Redis開発ガイドライン
Redisインスタンス
GitLabはRedisを以下のような明確な目的で使用します:
- キャッシュ (主に
Rails.cache
経由)。 - Sidekiqによるジョブ処理キューとして。
- 共有アプリケーションの状態を管理します。
- CI トレースチャンクの保存。
- ActionCableのPub/Subキューバックエンドとして。
- 状態保存のレート制限。
- セッション
ほとんどの環境(GDKを含む)では、これらはすべて同じRedisインスタンスを指しています。
GitLab.comでは、別々のRedisインスタンスを使っています。設定の詳細についてはRedis SRE ガイドを参照ください。
どのアプリケーションプロセスも同じRedisサーバーを使うように設定されているので、PostgreSQLが適切でない場合にプロセス間通信に使うことができます。例えば、一時的な状態や、読み込むよりも書き込む頻度が高いデータなどです。
Geoが有効になっている場合、各 Geo ノードはそれぞれ独立した Redis データベースを取得します。
新しい Redis インスタンスの追加に関する開発者向けドキュメントがあります。
キーの命名
Redisは階層のないフラットな名前空間なので、衝突を避けるためにキーの名前に注意を払う必要があります。一般的には、コロンで区切られた要素を使用して、アプリケーション・レベルでの構造の体裁を整えます。例えば、projects:1:somekey
。
GitLab.com のような可用性の高い設定では、Redis の利用を目的別に分けて別々の Redis サーバーにマッピングしますが、デフォルトの Omnibus と GDK ではひとつの Redis サーバーを共有します。つまり、キーはすべてのカテゴリで常にグローバルに一意でなければなりません。
通常、Redisのキー名には不変の識別子、例えばフルパスではなくプロジェクトIDを使う方がよいでしょう。フルパスを使用すると、プロジェクトの名前が変更されたときにキーが参照されなくなります。キーの内容が名前の変更によって無効になる場合は、キーの変更に依存するのではなく、エントリを期限切れにするフックを含めるほうがよいでしょう。
マルチキーコマンド
GitLabはエピック823で導入されたRedisレート制限タイプでのみRedisクラスターをサポートします。
GitLabが複数のキーを同じRedisサーバーに保持させるオペレーションを行う場合、例えばRedisに保持されている2つのセットを差分する場合、キーは変更可能な部分を中かっこで囲む必要があります。例えば
project:{1}:set_a
project:{1}:set_b
project:{2}:set_c
set_a
とset_b
は同じ Redis サーバーにあることが保証されますが、set_c
は保証されません。
現在、cache
とshared_state
Redisインスタンスで有効になっているRedisClusterValidator
、開発者とテスト環境で検証しています。
開発者は、より多くのRedisタイプで将来Redisクラスターを採用しやすくするために、必要に応じてハッシュタグを使用することを強く推奨します。たとえば、Namespaceモデルは設定キャッシュキーにハッシュタグを使用しています。
構造化ロギングにおけるRedis
GitLabチームメンバーの方へ: 、基本的な GitLab.comのRedis構造化ロギング・フィールドの使い方を紹介する上級者向けのビデオがあります。
ウェブリクエストと Sidekiq ジョブの構造化ロギングには、Redis インスタンスごとの継続時間、コールカウント、書き込みバイト数、読み込みバイト数のフィールドと、すべての Redis インスタンスの合計が含まれます。特定のリクエストでは、次のようになります:
項目 | 値 |
---|---|
json.queue_duration_s | 0.01 |
json.redis_cache_calls | 1 |
json.redis_cache_duration_s | 0 |
json.redis_cache_read_bytes | 109 |
json.redis_cache_write_bytes | 49 |
json.redis_calls | 2 |
json.redis_duration_s | 0.001 |
json.redis_read_bytes | 111 |
json.redis_shared_state_calls | 1 |
json.redis_shared_state_duration_s | 0 |
json.redis_shared_state_read_bytes | 2 |
json.redis_shared_state_write_bytes | 206 |
json.redis_write_bytes | 255 |
これらのフィールドはすべてインデックス化されているため、本番環境でのRedisの使用状況を調査するのは簡単です。たとえば、キャッシュから最も多くのデータを読み込んだリクエストを見つけるには、redis_cache_read_bytes
で降順に並べ替えます。
遅いログ
GitLab.com では、Redis のスローログのエントリがredis.slowlog
タグでpubsub-redis-inf-gprd*
インデックスに表示されます。これは、長い時間がかかり、パフォーマンスが懸念されるコマンドを示しています。
fluent-plugin-redis-slowlog
プロジェクトは、Redis からslowlog
エントリを取り出し、Fluentd(そして最終的には Elasticsearch)に渡す役割を担っています。
鍵空間全体の分析
Redis Keyspace Analyzerプロジェクトには、Redisインスタンスの完全なキーリストとメモリ使用量をダンプし、結果から潜在的にセンシティブなデータを除外しながらそれらのリストを分析するためのツールが含まれています。最も頻繁に使用されるキーパターンや、最もメモリを使用するキーパターンを見つけるために使用できます。
現在のところ、これはGitLab.comのRedisインスタンスでは自動的には実行されず、必要に応じて手動で実行されます。
N+1コールの問題
spec/support/helpers/redis_commands/recorder.rb
経由で内部導入。f696f670
RedisCommands::Recorder
はテストから Redis の N+1 コールの問題を検出するツールです。
Redis はしばしばキャッシュ目的で使用されます。通常、キャッシュの呼び出しは軽量で、Redis インスタンスに影響を与えるほどの負荷は発生しません。しかし、そうとは知らずに高価なキャッシュの再計算をトリガーしてしまう可能性はあります。このツールを使ってRedisコールを分析し、予想される制限を定義します。
テストの作成
ActiveSupport::Notifications
instrumenterとして実装されています。
テスト可能なコードが Redis を 1 回だけ呼び出すことを検証するテストを作成できます:
it 'avoids N+1 Redis calls' do
control = RedisCommands::Recorder.new { visit_page }
expect(control.count).to eq(1)
end
または、特定の Redis 呼び出しの回数を検証するテストを作成します:
it 'avoids N+1 sadd Redis calls' do
control = RedisCommands::Recorder.new { visit_page }
expect(control.by_command(:sadd).count).to eq(1)
end
特定の Redis 呼び出しのみをキャプチャするパターンを指定することもできます:
it 'avoids N+1 Redis calls to forks_count key' do
control = RedisCommands::Recorder.new(pattern: 'forks_count') { visit_page }
expect(control.count).to eq(1)
end
また、特別なマッチャーexceed_redis_calls_limit
やexceed_redis_command_calls_limit
を使って、Redis 呼び出し回数の上限を定義することもできます:
it 'avoids N+1 Redis calls' do
control = RedisCommands::Recorder.new { visit_page }
expect(control).not_to exceed_redis_calls_limit(1)
end
it 'avoids N+1 sadd Redis calls' do
control = RedisCommands::Recorder.new { visit_page }
expect(control).not_to exceed_redis_command_calls_limit(:sadd, 1)
end
これらのテストは、Redis呼び出しに関連するN+1の問題を特定し、その修正が期待通りに動作することを確認するのに役立ちます。
こちらもご覧ください
ユーティリティクラス
特定のユースケースを支援するために、追加のクラスをいくつか用意しています。これらは主にRedisの使い方を細かく制御するためのものなので、Rails.cache
ラッパーと組み合わせて Rails.cache
使うことはありませんRails.cache
。 Rails.cache
私たちはRails.cache
、将来Railsに施される最適化の恩恵を享受できるようにしたいと考えています。RubyオブジェクトはRedisに書き込まれるときにマーシャルされるので、巨大なオブジェクトや信頼できないユーザーの入力を保存しないように注意しなければなりません。
通常、これらのクラスは次のうち少なくとも1つが当てはまる場合にのみ使用します:
- 非キャッシュ Redis インスタンスでデータを操作したい場合。
-
Rails.cache
は実行したいオペレーションをサポートしていません。
Gitlab::Redis::{Cache,SharedState,Queues}
これらのクラスはRedisのインスタンスを(Gitlab::Redis::Wrapper
を使って)ラップし、直接操作できるようにします。典型的な使い方は、.with
を呼び出すことです。 は Redis 接続を返すブロックを受け取ります。例えば
# Get the value of `key` from the shared state (persistent) Redis
Gitlab::Redis::SharedState.with { |redis| redis.get(key) }
# Check if `value` is a member of the set `key`
Gitlab::Redis::Cache.with { |redis| redis.sismember(key, value) }
Gitlab::Redis::Boolean
Redis では、すべての値は文字列です。Gitlab::Redis::Boolean
は、ブーリアンが一貫してエンコードおよびデコードされるようにします。
Gitlab::Redis::HLL
RedisのPFCOUNT
、PFADD
、PFMERGE
コマンドはHyperLogLogsでオペレーションします。HyperLogLogsは、少ないメモリ使用量でユニークな要素の数を推定できるデータ構造です。詳細については、RedisのHyperLogLogsを参照してください。
Gitlab::Redis::HLL
はHyperLogLogsに値を追加したりカウントしたりするための便利なインターフェースを提供します。
Gitlab::SetCache
アイテムがアイテムのグループ内にあるかどうかを効率的にチェックする必要がある場合、Redisセットを使用することができます。Gitlab::SetCache
は、SISMEMBER
コマンドを使用する#include?
メソッドと、セット内のすべてのエントリをフェッチする#read
を提供しています。
これはRepositorySetCache
、ブランチ名などのリポジトリデータをキャッシュするためにセットを使用する便利な方法を提供するために使用されます。
バックグラウンドマイグレーション
Redisベースのマイグレーションでは、SCAN
コマンドを使用して、特定のキーパターンについてRedisインスタンス全体をスキャンします。大規模なRedisインスタンスの場合、マイグレーションは通常のマイグレーションやデプロイ後のマイグレーションの制限時間を超える可能性があります。RedisMigrationWorker
、バックグラウンドマイグレーションとして長時間実行されるRedisマイグレーションを実行します。
クラスを作成してバックグラウンドマイグレーションを実行するには、次のようにします:
module Gitlab
module BackgroundMigration
module Redis
class BackfillCertainKey
def perform(keys)
# implement logic to clean up or backfill keys
end
def scan_match_pattern
# define the match pattern for the `SCAN` command
end
def redis
# define the exact Redis instance
end
end
end
end
end
デプロイ後のマイグレーションによってワーカーを起動する場合:
class ExampleBackfill < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
MIGRATION='BackfillCertainKey'
def up
queue_redis_migration_job(MIGRATION)
end
end