【実務・中級編】 Stream基本操作コマンド – Redis

Redis Streamsの核心:なぜ「ただのログ」として使うとシステムが崩壊するのか

チーフアーキテクトの私だ。コードレビューのたびに言っているはずだ。「RedisのStreamは、単なるDBの代わりのイベントログではない」と。

KafkaやRabbitMQの代替として、あるいは堅牢なメッセージキューとしてRedis Streams(`XADD`, `XDEL`, `XRANGE`, `XREVRANGE`)を導入するプロジェクトが後を絶たない。しかし、メモリ構造の特性を理解せずに「とりあえずエントリを突っ込んで、適当に消す」という実装をしている現場があまりにも多すぎる。本番環境でメモリが爆発し、OOM Killerにプロセスを屠られてから泣きを見るのはごめんだ。

今回は、Redis Streamsの基本操作コマンド群のメカニズムを解剖し、「実務で絶対に事故らない設計パターン」を叩き込む。

—

1. メモリの魔獣:`XADD` と ID自動生成の罠

Streamは、実態として radix tree(基数木)と Consumer Group のメタデータの組み合わせで構築されている。まずは基本のエントリ追加から見ていこう。

基本的なエントリの追加(IDは自動生成: タイムスタンプ-シーケンス)
127.0.0.1:6379> XADD mystream sensor_id “temp-1” value “23.5”
“1708310400000-0”

ここで “ を指定すると、Redisはミリ秒単位のUnixタイムスタンプと、同一ミリ秒内の連番(シーケンス番号)を組み合わせて自動的にIDを生成する。

テクニカルリードからの警告:カスタムIDを使うな

`XADD`の第一引数に明示的なID(例: `1000-0`)を指定することもできるが、特段の理由がない限り、自動生成(“)以外のIDを手動でぶち込むな。
RedisのStream IDは「厳密な単調増加(Monotonically Increasing)」でなければならない。クライアント側の時計のズレやロジックのバグにより、既存のIDより小さい、あるいは同等のIDを挿入しようものなら、容赦なくエラー(`ERR The ID specified in XADD is equal or smaller than the target stream top item`)が返る。タイムスタンプの逆転はシステム全体の致命傷になる。

—

2. 「消せない」現実:`XDEL` の本当の挙動と省メモリ設計

「エントリを処理したから `XDEL` で消す」——これはアンチパターンだ。

エントリの削除
127.0.0.1:6379> XDEL mystream 1708310400000-0
(integer) 1

なぜ `XDEL` だけではメモリが解放されないのか?

`XDEL` は、指定したIDのエントリを論理削除(Tombstone)する。エントリが指し示す実データ(ペイロード)の領域は、ラディックスツリーのノード内に残り続ける。真にメモリが解放されるのは、そのノード内の全てのエントリが削除され、ノード自体がパージされた時だ。

実務でログやイベントを流す場合、`XDEL` を個別に叩く設計は無駄なCPUサイクルとメモリ断片化を招くため即刻廃止せよ。代わりに、`XADD` の時点で上限を決めるか、後述するトリム機構を使うべきだ。

【推奨】MAXLENを使った上限管理付きのXADD
近似的な長さ(~)を指定することで、メモリ効率を保ちながら古いエントリを自動パージする
127.0.0.1:6379> XADD mystream MAXLEN ~ 10000 sensor_id “temp-1” value “24.0”

—

3. タイムトラベルと範囲走査:`XRANGE` と `XREVRANGE`

データの取得には `XRANGE`(古い順)と `XREVRANGE`(新しい順)を使用する。

全範囲から最新の2件を取得したい場合(XREVRANGEの真骨頂)
127.0.0.1:6379> XREVRANGE mystream + – COUNT 2
1) 1) “1708310400001-0”
2) 1) “sensor_id”
2) “temp-2”
3) “value”
4) “24.1”
2) 1) “1708310400000-0”
2) 1) “sensor_id”
2) “temp-1”
3) “value”
4) “23.5”

  • `-` は最小ID(`-inf`)、`+` は最大ID(`+inf`)を意味する。

実務におけるパフォーマンスの罠:巨大なレンジ取得

`COUNT` オプションを付けずに `XRANGE mystream – +` のようなクエリを発行するとどうなるか?
Streamに数百万件のエントリが溜まっている状態でこれをやると、Redisのシングルスレッドがブロックされ、他のすべてのクライアントリクエストが数秒間にわたって停止(レイテンシスパイク)する。

【鉄則】
1. `XRANGE` / `XREVRANGE` をバッチ処理で回す際は、必ず `COUNT` を指定し、前回取得した最後のIDを次のリクエストの始点(排他制御)として使う「ページネーション設計」にすること。
2. 無限の過去を遡るクエリは禁忌とする。TTLや `MAXLEN` で保持期間を物理的に制限しておけ。

—

4. チーフアーキテクトが推す:堅牢な設計パターン

最後に、実務の現場で私が承認するRedis Streamsのデータフロー設計を提示する。

[Web/IoT Clients]
│
▼ (XADD + MAXLEN ~ 50000)
┌─────────────────────────────┐
│ Redis Stream │
└──────────────┬──────────────┘
│
├──────────────────────────────┐ (Consumer Groupで水平分散)
▼ ▼
[Worker Node A] [Worker Node B]

アーキテクチャの要件定義:

1. ストリーム長の上限を必ず設ける: `MAXLEN ~` を用いて、メモリの暴走を防ぐガードレールを敷く。
2. 生データではなくIDベースでカーソル管理する: 単発の `XDEL` でゴミ掃除をするな。自動パージと、Consumer Group(`XREADGROUP`)によるオフセット管理を組み合わせよ。
3. エラーハンドリングを見据えたレンジ検索: 万が一のコンシューマ障害時に `XRANGE` で未処理データを再スキャンする場合も、一度に取得するチャンクサイズ(`COUNT`)は1,000件程度に絞れ。

—

結び

Redisは高速だが、それは「正しく使われている時」の条件付きだ。データ構造の裏側にあるメモリ管理の仕組み(ラディックスツリーとパージの挙動)を理解せず、リファレンスをなぞっただけのコードは、高負荷時に必ずシステムを沈没させる。

次のコードレビューでは、`XADD` に `MAXLEN` が入っているか、そして不要な `XDEL` を乱発していないか、私が厳しくチェックさせてもらう。準備を怠るな。

コメント

タイトルとURLをコピーしました