世界を動かす技術を、日本語で。

カフカは速い – ポストグレスを使います

概要

  • 技術界隈には「バズワード追求派」と「常識重視派」の2つの陣営が存在
  • 最近は「Small Data」ムーブメントと「Postgresルネサンス」が常識派を後押し
  • Postgresは多くの用途でシンプルかつ十分な性能を発揮
  • Kafkaのような高度な分散システムが不要なケースが多い現実
  • 本記事ではPostgresのpub/sub・キュー用途でのスケーラビリティをベンチマーク

技術界隈の2つの陣営

  • バズワード追求派 :流行りの技術やキーワード(例:real-time, cloud-native, AI-powered)を深く考えずに採用

  • 履歴書駆動設計 (Resume-driven design)の蔓延

  • コンサルタントやシステム設計面接 が「Googleスケール」な技術選定を推奨

  • キャリア評価 も「最新スタック」へのリプレースが重視される傾向

  • 常識重視派 :本質的な要件から技術選定を行い、複雑化や過剰設計を避ける

  • ベンダーの主張やマーケティング に懐疑的な姿勢

  • シンプルで堅牢な設計 を優先

最近のトレンド

  • Small Dataムーブメント

    • 多くの組織のデータ量は想定より小規模
    • 最新ハードウェア (例:128コア/4TB RAMサーバ)が容易に利用可能
    • 「それで十分」なケースが増加
  • Postgresルネサンス

    • Postgres一択 (Just Use Postgres)の風潮
    • 既存の多用途DBとしての進化(例:全文検索、JSON、UNLOGGED TABLE、ベクトルDB対応)
    • Elasticsearch、MongoDB、Redis、Snowflake、Kafka など専用システムの8割以上の用途を2割の工数でカバー(パレートの法則)

PostgresとKafkaの比較

  • Postgres :シンプル、スケーラブル、信頼性高、長年の実績
  • Kafka :より大規模なスケール対応、堅牢なpub/sub基盤
  • 小規模〜中規模用途ではPostgresで十分 なケースが多い
  • 「最適な技術選択」は技術的観点だけでなく実用性重視が重要

本記事の目的

  • Postgresのpub/sub用途でのスケール限界のベンチマーク
  • Postgresのキュー用途でのスケール限界のベンチマーク
  • どんな場合にPostgresが適しているかの考察
  • 詳細な技術評価や網羅的な検証ではなく、実用的なデータポイント提示が目的

ベンチマーク結果(要約)

  • Pub/Sub

    • シングルノードc7i.xlarge:書込4.8MiB/s、読込24.6MiB/s、最大60msレイテンシ
    • 3ノードレプリケーション:書込4.9MiB/s、読込24.5MiB/s、最大186msレイテンシ
    • シングルノードc7i.24xlarge:書込238MiB/s、読込1.16GiB/s、最大853msレイテンシ
  • Queue

    • シングルノードc7i.xlarge:合計2.81MiB/s、最大17.7msレイテンシ
    • 3ノードレプリケーション:合計2.34MiB/s、最大920msレイテンシ(レプリケーション遅延)
    • シングルノードc7i.24xlarge:合計19.7MiB/s、最大930msレイテンシ

Postgresのpub/sub・キュー構成

  • Queue :ポイント・ツー・ポイント通信、1回消費で即削除、厳密な順序保証なし
  • Pub/Sub :1対多通信、ディスク保存で読者と書込を分離、厳密な順序保証あり
  • Kafka :Log構造を用いた標準的pub/subシステム、Postgresで再現可能

実装概要

  • 書込

    • 各トピックパーティションごとに専用テーブル
    • log_counterテーブル でオフセット管理
    • バッチ書込+オフセット更新 をトランザクションで一括実行
  • 読込

    • consumer_offsetsテーブル で各コンシューマグループの進捗管理
    • トランザクション内でバッチ取得+オフセット更新
    • Kafka同様のat-least-once/at-most-once処理
  • NOTIFY/LISTEN は最適化用途で完全な信頼不可、基本はポーリング設計

結論・考察

  • Postgresは多くのpub/sub・キュー用途で十分な性能
  • Kafkaのような分散システムは本当に必要な場合のみ選択
  • 技術選定は「実用性」「シンプルさ」「運用容易性」を重視
  • 小規模・中規模用途ではPostgresの活用が合理的

参考文献・関連リンク

Hackerたちの意見

めっちゃ共感するわ(笑)。データエンジニアリングやってるけど、最初の段落はいつも自分のことみたい。かっこいい技術がたくさんあるけど(時系列データベース、ベクトルデータベース、AzureのSynapseとか「レイクハウス」とか)、ほとんどは特別なケース用なんだよね。無駄だとは言わないけど、そういうのが転がってるのを見ると、実際のエンジニアリングの必要性よりも「雰囲気」で置かれた可能性が高い。PostgresはOpenAIにとって十分だし、君にも十分だと思うよ。

Postgresを何にでも使うアプローチには注意が必要だよ。テーブルや行のロックの仕組みや、保証されるシリアル化レベルは多くの人にはすぐには分からないし、パフォーマンスに敏感なワークロードには深刻なボトルネックになることもある。自分は数十年の間、Postgresを使ってきて幸せだよ。Postgresはたくさんのことができる!でも、何事も格言に頼りすぎない方がいいよ。

確かに、Postgresのパフォーマンスは大きな問題になりうるね。特にトラフィックが急増したときに、垂直スケーリングが厄介になることもある。Kafkaに使うのは、Kafkaの素晴らしい使い方の一つであるトラフィックバーストへの対処を誤解していることになる。突然、Postgresサーバーが圧倒されちゃって、Kafkaサーバーは問題ないのに。

自分の戦略は、まずPostgresを使うこと。アイデアを形にして、Postgresがボトルネックになったら切り替える。それが多くの場合、ボトルネックにならないんだよね。

誰かが「Postgresを使えばいい」と言ったとき、その人はデータとキューで同じインスタンスを使ってるのかな?

100% Postgresは永続的な置き換えを保証するものではないよ。もっと柔軟性があって、初めから速度も上がるシンプルなスタックの出発点としてよく使われる。Postgresから始めることでボトルネックが見えてきて、そこから最適化できるんだ。Postgresを調整するか、リソースを見直すか、Kafkaに飛び込むことも考えてみて。

これはどんなデータストレージにも当てはまるよ。並行性モデルや前提条件を理解して、ボトルネックがどこで発生するかを知っておく必要がある。リレーショナルデータベースの中でも、かなりの違いがあるからね。

自分にとってKafkaのキラーフィーチャーは、各コンシューマーごとにオフセットを独立して設定できることだった。うちの会社では、ほとんどのトピックを複数のアプリケーションやチームが消費する必要があるから、この機能は必須なんだ。オフセットをプログラム的に前後に動かせる能力も、何度も助けられたよ。Postgresはキューに対してこの機能をサポートしてるの?

各コンシューマが自分のオフセットを使うだけの話じゃない?キューのテーブルが順番通りか時間でインデックス付けされてれば、コンシューマは小さい/早いキーを提供するだけでオフセットを達成できるんじゃないかな?(何か見落としてる?)

記事は基本的に、大量のスループットが必要ないなら、Kafkaは多分必要ないって言ってるね。(私の解釈では)オフセットも必要ないかもしれないし、マルチスレッドのサポートも必要ないから、複数のスレッドも必要ないってことだと思う。PGがキュー管理にどんなネイティブサポートを持ってるかはわからないけど、基本的に「見たらタスクを殺す」ってのが十分だって前提がある。スクリプトを書くことと実行することのシンプルさは、Kafkaの開発、インフラ、DevOpsコストをはるかに上回るよ。でも、15秒で何かをする必要があるのか、5分でいいのか、1時間でいいのかはビジネスの判断だし、持ってるワークロードの成長パターンを理解することも大事だよね。

少なくとも、Postgresの上にかなり厚いアプリケーションレイヤーが必要になると思う。それだと、結局別のメッセージングシステムを作っただけになりそう。みんながその一つのテーブルを使うことに同意するような小さなチームじゃない限り、トランザクションの管理やcronジョブの保持、YOLOクラスタリングとかちゃんとやらないとね。パフォーマンスは、PostgresよりKafkaを選ぶ理由の中で多分最後の方だと思うよ。

APIをPostgresで公開するのは、他の開発者たちが使っているのと同じようにやればいいよ。それで終わり。キューの実装はいくつかあって、目的を達成する確率を上げることができるよ。https://github.com/dhamaniasad/awesome-postgres

「ユニークで単調に増加するオフセット番号」をどう実装するの?シーケンスを使った単純なアプローチ(またはシーケンスを自動的に使うシリアルタイプ)はうまくいかないよ。トランザクション「1」が「123」を取得して、トランザクション「2」が「124」を取得する。トランザクション「2」がコミットすると、テーブルには「122」と「124」の行があって、リーダーはそれを処理し始める。でもトランザクション「1」が「123」でコミットすると、リーダーはすでに「124」を過ぎちゃってる。トランザクション「1」はいろんな理由でコミットされないかもしれないし(例えば、クライアントが電源を切られたとか)、ずっと「123」を待ってるのは無理だよ。このアプローチには通知が役立つかもしれないけど、そうすると古いリーダーを再起動できなくなっちゃうし、単調な番号は全く必要ないよね。

ユニークな単調増加オフセット番号 すべての購読者の問題を解決できるユニオンって、ちょっと白鯨的なものじゃない?私の知る限り、Kafkaでも完全に水-tightじゃないよ。

コミット時にだけシーケンスをインクリメントするDEFERRABLE INITIALLY DEFERREDトリガーはどう?

log_counterテーブルがこれを追跡してるよ。シーケンスを使ったナイーブな解決策がうまくいかないのは、君が言った理由そのものだね。

難しい問題だね。DDIAを読むことをおすすめするよ。これについて詳しく書いてあるから。https://www.oreilly.com/library/view/designing-data-intensiv... ラムポートクロックを使えば、分散モノトニック数列を生成できるよ。https://en.wikipedia.org/wiki/Lamport_timestamp ウィキペディアの説明はその本ほど詳しくないけどね。分散システムのパズルがこれで終わるわけじゃないけど、かなり進むことができるよ。ベクトルクロックについても見てみて。https://en.wikipedia.org/wiki/Vector_clock 編集: このスライドを見つけたんだけど、問題解決のための良い入門書になってるよ。70ページ以降の「論理時間」をチェックしてみて。https://ia904606.us.archive.org/32/items/distributed-systems...

僕が関わってるシステムにはこの問題があるんだけど、経験上の短い答えは、スケールが大きくなると、順序の問題を完全には防げなくて、アーキテクチャや問題の枠組みにレジリエンスを組み込む必要があるってこと。整合性を保つためには、遅延を支払うことになることが多いよ。

陣営は間違ってる。極端な意見がある。1. 新しい技術を常に取り入れる人たち、動機は何でもいい、2. 一つのことを学んだら、もう何も学ばないと決めた人。もちろん、実際にはどちらの極端にも存在する人はいないけど、どちらかに近づくほど、実用的でなくなる可能性が高いよ。

私は第三の極だね:3. 現在あるものは全部クソで、新しいものも何らかの未知の理由でクソになるだろう。

これがまさにそれだね。自分の対抗馬はElasticsearchの代替品で、PGは逆インデックスを持ってるから。PGの使いやすさや調整のしやすさはESに比べてひどいよ。確かに検索はできるけど、その検索を構築したり維持したりするのには関わりたくないな。

この人、実際にKafkaのベンチマーク取ったの?96 vCPUの設定で得られる結果は、4 vCPUの設定でも達成できるよ。PGの結果はめちゃくちゃ遅いし。Kafkaが必要ないなら使わない方がいいけど、5k msg/sのPG設定で何か特別なことをしてるみたいに振る舞わないでほしいな。

実際、適切に設定されたKafkaクラスターは、最小限のハードウェアでもCPUやディスクのボトルネックに達する前にネットワークリンクを飽和させるよ。

Kafkaが提供するものが必要ないなら、使わない方がいい。 これはまさに著者が言いたいことだね。

その通り。昨日、誰かが自分のノートパソコンでRedpanda(Kafka互換の実装)を使って250kメッセージ/秒ができるって投稿してたよ。https://www.youtube.com/watch?v=7CdM1WcuoLc 288 vCPUの3x c7i.24xlargeでそれ以下のスループットって、信じられないくらい無駄だよ。Postgresで何かできるからって、必ずしもやるべきとは限らない。 > 1. 一方の陣営はバズワードを追いかける。 > 2. もう一方は常識を追いかける。 この場合、「Postgres」はただのバズワードとして使われてるの? [開示:私はRedpandaで働いています;Kafka互換のサービスを提供しています。]

同じこと考えてた、Kafkaの数が入ってないのは変だね。自分はKafka使ったことないけど、Redisキューを使ってスクリプトで永続性を確保してるから、同じプロダクション環境よりもはるかに高いスループットを出してるよ。Redisのpubsubも同じだけど、あれは普通の非永続pubsubだから、ちょっと優位性があるかもね。

だから、ベンチマークはハードウェアの限界に基づくべきだと思う。例えば、このSSDのIOPSやスループットを最大限に引き出すとか、ネットワークカードをフル活用するとかね。CPUはちょっと難しいけど、何とか示せると思うよ。

96 vCPUのセットアップは、AWSで約2万ドル/月かかるよ、割引前でね。そして、Pub/Subシステムで避けたいのは、単一のインスタンスがすべての読み書きを処理することだよ。AWSでそのくらいの金額なら、かなり大きなKafkaクラスタを運用できるよ。

ちょっと余計なことを言ってるかもしれないけど、私の主な考えはこうだね。「アプリにはたぶんPostgreSQLがもうあるよね。追加のユースケースをカバーするために新しいインフラを設定する必要はないよ。既存のツールを再利用すればいいんだ。」技術的には新しいものでカバーできるユースケースが増えていくけど、実際には既存のインフラで十分対応できることが多いよ。成長が必要だと証明されるまではね。

この人、実際にKafkaのベンチマークを取ったの? 誰かがこの記事全体を読んでるのか、それとも最初の印象が良くない数字に反応して、ここで見つけた最初の否定的なコメントに飛びついてるだけなの? Kafkaのベンチマークを取ることが目的じゃないんだよ。著者はPostgresがKafkaより優れているとは言ってない。主張は、PostgresがKafkaの運用の複雑さを避けたいチームにとって、適度なメッセージワークロードを十分に処理できるってことだよ。そう、スループットはそんなに強力なCPUにしては驚くほど低いけど、それがポイントなんだ。今、Postgresが高性能なマシンでどれくらい動くかがわかるよ。Kafkaレベルのスケールが常に必要ってわけじゃない。要するに、Postgresがすでにあるなら実用的な選択肢になりうるってこと。だから、最初の印象が良くない数字で否定するんじゃなくて、TFAの実際の問題に応じて反応してみて。Postgresが「十分良い」とされるラインはどこなのか? それについて話すのは面白いと思うよ。

900kの書き込み/s(非複製)を、Kafka 0.8の頃にランダムな物理サーバーで古いFusion-ioドライブを使ってやってたのを覚えてるよ(どれくらい前のことかがわかるね :D)。もしすでにpgsqlのセットアップがあって、たまにメッセージが必要なだけなら、pgは十分だよね。でも、96 vCPUのセットアップは異常だよ。

OPがKafkaのパーティションやコンシューマオフセットを真似するのではなく、別のスキーマを実装していたら、違った結果が得られたかもしれないね。もしかしたら無知なことを言ってるかもしれないけど、Postgresでpub/subを実装するなら、その強みを活かして、イベントソーシングの基本に戻るのが良いと思う。

一方のグループはバズワードを追いかけ、もう一方は常識を重視している。 PostgresでKafkaを再実装しようとするのが常識ってどういうこと?似たようなものが必要だけど、もっとシンプルなやつがいいんじゃない?それを実装すればいいのに!でも本当にKafkaみたいなものが必要なら、Kafkaを使えばいいと思うよ。個人的には、著者はKafkaのエバンジェリストの中にはデータベースをKafkaで実装しようとする人たちと同じ間違いをしてる気がする。

pub/subシステムの例を挙げてるんだ。Kafkaに一番詳しいから、それに例を引き合いに出しただけ。実際にはKafkaが提供する全てを実装したわけじゃなくて、シンプルなpub/subのクエリを2つだけやったんだ。

私の一般的な意見だけど、小規模(1時間あたり数百イベント)と大規模(1時間あたり数兆イベント)の両方で働いた経験から言うと、 1. 本当にキューが必要?(代替案:DBの定期的なポーリング) 2. イベントの量はどれくらいで、将来的に1ノードに収まるのか、あるいはサーバーレスコンピューティングでも(あまり高くなければ)収まるのか?(代替案:軽量な単一プロセスのウェブサービス、または1ノード上のいくつかのインスタンス) 3. 1ノードに収まらない場合、本当に分散キューが必要?(代替案:昔ながらの負荷分散とREST API、もしかしたら非同期セマンティクスや再試行セマンティクスを使って) 4. もし本当に分散キューが必要なら、Kafkaのような分散キューを使うのがいいよ。Kafkaクラスタの管理の複雑さを引き受けても、プログラミングやパフォーマンスのセマンティクスは、SQL DBに分散キューを無理やり当てはめるよりも簡単に考えられるから。

セットアップを少し見直したいんだけど、IoTをやってて、MQTT -> Redpanda(メッセージログやリプレイ用)-> Postgres/Timescaledb(データ用)+ S3(アーカイブ用)って考えてるんだ。そこにFlink/RisingWave/Arroyoを加えて、アラートやインクリメンタル更新のマテリアライズドビューをやるかも。これって「シンプルそう」なんだけど(Redpandaの経験はないけど)、MQTT -> Postgres(キューとして)-> Postgres/Timescaledb + S3に比べると、確かにもう一つ動く部分が増えるよね。質問がいくつかあるんだけど、1. 同じPostgresをキューとビジネスデータベースで使うと、「メッセージ取り込み」の部分が「ビジネス」部分をブロックしちゃうことがあるかな(ロックとか)?データベースのスキーマを更新したいときに、メッセージの流入を「止めたくない」んだけど、これって簡単にできるのかな?2. メッセージをキューに書き込んでから削除するから、ビジネスデータベースに比べてGCやバキュームが多くなるのかな?3. 「Postgresキュー」と「Postgresデータベース」を別のプロセスに分けると、確かに「覚える技術が一つ減る」けど、pgmqに慣れたり統合したりする必要があるよね。それってRedpandaを追加するよりずっと簡単なの?4. 大抵のPostgresキューは「シンプル」で、複数のことに「ファンアウト」を提供してない気がする(例えば、IoTメッセージの一つを取り出して、クリーンアップしてTimescaledbに保存し、S3にもアーカイブして、アラート検出器を動かす、みたいな)。おすすめは何かな?

一つのノードに収まらないなら、本当に分散キューが必要なの?(代替案:昔ながらの負荷分散とREST API、もしかしたら非同期セマンティクスやリトライセマンティクスを使って)それって分散してると思うけど、異なる技術を組み合わせて実現してるだけだよね。異なるDBノードにRESTリクエストを負荷分散するのが、Kafkaよりも簡単なことってあるの?

小規模プロジェクトでよくある問題は、関わっているエンジニアが「このプロジェクトにうまくいくもの」と「次のプロジェクト/仕事にうまくいくもの」を共同で最適化することが多いってことだと思う。特にスタートアップでは、離職率や雇用の安定性が低いから、関わっているエンジニアにとっては最適な行動なんだよね。もし従業員が、現在のプロジェクトをできるだけシンプルで効果的にすることから得られる報酬が一番だと思っていない限り、現在のプロジェクトが本当にシンプルになることはほとんどないと思う。

Postgresにはpub-sub用の人気ライブラリがなさそうだから、自分で書かなきゃいけなかったんだ。つまり、Kafkaを動かす代わりに、自分たちで開発する時間を使うってこと?

pgmqみたいなライブラリが作られたらいいな。需要がどれくらいあるかは分からないけど、ニッチな感じがする。