Postgres không cần Server: Chạy PGlite trong Trình duyệt và Node.js

Database tutorial - IT technology blog
Database tutorial - IT technology blog

Sự cố cơ sở dữ liệu lúc 2 giờ sáng

Đó là một đêm thứ Ba muộn khi môi trường phát triển của tôi gặp bế tắc. Tôi đang debug một bản migration phức tạp, nhưng Docker Desktop lại mắc kẹt trong vòng lặp ‘Starting’, làm quạt laptop kêu rú lên tới 5.000 vòng/phút mà vẫn không nhúc nhích. Tôi không muốn dành cả đêm để dọn dẹp container hay cài đặt lại toàn bộ engine cơ sở dữ liệu chỉ để chạy ba câu lệnh SQL. Tôi cần sức mạnh logic của Postgres mà không muốn sự nặng nề của một server Postgres.

Sau nhiều năm chuyển đổi giữa MySQL, Postgres và MongoDB, tôi nhận ra rằng dù mọi công cụ đều có vị trí riêng, nhưng schema chặt chẽ và hỗ trợ JSONB của Postgres thường là những thứ không thể thiếu trong các dự án của tôi. Tuy nhiên, chi phí vận hành một instance cục bộ là một gánh nặng thường trực đối với năng suất. Sự ức chế này đã dẫn tôi đến với PGlite.

Vấn đề thực tế: Rào cản của Postgres

Hầu hết các lập trình viên đều đã đối mặt với nút thắt cổ chai này. Bạn muốn xây dựng một công cụ CLI nhanh, một bản demo proof-of-concept, hoặc một ứng dụng web local-first. Theo cách truyền thống, các lựa chọn của bạn bị hạn chế và đều đi kèm với sự đánh đổi. Bạn hoặc là cài đặt một server PostgreSQL đầy đủ (quá dư thừa cho một script 50 dòng), sử dụng SQLite (thiếu lập chỉ mục JSONB và các toán tử Postgres đặc thù), hoặc kết nối với một DB đám mây từ xa khiến mỗi request bị trễ thêm 150ms.

Rào cản này đặc biệt khó chịu khi xây dựng phần mềm “Local-First”. Nếu bạn muốn một cơ sở dữ liệu bên trong trình duyệt để đồng bộ với backend sau này, SQLite WASM thường là lựa chọn mặc định. Nhưng nếu backend production của bạn là Postgres, bạn sẽ phải viết các lớp chuyển đổi logic lộn xộn để xử lý sự khác biệt về cú pháp. Đây chính là nguồn cơn của những lỗi chỉ xuất hiện trên môi trường production.

Tại sao Postgres tiêu chuẩn lại gặp khó khăn trong môi trường gọn nhẹ

PostgreSQL được thiết kế cho kỷ nguyên client-server. Nó được kỳ vọng là một tiến trình chạy lâu dài với quản lý bộ nhớ riêng biệt, các khóa hệ thống tệp phức tạp và các trình lắng nghe mạng (network listeners) hoạt động liên tục. Khi bạn cố gắng ép kiến trúc đó vào một trình chạy thử nghiệm tạm thời hoặc một tab trình duyệt, hệ thống sẽ phản kháng. Nó vốn không được xây dựng để tồn tại ngắn hạn.

Các tệp nhị phân Postgres tiêu chuẩn thường nặng hơn 100MB và được biên dịch cho các hệ điều hành cụ thể. Chạy chúng trong trình duyệt thường yêu cầu một máy ảo nặng nề hoặc một container. Điều này làm cho nó không khả thi đối với một số trường hợp sử dụng hiện đại:

  • Môi trường phát triển khởi động tức thì trong vài mili giây.
  • Tài liệu tương tác nơi người dùng có thể thực thi SQL trực tiếp trong trình duyệt.
  • Ứng dụng phía client yêu cầu tính toàn vẹn quan hệ mà không cần khứ hồi (round-trip) về backend.

Đánh giá các giải pháp thay thế

Trong lúc tìm kiếm một giải pháp tốt hơn vào đêm muộn đó, tôi đã cân nhắc ba phương án phổ biến:

  1. SQLite WASM: Nhanh và đáng tin cậy, nhưng nó không hỗ trợ pgvector hoặc các phương ngữ (dialect) cụ thể mà ứng dụng của tôi yêu cầu.
  2. Embedded Postgres qua Docker: Hoạt động được, nhưng thời gian khởi động 5-10 giây là kẻ tiêu diệt sự tập trung khi chạy unit test.
  3. Mocking DB: Nhanh, nhưng mock không bao giờ bắt được những trường hợp biên (edge cases) tinh vi của việc thực thi SQL thật, dẫn đến cảm giác an toàn giả tạo.

Sau đó, tôi tìm thấy PGlite. Đây là một bản build WebAssembly (WASM) của PostgreSQL 15/16 được nén gọn trong một gói gzipped chỉ 3.7MB. Nó không phải là một trình giả lập; nó chính là engine Postgres thực sự chạy bên trong tiến trình Node.js hoặc tab trình duyệt của bạn mà không có phụ thuộc bên ngoài.

Triển khai: Cách sử dụng PGlite

PGlite cung cấp một giải pháp chính xác. Nó loại bỏ lớp mạng và thay thế bằng một API nội bộ trực tiếp. Đây là cách tôi đã sử dụng nó để cứu vãn dự án của mình.

1. Thiết lập tức thì trong Node.js

Bạn có thể bỏ qua hoàn toàn các lệnh apt-get hoặc brew install. Đối với một script backend hoặc một bộ test tạm thời, việc thiết lập chỉ mất một dòng lệnh.

npm install @electric-sql/pglite

Khởi tạo một cơ sở dữ liệu trong bộ nhớ (in-memory) rất đơn giản:

import { PGlite } from "@electric-sql/pglite";

const db = new PGlite();

// Tạo bảng người dùng với các trường id, tên và dữ liệu JSONB
await db.query("CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT, data JSONB);");
await db.query("INSERT INTO users (name, data) VALUES ($1, $2);", [
  "John Doe",
  { role: "admin", permissions: ["read", "write"] },
]);

const result = await db.query("SELECT * FROM users WHERE data->>'role' = 'admin';");
console.log(result.rows);

2. Postgres trong trình duyệt với khả năng lưu trữ bền vững

Điều kỳ diệu thực sự nằm ở trình duyệt. PGlite có thể lưu trữ dữ liệu bền vững bằng IndexedDB, nghĩa là cơ sở dữ liệu của bạn vẫn tồn tại sau khi tải lại trang. Điều này lý tưởng cho các dashboard hoạt động offline hoặc các công cụ cục bộ phức tạp.

import { PGlite } from "@electric-sql/pglite";

// Tiền tố 'idb://' tự động xử lý việc lưu trữ dữ liệu bền vững
const db = new PGlite("idb://my-local-store");

async function runPerformanceCheck() {
  const start = performance.now();
  await db.query("SELECT 1;");
  const end = performance.now();
  console.log(`Truy vấn cold start mất ${Math.round(end - start)}ms`);
}

3. Thêm tìm kiếm Vector với Extensions

Nhiều lập trình viên chọn Postgres cụ thể vì hệ sinh thái của nó. PGlite hỗ trợ các extension như pgvector, thứ thiết yếu cho các ứng dụng RAG (Retrieval-Augmented Generation) cục bộ và thử nghiệm AI.

import { PGlite } from "@electric-sql/pglite";
import { vector } from "@electric-sql/pglite/vector";

const db = new PGlite({
  extensions: { vector }
});

// Khởi tạo extension vector nếu chưa tồn tại
await db.query('CREATE EXTENSION IF NOT EXISTS vector;');
await db.query('CREATE TABLE items (id SERIAL, embedding vector(3));');
await db.query('INSERT INTO items (embedding) VALUES ("[1.1, 2.2, 3.3]");');

const res = await db.query('SELECT * FROM items ORDER BY embedding <-> "[1,2,3]" LIMIT 1;');
console.log("Kết quả khớp gần nhất:", res.rows);

Cách điều này thay đổi quy trình làm việc của bạn

PGlite không chỉ là một kỳ tích kỹ thuật thông minh; nó thay đổi cách chúng ta kiểm chứng mã nguồn. Thay vì mock một cơ sở dữ liệu trong Vitest hoặc Jest, bạn có thể chạy một instance Postgres thực thụ cho mỗi tệp test. Vì nó khởi động trong chưa đầy 100ms, bộ test của bạn vẫn nhanh nhạy trong khi vẫn đảm bảo chính xác 100% so với môi trường production.

Gần đây tôi đã bắt đầu sử dụng nó cho các “môi trường xem trước” (preview environments). Giờ đây tôi có thể cung cấp cho các bên liên quan một phiên bản đầy đủ chức năng của ứng dụng—bao gồm cả cơ sở dữ liệu đang hoạt động—chỉ bằng cách chia sẻ một URL. Không tốn chi phí RDS và không có hạ tầng backend nào cần quản lý. Trình duyệt xử lý tất cả mọi thứ.

Tổng kết: Kỷ nguyên mới cho cơ sở dữ liệu di động

Khi bạn đang nhìn chằm chằm vào một terminal bị lỗi giữa đêm, bạn không muốn sự phức tạp; bạn muốn những công cụ không gây cản trở mình. PGlite loại bỏ định kiến “Postgres rất nặng nề”.

Nó cho phép chúng ta sử dụng cơ sở dữ liệu quan hệ đáng tin cậy nhất thế giới trong những môi trường mà trước đây chúng ta chưa từng cân nhắc. Cho dù bạn đang xây dựng một tiện ích CLI, một bộ test tốc độ cao, hay một ứng dụng web local-first, PGlite là một cú hích lớn cho trải nghiệm của lập trình viên. Nó đã cứu vãn đêm của tôi, và rất có thể nó cũng sẽ cứu vãn dự án tiếp theo của bạn.

Share: