lobste.rs chuyển từ MariaDB sang SQLite
(lobste.rs)- Sau khi chuyển từ MariaDB sang SQLite, lobste.rs đã xử lý ổn định cả đợt tăng vọt lưu lượng vào thứ Hai, đồng thời ghi nhận mức sử dụng CPU và bộ nhớ giảm cùng cảm nhận phản hồi nhanh hơn
- Khi tắt VPS MariaDB riêng, chi phí VPS giảm một nửa, và nhiệm vụ migration bắt đầu từ năm 2019 cũng được hoàn tất
- Ở lần triển khai đầu tiên, chỉ riêng lưu lượng đọc cũng đã khiến toàn bộ CPU chạm 100% nên phải rollback, nhưng sau khi sửa 2 lần quét toàn bộ bảng và vấn đề N+1 trên các bảng lớn thì đã triển khai lại thành công
- Bên cạnh việc triển khai
regexp,if,stddevbằng hàm do người dùng định nghĩa của SQLite, nhóm cũng phải xử lý các giới hạn tương thích như không hỗ trợ unsigned bigint vàNOCASEchỉ xử lý ASCII - Để thay đổi cơ sở dữ liệu nền mà không truy cập vào cơ sở dữ liệu production, cần có dữ liệu kiểm thử với quy mô thực tế, test suite, checklist triển khai/rollback và sự phối hợp chặt chẽ
Từ thảo luận năm 2019 đến việc chuyển sang SQLite
- Thảo luận về migration bắt đầu từ issue #539 vào năm 2019
- Khi đó, MySQL được đề xuất như một phương án thay thế để cân nhắc tính tương thích với MariaDB, nhưng quá trình thảo luận chuyển sang PostgreSQL đã diễn ra sẵn
- Năm 2025, khi Rahul nhắc đến việc K1 mua lại MariaDB, các trao đổi chi tiết về migration sang PostgreSQL tiếp tục được nối lại
- Đến tháng 2, Rahul hỏi liệu lobsters có thể chạy trên SQLite hay không, và từ tháng 6 năm 2025, việc tham gia vào dự án bắt đầu được đẩy mạnh
- PR đầu tiên vào tháng 8 năm 2025 đã bị đóng như một PR cũ trên GitHub sau khi công việc bị gián đoạn, nên không thể mở lại
- PR thứ hai bao gồm kiểm thử hiệu năng, script migration giữa các DB tự viết, và việc debug liên quan đến tính toàn vẹn dữ liệu
- Không có script chuyển đổi MariaDB/MySQL→SQLite sẵn có nào đáp ứng được yêu cầu, nên nhóm đã viết một script riêng
Thất bại ở lần triển khai đầu và sửa nút thắt hiệu năng
- Lần triển khai đầu tiên vào ngày 21 tháng 2 được thực hiện sau khi chuẩn bị checklist triển khai, nhưng ngay sau khi triển khai, chỉ riêng lưu lượng trong trạng thái chỉ đọc đã khiến toàn bộ CPU chạm 100%
- Do không tìm được nguyên nhân nên đã rollback
- Vì không thể truy cập DB production nên rất khó tái hiện trước các vấn đề hiệu năng thực tế
- Hai ngày sau thất bại đó, PR thứ ba và cũng là PR cuối cùng được mở để sửa các vấn đề liên quan đến tìm kiếm, đồng thời bổ sung script tạo dữ liệu dung lượng lớn nhằm tạo dữ liệu cục bộ có kích thước bằng một nửa dữ liệu lobsters thực tế
- Mất một tuần để tạo dữ liệu
- Nút thắt ở lần triển khai đầu là các lần quét toàn bộ bảng do hai truy vấn gây ra trên các bảng lớn, cùng với một vấn đề N+1 riêng biệt
- Nhóm đã áp dụng thay đổi 1, thay đổi 2, thay đổi 3 để xử lý chúng
- Sáng ngày triển khai, nhóm còn thêm slow query log để có thể theo dõi thêm các vấn đề hiệu năng
Kết quả triển khai lại và giảm chi phí vận hành
- Ở lần triển khai thứ hai vào ngày 11 tháng 7, checklist triển khai/rollback tiếp tục được chuẩn bị lại, và sau khi chuyển đổi, website vẫn vận hành bình thường, mức sử dụng CPU và bộ nhớ cũng được duy trì ổn định
- Trong khi theo dõi các chỉ số của website và IRC, nhóm đã xử lý ngay các vấn đề được báo cáo bằng bản sửa 1, bản sửa 2
- Ngay cả trong đợt tăng vọt lưu lượng vào thứ Hai cũng không phát sinh vấn đề, đồng thời xác nhận mức sử dụng CPU/bộ nhớ giảm và cảm nhận phản hồi nhanh hơn
- Khi dừng VPS MariaDB, chi phí VPS giảm một nửa, và issue migration cũng được đóng lại
Những giới hạn của SQLite khác với MariaDB
- Tận dụng hỗ trợ hàm do người dùng định nghĩa (UDF) của gem SQLite, nhóm đã tự triển khai
regexp,if,stddevvốn không có sẵn trong SQLite- Nhờ đó không cần thêm quá nhiều cách обход cho SQL migration
- SQLite không hỗ trợ unsigned bigint vốn được dùng cho một số ID trong MariaDB, nên đã đổi sang bigint
- Quy tắc sắp xếp cũng bị giới hạn hơn MariaDB
- MariaDB dùng
utf8mb4_general_ci, còn trên SQLite áp dụngNOCASE NOCASEkhông hỗ trợ chuyển đổi hoa/thường đầy đủ cho toàn bộ UTF mà chỉ xử lý ký tự ASCII
- MariaDB dùng
- Với bảng tìm kiếm toàn văn của SQLite, cách phù hợp là Contentless-Delete Tables, vốn là phương pháp được khuyến nghị thay vì cấu hình mặc định
- Thiết lập PRAGMA mặc định của Rails hoạt động bình thường trên lobsters
- Vì migration cơ sở dữ liệu khác nhau theo từng DB, các migration cũ được chuyển sang thư mục old_migrations để
db:migratetiếp tục hoạt động
- Vì migration cơ sở dữ liệu khác nhau theo từng DB, các migration cũ được chuyển sang thư mục old_migrations để
Bài học rút ra về kiểm thử và phối hợp
- Trong codebase của lobsters, có search parser và heinous_inline_partials, một kiểu hack để tăng tốc độ render
- Nhờ test suite, có thể xác minh tính chính xác của việc chuyển sang SQLite mà không cần lượng lớn kiểm thử thủ công
- Giao tiếp trơn tru giữa những người tham gia là yếu tố quyết định thành công của migration
- Việc thay thế cơ sở dữ liệu nền trong khi không thể truy cập DB production là cực kỳ khó
- Nếu phải làm lại công việc này, cần chuẩn bị trước quy mô dữ liệu thực tế
- Nếu trong quá trình test có thể cấu hình để thất bại ngay khi xuất hiện quét toàn bộ bảng, thì đã có thể phát hiện sớm vấn đề hiệu năng của lần triển khai đầu
- Cần có công cụ để dễ dàng tạo bộ dữ liệu tương tự production mà không phải tự viết thủ công hoặc chờ cả một tuần
1 bình luận
Ý kiến trên Lobste.rs
@thomas0 đã cẩn thận phụ trách đợt di chuyển quy mô lớn, @355E3B hỗ trợ lập kế hoạch và vận hành, và nhờ những người đóng góp liên quan đến #539 cùng #lobsters nên cả khoảng thời gian chỉ đọc cũng trôi qua khá vui vẻ
Ban đầu ở #539 họ dự định dùng PostgreSQL, nhưng vì người tình nguyện trực tiếp hỗ trợ công việc này đã dùng SQLite và họ muốn tránh một giải pháp lớn, phức tạp hơn nhu cầu thực tế nên đã chọn SQLite. PostgreSQL thường là lựa chọn mặc định của họ, nhưng nó kéo theo gánh nặng phải vận hành, điều phối và bảo trì một dịch vụ riêng
Đáng ngạc nhiên là sau khi di chuyển, mức sử dụng CPU và RAM đều giảm, và biểu đồ chi tiết nằm ở phần cuối của #539
Mỗi lần nhìn vào cấu hình mặc định của SQLite đều thấy bất ngờ. Sẽ rất tốt nếu có một chế độ cho dự án mới với WAL, khóa ngoại,
synchronous=NORMALv.v. được bật sẵn theo mặc định, và có lẽ vẫn còn thêm vài giá trị mặc định khác nên được cải thiệnTôi tò mò về chiến lược sao lưu cho cơ sở dữ liệu SQLite. Không rõ họ có dùng công cụ như litestream hay áp dụng cách khác không
Tôi muốn biết lobste.rs xử lý các tác vụ cập nhật như thế nào
SQLite đã có sẵn hàm iif, nhưng bản thân việc vận hành hàm do người dùng định nghĩa trong cùng khu vực đó cũng khá thú vị
Trước đây tôi nghĩ SQLite không hỗ trợ đồng thời ở mức như PostgreSQL hay MySQL, nhưng đọc tài liệu tổng quan thì có vẻ giờ nó có thể được dùng từ nhiều tiến trình; vậy có đúng là lobste.rs vẫn phải chạy trên một máy chủ duy nhất không
Tôi cũng muốn biết liệu các tiến trình có đồng bộ với nhau bên trong SQLite bằng bộ nhớ chia sẻ hay
mmaphay cách nào đó, để chỉ có một tiến trình được ghi tại một thời điểm còn nhiều tiến trình được đọc đồng thời, và các tác vụ ghi đang chờ có xếp hàng lần lượt hay khôngVới các tác vụ ghi phải chờ, có vẻ nó không dùng hàng đợi bộ nhớ chia sẻ tường minh mà là đợi một lúc rồi thử lại. Tôi không chắc việc dùng SQLite trên nhiều máy là hoàn toàn bất khả thi, nhưng vì cách đơn giản là chia sẻ tệp qua NFS bị khuyến cáo rõ ràng là không nên, nên xem đây là cấu hình một máy chủ duy nhất là một cách gần đúng hợp lý; mà hiện nay một máy cũng có thể xử lý quy mô khá lớn
busy_timeouthoạt động Tài liệu tham khảo: https://fractaledmind.github.io/2024/04/… , https://kerkour.com/sqlite-for-servers và https://www.sqlite.org/lang_transaction.html#immediate ; có thể xem chi tiết ở phầnSQLITE_BUSYcủa liên kết thứ hai và liên kết thứ baĐây là một bản nâng cấp ấn tượng, với các chi tiết được ghi chép cực kỳ tốt trong PR, issue và Gist. Tôi tò mò liệu thay đổi này có tạo ra khác biệt về tính năng như tìm kiếm trên trang hay không, và hiện tại tệp SQLite trên đĩa có kích thước bao nhiêu
Tôi nhớ là đã có chỗ nói tệp SQLite có kích thước 3.8GB