4 ポイント 投稿者 GN⁺ 2024-04-08 | 1件のコメント | WhatsAppで共有
  • pgmock は、ユニットテストとE2Eテスト向けのインメモリPostgreSQLモックサーバーで、外部依存なしにNode.jsとブラウザ上でWebAssemblyとして実行される
  • node-postgres ユーザーは、ポートを開かない設定オブジェクトを受け取って接続でき、この方式はブラウザでも動作する
  • ブラウザではWebアプリがTCPポートを開けないが、PostgresMock.createSocketnode-postgres の設定を利用できる。バンドラーが静的importを解析すると、任意のNode.jsモジュールに関する警告が出る場合がある
  • 実装は現在、x86エミュレーター内でPostgreSQLサーバーを実行する方式を採用しており、テストと本番環境間の動作差異の防止を性能より優先している
  • 長期的には、ネイティブPostgreSQL WASMフォークが成熟すれば両方式を提供し、その後ネイティブWASMをデフォルトに切り替える計画がある

pgmockが提供するもの

  • pgmock は、ユニットテストとE2Eテスト用のインメモリPostgreSQLモックサーバー
  • 外部依存は不要で、Node.jsとブラウザの両方で WebAssembly 内で実行される
  • npmでインストールできる
npm install pgmock

基本的な利用フロー

  • インメモリサーバーは PostgresMock.create() で作成し、listen(5432) で接続文字列を受け取れる
import { PostgresMock } from "pgmock";

const mock = await PostgresMock.create();
const connectionString = await mock.listen(5432);
  • node-postgres を使う場合、mock.getNodePostgresConfig() がポートをlistenせずに接続できる設定オブジェクトを提供する
  • 作業が終わったら、リソースを解放するために mock.destroy() を呼び出す方法が推奨される
mock.destroy();

ブラウザ対応とpgliteとの違い

  • pgmockブラウザ環境を完全にサポートする
  • WebアプリはTCPポートを開けないが、PostgresMock.createSocketnode-postgres の設定を利用できる
  • バンドラーがimportを静的解析すると、任意のNode.jsモジュールがないという警告が出る場合があり、Webpack設定例は examples/web-demo/next.config.mjs にある
  • ブラウザでデータベースだけを実行したい場合は pglite を検討できる
    • pgliteはより高速で軽量だが、機能セットは限定的
    • pgmock は、テスト環境で求められる本番PostgreSQLとの機能同等性を目標に設計されている

WebAssemblyでPostgreSQLを実行する方式

  • WebAssemblyでPostgreSQLを実行する方式は2つある
  • ネイティブWASMフォーク方式はより高速でメモリ使用量もはるかに少ないが、シングルユーザーモードのみをサポートし、接続と拡張をサポートしていない
  • pgmock は現在x86エミュレーター方式を使用している
    • テストと本番環境間の不一致を防ぐことが目的
    • テストでは通常、性能が大きな問題にならないため
  • 中期的には、ネイティブPostgreSQL WASMフォークが成熟すれば、両方のオプションを提供する計画
  • その後はネイティブWASMをデフォルトに切り替える予定で、PostgresMock.subtle 内部API以外には大きなbreaking changeはあまり多くないと見込んでいる

既存のブラウザPostgreSQLプロジェクトとの違い

  • pgmock はJavaScriptランタイム内で完全な機能互換性を提供し、通信のためにネットワークプロキシへ依存しない
  • JavaScriptでネットワークスタックをシミュレートして実際のネットワークのように動作させ、raw socketアクセスを許可しないプラットフォームでもTCP接続をシミュレートできる

拡張可能性と関連プロジェクト

  • 他のDockerイメージやデータベースも理論上は実行可能だが、テストはされていない
  • 関連する実装と基盤プロジェクトとして以下が挙げられている
    • v86: x86エミュレーター
    • Supabase & Snaplet: WebAssembly内でPostgreSQLを実行するアプローチの基盤
    • Stackframe: pgmock 開発中に給与を提供した会社として言及されている

1件のコメント

 
GN⁺ 2024-04-08
Hacker News のコメント
  • 数か月にわたって会社で インメモリ Postgres 版を作っていて、本番データベースと機能的に同等になっています。
    利点は、外部プロセスやプロキシが不要なことです。プラットフォームが WASM を実行できれば、Node.js やブラウザでも pgmock を動かせますし、モックデータ入りの新しいデータベースを作るのも JavaScript オブジェクトを作るくらい簡単です。
    pgmock をオープンソース化するきっかけになった pglite とは少し違います。pgmock は元の Postgres を x86 エミュレータ内で実行し、pglite は Postgres のフォークをネイティブ WASM に直接コンパイルしているため、より高速で軽量です。
    ただし pglite はシングルユーザーモードと一部の拡張機能しかサポートしていないため、通常の Postgres クライアントでは接続できません。これは E2E テストではかなり重要です。
    理論上は、WebAssembly プラットフォーム上で任意の Docker イメージを実行できるように変更できるはずですが、見てみたい具体的な対象があるか気になります。

    • 素晴らしい仕事です。PGlite が現在 シングルユーザーのみをサポートしているのはその通りで、一部の環境では統合テストに使ううえで問題になり得ます。
      複数接続モードを追加する方法はいくつか考えていますが、少し時間がかかりそうです。PGlite にはシングルユーザーモードに関連する他の制限もあり、たとえば pg_notify はまだサポートしていませんが、これも修正する予定です。
      一方でこのプロジェクトは実際の Postgres にはるかに近いので、そのまま動く可能性が高いです。こうしたインメモリ Postgres プロジェクトは、テスト実行時間を 4分の1以下に短縮できそうで、テスト分野で大きな可能性があります。
      PGlite に取り組んでいる者としての考えです。
    • Docker イメージを WASM で実行するというアイデアは、いろいろな問題に有望そうです。
      最近、クライアント側で FFMPEG/SoX パイプラインを動かしたかったのですが、依存関係が多すぎて Emscripten で簡単に再コンパイルするのが難しかったです。このアプローチがそうしたケースにも役立つのか気になります。
    • pgvector 拡張をサポートできるなら、Postgres の力をすべて備えた非常に高速なベクトルデータベースになり得ます。
      リレーショナル機能のおかげで、通常リレーショナルデータベースに入っている豊富なドメイン固有メタデータを追加し、一緒にクエリできます。
    • 参考までに、オンラインデモは気に入らないクエリで壊れるようです。
      select foo(); を実行すると Error.captureStackTrace is not a function が出ます。Linux の Firefox 124.0.2 で発生しました。
    • 素晴らしいですが、E2E テストという概念自体は、コンポーネントをモックに置き換えず実際の環境を使うことを意味するのではないかと思います。
  • 単に Postgres のファイルを ramdisk に置いて実行すればよいのでは、と思いました。
    追記: ブラウザ/Node 環境で実行できるので、テストが作成・更新・削除できるようです。自分があまりにバックエンド開発者なので、一般的な開発環境と比べた利点がよく分かりません。どこで、いつ、どのようにより良いのか説明してもらえるとうれしいです。

    • エミュレータ内部でも、おおむね似たことが起きています。エミュレートされたディスクは インメモリ 9P ファイルシステムです。
      WebAssembly にした理由は、プラットフォーム、アーキテクチャ、ブラウザやエッジ環境まで含めて動作をより移植性高くそろえられ、Docker すら不要な外部依存なしの構成にできるからです。
      エミュレータはすでに起動済みの状態をそのままブートできるため、実データベースや Docker コンテナを立ち上げるよりも、エミュレートされたデータベースの起動のほうが速いです。ただしこれは設計目標というより、運よく得られた効果に近いです。
    • 私にもよく分かりません。エミュレータやネットワークスタックなど、不要なコードが多すぎるように見えます。
      https://testcontainers.com/ のようなものを使えばよいのでは、と思います。コンテナエンジンが外部依存であることが、そんなに悪いことなのか疑問です。
    • E2E テストの目的は、システムを 実際の状態でテストすることです。本番環境のエミュレーションなので、電源を抜いたりディスクが満杯になったりしたときに何が起きるかも確認できます。
      そこにモックを押し込んだ瞬間に単体テストになります。効果はありますが、同じものではありません。E2E の核心の一つは、モックがないからこそテストが正確だと分かる点です。これは Postgres をテストしているのではなく、毎回このシステムをテストしているということです。
      組み込み、軽量、低性能システム向けの PG を作っているなら、遅い実際の E2E テストの前の検証テストとしては筋が通ります。そういう用途は私にもあります。
      それ以外では、素晴らしいプロジェクトであり、PG shim が必要なときに使えるツールに見えます。
    • テストの分離に役立つかもしれません。Redis バックエンドをテストで FakeRedis に移したところ、テストスイートのノイズがかなり減りました。
      Postgres では savepoint を使っていますが、ramdisk 上でもそれほど速くありません。
  • 以前はテストでさまざまなカスタムの 偽インメモリサーバーを動かしていました。最近は https://testcontainers.com で実物を実行しています。

  • Prisma/Node.js 開発者がローカル開発用に「缶詰の Postgres」だけを求めているなら、最近出た PGlite のサーバー版である pglite-server のほうがよいかもしれません: https://github.com/kamilogorek/pglite-server
    より高速で、ファイルシステムにデータを保持できますが、利用が多いときは x86 エミュレータ全体を使う E2E テストサーバーより安定性は低いです。pglite-server はメモリを 150MB しか使わない一方、pgmock-server は 830MB 使いました。
    dotenv で新しい .env.local をチェックアウトし、すべての nextjs/prisma の package.json 実行スクリプトの DATABASE_URL を変更すればよいです。
    DATABASE_URL="postgresql://postgres@localhost:5432/awesomeproject"
    "db:pushlocal": "dotenv -e .env.local -- pnpm prisma db push"
    どんなプロジェクトにも非常に簡単に組み込めますし、Neon がこの領域を支援しているのも理解できます。

  • 水を差したいわけではありませんが、私はこれを使うつもりはありません。
    単純なアプリケーションなら動くかもしれませんが、デッドロックのリスクがあったり、データベースの形に依存していたりするなど複雑性が増すと、小さな挙動の違いでも致命的な問題に発展し得るため、価値は下がります。
    最近はリソース制約のある E2E 環境を好んでいます。ローカルのテストランナーが、誰かがひどく非効率なコードを書いたときに壊れる機会を与えてくれるからです。
    また、数秒後にデータベースをスナップショット化し、そのスナップショットをテストパーティションに配布する方法は非常に高速で、テストスイートから何分も削減できたことが何度もあります。
    興味深いアイデアで、良い学習経験にはなるでしょうが、対象ユーザーは限られると思います。

  • タイトルが少し紛らわしいです。「会社で作った」ということなら、会社のリソースを使ったという前提で、このプロジェクトの知的財産権は雇用主にあるのではないかと思います。
    だとすると、技術的にオープンソースとして公開してよいのか気になります。

    • スタートアップなので、オープンソース公開はチームの他のメンバーに承認してもらうのと同じくらい簡単でした。
    • リポジトリは Stackframe の所有で、LICENSE ファイルには Copyright 2024 Stackframe. とあります。作者は Stackframe で働いているようです。
  • これは H2 の Postgres 互換モードと比べるとどうなのか気になります。

    • 間違っているかもしれませんが、H2 では PostgreSQL のストアドプロシージャは使えない気がします。
  • かなりいいですね。答えられるなら、いくつか気になります。
    会社でこのプロジェクトを作ることになったきっかけは何だったのか、Docker コンテナで Postgres を実行するのが遅すぎたのかが気になります。
    pgmock をフローに統合する前後で、E2E テスト用の CI 構成がどう変わったのかも知りたいです。
    このソリューションへ移行する過程が難しかったのかも気になります。

  • 本番データをダンプして機密データをすべて除去し、ログテーブルのような不要なテーブルは truncate すれば、開発用の良いコピーができます。
    それを開発、QA、E2E などに複製すればよいです。E2E に必要なのはまさにその拡張、トリガー、関数、ビュー、インデックス、データです。

  • 普通に Docker を使って、別のテスト用データベースを用意すればよいのではないかと思います。
    Elixir ではそうしていて、テストフレームワークが各テストをトランザクションで包み、分離のためにロールバックします。このアプローチの利点が何なのか分かると興味深いです。