Elevare DigitalのAIエージェントがアイドル状態になりました。原因は、新しく追加されたPostgreSQLの行レベルセキュリティ(RLS)ポリシーがすべてのジョブ行をフィルタリングしてしまい、キューが空であるように見えたためです。このミスは、ジョブが溜まり続けてしまうまで気づかれず、チームはオーケストレーターの空キュー検知方法の再設計を余儀なくされました。

隠れたブラインドスポット

Elevareの自律型AIシステムであるARIAは、保留中のジョブを確認するためにPostgreSQLのテーブルをポーリングしています。クエリは成功し、0行が返されたため、エージェントはスリープ状態に入りました。実際にはテーブルは満杯でした。RLSポリシーによって、SELECTアクセスが特定のユーザーセットに制限されていたのです。オーケストレーターは、バイパス権限を持たないサービスロールで接続していたため、データベースは結果セットからすべての行を静かに削除しました。PostgreSQLは、フィルタリングされた読み取りを空のテーブルと同じように扱うため、エラー、警告、または失敗コードは一切表示されませんでした。アイドル状態のエージェントからの正常なハートビートは、何かがおかしいという兆候を全く示しませんでした。

RLSがいかにして満杯のキューを沈黙に変えたか

RLSは、SELECT実行時に各行に述語(predicate)を追加します。述語が偽(false)の場合、その行は結果から消えます。クライアントにはポリシーを満たす行のみが表示され、行が隠されていることは決して分かりません。キューワーカーにとって、空の結果セットは、本当に空のキューと全く同じに見えます。オーケストレーターは「行がない = 仕事がない」と判断し、裏側でジョブが蓄積している間にアイドルループに入ってしまいました。

チームは、個々のユーザーに読み取り範囲を限定するためのポリシーが、意図せずサービスロール自体にも適用されていたことを発見しました。そのロールに特別な「bypass RLS」属性がなかったため、オーケストレーターが発行するすべてのクエリにポリシーが適用されてしまったのです。これは、セキュリティとオブザーバビリティ(可観測性)の典型的なトレードオフを示しています。RLSは権限のないユーザーからデータを保護しますが、可視性に依存するシステムコンポーネントにとって有用な失敗シグナルをも奪ってしまうのです。

カナリアチェック・パターン

静かな空の結果に依存する状態を打破するため、Elevareは「カナリア」チェックを追加しました。新しいフローは以下の通りです:

  1. 保留中のジョブのテーブルをクエリする。
  2. 行が返された場合は、従来通り処理する。
  3. 結果が空の場合は、常に存在しなければならない専用のカナリア行に対して2番目のクエリを発行する。
  4. カナリアクエリが期待通りの行を返した場合、キューは本当に空である。アイドル状態のハートビートをログに記録する。
  5. カナリアクエリも何も返さない場合、エージェントは「盲目」状態である。直ちにアラートを発生させる。

これにより、オーケストレーターは3つの状態を区別できるようになりました:

  • Jobs found – 通常の処理。
  • No jobs, canary OK – 真のアイドル期間。
  • No jobs, canary failed – RLSによる隠れたブロック。アラートをトリガー。

カナリアテーブルは、決して変化しない単一の行です。セットアップには約1時間しかかかりませんでしたが、これにより、ある種の「静かな失敗」を完全に排除することができました。

チームがすべきこと

PostgreSQLまたはその上で構築されたホストサービス(Supabaseなど)に対してキューワーカーを実行している場合は、以下の手順に従ってください:

  • 「bypass RLS」フラグを持つサービスロールの認証情報を使用する。これにより、ユーザーレベルのポリシーに関係なく、システムコンポーネントがすべての行を確認できるようになります。
  • RLSポリシーを監査し、サービスロールに対するバイパス権限の欠落がないか確認する。エンドユーザーには正しく見えるポリシーでも、意図せず内部サービスをトラップしてしまう可能性があります。
  • カナリアテーブルを追加する(または、常に存在する同等の行を用意する)ことで、ワーカーのアイドルロジックにカナリアチェックを組み込む。追加のクエリは低コストであり、明確なセーフティネットを提供します。

トレードオフ

RLSは、きめ細かなデータアクセスを強制するための強力なツールであり続けています。アプリケーションレベルのフィルタをコードベース全体に散りばめることなく、偶発的なデータ漏洩を防ぎ、マルチテナント・アーキテクチャをサポートします。欠点は、「行がない」という単純なシグナルを「何もすることがない」という意味として期待しているコンポーネントに対して、失敗を隠してしまう可能性があることです。カナリアパターンはRLSを弱めるものではありません。オブザーバビリティを回復させる軽量な検証ステップを追加するものなのです。

まとめ

隠れたRLSポリシーは、忙しいキューを静かな行き止まりに変え、仕事が溜まっている間もAIエージェントをアイドル状態にしてしまう可能性があります。サービスロールに適切なバイパス権限を付与し、空のキューの読み取りには必ずカナリアチェックを組み合わせるようにしてください。そうすることで、チームは自律型ワーカーの正確性を保ち、コストのかかるブラインドスポットを回避できます。