- OpenRun là nền tảng triển khai ứng dụng web cho công cụ nội bộ; thay vì hệ thống tệp, nền tảng này lưu tệp tĩnh, mã ứng dụng và tệp cấu hình trong SQLite, quản lý trạng thái triển khai theo hướng lấy cơ sở dữ liệu làm trung tâm
- Mục tiêu cốt lõi là xử lý các bản cập nhật ứng dụng, trong đó nhiều tệp thay đổi cùng nhau, như một transaction duy nhất, nhằm tránh tình trạng trang web bị lỗi được phục vụ trong lúc chuyển đổi phiên bản
- Bằng cách dùng SHA256 hash trước khi nén làm khóa chính, hệ thống giảm việc lưu trữ trùng lặp tệp giữa các phiên bản ứng dụng và giữa các ứng dụng staging, preview, production
- Cách lưu trữ bằng SQLite giúp đơn giản hóa rollback, backup, lưu hash cho ETag và lưu dạng nén Brotli; nếu cần, cũng có thể xử lý dữ liệu GZip hoặc không nén bằng cách thêm cột
- Hiện hệ thống chạy trên một node; khi hỗ trợ multi-node, dự kiến sẽ dùng Postgres dùng chung cùng với cache tệp SQLite cục bộ để giảm độ trễ
Cách OpenRun lưu trữ tệp
- OpenRun là nền tảng triển khai mã nguồn mở dành cho các công cụ nội bộ theo hướng code-first, triển khai ứng dụng web theo kiểu GitOps lên một node đơn hoặc cụm Kubernetes
- Thay vì đặt nội dung tĩnh trên hệ thống tệp như máy chủ web thông thường, OpenRun lưu dữ liệu ứng dụng như tệp tĩnh, mã ứng dụng và tệp cấu hình trong SQLite
- Vì metadata của ứng dụng được tạo động, việc lưu trong cơ sở dữ liệu là tự nhiên; nếu xử lý cả tệp trong cùng một tầng lưu trữ thì cũng dễ quản lý trạng thái triển khai cùng nhau hơn
- Khi tạo và cập nhật ứng dụng, các tệp được upload từ GitHub hoặc ổ đĩa cục bộ vào cơ sở dữ liệu SQLite
- Chỉ trong chế độ phát triển mới dùng hệ thống tệp cục bộ
Lý do chọn SQLite
- Cập nhật bằng transaction là lợi thế lớn nhất
- Có thể gom nhiều thay đổi tệp vào một transaction để xử lý
- Nhờ tính cô lập, ứng dụng web bị lỗi sẽ không được phục vụ trong lúc cập nhật
- Nếu xảy ra lỗi triển khai, có thể rollback theo đơn vị transaction của cơ sở dữ liệu
- Ngay cả khi nhiều ứng dụng được cập nhật đồng thời, cũng có thể hoàn tác cùng lúc
- Đơn giản hơn so với cách tìm và dọn dẹp các tệp đã thay đổi trên hệ thống tệp
- OpenRun tự động quản lý phiên bản mọi bản cập nhật, và dữ liệu tệp được lưu trong bảng có schema sau
CREATE TABLE files (sha text, compression_type text, content blob, create_time datetime, PRIMARY KEY(sha));
- Vì dùng SHA256 hash của nội dung trước khi nén làm khóa chính, cùng một nội dung tệp chỉ được lưu một lần qua nhiều phiên bản
- Mỗi ứng dụng production có một ứng dụng staging, và có thể có nhiều ứng dụng preview, nên có thể phát sinh trùng lặp tệp
- Lưu trữ dựa trên SQLite giúp không lưu trùng lặp các tệp có cùng nội dung ngay cả giữa các ứng dụng
Backup, caching và xử lý nén
- Có thể backup toàn bộ trạng thái hệ thống, metadata và tệp bằng các công cụ backup SQLite như Litestream
- Nếu lưu một lần SHA của nội dung cần cho header ETag dùng để cache trên trình duyệt khi upload tệp, về sau không cần tính lại
- Nội dung tệp được lưu trong bảng SQLite ở dạng nén Brotli
- Với cách dùng cơ sở dữ liệu, có thể thêm cột vào bảng
files để lưu cả dữ liệu nén GZip hoặc dữ liệu không nén
Hiệu năng và kế hoạch multi-node
- Trong OpenRun, cách dùng cơ sở dữ liệu SQLite mang lại hiệu năng tốt
- Do không có triển khai tương đương dựa trên hệ thống tệp, nhóm không thực hiện benchmark so sánh trực tiếp
- Theo benchmark của nhóm SQLite, trong một số workload, SQLite có thể cho hiệu năng tốt hơn so với dùng trực tiếp hệ thống tệp
- OpenRun hiện chạy trên một node
- Trong tương lai, khi bổ sung hỗ trợ multi-node, dự kiến sẽ dùng cơ sở dữ liệu Postgres dùng chung để lưu metadata và dữ liệu tệp thay cho SQLite cục bộ
- Cách này có thể phát sinh vấn đề độ trễ
- Để tránh độ trễ khi truy cập Postgres, dự kiến sẽ dùng cơ sở dữ liệu SQLite cục bộ làm cache tệp
Vì sao cách dùng hệ thống tệp phổ biến hơn
- Một trong những lý do hầu hết máy chủ web dùng hệ thống tệp là sự tiện lợi
- Có thể sao chép và cập nhật tệp bằng các công cụ hệ thống tệp sẵn có như rsync, tar
- Một lý do khác là bối cảnh lịch sử
- Hệ thống tệp đã được sử dụng từ trước khi có các cơ sở dữ liệu quan hệ in-process tốt
- Để dùng cơ sở dữ liệu làm kho lưu trữ tệp, cần có giao diện API để upload tệp, và đây không phải lúc nào cũng là cách khả thi
1 bình luận
Ý kiến trên Hacker News
Tôi đã thử nghiệm ý tưởng này vài năm trước, một phần lấy cảm hứng từ bài viết “35% Faster Than The Filesystem”: https://www.sqlite.org/fasterthanfs.html
Ghi chú khi đó ở đây: https://simonwillison.net/2020/Jul/30/fun-binary-data-and-sq...
Tôi đã tạo plugin https://datasette.io/plugins/datasette-media để phục vụ file tĩnh từ SQLite trong Datasette, và nó hoạt động tốt, nhưng thành thật mà nói thì sau khi làm xong tôi cũng không dùng nhiều
Một khái niệm liên quan là phục vụ map tile từ SQLite, và https://datasette.io/plugins/datasette-tiles làm việc đó. Hóa ra định dạng MBTiles là một cơ sở dữ liệu SQLite đầy các ảnh PNG
Nếu muốn thử nghiệm SQLite để phục vụ file, công cụ CLI “sqlite-utils insert-files” có thể hữu ích cho bước thiết lập cơ sở dữ liệu ban đầu: https://sqlite-utils.datasette.io/en/stable/cli.html#inserti...
Content hash chỉ cần tạo một lần khi upload file, không cần tạo lại mỗi lần web server khởi động hoặc thêm bước build để đổi tên file thực tế. Cũng có thể áp dụng động cho file trên file system (https://github.com/benbjohnson/hashfs xem triển khai embedFS), nhưng cơ sở dữ liệu giúp việc này dễ hơn một chút
Nếu tôi nhớ không nhầm, requests-cache lưu cache request trong SQLite theo
(date, URI): https://github.com/requests-cache/requests-cache/blob/main/r...Tìm kiếm pyfilesystem SQLite: https://www.google.com/search?q=pyfilesystem+sqlite
Tìm kiếm sendfile mmap SQLite: https://www.google.com/search?q=sendfile+mmap+sqlite
https://github.com/adamobeng/wddbfs là “một nhà cung cấp webdavfs có thể đọc nội dung cơ sở dữ liệu sqlite”
Có lẽ cũng có một cách hay để triển khai file system trên SQLite, chồng thêm quyền file Unix và thuộc tính file mở rộng xattrs
Liệu SQLite có nhanh hơn hoặc tiện hơn, chẳng hạn so với ngx_http_memcached_module.c không? Tôi cũng tò mò không biết SQLite có ACL ở mức ô hay không
Khi đọc file tĩnh, mỗi request đều phải mở file, đọc rồi đóng lại, nên ngay cả khi tầng file system đã cache nội dung file thì vẫn phát sinh nhiều context switch hơn. Nếu muốn tăng tốc chuyện này thì gắn một frontend cache sẽ phù hợp hơn là biến mọi thứ thành cơ sở dữ liệu. Nó nhanh hơn SQLite và cũng dễ bảo trì, dễ xử lý sự cố hơn
Kể cả file system chạy hoàn toàn trong user space. FUSE thì không tính vì lời gọi vẫn phải đi qua kernel
Nói rằng lợi ích chính là “cập nhật theo giao dịch” thì vẫn còn hạn chế. Dù máy chủ dùng SQLite hay hệ thống tệp, chỉ riêng điều đó cũng không thể ngăn một ứng dụng web bị hỏng trong lúc cập nhật
Mỗi trang trong trình duyệt là một cây tài nguyên được tải qua các yêu cầu HTTP riêng biệt, nên nó không phải đối tượng của hệ thống giao dịch/cập nhật nguyên tử ở phía máy chủ. Dù bạn biến mọi tài nguyên trên máy chủ thành giao dịch, trình duyệt vẫn có thể thấy tổ hợp lẫn lộn giữa tài nguyên cũ và mới
Cách giải quyết thông thường là đặt mọi tài nguyên con của trang (bundle JavaScript, stylesheet, media, v.v.) bằng tên (URL) có chứa content hash hoặc phiên bản. Nếu tài liệu HTML gốc tải phiên bản X thì mọi tài nguyên con cũng phải tải phiên bản X tương ứng
Ngoài ra, khi cập nhật từ X sang Y, sau khi bắt đầu phục vụ trang Y thì vẫn phải tiếp tục cung cấp các tài nguyên con của X thêm một thời gian. Nếu không duy trì cho đến khi có thể chắc chắn hợp lý rằng không còn trình duyệt nào đang tải trang X nữa, thì trang X có thể bị hỏng
Vì vậy, nếu muốn đặt HTML gốc và các tài nguyên con vào một bundle được thay thế nguyên tử duy nhất thì ngược lại lại không ổn. Lý do là các tài nguyên con cũ vẫn có thể còn được tham chiếu, nhưng bạn đã xóa chúng đi
Trong một số trường hợp, có thể bạn muốn quản lý phiên bản một số tài nguyên con như tệp media tách biệt với tài liệu HTML. Nếu muốn cập nhật mà không làm mất hiệu lực toàn bộ cache của các thành phần cấu trúc ứng dụng như các khối JavaScript hay stylesheet, thì hệ thống build trang cũng có thể cần tính đến điều này
Khi một công ty lớn thử nghiệm việc này (vào thời điểm đó họ quan sát được phần đáng kể của web), đa số người dùng (hơn 80%) giữ web app mở khoảng 2~3 ngày. Có lẽ bị lệch vì nhiều người để tab mở suốt cuối tuần
Mốc 95% là khoảng 2 tuần, còn 100% là khoảng 600 ngày. Tức là có người đã để tab mở gần 2 năm
Nếu nhắm tới 100% thì sẽ phải chờ khá lâu. Toàn bộ các con số này đều dựa trên trí nhớ, và giờ tôi cũng không còn làm ở công ty đó nữa
Kịch bản người dùng ở lại trên một trang rất lâu rồi nhận một liên kết hỏng là vấn đề gần với phía SPA hơn
Nhìn chung tôi đồng ý, nhưng cập nhật theo giao dịch chỉ ngăn được một nhóm vấn đề liên quan đến cập nhật mà thôi. Những vấn đề khác ở cấp ứng dụng vẫn có thể tạo ra trải nghiệm bị hỏng
Việc tiếp tục phục vụ các phiên bản cũ của nội dung tĩnh được tham chiếu bằng content hash là khả thi, nhưng hiện tại Clace chưa triển khai điều đó
Mẹo cốt lõi là tải lên các thay đổi không phải HTML trước rồi mới thay đổi HTML, để không tham chiếu tới tệp chưa tồn tại. Nếu muốn làm ứng dụng phức tạp hết mức có thể, bạn có thể áp dụng duyệt sâu trước cho quá trình upload. Nhưng nếu coi trọng sức khỏe tinh thần thì tốt hơn nên giảm nhẹ vấn đề và chọn upload tài sản trước trong ứng dụng
Hồi 2011/2012, khi làm ở một studio game nhỏ, tôi đã đề xuất chuyển toàn bộ tài sản dưới 100KB sang DB sqlite3, rồi tạo “pak file” và lưu offset của các tệp đó trong DB sqlite3
Lựa chọn này chịu ảnh hưởng từ một bài nói chuyện hồi cứu của Richard Hipp, trong đó ông nói rằng nhìn lại thì giá mà đã xử lý BLOB giống inode, đặt chúng ở các offset sâu hơn trong cơ sở dữ liệu và append BLOB vào tệp
Việc tải tài sản nhanh khủng khiếp. Đây là game di động nên chỉ có rất ít tài sản không nằm trong DB. Sau đó cũng thú vị khi thấy ngày càng nhiều người áp dụng cách này
Một lợi thế khác dễ bị bỏ qua là bạn có thể gắn gần như vô hạn metadata bên cạnh nội dung, nhờ đó có thể tìm các tệp “tương tự” bằng truy vấn cơ sở dữ liệu
Chúng tôi nhét rất nhiều metadata vào DB, và pak file cuối cùng hình như khoảng 200MB còn cơ sở dữ liệu khoảng 20MB. Nhắc lại là game di động
Điều tệ nhất ở phía client là một phép tự join kép mà chúng tôi không thể giảm bớt vì độ phức tạp phía server. Điều khiến tôi bực nhất là chúng tôi không thể tự làm phần triển khai server; bên cùng làm rất kém về phát triển phần mềm nên họ thường thay đổi mà không thông báo đầy đủ đặc tả backend, khiến bản build đột nhiên hỏng
Chúng tôi cũng dùng một cơ sở dữ liệu sqlite3 riêng cho replay của game, và sau khi trận đấu kết thúc có thể phát lại toàn bộ ván để xem từng đối thủ đã làm gì. Nó cũng cực kỳ tốt cho kiểm thử tự động
Trong hệ thống kiểm soát thay đổi lix, thay vì xử lý hệ thống tệp và git, chúng tôi cũng đi theo hướng đưa tệp vào SQLite. Bài này nói về vấn đề mà chúng tôi gặp phải: https://opral.substack.com/i/150054233/breaking-git-compatib...
Những vấn đề như khóa tệp, đồng thời hóa thì SQLite giải quyết giúp
Khi dùng SQLite, bạn có thể truy vấn tệp bằng SQL thay vì API hệ thống tệp khác nhau theo từng nền tảng
Có thể viết truy vấn SQL an toàn kiểu với Kysely https://kysely.dev/ mà không cần ORM
Tuy nhiên cần cẩn thận ở chỗ cơ sở dữ liệu SQLite sẽ không thu nhỏ lại nếu không vacuum. Về cơ bản đây là thao tác sao chép dữ liệu sang một tệp riêng rồi xóa bản gốc
Đây là việc bạn phải tự làm thủ công vào thời điểm hợp lý trong ứng dụng, nên nếu dùng theo kiểu ghi và xóa dữ liệu nhị phân thì cần để ý mức sử dụng đĩa
Thú vị là CMS static site generator tôi làm lại hoạt động chính xác theo hướng ngược lại với cách ở đây
Trong lúc phát triển/cập nhật website, mọi trang và bài viết đều là entry trong cơ sở dữ liệu SQLite, và được thao tác thông qua giao diện web hiển thị phiên bản website có thể chỉnh sửa
Sau đó website được dump thành các trang tĩnh ra hệ thống tệp để tự triển khai trực tiếp, hoặc tải xuống dưới dạng zip rồi upload lên nơi khác để triển khai, bao gồm cả các dịch vụ hosting tĩnh hoàn toàn
Theo “Appropriate Uses For SQLite” của SQLite https://www.sqlite.org/whentouse.html, lượng truy cập web mà SQLite có thể xử lý phụ thuộc vào mức độ trang web sử dụng cơ sở dữ liệu nặng đến đâu
Nói chung, các trang có dưới 100K lượt hit mỗi ngày sẽ hoạt động tốt với SQLite. 100K/ngày là ước tính thận trọng chứ không phải giới hạn cứng. Đã có trường hợp SQLite xử lý lượng truy cập gấp 10 lần con số đó
Trang web SQLite(https://www.sqlite.org/) dĩ nhiên cũng dùng SQLite, và tính đến năm 2015 đã xử lý khoảng 400K~500K yêu cầu HTTP mỗi ngày, trong đó 15~20% là các trang động có đụng tới cơ sở dữ liệu. Nội dung động sử dụng khoảng 200 câu lệnh SQL cho mỗi trang web
Cấu hình này chạy trên một VM duy nhất, dùng chung máy chủ vật lý với 23 VM khác, mà vẫn giữ load average dưới 0.1 trong phần lớn thời gian. Tham khảo: https://news.ycombinator.com/item?id=33975635
Với workload thiên về đọc như phục vụ tệp tĩnh, SQLite có thể xử lý nhiều hơn rất nhiều. Nếu đã đặt header cache nội dung thì trình duyệt sẽ tự cache, nên máy chủ chỉ cần phục vụ cho client mới
Trong phần lớn trường hợp, có vẻ SQLite sẽ không trở thành nút thắt cổ chai
Ý tưởng phục vụ nội dung tĩnh bằng SQLite chỉ dựa trên trang “35% Faster Than The Filesystem” năm 2017 thì nói nhẹ cũng là còn non
Các web server hiện đại như Nginx dùng những chiến lược tối ưu cho xử lý tệp tĩnh. Bắt đầu từ sendfile rồi đến io_uring và các thao tác splice, chạy trong thread pool được thiết kế tốt trên nền epoll, kqueue hoặc eventport tùy nhu cầu
Trong khi đó, điều tốt nhất mà SQLite về cơ bản có thể cung cấp chỉ là hỗ trợ I/O ánh xạ bộ nhớ(https://www.sqlite.org/mmap.html)
Cách này có thể phù hợp với dịch vụ một client như web app host cục bộ(https://github.com/electron/asar cũng đáng tham khảo). Nhưng với website lớn thì giống như các bình luận khác nói, đây là đang cố giải một vấn đề vốn không tồn tại
Tôi làm khá nhiều tính toán khoa học hiệu năng cao, và nhiều khi cách linh hoạt nhất và nhanh nhất, nhất là khi truy cập dữ liệu song song, lại là cơ sở dữ liệu SQLite chỉ đọc trên RAM disk
Nghe rất hacky, nhưng đến giờ vẫn là cách dễ cấu hình và nhanh hơn mọi phương án khác mà tôi từng tìm thấy
Tôi từng thấy một người bạn bên thiên văn học nói rằng rất nhiều người trong giới khoa học nên làm quen với cơ sở dữ liệu. Nếu không, cuối cùng họ sẽ đổ cả đống công sức để vô tình tự tạo ra một cơ sở dữ liệu tệ hại của riêng mình
Lý do cách tiếp cận này không phổ biến hơn là vì hệ thống tệp vốn đã rất giỏi xử lý tệp
Nếu cần cập nhật nguyên tử thì chỉ việc checkout sang thư mục mới rồi đổi symbolic link là xong
Tôi đã thấy nhiều phiên bản của kiểu dùng cơ sở dữ liệu như hệ thống tệp; đúng là có mặt hay, nhưng khi có sự cố thì cũng là ác mộng
v2/index.htmlđã biến đi đâu rồiLàm vậy thì còn có thể dùng thứ như btrfs để khử trùng lặp ở tầng hệ thống tệp
Có ý kiến cho rằng vì khi cập nhật app có thể có rất nhiều tệp thay đổi, nên dùng cơ sở dữ liệu sẽ giúp xử lý mọi thay đổi một cách nguyên tử trong một transaction và tránh phục vụ các trang web bị hỏng trong lúc chuyển phiên bản, nhưng lập luận này có vấn đề
Lý do là tệp SQLite sẽ khóa việc đọc trong lúc ghi để đạt được mức cô lập serializable. Vì vậy kết luận lại là nên làm việc với cơ sở dữ liệu trên tệp offline, rồi thay thế các tệp cũ và tệp mới trong production
Điều này rốt cuộc gần như đang nói rằng hãy dùng tar file, hoặc dùng một thư mục riêng để thay thế bằng nội dung mới
Phục vụ tệp tĩnh theo cách tĩnh thì dễ hơn nhiều. Không cần phải phục vụ chúng từ một chương trình quản lý kết nối SQLite trực tiếp và cố đạt được thứ “ma thuật đồng thời khi cập nhật” kỳ quặc nào đó. Bài toán này hoàn toàn có thể giải quyết mà không hề khó
Quản lý CMS bằng cơ sở dữ liệu SQLite thì ổn, nhưng nếu nội dung là tĩnh và được phục vụ theo thời gian thực thì dùng tệp tĩnh sẽ tốt hơn