PostgreSQL アダプタでバイト長基準のチャネル識別子ハッシュ化

rails/rails

Action Cable の PostgreSQL サブスクリプションアダプタは、チャネル名が PostgreSQL の識別子上限(63 バイト)を超える場合に SHA‑1 ハッシュで置き換えるよう変更されました。これにより、マルチバイト文字を含む長いチャネル名でも通知が正しく配信され、サイレントなメッセージロスや誤配信が防止されます。

背景

PostgreSQL の識別子は 63 バイト までしか許容されず、超過した場合は自動的に切り詰められます(NAMEDATALEN‑1)。従来の実装は String#size(文字数)で長さチェックを行っていたため、マルチバイト文字列が 63 文字以内でもバイト数が 63 バイトを超えるケースでハッシュ化が行われませんでした。この結果、LISTEN/NOTIFY が切り詰められた名前で通知を返し、登録時のキーと不一致になることで 2 種類の障害 が発生しました。

  • サイレントメッセージロス: 長いマルチバイトチャネルの購読者はハッシュ化されずに登録される一方、通知は切り詰め後のキーで配信されるため、該当チャネルの購読者が全く受信できません。
  • クロスストリーム配信: 別のチャネルが切り詰め後の同一キーで登録されている場合、意図しないチャネルへ通知が漏れることがあります。

上記の問題は、#28751 で報告された Action Cable のストリーム識別子が PostgreSQL の上限を超えるケースに類似しています。実際に Japanese 文字列(例: "あ" * 30 + "X")でテストすると、String#size ではハッシュ化されずに 91 バイトの名前が LISTEN され、PostgreSQL が自動で 63 バイトに切り詰めることが確認されています。

技術的な変更

actioncable/lib/action_cable/subscription_adapter/postgresql.rbchannel_identifier メソッドが次のように修正されました。

-def channel_identifier(channel)
-  channel.size > 63 ? OpenSSL::Digest::SHA1.hexdigest(channel) : channel
-end
+def channel_identifier(channel)
+  # PostgreSQL identifiers are limited to NAMEDATALEN-1 (63) *bytes*, not characters.
+  # Hash on byte length so multibyte channel names that exceed the limit stay within it.
+  channel.bytesize > 63 ? OpenSSL::Digest::SHA1.hexdigest(channel) : channel
+end

主な変更点は channel.sizechannel.bytesize の置き換えです。コメントが追加され、バイト単位で判定すべき根拠が明示されています。この修正により、マルチバイト文字列でもバイト数が 63 を超えると即座に SHA‑1 ハッシュ(40 バイト)へ変換され、PostgreSQL の上限を超えることはなくなります。ASCII 名はバイト数と文字数が一致するため、従来通りの動作が保持されます。

テストコードも actioncable/test/subscription_adapter/postgresql_test.rbtest_long_multibyte_identifiers が追加され、実際の LISTEN/NOTIFY を用いたエンドツーエンド検証が行われます。テストは長いマルチバイトチャネルと、その 63 バイトに切り詰められた短いチャネルの両方を購読し、長いチャネルへのブロードキャストが正しく自身のキューに届き、短いチャネルへは漏れないことを確認します。これにより、バイト長基準への変更が期待通り機能することが自動的に保証されます。

設計判断

バイト長でのハッシュ化判定 を採用した理由は、PostgreSQL がバイト単位で識別子を評価する点に完全に合わせることが最小限の侵入で済むからです。元々存在したハッシュガードは channel.size > 63 という文字数チェックでしたが、バイト単位へ置き換えるだけで既存の API(channel_identifier の公開インタフェース)は変わりません。したがって、外部からの呼び出しや既存の設定に対して 後方互換性が保たれ ます。

また、テストの追加 によって回帰防止が明示的に組み込まれました。バイト長判定はコード変更だけでなく、実際の PostgreSQL 接続を通す統合テストで検証されるため、将来的なリファクタリングや別アダプタへの流用時にも同様の問題が再発しにくくなります。これらの判断は、バグ修正と信頼性向上を同時に実現する、実務的かつ保守的なアプローチと言えます。

まとめ

Action Cable PostgreSQL アダプタは、String#bytesize による長さ判定へ置き換えることで、マルチバイトチャネル名のハッシュ化を正しく行うようになり、PostgreSQL の識別子切り詰めによるメッセージロスと誤配信を防止しました。最小限のコード変更と新規テストの追加により、既存機能への影響を抑えつつ信頼性が向上しています。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
44254ea0

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文、背景、技術的な変更、設計判断(任意)およびまとめが明確に分かれており、総論→各論→結論の流れが保たれています。

カスタムMarkdown構文 ⚠ WARNING

シンタックスハイライト・GitHubリンク記法の正確性

コードブロックのファイル名付きシンタックスハイライトは正しい形式です。ただし、PRリンクは `[PR #57514](URL)` ではなく、仕様通りの `[#57514](URL)` の形式にすべきです。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

対象はRails/Action Cable に詳しいエンジニアであり、専門用語が中心なので適切です。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションは総論・各論・結論の構成で、段落はトピックセンテンスで始まり1トピックに絞られ、長さも適切です。

Diff内容との照合 ⚠ WARNING

コードブロックとDiff内容の一致

記事のコードブロックはDiffの変更点を正しく示していますが、コメント行がDiff全体から一部省略されています(機能に影響はありません)。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

使用されている用語(bytesize、SHA‑1、channel_identifier など)はPRと一致し、誤用はありません。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

技術的な説明はPRの内容と整合しており、因果関係も論理的です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内の主張はすべてPRの情報で裏付けられており、捏造や推測はありません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

63 バイト、91 バイト、30文字+"X" などの数値はPRと一致しています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトルはPRの内容(バイトサイズでハッシュ化)を正確に表現しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

LTS・リリース日程等の外部知識は含まれていません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現は使用されておらず、PRと食い違いもありません。