- Cấu trúc một writer duy nhất và đặc tính nhúng của SQLite được chứng minh qua thực nghiệm là yếu tố giúp tăng khả năng mở rộng và hiệu năng
- Trong cùng điều kiện, Postgres giảm xuống 348 TPS khi có độ trễ mạng, trong khi SQLite đạt 44.096 TPS nhờ loại bỏ mạng
- Tận dụng mô hình một writer duy nhất với xử lý theo lô và giao dịch phân nhỏ dựa trên SAVEPOINT giúp đạt tối đa 186.157 TPS, và 102.545 TPS trong cấu hình ổn định
- Định luật Amdahl giải thích nút thắt cổ chai của cơ sở dữ liệu dựa trên mạng, còn SQLite duy trì hiệu quả cao nhờ tránh được điểm nghẽn này
- Kết quả này nhấn mạnh khả năng ứng dụng SQLite trong môi trường cục bộ và tầm quan trọng của việc loại bỏ nút thắt mạng
Cấu trúc của SQLite và môi trường thử nghiệm
- SQLite không có MVCC và chỉ cho phép một writer, nhưng chính cấu trúc này lại tạo điều kiện cho khả năng mở rộng cao
- Là cơ sở dữ liệu nhúng nên không có overhead mạng
- Benchmark được thực hiện trên MacBook Pro (2021) dùng Apple M1 Pro, RAM 16GB
- Thử nghiệm không nhằm tối ưu hóa tuyệt đối mà để cho thấy vẫn có thể đạt thông lượng ghi cao trong các điều kiện thông thường
Định nghĩa TPS và ví dụ về giao dịch
- TPS không chỉ là tốc độ ghi đơn thuần mà là giao dịch tương tác (Interactive Transaction)
- Ví dụ: khi chuyển tiền giữa các tài khoản, nhiều truy vấn và mã ứng dụng được thực thi trong cùng một giao dịch
- Giao dịch có thể rollback khi xảy ra lỗi nên đóng vai trò then chốt trong việc duy trì tính nhất quán
Cấu hình benchmark
- Sử dụng virtual threads dựa trên Clojure để mô phỏng lượng lớn yêu cầu đồng thời
- Postgres được cấu hình với connection pool dựa trên HikariCP, còn SQLite dùng một writer duy nhất và số kết nối đọc bằng số lõi CPU
- Cả hai cơ sở dữ liệu đều dùng bảng account đơn giản với các trường id, balance và chèn 1 tỷ hàng
- Hoạt động người dùng tuân theo phân phối power law (0.9995), với khoảng 100 nghìn người dùng hoạt động
Hiệu năng của cơ sở dữ liệu mạng (Postgres)
- Trên cùng một máy chủ, Postgres đạt 13.756 TPS
- Khi thêm độ trễ mạng 5ms, hiệu năng giảm mạnh xuống 1.214 TPS, và ở 10ms là 702 TPS
- Sau khi áp dụng mức cô lập tuần tự hóa, con số giảm còn 660 TPS, và khi thêm truy vấn bổ sung thì xuống 348 TPS
- Điều này cho thấy theo định luật Amdahl, nút thắt mạng giới hạn toàn bộ hiệu năng
- Khi độ trễ mạng tăng, tranh chấp khóa giao dịch trở nên nghiêm trọng hơn, khiến hệ thống không thể mở rộng
Lợi thế nhúng của SQLite
- Sau khi loại bỏ mạng, SQLite đạt 44.096 TPS
- Khi nút thắt mạng biến mất, tác động của định luật Amdahl được giảm xuống mức tối thiểu
- Khi áp dụng xử lý theo lô (batch processing) bằng cách tận dụng cấu trúc một writer duy nhất, hiệu năng tăng lên 186.157 TPS
- Điều chỉnh kích thước lô động để tự động tối ưu độ trễ (latency) và thông lượng (throughput)
Giao dịch phân nhỏ bằng SAVEPOINT
- Để tránh lỗi giao dịch riêng lẻ trong một lô, áp dụng giao dịch lồng nhau dùng SAVEPOINT
- Khi lỗi xảy ra, chỉ rollback giao dịch đó, còn toàn bộ lô vẫn được giữ nguyên
- Với cách này vẫn duy trì được 121.922 TPS
Kiểm thử tải hỗn hợp đọc/ghi
- Cấu hình 75% tổng yêu cầu là đọc, 25% là ghi
- Dùng thread pool đọc riêng để tách biệt, tránh yêu cầu đọc cản trở ghi
- Kết quả đạt 102.545 TPS
Tóm tắt so sánh hiệu năng
| Điều kiện |
Postgres |
SQLite |
| Không có mạng |
13.756 |
44.096 |
| Độ trễ 5ms |
1.214 |
n/a |
| Độ trễ 10ms |
702 |
n/a |
| 10ms + tuần tự hóa |
660 |
n/a |
| Xử lý theo lô |
n/a |
186.157 |
| Xử lý theo lô + SAVEPOINT |
n/a |
121.922 |
| Xử lý theo lô + SAVEPOINT + đọc |
n/a |
102.545 |
Kết luận
- SQLite đạt TPS cao hơn rất nhiều so với cơ sở dữ liệu dựa trên mạng nhờ mô hình một writer duy nhất và cấu trúc nhúng
- Hiệu quả được tối đa hóa bằng cách tránh giới hạn nút thắt mạng mà định luật Amdahl chỉ ra
- Toàn bộ mã nguồn đã được công khai trên GitHub, và các tài liệu về định luật Amdahl, power law, các trường hợp mở rộng SQLite cũng được cung cấp kèm theo
- SQLite là một lựa chọn rất hiệu quả cho xử lý giao dịch hiệu năng cao trong môi trường cục bộ
2 bình luận
Nếu chỉ dùng trong môi trường cục bộ, không cần ra máy chủ bên ngoài, thì ý là không cần phải nộp thứ “thuế” mang tên mạng đúng không. (VFS vs Socket)
Ý kiến Hacker News
Tôi đang xây dựng một máy chủ ORM/CRUD protobuf lai dựa trên SQLite
Mã nguồn và giải thích có tại GitHub - accretional/collector
Có thể đạt downtime 5~15ms khi sao lưu thời gian thực, xếp hàng hàng trăm yêu cầu đọc/ghi, độ trễ CRUD tổng thể ở mức khoảng 1ms, và cả sao lưu streaming dựa trên WAL
Trước đây tôi chỉ dùng Postgres và Spanner, nhưng nếu chỉ cần thêm tính năng phân vùng vào Collector thì có lẽ tôi sẽ không quay lại dùng Postgres nữa
Điểm yếu là toàn bộ dữ liệu và xử lý đều phải nằm trên một máy duy nhất
Nếu dùng instance AWS u-24tb1.112xlarge (448 vcore, 24TB RAM, 64TB EBS) thì vẫn còn khá dư dả
Bài viết nhấn mạnh hiệu quả của SQLite, nhưng tôi cảm thấy tiêu chí so sánh chưa rõ ràng
Vì ban đầu giả định kiến trúc máy chủ tách biệt, rồi lại đo hiệu năng của DB nhúng cục bộ
Nếu cùng điều kiện, Postgres chạy local và được tinh chỉnh cũng có thể cho hiệu năng tương tự
Việc giới hạn Postgres ở 8 kết nối có thể là một nút thắt cổ chai
Sẽ tốt hơn nếu công bố cả mức sử dụng CPU và thread, rồi thử lại với connection pool lớn hơn
Nếu tăng lên 64 kết nối thì throughput có thể tăng gấp 8 lần. Cần mở rộng cấu hình client cho đến khi chạm giới hạn
Điểm cốt lõi là nhận ra độ trễ mạng có phải nút thắt hay không
Với nhiều workload, một DB local bình thường còn nhanh hơn một DB remote xuất sắc
Điều quan trọng không phải là “DB nào tốt nhất” mà là “có cần vượt qua ranh giới mạng hay không”
DB kiểu mạng có ưu điểm là dễ tái triển khai ứng dụng
Có thể khởi chạy instance mới rồi tắt cái cũ, gần như đạt triển khai không gián đoạn
Nếu đặt SQLite trên cùng instance thì khi thay thế phải dựng lại DB nên phức tạp hơn. Tôi muốn biết trong vận hành thực tế bạn có gặp vấn đề này không
Khi migration có thể sẽ có downtime. Nhờ Litestream mà bây giờ việc sao chép và sao lưu đã dễ hơn
Tác giả đặt
PRAGMA synchronous="normal", mà thiết lập này không thực hiện fsync mỗi lầnĐể so sánh công bằng thì nên đặt thành
"full""normal"cũng ổn. Khi mất điện thì mất độ bền, nhưng tính nhất quán của transaction vẫn được giữTôi tò mò cấu hình HA (khả dụng cao) của SQLite sẽ như thế nào
Ít nhất cũng phải đạt mức có thể tự động failover
Hiện tôi đang phân vân giữa Postgres và SQLite (bao gồm litestream).
Ứng dụng của tôi cho phép một ít downtime, nên scale dọc trên một máy đơn giản và rẻ hơn
Marmot GitHub mới được thêm cơ chế sao chép dựa trên gossip
Tôi muốn biết có trường hợp nào thực sự đẩy SQLite đến giới hạn trong production hay không
Tôi muốn hỏi ý kiến về giới hạn số người dùng của SQLite vs Postgres trong môi trường web app hoặc thương mại điện tử thông thường là khoảng bao nhiêu
SQLite với các bản cập nhật gần đây cho phép đọc đồng thời nhưng vẫn chỉ cho phép một ghi duy nhất
Tôi muốn biết khi nào điều này trở thành vấn đề, và nếu có tính đến mở rộng thì liệu bắt đầu với Postgres có tốt hơn không