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

Redisは高速です – Postgresにキャッシュします

概要

  • RedisとPostgresをキャッシュ用途で比較した検証結果のまとめ
  • Kubernetesクラスタ上で、CPUやメモリを制限した環境でベンチマークを実施
  • Redisの方が一貫して高いパフォーマンスと低レイテンシを示した
  • Postgresの「UNLOGGED TABLE」利用による効果も確認
  • プロジェクトの要件次第でPostgresをキャッシュとして使う選択肢も有効

Redis vs Postgresでキャッシュを検証

  • PostgresをRedisの代わりにキャッシュ用途で使う是非を検証テーマ
  • RedisとPostgres(UNLOGGED TABLE利用)を比較するため、シンプルなHTTPサーバをGoで実装
  • APIサーバ、キャッシュ(Redis/Postgres)、ベンチマーク(k6)をKubernetesクラスタの異なるノードで稼働
  • 各キャッシュに3,000万件のデータを投入し、既存/新規キーの割合を調整しつつベンチマーク実施
  • 計測項目はリクエスト毎秒(RPS)、レイテンシ、CPU・メモリ使用量

Redisのキャッシュ実装

  • github.com/redis/go-redis/v9 ライブラリ利用
  • Get/Set メソッドを実装し、Redisサーバとやり取り
  • Redisサーバはデフォルト設定で起動

Postgresのキャッシュ実装

  • github.com/jackc/pgx/v5 ライブラリ利用
  • UNLOGGED TABLE を使い、WAL(Write Ahead Log)を無効化し書き込み高速化
  • Get/Set メソッドを実装し、Postgresサーバとやり取り
  • プール設定で接続数を調整

ベンチマーク条件

  • 2CPU・8GiBメモリに制限したノード上でRedisまたはPostgresを稼働
  • APIサーバとベンチマークPodも別ノードで稼働
  • 2分間のGET/SET/混合ワークロードをそれぞれ実施
  • GETは80%既存キー、SETは10%既存キーを更新
  • 混合はGET:SET=8:2の割合

ベンチマーク結果

GETリクエスト性能

  • RedisがPostgresより高いRPSを記録
    • RedisはCPU使用率~1280mCPU、メモリ~3800MiBで安定稼働
    • PostgresはCPU2コアを常にフル活用、メモリ~5000MiB
  • レイテンシもRedisが優秀

SETリクエスト性能

  • RedisがPostgresより高いRPSを維持
    • Redisは新規キー追加でメモリ~4300MiBに増加
    • Postgresは書き込み時もCPUボトルネック、メモリ~5500MiB
  • レイテンシもRedisが優位

混合ワークロード

  • Redisが一貫して高性能
    • RedisはCPU~1280mCPU、メモリも適度に増加
    • Postgresは2コアを常時フル活用、メモリ~6GiB
  • レイテンシもRedisが勝る

UNLOGGED TABLEの効果

  • 書き込み系ベンチマークで大きな差
    • UNLOGGED TABLEはWALをスキップするため、書き込みが高速化
    • 読み取り性能には大きな差は見られず

結論と考察

  • Redisはキャッシュ用途で圧倒的に高速

    • TTLや豊富なキャッシュ機能も標準搭載
  • Postgresをキャッシュとして使う理由も存在

    • 追加ミドルウェア不要、依存性削減
    • キーの有効期限管理はカラム追加+定期削除で代替可能
    • 1秒あたり7425リクエスト(1日5億リクエスト超)でも十分運用可能
    • プロジェクト規模や要件によってはPostgresだけで完結も現実的
  • キャッシュ層のインターフェース設計推奨

    • 将来的なストアの切り替えが容易
  • Redisがベストだが、Postgres単体運用も十分選択肢

    • 規模や要件、運用コストを考慮した柔軟な設計が重要

Hackerたちの意見

同じような感じで、Redka(https://github.com/nalgeon/redka)は、SQLiteかPostgresでサポートされたRedis APIの一部にアクセスできるから、いいアイデアだと思ってた。

これを投稿しに来た。RedkaのパフォーマンスがPostgres(とRedis)と比べてどうなるのか気になるな。編集: https://antonz.org/redka/#performance

キャッシュキーにTTLを設定できる機能は、キャッシュの重要な特徴で、後から追加できるものじゃないよね。「Redisを使うな」っていう投稿はいつも変に感じる。Redisはどんな規模でも簡単に操作できるから、なんでそれを排除することが重要なのかよくわからない。

そうそう、記事では「どうせDBは必要だし」って言ってるけど、キーの期限切れのために余計なcronジョブを設定して、さらにコードも増やしてる。YAGNIや依存関係を避けるのはわかるけど、これは本当に余計な手間だよね。Postgresにもキャッシュ機能があればいいのに。そうなるまでは、再発明するよりもRedisかmemcachedを使うつもり。

悪いわけじゃないけど、運用するシステムの数を減らすって感じかな。

いつかPostgresがタイムスタンプ列を期限切れ列としてマークできるようになることを期待してる。キャッシュ以外にも、セッショントークンやフィーチャーフラグ、バックグラウンドジョブ、レート制限、遅延削除(ソフト削除のバリエーション)など、いろんなことに役立つと思う。自動バキュームが定期的に期限切れの行を処理できるみたいだし、クエリプランナーが自動的に期限切れの行を除外する条件を追加して、期限切れの行が自動バキュームでクリーンアップされるまで見えないようにできるんじゃないかな。

キャッシュキーにTTLを設定できる機能は、キャッシュにとって重要な要素だよね。後から追加できるものじゃない。具体的にどんな課題があるの?少なくとも、DBエントリの一部として有効期限のタイムスタンプを保存することはできるよ。一般的なキャッシング戦略では、キャッシュが期限切れになる前に再検証することが含まれているし、再検証中に古いデータを返すのは全く珍しいことじゃないから。

なんでこんなにグラフがきれいでよく書かれた記事を推すのか、実際のベンチマークスタディとしては「F」評価に値するのに。こういう風に提示されると、カジュアルな読者はPostgresがRedisの2/3のパフォーマンスだと思っちゃうよ。まじで。彼はPostgresが2コアをフルに使ってるのを認めてるけど、RedisはHTTPサーバーにボトルネックがあったって言ってる。ベンチマークにはもっとアカデミックな文化が必要だよ、ハッカー文化じゃなくて。

これが僕のブログに載ってる理由があるんだ。これはどちらのツールの絶対的な速度を示すものじゃないし、ベンチマークもそのために設定されてない。ブログ記事ではRedisがもっとパフォーマンスを発揮できるって言ってるよ。

ブログやってる人の大半は、自分が何やってるか分かってないか、知ろうともしてないんだよね。悲しいことに、そういう人が会社に雇われて、ブログがあるからみんなその言うことを聞く。あの業界では本当にひどいことを見てきたよ。何人かは本当に詐欺師みたいなもんだ。

彼はPostgresが2コアをフルに使っていることを認めているけど、RedisはHTTPサーバーによってボトルネックになっているんだね。あなたの言いたいことは何なの?どちらかをさらに最適化できるってこと?まあ、そうだろうね。それは驚くべきことじゃないよ。レイテンシーだけでも、いくつかの横断的リクエストの範囲に入ってるし。RedisがPostgresよりも優れていることに驚いた?そんなことないと思うけど。じゃあ、問題は何なの?証明されている主なポイントは、パフォーマンスに関しては確かに収穫逓減があるってことだよ。キャッシュにアクセスする際に20msの余裕があるアプリケーションなら、永続データベースを使ったキャッシングも選択肢だよ。そういう事実に驚く人もいるみたいだけど、それは考える材料になるよね。

あなたの言いたいことが分からない。この結果は、すべてのパラメータが文書化されているから、Fはつかないよ。結論も間違ってないし、じゃあ問題は何なの?

ハッカーニュースにはハッカーが多すぎる!

25年前のドットコムバブルが崩壊した時のことを知らないかもしれないね。あの頃、私たちはベイエリアに留まるために、すごく苦労したんだ。すごいエンジニアたちが、ひどい仕事をしながら、サイドで「契約」してた話もあるよ。家でスタートアップを立ち上げたり、レンタルホスティングのスライスでやるなんて無理だった。ハードウェアは高かったし、何も簡単じゃなかった。今は、月10ドルで1000ユーザーを抱えるビジネスを立ち上げて成功できる時代だよ。これは実現可能で、簡単に構築できるビジネスモデルだと思う。でも、アマゾンの無限の請求書で2012年の価格で利益を上げるためにホスティングを選ぶつもりはない。固定費のホスティングがあるのに、そんなことはしないよ。私にとって、テクノロジーで面白いことは、FBやGoogle、ハイパースケーラーからは出てこない。AIやMLでもないしね。もう一つのKubernetesやKafka、Reactは必要ない(コンウェイの法則プロジェクトももういらない)。もっと面白い仕事は、限られた時間と予算で問題を解決している小さな2人や3人のチームで行われている。彼らの仕事は、クラウドフレアの最新の大規模Rustプロジェクトについてのエンジニアリングブログよりも、HNを読んでいるほとんどの人にとってはずっと適用可能だと思う。

すごいベンチマークが内部調整の中で消えてしまっていることが多いよね。運が良ければブログ記事が出るけど、作成者が結果を共有することに対してインセンティブがなかったり、雇用主から共有を discouraged されていたりすると、結果は日の目を見ないままになる。

主なポイントは、両者を完全にベンチマークして比較することじゃなくて、Postgresのキャッシュが実際に役立つくらい速いかどうかをざっくり把握することだったんだ。Redisとの比較は、あくまでその感覚を得るための補助的なもので、「堅実なベンチマーク」を装うものではなかったよ。

なんか、騒ぎすぎな気がする。

こういう風に提示されると、普通の読者はPostgresがRedisのパフォーマンスの2/3くらいだと思っちゃうよね。 もし読者が技術的な選択に興味があるなら、人気のユースケースのベンチマークくらいは読むだろうし、結論も知るはずだよね。 キャッシュに関しては、Redisの方がPostgresより速いのは間違いない。 キャッシュに期待される便利な機能、例えばTTLもついてるしね。 それに、ハードウェアやサービスのボトルネックもあったから、もっと良い数字が出せたはず。 じゃあ、キャッシュが必要ならみんなRedisを使うべきだよね? でも、俺はやっぱりPostgresを使うかな。 ほとんどのプロジェクトにはデータベースが必要だし、別の依存関係を追加しなくて済むのは大きなメリットだよ。 もしキーが期限切れになってほしいなら、カラムを追加して、クロンジョブでそのキーをテーブルから削除するようにするよ。 スピードに関しては、7425リクエスト/秒ってまだまだ多いし、1日で5億リクエスト以上だよ。 しかも、10年前のハードウェアでノートPCのCPUを使ってるんだから。 これだけのスケールに達するプロジェクトは少ないし、もし達したらPostgresのインスタンスをアップグレードするか、必要ならRedisを立ち上げればいいしね。 キャッシュのインターフェースを持っておくことで、簡単にストレージを切り替えられるのは、まさにこの目的のために続けるつもりだよ。 最初の文にはちょっと異議があるかも(「…少なくとも俺のハードウェアと設定に関しては。」って付け加えようかな)けど、他は大体大丈夫そう。 普通の読者としては、だいたいこんな感じだよね:

  • お、誰かの経験とデータポイントだ。これだけで自分の意見を決めるつもりはないけど、みんなが経験を共有してるのはいいことだね。
  • どっちを使いたいにしても、ボトルネックやHTTPサーバーについても調べる必要があるかも、最初は気にしないかもしれないけど!
  • そんなにチューニングに手をかけなくても、どちらのソリューションも大量のデータを処理できて、パフォーマンスも同じオーダーに収まってる。
  • だから、普通の読者としては、カジュアルなユースケースには、簡単そうな方を選べばいいって感じかな。 もし本格的なベンチマークを読みたかったら、そういうのを探しに行くし(それも詳細が多すぎてカジュアルな読み物にはならないけど、要約だけだと結局多くを見逃しちゃうし)、自分でやるかもね。 これは普通のポップサイエンスの記事みたいなもので、特に問題はないけど、他のものを探してるなら別だけどね。 ボトルネックを排除する方法についてのフォローアップ記事があったら面白いかも!

PostgresとRedisは、デフォルトの設定で使われてるんだね。うーん、これが公平感を与えるのは分かるけど、自己尊重のあるソフトウェアエンジニアならベンチマークにこんなアプローチはしないよ。ハードウェアを持ってるなら、仮想化されたハードウェアかもしれないし、それに合わせて調整するのが普通だよ。真剣に考えたいなら、他に方法はない。コンテナオーケストレーション環境では、オーケストレーターがサービスをどこにスケジュールするか分からないから調整は無理だって言う人もいるけど、それはおかしいよ。オーケストレーター用の基本的なデプロイメント設定を書く時間があるなら、PostgreSQLやRedisのメモリ使用設定をサイズ調整する時間もあるはず。ほんとにそれだけのことなんだ。この手のことは「難しくて面倒」なのは最初の5分だけで、その後は再度見直す必要はないよ(オーケストレーターのデプロイメント設定を変えてリソースを増やしたり減らしたりしない限り)。特に結果を公表するつもりなら、永続サービスのサイズを適切に調整することに論争は生まれないよ。

完全に同意。Postgresは多くのユースケースに使えるパワーツールだよ。パフォーマンスを求めるなら、調整が必要だね。Postgresを調整せずに判断するのは、Postgresが遅いんじゃなくて、開発者がナイーブなだけだよ。

一方では君に同意するけど、もう一方ではデフォルトが重要だと思う。デフォルト設定のままで調整を試みないシステムをよく見るからね。デフォルトをベンチマークするのと、調整されたセットアップをベンチマークするのでは、測定するものが全然違うけど、どちらも重要だよ。

反対だね。大多数のソフトウェアはデフォルトで動いているから、こうやって比較するのは理にかなってる。

私は反対だな。調整なしのPostgresが、低レベルのハードウェアでも十分に速いことがわかったし、別のサービスを展開しないという利点もある。さらに、調整することはあまり関係ないと思う。デフォルトがユースケースに合っているなら、個人的な興味で調整したい場合を除いて、それは私の楽しみの時間の無駄か、クライアントの資金の無駄になるだけだよ。

「たった5分のLLMクエリやウェブ検索で、LLMの出力やあまり理解していないかもしれない設定をコピペするよりも、PGのデフォルトの方が信頼できると思う…」

まあ、そうかもしれないけど、少なくともこのケースでは、チューニングされていないPostgresでも多くの実際のユースケースにおいて速いキャッシュとして使えるってことがわかるね。

これってエンゲージメントベイト?ため息…まあ、ここで釣られてみるよ:1. ユースケースは、整合性が重要じゃないホームラボに特化してる。Redisの永続性設定を見せてくれなかったね。永続性/耐久性の設定は何?ホームラボのブレーカーを切り忘れた日にはデータが失われると思うよ。2. RedisのRAMが8GBを超えたらどうなるの?3. PGの設定も見せてくれなかったし、RAMをバッファとキャッシングに全部使うこともできるよ。4. Postgresはプロセスがたくさんあるのに、CPUを2つしか与えないの?バニラRedisはシングルコアだから、このレースは最初から不公平だよ。UNLOGGEDテーブルは少し均等にするけど。一般的に、この「ベンチマーク」で何を達成しようとしてるの?どんな結果を知りたいの?この「ベンチマーク」は、プロダクション環境で知っておくべきことを教えてくれないよ。HNの他の読者への補足:UNLOGGEDテーブルはユニットテストを速くするための便利なトリックだよ。CI/CD用に専用のPG内でUNLOGGEDテーブルにALTERを実行すればいい:ALTER TABLE my_test_table SET UNLOGGED;

  1. Redisには永続性がない。2. RedisはOOMキルされる。3. 画像に付属しているデフォルト設定を使った。4. そう、CPUを2つ与えた。Postgresをキャッシングに使った場合とRedisを使った場合のHTTPサーバーの挙動を比較したかったんだ。このベンチマークは、時には専用のKVストアが必要ないことを示すためだけにある。もしかしたら、これにPostgresを使うのがあなたのユースケースには十分かもしれない。プロダクション環境という用語は色々な意味があるかもしれない。もし毎秒何十万件のリクエストを処理しているなら、HAやスケーリング、専用の共有キャッシュなど、異なるアーキテクチャが必要になるだろう。でも、そんなに多くのアプリケーションはその境地に達しないし、しばしば消費者にサービスを提供するために必要以上のものを使ってしまう。だから、シンプルに保つことが大事だと言いたいんだ。

何がそんなに騒がれているの?昔は、データベース(どんなデータベースでも)にエントリーを入れて、TTLやキャッシュタグを使って、定期的にテーブルをフラッシュするcronジョブを設定してたんだ。アプリケーション内で、メモリにキャッシュされたエントリーがあるか確認して、なければDBをチェックして、そこにもなければゼロから構築してキャッシュする。なんで人々は物事を複雑にするの?キャッシングはずっと前に解決済みだよ。

俺はPostgresを何にでも使うのが好きで、インフラをシンプルにできるからね。でもキャッシュとして使うのは、信頼性の面でちょっと心配だと思う。DBがかなり劣化する場面を何度も見てきたから。ただ、RedisやMemcacheのキャッシュのおかげで、多くのリクエストは遅延を最小限に抑えて処理できた。もし同じDBインスタンスからキャッシュを提供してたら、DBに問題があったときにキャッシュも劣化しちゃうだろうね。

IDでの選択は速いよ。キャッシュとして使ってるのにIDでの選択をしてないなら、それはキャッシュじゃないね。

でもキャッシュとして使うのは、信頼性の面でちょっと心配だと思う。これまでにPostgresにデータを保存することが信頼性の面で懸念だと誰かが言うのを聞いたのは初めてだし、君がこの問題に関して重要な洞察を持っている唯一の人間だとは思えない。君の以前の信念が根拠のないものである可能性はないの? > DBがかなり劣化する場面を何度も見てきた。これって漠然とした逸話で意味がないよ。具体的なシナリオはあるの?誰でも十分なミスをすれば、Redisでも「かなり劣化」させることができるからね。

キャッシュにはTTLが不可欠だよ。Postgresでクリーンアップジョブを調整するのは面倒だし、削除や更新でデッドロウができるから、バキュームにも対処しなきゃならない。著者が提案した方法は、サービスが予想以上にスケールアップする必要が出てきたら、Redisのような従来の専用キャッシュよりも多くの問題に直面することになるよ。

これまでにいくつかベンチマークをやってきたけど、基本的なPostgresのインストールからシンプルなデータを取り出すのが6-7k/sってのはかなり正確だと思う。問題は、Postgresをキャッシュとして使うときに、実際の複雑なビジネスロジックのクエリのパフォーマンスを奪っているかどうかだね。それはキャッシュを使うエンドポイントと、より複雑なクエリが必要なエンドポイントの重なり具合によると思う。興味があるのは、たくさんの単純なキャッシュクエリと、通常はもっと複雑なビジネスロジックのクエリを混ぜたベンチマークで、Postgresのパフォーマンスが高い同時負荷の中でどれだけ落ちるかを見てみたいな。

キャッシュっていうのはすごく相対的な用語だね。もし重い計算をキャッシュしているなら、あるいは外部から取得したリソース(例えばウェブスクレイピングしたデータ)をキャッシュしているなら、データベースをキャッシュとして使うだろうね。でもデータベースの結果をキャッシュしているなら、当然それより速い何かを使うよ。