4 điểm bởi GN⁺ 2024-04-08 | 1 bình luận | Chia sẻ qua WhatsApp
  • pgmock là máy chủ giả lập PostgreSQL in-memory cho kiểm thử đơn vị và kiểm thử E2E, chạy bằng WebAssembly trên Node.js và trình duyệt mà không cần phụ thuộc bên ngoài
  • Người dùng node-postgres có thể nhận một đối tượng cấu hình để kết nối mà không cần mở cổng, và cách này cũng hoạt động trong trình duyệt
  • Trên trình duyệt, ứng dụng web không thể mở cổng TCP nhưng vẫn có thể dùng PostgresMock.createSocket và cấu hình node-postgres; nếu bundler phân tích static import thì có thể xuất hiện cảnh báo về các mô-đun Node.js tùy chọn
  • Hiện tại, phần triển khai chạy máy chủ PostgreSQL bên trong trình giả lập x86, ưu tiên tránh khác biệt hành vi giữa môi trường kiểm thử và production hơn là hiệu năng
  • Về lâu dài, khi nhánh fork PostgreSQL WASM native trở nên hoàn thiện hơn, dự án sẽ cung cấp cả hai cách tiếp cận và sau đó chuyển WASM native thành mặc định

Những gì pgmock cung cấp

  • pgmock là máy chủ giả lập PostgreSQL in-memory cho kiểm thử đơn vị và kiểm thử E2E
  • Không cần phụ thuộc bên ngoài và chạy trong WebAssembly trên cả Node.js lẫn trình duyệt
  • Có thể cài đặt bằng npm
npm install pgmock

Luồng sử dụng cơ bản

  • Máy chủ in-memory được tạo bằng PostgresMock.create() và có thể nhận chuỗi kết nối qua listen(5432)
import { PostgresMock } from "pgmock";

const mock = await PostgresMock.create();
const connectionString = await mock.listen(5432);
  • Nếu dùng node-postgres, mock.getNodePostgresConfig() sẽ cung cấp đối tượng cấu hình để kết nối mà không cần lắng nghe cổng
  • Sau khi xong việc, nên gọi mock.destroy() để giải phóng tài nguyên
mock.destroy();

Hỗ trợ trình duyệt và khác biệt với pglite

  • pgmock hỗ trợ đầy đủ môi trường trình duyệt
  • Ứng dụng web không thể mở cổng TCP, nhưng vẫn có thể dùng PostgresMock.createSocket và cấu hình node-postgres
  • Nếu bundler phân tích import theo cách tĩnh, có thể xuất hiện cảnh báo thiếu các mô-đun Node.js tùy chọn; ví dụ cấu hình Webpack nằm tại examples/web-demo/next.config.mjs
  • Nếu chỉ muốn chạy cơ sở dữ liệu trong trình duyệt, có thể cân nhắc pglite
    • pglite nhanh hơn và nhẹ hơn, nhưng tập tính năng bị giới hạn
    • pgmock được thiết kế với mục tiêu đạt mức tương đương tính năng với PostgreSQL production trong môi trường kiểm thử

Cách chạy PostgreSQL trong WebAssembly

  • Có hai cách để chạy PostgreSQL trong WebAssembly
  • Cách dùng fork WASM native nhanh hơn và dùng ít bộ nhớ hơn rất nhiều, nhưng chỉ hỗ trợ chế độ một người dùng và không hỗ trợ kết nối hay extension
  • Hiện tại pgmock dùng cách trình giả lập x86
    • Mục tiêu là ngăn sự không khớp giữa kiểm thử và production
    • Vì trong kiểm thử, hiệu năng thường không phải vấn đề lớn
  • Trong trung hạn, khi fork PostgreSQL WASM native trưởng thành hơn, dự án dự định cung cấp cả hai tùy chọn
  • Sau đó, dự án có kế hoạch chuyển WASM native thành mặc định và dự kiến sẽ không có nhiều breaking change lớn ngoài API nội bộ PostgresMock.subtle

Khác biệt với các dự án PostgreSQL trên trình duyệt hiện có

  • pgmock cung cấp khả năng tương thích đầy đủ về tính năng bên trong runtime JavaScript và không phụ thuộc vào proxy mạng để giao tiếp
  • Dự án mô phỏng ngăn xếp mạng bằng JavaScript để hoạt động như mạng thực, nhờ đó có thể mô phỏng kết nối TCP ngay cả trên các nền tảng không cho phép truy cập raw socket

Khả năng mở rộng và các dự án liên quan

  • Về mặt lý thuyết, cũng có thể chạy các image Docker hoặc cơ sở dữ liệu khác, nhưng chưa được kiểm thử
  • Các triển khai và dự án nền tảng liên quan được nhắc đến gồm
    • v86: trình giả lập x86
    • Supabase & Snaplet: nền tảng cho cách tiếp cận chạy PostgreSQL trong WebAssembly
    • Stackframe: công ty được nhắc đến là đã trả lương trong quá trình phát triển pgmock

1 bình luận

 
GN⁺ 2024-04-08
Ý kiến trên Hacker News
  • Trong vài tháng qua, chúng tôi đã xây dựng một phiên bản Postgres in-memory tại công ty, và nó có mức tương đương về tính năng với cơ sở dữ liệu production
    Ưu điểm là không cần tiến trình bên ngoài hay proxy. Nếu nền tảng có thể chạy WASM, bạn có thể chạy pgmock cả trong Node.js hoặc trình duyệt; việc tạo một cơ sở dữ liệu mới có dữ liệu mock cũng đơn giản như tạo một object JavaScript
    Nó hơi khác với pglite, thứ đã khiến chúng tôi quyết định mở mã nguồn pgmock. pgmock chạy Postgres gốc bên trong một trình giả lập x86, còn pglite biên dịch trực tiếp một fork của Postgres sang WASM native nên nhanh và nhẹ hơn
    Tuy nhiên pglite chỉ hỗ trợ chế độ một người dùng và một số extension, nên không thể kết nối bằng các client Postgres thông thường; điều này khá quan trọng trong kiểm thử E2E
    Về lý thuyết, có thể sửa để chạy bất kỳ Docker image nào trên nền tảng WebAssembly; tôi tò mò liệu có đối tượng cụ thể nào mọi người muốn thấy không

    • Công trình rất ấn tượng. Đúng là PGlite hiện chỉ hỗ trợ một người dùng, và điều đó có thể là vấn đề khi dùng cho kiểm thử tích hợp trong một số môi trường
      Chúng tôi đang nghĩ đến vài cách để thêm chế độ đa kết nối, nhưng có lẽ sẽ mất chút thời gian. PGlite cũng có các hạn chế khác liên quan đến chế độ một người dùng; ví dụ hiện chưa hỗ trợ pg_notify, và chúng tôi cũng có kế hoạch sửa điều này
      Ngược lại, dự án này gần với Postgres thật hơn nhiều nên khả năng cao là cứ thế chạy được. Những dự án Postgres in-memory như thế này có vẻ có thể giảm thời gian chạy test xuống dưới một phần tư, và có tiềm năng lớn trong lĩnh vực kiểm thử
      Đây là suy nghĩ từ góc nhìn của người làm PGlite
    • Ý tưởng chạy Docker image trong WASM có vẻ hứa hẹn cho nhiều bài toán
      Gần đây tôi muốn chạy pipeline FFMPEG/SoX ở phía client, nhưng có quá nhiều dependency nên khó biên dịch lại dễ dàng bằng Emscripten. Tôi tự hỏi cách tiếp cận này có thể giúp ích trong những trường hợp như vậy không
    • Nếu có thể hỗ trợ extension pgvector, nó có thể trở thành một cơ sở dữ liệu vector rất nhanh với toàn bộ sức mạnh của Postgres
      Nhờ các tính năng quan hệ, có thể thêm và truy vấn cùng lúc lượng metadata phong phú, đặc thù theo domain, vốn thường nằm trong cơ sở dữ liệu quan hệ
    • Nhân tiện, bản demo online có vẻ bị lỗi với những query không hợp ý nó
      Khi chạy select foo(); thì xuất hiện Error.captureStackTrace is not a function, xảy ra trên Firefox 124.0.2 trên Linux
    • Tuyệt vời, nhưng tôi nghĩ bản thân khái niệm kiểm thử E2E chẳng phải là dùng môi trường thật thay vì thay các thành phần bằng mock sao
  • Tôi tự hỏi liệu chỉ cần đặt file Postgres lên ramdisk rồi chạy có được không
    Cập nhật: Có vẻ nó có thể chạy trong môi trường trình duyệt/Node, nên test có thể tạo, cập nhật và xóa. Tôi quá là backend developer nên chưa rõ lợi thế so với môi trường phát triển thông thường. Sẽ rất hay nếu ai đó giải thích được nó tốt hơn ở đâu, khi nào và như thế nào

    • Bên trong trình giả lập về cơ bản cũng diễn ra điều tương tự. Đĩa được giả lập là một hệ thống tệp 9P in-memory
      Lý do dùng WebAssembly là để hành vi có thể được điều chỉnh theo hướng portable hơn trên nhiều nền tảng, kiến trúc, cả trình duyệt hoặc môi trường edge, và tạo thành cấu hình không có dependency bên ngoài, thậm chí không cần Docker
      Vì trình giả lập cho phép boot trực tiếp vào trạng thái đã chạy sẵn, việc khởi động cơ sở dữ liệu được giả lập nhanh hơn so với dựng một cơ sở dữ liệu thật hoặc container Docker. Tuy vậy, đây giống một hiệu ứng may mắn có được hơn là mục tiêu thiết kế
    • Tôi cũng không rõ. Có vẻ có quá nhiều mã không cần thiết như trình giả lập, network stack, v.v.
      Tôi tự hỏi sao không dùng thứ như https://testcontainers.com/. Việc container engine là dependency bên ngoài có tệ đến thế không
    • Mục đích của kiểm thử E2E là kiểm thử hệ thống ở trạng thái thực. Vì đó là mô phỏng môi trường production, bạn cũng có thể kiểm tra chuyện gì xảy ra khi rút điện hoặc khi đĩa đầy
      Ngay khi nhét mock vào trong đó, nó trở thành unit test. Có hiệu quả, nhưng không phải cùng một thứ. Một trong những điểm cốt lõi của E2E là vì không có mock nên bạn biết test là chính xác. Đây không phải là kiểm thử Postgres, mà là kiểm thử hệ thống này mỗi lần
      Nếu bạn đang xây PG cho hệ thống embedded, nhẹ, hiệu năng thấp, thì dùng làm test xác minh trước các bài E2E thật chậm hơn là hợp lý. Tôi cũng có nhu cầu như vậy
      Ngoài ra thì đây là một dự án thú vị, và có vẻ là công cụ có thể dùng khi cần PG shim
    • Nó có thể hữu ích cho việc cô lập test. Khi chuyển backend Redis trong test sang FakeRedis, độ nhiễu của test suite đã giảm đáng kể
      Với Postgres tôi đang dùng savepoint, nhưng ngay cả trên ramdisk cũng không nhanh lắm
  • Trước đây trong test chúng tôi chạy đủ loại server in-memory giả tùy biến. Giờ thì dùng https://testcontainers.com để chạy đối tượng thật

  • Nếu developer Prisma/Node.js chỉ muốn một “Postgres đóng hộp” cho phát triển local, bản server hóa gần đây của PGlite là pglite-server có thể phù hợp hơn: https://github.com/kamilogorek/pglite-server
    Nó nhanh hơn và có thể giữ dữ liệu trên hệ thống tệp, nhưng khi tải cao thì kém ổn định hơn so với server kiểm thử E2E dựa trên toàn bộ trình giả lập x86. pglite-server chỉ dùng 150MB bộ nhớ, trong khi pgmock-server dùng 830MB
    Chỉ cần checkout một .env.local mới bằng dotenv và đổi DATABASE_URL trong tất cả các script chạy package.json của nextjs/prisma
    DATABASE_URL="postgresql://postgres@localhost:5432/awesomeproject"
    "db:pushlocal": "dotenv -e .env.local -- pnpm prisma db push"
    Có thể gắn vào bất kỳ dự án nào rất dễ dàng, và tôi cũng hiểu vì sao Neon tài trợ cho mảng này

  • Không muốn dội gáo nước lạnh, nhưng tôi không định dùng cái này
    Với một ứng dụng đơn giản thì có thể hoạt động, nhưng khi độ phức tạp tăng lên — chẳng hạn có nguy cơ deadlock hoặc phụ thuộc vào dạng/thực thể của cơ sở dữ liệu — thì chỉ một khác biệt nhỏ về hành vi cũng có thể lan thành vấn đề nghiêm trọng, khiến giá trị của nó giảm đi
    Dạo này tôi thích môi trường E2E bị giới hạn tài nguyên hơn. Vì trình chạy test cục bộ sẽ có cơ hội bị vỡ khi ai đó viết đoạn code cực kỳ kém hiệu quả
    Ngoài ra, cách tạo snapshot cơ sở dữ liệu sau vài giây rồi phân phối snapshot đó cho các partition test rất nhanh, và đã nhiều lần giúp giảm vài phút trong test suite
    Đây là một ý tưởng thú vị và là trải nghiệm học hỏi tốt, nhưng tôi cho rằng nhóm người dùng mục tiêu khá hạn chế

  • Tiêu đề hơi gây nhầm lẫn. Nếu “làm ở công ty” thì với giả định đã dùng tài nguyên của công ty, quyền sở hữu trí tuệ của dự án này chẳng phải thuộc về chủ lao động sao
    Nếu vậy tôi thắc mắc về mặt kỹ thuật có được công bố mã nguồn mở hay không

    • Vì là startup nên việc công bố mã nguồn mở dễ như xin phê duyệt từ các thành viên còn lại trong đội
    • Repository thuộc sở hữu của Stackframe và file LICENSE ghi Copyright 2024 Stackframe.. Có vẻ tác giả làm việc tại Stackframe
  • Tôi tò mò cái này so với chế độ tương thích Postgres của H2 thì thế nào

    • Có thể tôi sai, nhưng có lẽ trong H2 không dùng được stored procedure của PostgreSQL
  • Khá hay. Nếu có thể trả lời thì tôi có vài điều muốn hỏi
    Tôi tò mò điều gì đã khiến công ty tạo ra dự án này, có phải chạy Postgres trong Docker container quá chậm không
    Tôi cũng muốn biết cấu hình CI cho E2E test đã thay đổi thế nào trước và sau khi tích hợp pgmock vào luồng
    Và cũng tò mò quá trình chuyển sang giải pháp này có khó không

  • Dump dữ liệu production, loại bỏ toàn bộ dữ liệu nhạy cảm, rồi truncate những bảng không cần thiết như bảng log, sẽ tạo được một bản sao tốt cho môi trường phát triển
    Chỉ cần sao chép nó sang dev, QA, E2E, v.v. Thứ E2E cần chính là các extension, trigger, function, view, index, dữ liệu đó

  • Tôi tự hỏi sao không cứ dùng Docker và có một cơ sở dữ liệu test riêng
    Elixir làm như vậy, và test framework bọc mỗi test trong một transaction rồi rollback để đảm bảo cách ly. Sẽ thú vị nếu biết ưu điểm của cách tiếp cận này là gì