Mọi Người Đã Hiểu Sai về SQLite: Viên Ngọc Ẩn Cho Dự Án SaaS Của Bạn?

Quên những gì bạn từng nghĩ về SQLite đi. Dưới đây là một quan điểm có thể khiến bạn bất ngờ: SQLite *hoàn toàn có thể* là lựa chọn database tốt nhất cho dự án SaaS tiếp theo của bạn. Vâng, thật đấy. Cùng một database đang lưu trữ lịch sử duyệt web trên máy tính của bạn lại có khả năng xử lý tải công việc production hiệu quả hơn hẳn cụm Postgres phức tạp mà bạn đang cân nhắc.

Trước khi vội vàng đóng tab này trong sự thất vọng, hãy dành chút thời gian lắng nghe. Tôi đã chứng kiến vô số nhà phát triển gạt SQLite sang một bên như một “database đồ chơi”, trong khi vật lộn với vấn đề về connection pool, độ trễ replication và hóa đơn database lên tới 500 USD/tháng. Trong khi đó, các công ty như Expensify đang xử lý 10 tỷ USD giao dịch trên SQLite.

Vấn đề không nằm ở SQLite. Vấn đề là chúng ta đã nhìn nhận nó hoàn toàn sai cách.

Vấn đề mang tên “Lite”

Hãy đối mặt với điều hiển nhiên: cái tên “lite” không may mắn. Nó giống như đặt tên một chiếc xe thể thao là “Slow McLaren” – nghe có vẻ đúng trong một khía cạnh hẹp nào đó, nhưng lại hoàn toàn gây hiểu lầm về khả năng thực sự của nó.

SQLite không phải là “Postgres Lite” hay “MySQL cho người mới bắt đầu”. Nó là một loại database hoàn toàn khác. Trong khi Postgres được xây dựng cho kiến trúc client-server với các giao thức mạng và quản lý kết nối phức tạp, SQLite là một công cụ database nhúng (embedded). Nó “lite” không phải vì nó làm được ít hơn – nó “lite” vì nó không cần cả một tiến trình server, ngăn xếp mạng và hệ thống xác thực riêng biệt.

Dưới đây là những gì SQLite thực sự là:

  • Công cụ database được triển khai rộng rãi nhất trên thế giới (hàng tỷ instance).
  • Đã được thử nghiệm và chứng minh trong môi trường production bởi các công ty xử lý hàng triệu request.
  • Nhanh hơn các database client-server đối với nhiều loại tải công việc.
  • Tuân thủ ACID đầy đủ với hỗ trợ transaction hoàn chỉnh.
  • Có khả năng xử lý các database lên đến 281TB.

Nhưng chắc chắn rồi, hãy cứ gọi nó là “đồ chơi” chỉ vì cái tên đó.

Khi Nào SQLite Thực Sự Tỏa Sáng?

Đây là lúc quan điểm truyền thống đi ngược lại sự thật. SQLite không chỉ đơn giản là “đủ tốt” cho một số trường hợp – nó thực sự là **lựa chọn vượt trội** cho nhiều kịch bản production:

1. Tải Công Việc Đọc Nhiều (Read-Heavy)

Nếu ứng dụng của bạn có 95% là hoạt động đọc (giống như hầu hết các ứng dụng SaaS), SQLite sẽ khiến thiết lập Postgres của bạn “xấu hổ”. Không có roundtrip mạng. Không có chi phí kết nối. Chỉ đơn giản là đọc trực tiếp từ đĩa với bộ đệm trang thông minh. Tôi đã thấy thời gian truy vấn giảm từ 50ms xuống còn 0.5ms chỉ bằng cách chuyển từ Postgres sang SQLite.

2. Triển Khai Đơn Server (Single-Server Deployments)

Bạn đang chạy mọi thứ trên một server mạnh mẽ duy nhất? SQLite loại bỏ hoàn toàn một nhóm các vấn đề:

  • Không bị cạn kiệt connection pool.
  • Không có độ trễ mạng.
  • Không có kịch bản split-brain.
  • Không có độ trễ replication.
  • Sao lưu đơn giản chỉ là sao chép một tệp tin.

3. Edge Computing

Triển khai ứng dụng ở nhiều khu vực (regions)? Mỗi vị trí edge có thể có database SQLite riêng của mình. Các nền tảng như Cloudflare Workers, Fly.io làm điều này trở nên đơn giản. Người dùng của bạn nhận được thời gian phản hồi dưới 10ms, và bạn có một kiến trúc đơn giản.

4. Phân Tích Nhúng (Embedded Analytics)

Cần xử lý dữ liệu người dùng để phân tích? SQLite có thể xử lý các truy vấn phân tích phức tạp trên hàng gigabyte dữ liệu. Window functions, CTEs, các thao tác JSON – tất cả đều có sẵn. Và nó **nhanh** vì không có chi phí mạng.

Những Giới Hạn Thực Tế của SQLite (Không Phải Điều Bạn Nghĩ)

Hãy thành thật về những điểm mà SQLite gặp khó khăn, bởi vì đó không phải là những gì hầu hết mọi người nghĩ:

❌ Ghi Đồng Thời Cao (High Write Concurrency) – Nhưng Không Đơn Giản Như Thế

SQLite sử dụng mô hình một writer duy nhất. Chỉ một thao tác ghi tại một thời điểm. Nhưng điều mà những người “ghét” SQLite không nói cho bạn biết là: kể từ khi chế độ Write-Ahead Logging (WAL) được giới thiệu, SQLite có thể xử lý nhiều reader đồng thời KHI ĐANG GHI. Không còn tình trạng đọc bị chặn trong khi ghi nữa.

Ở chế độ WAL:

  • Writers không chặn readers.
  • Readers không chặn writers.
  • Nhiều reader làm việc đồng thời.
  • Hiệu suất ghi được cải thiện đáng kể.
-- Bật chế độ WAL (chỉ cần làm một lần)
PRAGMA journal_mode=WAL;

Với WAL được bật và cấu hình phù hợp, tôi đã thấy SQLite xử lý 500-1000+ thao tác ghi/giây trên phần cứng hiện đại, đồng thời phục vụ hàng nghìn lượt đọc đồng thời. Vâng, Postgres có thể đẩy lên số lượng cao hơn với nhiều writer, nhưng hãy tự hỏi: ứng dụng SaaS của bạn có thực sự cần hơn 1000 thao tác ghi mỗi giây một cách liên tục không? (Sự thật là: có lẽ là không).

❌ Nhiều Application Server Truy Cập Chung

Bạn cần mở rộng theo chiều ngang trên nhiều server cùng truy cập vào một database duy nhất? SQLite không được xây dựng cho điều này. Bạn sẽ cần Postgres hoặc MySQL. (Mặc dù các giải pháp như LiteFS và rqlite đang thay đổi cuộc chơi này).

❌ Kiểm Soát Truy Cập Phức Tạp (Complex Access Control)

SQLite không có khái niệm user, role, hay bảo mật cấp hàng (row-level security) tích hợp sẵn. Ứng dụng của bạn sẽ cần xử lý toàn bộ việc ủy quyền (authorization). Điều này thực tế không phải là vấn đề lớn đối với 99% ứng dụng SaaS, nơi bạn vẫn kiểm tra quyền hạn trong code anyway.

✅ Nhưng Đây Là Điều Mọi Người Hiểu Sai:

  • “SQLite không thể xử lý đọc đồng thời” – Sai. Nó xử lý số lượng đọc đồng thời không giới hạn.
  • “SQLite không hỗ trợ JSON” – Sai. Hỗ trợ JSON đầy đủ từ năm 2015.
  • “SQLite không thể tìm kiếm toàn văn (full-text search)” – Sai. FTS5 hoạt động rất tốt.
  • “Database SQLite dễ bị hỏng” – Sai. Nó là một trong những định dạng lưu trữ đáng tin cậy nhất từng được tạo ra.

Kiến Trúc Thay Đổi Mọi Thứ

Đây là cách để suy nghĩ về SQLite trong production:

Kiến Trúc Truyền Thống:
App Server → Mạng → Database Server → Đĩa

Kiến Trúc SQLite:
App Server → Đĩa

Chỉ vậy thôi. Các truy vấn database của bạn giờ đây giống như các lời gọi hàm. “Kết nối” của bạn là một handle tệp tin. Hệ thống sao lưu của bạn chỉ là lệnh

cp database.db backup.db

.

Sự đơn giản này không phải là giới hạn – nó là một siêu năng lực. Mỗi thành phần bạn loại bỏ là một thành phần không thể lỗi, không thể bị cấu hình sai và không cần giám sát.

Giấc Mơ Vận Hành Mà Bạn Không Biết Mình Cần

Hãy nói về điều sẽ khiến đội ngũ DevOps của bạn vui mừng khôn xiết: các thao tác vận hành với SQLite.

Sao Lưu? Chỉ Là Một Tệp Tin

# Toàn bộ chiến lược sao lưu của bạn
cp production.db backup-$(date +%Y%m%d).db

# Hoặc nâng cao hơn với phục hồi điểm thời gian bằng Litestream
# Tự động stream mọi thay đổi đến S3
litestream replicate production.db s3://mybucket/db

Chỉ thế thôi. Không cần `pg_dump`. Không cần điều phối các bản replica. Không cần lo lắng về tính nhất quán của bản sao lưu. Với Litestream, bạn có replication liên tục đến S3 với khả năng phục hồi điểm thời gian. Cài đặt trong 5 phút và quên nó đi.

Phục Hồi? Thậm Chí Còn Dễ Hơn

# Phục hồi từ hôm qua
cp backup-20250115.db production.db

# Hoặc phục hồi từ S3 với Litestream
litestream restore -o production.db s3://mybucket/db

Hãy thử so sánh điều này với việc phục hồi database Postgres 50GB xem. Tôi sẽ đợi.

Testing? Sử Dụng Dữ Liệu Production Thực Tế

# Clone prod để testing
cp production.db test.db
# Xong. Toàn bộ tập dữ liệu production. Không cần cấu hình gì.

Không cần “làm sạch” chuỗi kết nối. Không cần quản lý server database test. Không cần giải thích với bộ phận tài chính tại sao bạn cần thêm một instance RDS cho môi trường staging.

Giám Sát? Giám Sát Cái Gì?

  • Không có các metric về connection pool.
  • Không có độ trễ replication.
  • Không có cảnh báo về truy vấn chạy lâu.
  • Không có lịch trình vacuum.
  • Không có cảnh báo hết dung lượng đĩa.

Chỉ còn các metric ứng dụng thực sự quan trọng.

Các Mô Hình Production Thực Tế

Mô Hình 1: Write-Through Cache

// Thay vì các thiết lập phức tạp với Redis + Postgres
async function getUser(userId: string) {
    return db.get("SELECT * FROM users WHERE id = ?", userId);
    // SQLite *chính là* cache của bạn với độ trễ 0ms
}

Mô Hình 2: Database Riêng Theo Tenant

// Mỗi khách hàng có database SQLite riêng
function getCustomerDb(customerId: string) {
    return new Database(`./data/customers/${customerId}.db`);
    // Cô lập hoàn hảo, sao lưu dễ dàng, tuân thủ đơn giản
}

Mô Hình 3: Kiến Trúc Hybrid

// SQLite cho việc đọc, Postgres cho việc ghi
// Stream thay đổi từ Postgres sang các bản replica SQLite
// 99% truy vấn đến SQLite, 1% đến Postgres
const read = async (query: string) => sqlite.get(query);
const write = async (query: string) => postgres.execute(query);

Điều Quan Trọng: Khi Nào Nên Sử Dụng SQLite?

Hãy sử dụng SQLite khi:

  • Ứng dụng của bạn đọc nhiều (>90% reads).
  • Bạn có thể chạy trên một server (hiện tại server với 100GB RAM và 1TB storage có giá rất phải chăng).
  • Bạn coi trọng sự đơn giản và độ tin cậy.
  • Bạn muốn giảm thiểu chi phí vận hành (operational overhead).
  • Bạn đang xây dựng các ứng dụng nhúng (embedded) hoặc edge.
  • Bạn cần thời gian truy vấn nhất quán dưới mili giây.

Không nên sử dụng SQLite khi:

  • Bạn cần ghi đồng thời rất cao (>1000 ghi/giây liên tục).
  • Bạn yêu cầu nhiều writer từ các server khác nhau cùng ghi vào một database.
  • Bạn cần tính năng replication tích hợp sẵn (mặc dù LiteFS tồn tại để giải quyết phần nào).
  • Bạn cần kiểm soát truy cập (quyền hạn user/role) ở cấp database.

Cốt Truyện Bất Ngờ Mà Không Ai Nhắc Đến

Đây là bí mật “dơ bẩn” mà không ai nói ra: hầu hết các startup đang sử dụng Postgres sẽ tốt hơn nếu họ dùng SQLite. Họ đang trả tiền cho sự phức tạp của hệ thống phân tán mà họ không cần, trong khi lại nhận được hiệu suất kém hơn so với thiết lập SQLite đơn giản.

Tôi đã di chuyển một số hệ thống production từ Postgres sang SQLite. Kết quả:

  • Truy vấn nhanh hơn 10 lần (không mất thời gian qua mạng).
  • Giảm 90% sự phức tạp trong vận hành.
  • Tiết kiệm 500 USD/tháng chi phí database managed.
  • Thời gian sao lưu/phục hồi giảm từ hàng giờ xuống còn vài giây.
  • Không có downtime do các vấn đề về connection pool.

Ngừng Suy Nghĩ Theo Mô Hình Client-Server

Sự thay đổi tư duy lớn nhất là đây: hãy ngừng coi SQLite như một database server. Nó không phải. Nó là một **thư viện** mà ngẫu nhiên lại triển khai một database SQL. Khi bạn hiểu rõ điều này, mọi thứ sẽ trở nên mạch lạc.

Bạn sẽ không khởi tạo một server riêng biệt cho trình phân tích cú pháp JSON của mình. Bạn sẽ không tạo connection pool cho công cụ regex của mình. Vậy tại sao phải làm điều đó cho database khi SQLite có thể xử lý tải công việc của bạn như một thư viện?

Tương Lai Đã Ở Đây

Các nền tảng lớn đang đặt cược mạnh mẽ vào SQLite:

  • Durable Objects của Cloudflare sử dụng SQLite.
  • LiteFS của Fly.io cho phép triển khai SQLite phân tán.
  • Turso đang xây dựng một nền tảng SQLite phân tán.
  • Ngay cả Rails cũng đang thúc đẩy SQLite trở thành mặc định cho production.

Đây không phải là các dự án “đồ chơi”. Chúng là hạ tầng production phục vụ hàng tỷ request.

Đến Lượt Bạn

Trước khi mặc định chọn Postgres cho dự án tiếp theo, hãy tự hỏi:

  1. Bạn có thực sự cần hơn 1000 thao tác ghi liên tục mỗi giây không?
  2. Bạn có thực sự cần nhiều application server cùng ghi vào một database duy nhất không?
  3. Sự phức tạp trong vận hành (operational complexity) có xứng đáng với trường hợp sử dụng của bạn không?

Nếu câu trả lời là “không” cho bất kỳ câu hỏi nào trong số này, hãy nghiêm túc cân nhắc SQLite. Không phải như một bước đệm để chuyển sang một database “thực sự” sau này, mà là database production của bạn ngay từ đầu.

Chữ “lite” trong SQLite không có nghĩa là nó kém khả năng. Nó có nghĩa là nó mang theo ít “hành lý” hơn. Và trong môi trường production, ít “hành lý” hơn đồng nghĩa với tốc độ cao hơn, độ tin cậy tốt hơn và bạn sẽ ngủ ngon hơn.

Đừng suy nghĩ quá phức tạp nữa. Bắt đầu với SQLite. Bạn luôn có thể thêm sự phức tạp sau này nếu *thực sự* cần. Rất có thể, bạn sẽ không cần đến.

Một Lời Từ Tác Giả Đã Chọn Postgres (Và Lý Do)

Tôi xây dựng UserJot, một nền tảng quản lý phản hồi. Chúng tôi sử dụng Postgres, và vì những lý do chính đáng: chúng tôi cần khả năng mở rộng đa server, phụ thuộc nhiều vào pgvector cho tìm kiếm ngữ nghĩa, sử dụng LISTEN/NOTIFY cho cập nhật thời gian thực, và chạy các truy vấn phân tích phức tạp trên hàng triệu điểm dữ liệu.

Nhưng tôi cũng đã xây dựng một nền tảng blog hoàn toàn trên SQLite. Triển khai dưới dạng một binary duy nhất, replication bằng Litestream đến S3, mở rộng tuyệt vời trên một VPS giá 5 USD/tháng. Việc vận hành nó rất dễ chịu chính vì SQLite đã loại bỏ mọi sự phức tạp.

Bài học rút ra? Tôi chọn Postgres cho UserJot vì các nhu cầu cụ thể của chúng tôi đòi hỏi điều đó. Đối với nền tảng blog, SQLite là lựa chọn hiển nhiên. Hầu hết các dự án giống với nền tảng blog hơn là giống UserJot.

Hãy chọn công nghệ “nhàm chán” (boring technology), nhưng hãy chọn nó một cách có chủ đích.

Chỉ mục