概要
アプリケーションを高速化するためにローカルコピーを追加する前に、そもそもなぜ遅いのかを考える必要があります。
特にエージェントがワーキングセットを頻繁に変更する環境では、ローカル同期によって複雑さが増します。高速な NVMe を備えた中央の Postgres を使えば、よりシンプルな構成から始められます。直接クエリを実行し、レプリカを使って読み取りをスケールできます。
アプリケーションには高速な読み取りと書き込みが不可欠であり、それがなければ実用的ではありません。メインの Postgres インスタンスで十分なパフォーマンスが得られない場合、既存の解決策の 1 つは、ワーキングセットの一部をアプリケーションの近くに同期することです。読み取りがローカルになり、中央のデータベースへの負荷が軽減されます。これは効果的である一方で、管理すべきデータレイヤーがもう 1 つ増えるというトレードオフがあり、書き込みは依然として中央の Postgres に依存します。
本ブログでは、ローカルデータの同期がどのように機能するのか、AI エージェントによってローカルのワーキングセットの予測がなぜ難しくなるのか、そして最初から高速な中央 Postgres インスタンスを採用することでアーキテクチャをシンプルにできるのかどうかを探ります。
データベースのボトルネックから考える
多くのマネージド Postgres サービスは、ネットワーク接続ストレージ上にデータを保持しています。たとえば Amazon RDS for PostgreSQL は、ネットワーク経由で接続される EBS ボリュームにデータを保存しており、各ボリュームには独自の IOPS とスループットの制限があります。Neon などの他のサービスはさらに進んで、コンピュートとストレージを完全に分離しています。こうした設計により、コンピュートノードの喪失に耐えうるストレージと柔軟なプロビジョニングが実現しますが、キャッシュをミスした読み取りはすべてネットワークを経由しなければなりません。
これは、ワークロードが I/O バウンドになった途端に重大な問題となります。その段階に達すると、コンピュートを追加してもデータベースは高速化しません。読み取りはストレージからページを取得する必要があり、書き込みは依然として WAL を生成し、VACUUM などのバックグラウンド処理が同じ I/O バジェットを奪い合います。同時実行数が増えるにつれてクエリは遅くなり、テールレイテンシが増大します。
マネージド Postgres 上のアプリケーションで、CPU やメモリに問題がないように見えても利用規模の拡大に伴って処理速度が低下することが多いのはこのためです。そのレイテンシを回避するためにローカルデータレイヤーを追加する前に、真のボトルネックがデータベース配下のストレージにないか確認してください。
データの 2 つ目のコピーは本当に必要か?
メインデータベースが遅くなったときの通常の解決策は、その手前にレイヤーを追加することです。長年にわたり、それは Redis や Valkey のようなキャッシュを意味していました。最近では、同期エンジンがこの考え方をさらに進め、アプリケーションが必要とするデータの同期コピーをアプリケーション自体の内部に保持するようになっています。同期レイヤーはデータベースのロジカルレプリケーションストリームをサブスクライブし、変更を宣言されたサブセットに絞り込んで、そのサブセットをローカルストア内にマテリアライズドされた状態に保ちます。このストアはメモリ内、クライアント上の組み込みデータベース、あるいはエッジノードのキャッシュ内に配置できます。仕組みはどれも同じです。
開発者は事前にサブセットを宣言します。対象のテーブル、行(通常はテナントまたはユーザーのカラムによるフィルター)、そしてカラムです。マルチテナントアプリケーションでは、1 つのクライアントのサブセットは通常、そのテナントの行のみとなります。同じサブセットを宣言するすべてのクライアントは同じ変更ストリームを受信するため、多くのクライアントが同じデータを必要とする場合、同期レイヤーのコストは共有されます。
サブセットが配置されると、その内部に含まれるすべての読み取りはローカルで完結します。画面、フォーム、ダッシュボードはローカルストアにクエリを実行し、それらの読み取りパスから Postgres へのラウンドトリップは解消されます。サブセット外の読み取りは引き続きデータベースに送られるか、事前にサブセットを広げておく必要があります。
アプリケーションが得たものは、独自のライフサイクルを持つ、データの一部の 2 つ目のコピーです。何がサブセットに含まれるのか、いつ変更されるのか、クライアントがそれをどのくらいの期間保持するのか、そしてスキーマが変更されたときにどう対応するのかを誰かが決めなければなりません。これは、オフラインで動作するアプリケーションや信頼性の低いネットワーク上のアプリケーション、あるいは多数のクライアントが同一の安定したワーキングセットを読み取るアプリケーションにとっては価値があります。しかし、接続環境の良いオンラインデータベースからの高速な読み取りのみが要件である場合には、同じだけの価値があるとは言い切れません。クエリが予測可能であればサブセットの宣言は容易ですが、それだけでローカルコピーが必要になるわけではありません。
ほとんどのアプリケーションには書き込みも必要
同期レイヤーは読み取りパスのみを処理するため、アプリケーションは通常自前の API を通じて、中央の Postgres への独立した書き込みパスを維持する必要があります。トランザクションがコミットされると、変更はロジカルレプリケーション経由で同期レイヤーを通り、影響を受けた行を含むサブセットを宣言しているすべてのローカルストアへと送り返されます。したがって、書き込みによってクライアント上には 2 つの独立したイベントが発生します。API レスポンスと、その直後に届くローカルデータセットへのコミット済み変更の反映です。
このギャップを隠すために、アプリケーションは楽観的状態を保持します。サーバーによる確認を待たずに、変更がローカルビューに即座に適用されます。その後アプリケーションは、その暫定的な状態と、同期ストリーム経由で届くデータ(同じ行を変更した他のクライアントからの書き込みだけでなく、自身によるコミット済みバージョンの書き込みも含まれます)とを調整(リコンサイル)しなければなりません。ストリームに自身の書き込みが現れると、暫定コピーは破棄されます。書き込みが拒否された場合、暫定コピーをロールバックしてビューを修正する必要があります。
全体を通して Postgres が信頼できる唯一の情報源(Authoritative source)であり続け、トランザクションによって同時書き込みの順序付けが行われる場所であることに変わりはありません。Postgres から直接コミット済み状態を読み取るアプリケーションは、その順序付けの恩恵をそのまま得られます。ローカルコピーの追いつきを待つことも、同じ行の 2 つのバージョンを保持することもありません。ローカルコピーは高速な読み取りを提供する一方で、アプリケーション自身が解決しなければならない調整という課題をもたらします。保留中のローカル変更、非同期に到着するコミット済みデータ、そしてユーザーに見えるビューのすべてを一致させなければなりません。
エージェントによってワーキングセットの予測が難しくなる
従来のアプリケーションは、人間が予測可能なパターンで操作するため、限られたワーキングセットで動作します。商品ページは商品情報を読み込み、請求ページは特定の 1 つのアカウントを読み込み、検索ページは所定のフィールドセットをクエリします。AI エージェントの動作はこれとは異なり、実行時に提供されるプロンプトやコンテキストに基づいて、ある商品を読み込み、それを購入した顧客を特定し、類似商品を検索するといった処理をすべて行う可能性があります。
パラダイムは今や変化しています。エージェントを伴う場合、開発者はアプリケーションのワーキングセットを常に予測できるとは限らず、その境界を正確に定義することも困難です。サブセットの宣言自体は引き続き可能ですが、ワーキングセットの境界は、エージェントが取りうるクエリパスに対応できる十分な広さを持たせる必要があります。オンデマンド読み込みやプログレッシブ読み込みを使用すれば、サブセット全体を事前に同期することは避けられますが、まだマテリアライズドされていないデータは、ローカルでクエリ可能になる前にリモートリクエストを必要とします。
これにより、いくつかのトレードオフが生じます。
- 広い範囲を同期し、エージェントが使用しない可能性のあるデータまで転送する。
- プログレッシブに読み込み、同期処理を再び読み取りパスに組み込む。
- タスク固有のサブセットを作成し、そのライフサイクルを個別に管理する。
同期レイヤーによるファンアウトが最も効果を発揮するのは、多くのコンシューマーが同一のサブセットを再利用する場合です。エージェントがそれぞれ異なり、絶えず変化するデータのサブセットを必要とする場合、ローカルコピー間での再利用性は低下し、同期処理のコストを分散できる機会も少なくなります。データセットが流動的で増大を続け、ローカルに同期すべき新しいデータが絶えず発生する場合、このアーキテクチャは依然として適切であるかという疑問が生じます。
Postgres が最初から十分に高速だった場合はどうなるか?
ClickHouse Managed Postgres は、データベースのコンピュートに物理的に接続された NVMe ストレージ上で Postgres を実行します。ネットワーク接続ストレージと比較してディスクレイテンシが短縮され、読み取り、書き込み、WAL アクティビティ、メンテナンスにおいて大幅に高い IOPS が提供されます。その結果、ネットワーク接続型の Postgres ソリューションよりもクエリレイテンシが低く、クエリスループットが高くなります。
接続環境が良好なアプリケーションにおいて、中央の Postgres インスタンスへのクエリ実行でレイテンシとスループットの要件をすでに満たせている場合、同期されたローカルデータセットを維持することは、パフォーマンスの向上よりも複雑さの増大を招く可能性があります。
これはサーバーサイドのエージェントにとっても重要です。エージェントは通常バックエンドサービスの近くで動作し、すでに推論サービスや他のツールへのネットワークアクセスに依存しています。また、1 つのタスクのためだけに存在する可能性もあり、ローカルデータセットを同期するコストに見合うほど再利用することが難しい場合もあります。エージェントのワーキングセットが絶えず変化する場合、高速で信頼性の高いデータベースに直接クエリを実行する方がシンプルになります。
- 既存の API 経由で認可された任意の運用データにクエリを実行できる
- 同一の信頼できるシステムに対して読み取りと書き込みを行える
- 即時の一貫性が求められる場合に中央のインスタンスから読み取れる
- エージェントが決してクエリしない可能性のあるデータの同期を回避できる
アーキテクチャは以下のようになります。
ClickHouse Managed Postgres はリードレプリカをサポートしており、すべてのエージェントに対してローカルデータセットを作成することなく、読み取り負荷の高いエージェントのワークロードを水平方向にスケールできます。
NVMe は非効率なクエリを解決するものではありません。コネクションプーリング、クエリの最適化、同時実行制御は依然として重要です。ストレージの高速化によって中央データベースのキャパシティは増加しますが、すべての制限がなくなるわけではありません。アーキテクチャの観点からの利点は、アプリケーションレベルでデータの追加コピーを実行する必要が生じる前に、より多くの処理を通常の Postgres の処理パスに残せることです。
マルチ AZ の耐久性が必要な場合はどうなるか?
データベースが真に必要とするコピーは 1 つだけであり、それはアプリケーションレベルのものではありません。ローカル NVMe が高速なのはコンピュートの直近にあるためですが、これはコンピュートの喪失自体には単独で耐えられないことも意味します。ClickHouse Managed Postgres は、ディスクの複製ではなくデータベースレイヤーでのレプリケーションという、Postgres が従来行ってきた方法でこれに対処します。
サービスは、アベイラビリティゾーンをまたいで最大 2 台のスタンバイを配置して実行できます。スタンバイが 2 台の場合、レプリケーションは同期的です。プライマリは書き込みを確認する前に少なくとも 1 台のスタンバイがそれを承認するのを待つため、フェイルオーバーでコミット済みトランザクションが失われることはありません。スタンバイが 1 台の場合、レプリケーションは非同期的であり、書き込みレイテンシを短縮する代わりに最近のトランザクションのごくわずかな時間枠がトレードオフとなります。どちらの場合も、フェイルオーバーによってスタンバイが自動的に昇格します。スタンバイとは独立して、WAL はオブジェクトストレージに継続的にアーカイブされ、ベースバックアップによってポイントインタイムリカバリがサポートされます。
PostgresBench の HA の回では、これらの保証にかかるコストを測定しました。2 台の同期スタンバイ構成において、ClickHouse Managed Postgres は、同じ 2 台のスタンバイによるクォーラム構成を実行する Amazon RDS Multi-AZ クラスターの約 4 倍のスループットを達成しました。したがって、耐久性を確保したからといって、前のセクションで述べたパフォーマンスが損なわれることはありません。
本記事が投げかけている問いに関して重要なのは、それらのコピーがどこに存在するのかという点です。コピーはデータベースの配下に配置され、サービスによって管理されるため、アプリケーションがそれらを意識したり調整したりする必要はありません。
ClickHouse Managed Postgres を使ってみる
ローカル NVMe 上でお使いのワークロードがどのように動作するかをご確認ください。ClickHouse Cloud にサインアップして、最初のマネージド Postgres サービスをプロビジョニングしましょう。
サインアップPostgres を超えて分析をスケールさせる
データセットが拡大し、より多くのエージェントがアクセスするようになるにつれて、ワークロードには大量のデータを対象とする履歴スキャン、集計、比較がさらに含まれるようになります。システムに高速な NVMe ストレージが備わっていても、Postgres は依然として行指向のトランザクションデータベースであるため、こうしたクエリには ClickHouse のほうが適しています。
ClickHouse Managed Postgres は、これら 2 つのエンジンを単一のデータレイヤーに統合します。WalShadow は、物理 WAL を使用して特定の Postgres テーブルを ClickHouse に直接レプリケーションします。この機能は現在プライベートプレビュー中であり、コミットから可視化されるまでのレイテンシは ClickHouse のベンチマークで示されているように 約 200 ms です。レプリケーション処理は非同期のまま維持されます。
pg_clickhouse 拡張機能 により、Postgres 経由で ClickHouse テーブルへのアクセスが可能になり、サポートされている分析クエリが ClickHouse にプッシュダウンされます。アプリケーションやエージェントは Postgres への接続を維持したまま、トランザクション処理と分析目的で異なるエンジンを使い分けることができます。これを機能させるには、外部テーブルの設定とプッシュダウンのサポートが必要であり、すべてのクエリの自動ルーティングが提供されるわけではありません。
レプリケーションは依然として介在しますが、分析用のコピーが列指向エンジン上で大規模なスキャンや集計を実行することで、データレイヤーにおける共有ワークロードを処理します。各エージェントのランタイムにそうしたデータセットを分散させることなく、分析キャパシティを単独で増強できます。
最もシンプルなアーキテクチャから始める
ローカル同期は、アプリケーションがオフラインアクセスを必要とする場合や、ネットワーク接続が限られている場合、あるいは予測可能なワーキングセットを繰り返し読み取る場合に優れたソリューションです。通常のアプリケーションでは、データの別のコピーを導入する前に、中央のデータベースが必要なレイテンシとスループットを満たせるかどうかを測定することが第一歩となります。
AI エージェントの登場により、この判断はさらに重要になります。エージェントのクエリはタスクごとに変化する可能性があるため、ローカルデータセットの定義が難しくなるからです。ClickHouse Managed Postgres は、こうした進化するニーズに対応するための基盤を提供します。トランザクションの読み書きは Postgres に保持し、鮮度の要件が許す場所にはリードレプリカを追加します。そして、アプリケーションの利用規模が Postgres を超えた段階で、分析ワークロードを ClickHouse にオフロードします。
ClickHouse Managed Postgres でシンプルに始め、ClickHouse とともに成長させましょう。



