こんにちは!Redisの奥深い世界へようこそ。
今日は、現場でエンジニアが思わず冷や汗をかくトラブルワースト上位、「クライアント出力バッファ制限(client-output-buffer-limit)」についてお話しします。
「名前からして難しそう…」と思いましたか?大丈夫です。ここをクリアすれば、あなたもRedisのメモリ管理の仕組みが手に取るようにわかるようになりますよ。しっかり伴走しますので、リラックスして読んでくださいね。
—
1. なぜRedisは突然クラッシュするのか?
皆さんは、カフェのレジカウンターを想像してみてください。
お客さん(クライアント)が次々と注文をして、店員(Redis)が猛スピードで商品を渡していますよね。
通常、このやり取りはスムーズにい進みます。しかし、こんな状況を想像してください。
1. お客さん(アプリ)がスマホに夢中で、レジの前で全然商品を受け取らない。
2. 店員(Redis)は、お客さんが受け取らないので、商品をカウンターの上にどんどん積み上げていく。
3. ついにカウンターのスペースが限界を突破し、店が大混乱に陥る(OOM:Out Of MemoryでRedisが強制終了)。
これが、Redisの内部で起きている「出力バッファあふれ」の正体です。
Redisは超高速なインメモリデータベースです。データを取り出すスピードは光の如く速いのですが、それを外の世界(アプリ)へ送り出すときは、ネットワークの速度やアプリ側の処理能力に依存します。
アプリ側が遅くてデータを受け取りきれないとき、Redisは「相手が受け取るまで、一時的にデータを置いておく場所」を用意します。これが出力バッファです。そして、このバッファが無制限に膨らんでサーバーのメモリを食いつぶさないように制限をかけるのが、今回のお題である `client-output-buffer-limit` なんです。
—
2. 「出力バッファ制限」の仕組みを解剖する
Redisの設定ファイル(`redis.conf`)を覗くと、こんな見慣れない設定が書かれています。
書式: client-output-buffer-limit
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit pubsub 32mb 8mb 60
client-output-buffer-limit replica 256mb 64mb 60
呪文のように見えますよね。でも、分解して読むとすごくシンプルです。
Redisは、接続している相手(クライアント)を3つのタイプ(class)に分けて管理しています。
- `normal`:普通のアプリからの命令(GETやSETなど)
- `pubsub`:メッセージング機能(チャンネルの購読者)
- `replica`:データのバックアップや同期を取るための子Redis(レプリカ)
注目してほしいのは、後半の3つの数字です。ここには「ハードリミット」と「ソフトリミット」という2つの防衛ラインが設定されています。
① ハードリミット(即・強制切断のライン)
> 「一瞬でもこのサイズを超えたら、問答無用でコネクションをブチ切れ!」
例えば、上の設定の `pubsub` なら `32mb` です。一時的な置き場が32メガバイトを超えた瞬間、Redisは情け容赦なくそのクライアントとの接続を切ります。メモリを守るための最終防衛ラインです。
② ソフトリミット + 継続時間(猶予を持たせたライン)
> 「このサイズを超えた状態が、指定された秒数(例: 60秒)続いたら切断するよ」
こちらは少し優しさがあります。「ちょっと混雑しているだけなら、1分くらいは待ってあげる。でも、いつまでも受け取らないなら退場ね」という大人の対応です。
—
3. 実務でよくある「やっちまった」事例
私たちが現場でよく遭遇するのが、Pub/Sub(パブリッシュ・サブスクライブ)機能での事故です。
例えば、チャットアプリやリアルタイム通知システムで、次のようなコードを書いたとします。
- Redisのチャンネルに、膨大な量のメッセージが秒速で流れ込んでいる。
- しかし、それを購読(Sub)している特定のアプリサーバーのCPUが詰まってしまい、メッセージの処理が追いつかない。
こうなるとどうなるでしょう?
Redis側の `pubsub` 用出力バッファがモリモリと肥大化します。
「あれ?最近なんだかRedisのメモリ使用率が異常に高いぞ…?」と気づいた時には時すでに遅し。バッファがメモリを食いつぶし、OSのOOM KillerによってRedis本体が強制終了(Killed)させられてしまうのです。
—
4. 今日から使える!現場の最適化テクニック
では、私たちはどうやってこのリスクから身を守ればいいのでしょうか?
実務で使える3つのアプローチを伝授します。
アプローチ1: 現在のバッファ使用状況を覗き見する
まずは、今誰がどれくらいメモリを消費しているのかを確認しましょう。
RedisのCLIから次のコマンドを叩きます。
現在接続しているクライアントの一覧と、バッファの使用量(omem)を確認する
redis-cli client list
出力結果の中に、次のような項目が見つかります。
> `omem=1048576` (出力バッファで消費しているバイト数。この場合は約1MB)
もし、この `omem` が異常に膨らんでいるクライアントがいたら、そいつが「メモリ泥棒」の犯人です。アプリ側の処理速度やネットワークにボトルネックがないか調査しましょう。
アプローチ2: 用途に合わせてリミットをチューニングする
デフォルトの設定のままで安全なシステムも多いですが、扱うデータが大きい場合(例えば、巨大なJSONや画像をPub/Subで流すような狂った設計をしている場合など)は、制限値を調整する必要があります。
`redis.conf` を次のように書き換えてみましょう。
pubsubの制限を少し厳しめに、ハード50MB、ソフト20MB(30秒継続でアウト)に変更
client-output-buffer-limit pubsub 50mb 20mb 30
※ただし、制限を緩くしすぎると今度はRedis全体がOOMで落ちるリスクが増えるため、「アプリがどれくらい耐えられるか」の逆算が必須です。
アプローチ3: アプリ側の設計を見直す
根本的な解決は、「Redisに重い仕事をさせすぎない、溜め込ませない」ことです。
- Pub/Subには軽量なイベント通知だけを流し、実データはRDBやS3から取得させる。
- アプリ側のワーカースレッドを増やして、メッセージの処理スループットを上げる。
—
おわりに
いかがでしたでしょうか?
`client-output-buffer-limit` は、一見すると地味な設定項目ですが、「Redisの安定稼働を守るための最後の砦」とも言える非常に重要な機能です。
「データを受け取らないなら、縁を切る!」というRedisの冷徹とも言える優しさを知っていれば、万が一のメモリ枯渇インシデントにも冷静に対処できるようになります。
ここをクリアしたあなたなら、もうRedisのメモリ管理で迷うことはありません。自信を持って、日々の開発や運用に臨んでくださいね!それでは、また次の冒険でお会いしましょう。
コメント