Lưu trữ SQLite độ trễ bằng 0 trong mọi Durable Object
(simonwillison.net)- Durable Object của Cloudflare chuyển từ kho khóa/giá trị sang kho quan hệ dựa trên SQLite, tạo thành cấu trúc trong đó logic ứng dụng và dữ liệu chạy trên cùng một máy chủ vật lý
- Mở rộng được thực hiện bằng cách chia Durable Object theo từng đơn vị trạng thái logic như tài liệu, người dùng hoặc shard cơ sở dữ liệu, thay vì chỉ tăng quy mô một đối tượng; một đối tượng đơn lẻ bị giới hạn bởi một máy và một luồng
- Trong các hệ thống có đơn vị trạng thái rõ ràng như đặt chỗ chuyến bay, có thể đặt một Durable Object chuyên dụng và cơ sở dữ liệu SQLite riêng cho từng chuyến bay, nên mỗi hãng hàng không có thể phát sinh hàng nghìn cơ sở dữ liệu mới mỗi ngày
- Tính bền vững được xây dựng từ truyền luồng mục WAL, lưu vào object storage và sao chép sang các trung tâm dữ liệu lân cận; dữ liệu được gom lô mỗi 16MB hoặc 10 giây và hỗ trợ khôi phục theo thời điểm tối đa 30 ngày
- API JavaScript chọn cách thức chặn để phục vụ các thao tác lưu trữ bền vững đơn luồng tốc độ cao, và vị trí của Durable Object được cố định sau khi tạo nhưng có kế hoạch hỗ trợ tái phân bổ động trong tương lai
Thiết kế Durable Object đã chuyển sang SQLite
- Durable Object của Cloudflare được nâng cấp từ kho khóa/giá trị thành hệ thống quan hệ dựa trên SQLite
- Điểm cốt lõi là đặt logic ứng dụng cùng vị trí với dữ liệu mà logic đó xử lý
- Một Durable Object bao gồm mã chạy trên cùng máy chủ vật lý với cơ sở dữ liệu SQLite mà nó sử dụng
- Hướng đến xử lý đọc và ghi mà không cần vòng khứ hồi qua mạng, nên dễ giảm độ trễ hơn
- Xử lý ở quy mô lớn được tiếp cận bằng cách tạo thêm nhiều đối tượng thay vì tăng thông lượng cho một đối tượng duy nhất
- Một đối tượng đơn lẻ chạy trên một luồng duy nhất của một máy duy nhất, nên có giới hạn thông lượng mang tính bản chất
- Nếu các đơn vị trạng thái logic khác nhau như tài liệu, người dùng hoặc shard cơ sở dữ liệu do các đối tượng riêng đảm nhiệm thì sẽ dễ mở rộng hơn
- Trong hệ thống đặt chỗ chuyến bay, mỗi chuyến bay có thể được ánh xạ tới một Durable Object chuyên dụng và cơ sở dữ liệu SQLite riêng của nó
- Với cấu trúc này, mỗi hãng hàng không có thể tạo ra hàng nghìn cơ sở dữ liệu mới mỗi ngày
- Mỗi Durable Object có một tên duy nhất, và mạng Cloudflare sẽ định tuyến yêu cầu tới vị trí của đối tượng đó trong mạng toàn cầu
Đảm bảo độ bền bằng truyền WAL và sao chép
- Hệ thống dựa trên Durable Object lấy cảm hứng từ Litestream, liên tục truyền chuỗi mục WAL của từng đối tượng vào object storage
- Việc gom lô được thực hiện sau mỗi 16MB hoặc mỗi 10 giây
- Có thể phát lại các giao dịch đã ghi để khôi phục theo thời điểm trong tối đa 30 ngày
- Để bù cho độ bền trong cửa sổ 10 giây, các bản ghi được gửi tới 5 bản sao ở các trung tâm dữ liệu lân cận khác ngay sau khi commit
- Trong số đó, phải có 3 bản xác nhận thì phản hồi ghi mới được chấp nhận
- Hệ thống nền tảng của Durable Objects là Storage Relay Service, và đã vận hành riêng hệ thống D1 SQLite của Cloudflare trong hơn một năm
API và cách hoạt động của vị trí đối tượng
- API JavaScript sử dụng cách thức chặn thay vì async
- Mục tiêu thiết kế là cung cấp các thao tác lưu trữ bền vững đơn luồng tốc độ cao
- Mã ví dụ cố ý dùng mẫu truy vấn N+1: lấy danh sách tài liệu rồi truy vấn riêng tên tác giả của từng tài liệu
- Đây được liên hệ như một trường hợp mà SQLite rất phù hợp để xử lý
- Durable Object hiện không thay đổi vị trí sau khi được tạo
- Theo mặc định, nó được khởi tạo tại trung tâm dữ liệu gần nơi phát sinh yêu cầu
get()đầu tiên - Nếu muốn tạo thủ công ở vị trí khác, có thể cung cấp tham số tùy chọn
locationHintchoget() - Tái phân bổ động cho Durable Object hiện có là kế hoạch trong tương lai
- Theo mặc định, nó được khởi tạo tại trung tâm dữ liệu gần nơi phát sinh yêu cầu
- where.durableobjects.live là trang theo dõi nơi Durable Object mới được tạo trong mạng Cloudflare
- Trong ví dụ được truy cập, khi tải trang từ Half Moon Bay thì worker ở San Jose đã tạo Durable Object tại San Jose
1 bình luận
Ý kiến trên Hacker News
Có thêm vài điểm thú vị: API ghi là đồng bộ nhưng có phần chờ bất đồng bộ ẩn, nên nếu lần ghi thất bại khi phản hồi tiếp theo được xuất ra thì runtime sẽ chuyển phản hồi đó thành lỗi HTTP
Vì vậy ngay cả khi người dùng không xử lý lỗi hay chờ một cách tường minh, runtime vẫn có thể tự động gom lô các lần ghi và lạc quan giả định rằng chúng sẽ thành công
Không có giao dịch đọc, dù có vẻ nó sẽ hữu ích để lấy con trỏ snapshot tại một thời điểm nhất định
Mỗi phiên bản runtime bị giới hạn ở 128MB RAM
WebSocket có thể chuyển sang trạng thái ngủ đông và không bị tính phí trong thời gian ngủ, nên có thể giữ client ở trạng thái kết nối ngay cả khi DO đang ngủ
Còn có một tính năng gần giống RPC tự động, cho phép xử lý DO hay Worker khác như một lời gọi JS thông thường, nhưng thực tế có thể là gọi sang trung tâm dữ liệu khác và runtime sẽ lo phần tuần tự hóa lẫn phân tích dữ liệu
Litestream được nhắc tới trong bài cũng đề xuất kỹ thuật tương tự
Với SQLite thông thường, giao dịch đọc hữu ích vì nhiều tiến trình có thể đồng thời truy cập cùng một cơ sở dữ liệu
Ở đây chỉ có một tiến trình được truy cập cơ sở dữ liệu, nên có thể đạt hiệu quả tương tự bằng cách thực hiện mọi lần đọc trong một hàm đồng bộ duy nhất hoặc tự triển khai khóa ở cấp tiến trình
Việc các mục WAL được stream theo lô sang object storage mỗi 16MB hoặc mỗi 10 giây có vẻ đồng nghĩa với việc có thể mất tới 10 giây để đọc được dữ liệu ghi một cách bền vững trên phạm vi toàn cầu
Tôi không rõ điều này có thể thay thế cụm cơ sở dữ liệu theo khu vực cho phản hồi mili giây trên quy mô liên lục địa như thế nào
Tôi biết họ dùng stream, nhưng đích đến chỉ có 5 follower và Cloudflare có hàng trăm trung tâm dữ liệu
Nếu không phải mọi phiên bản SQLite đều luôn kết nối thì về mặt vật lý không thể bảo đảm đọc ở mức giây, và ngay cả khi có kết nối thì độ trễ gói tin vẫn có thể gây vấn đề
Ghi rồi đọc lại ngay sẽ phản ánh tức thì, vì thao tác ghi và đọc cũng cập nhật cả trạng thái bộ nhớ của tiến trình hiện tại
Nếu tiến trình khác, ví dụ DO hoặc Worker khác, muốn truy cập dữ liệu thì phải đi qua DO đang giữ dữ liệu đó, nên sẽ gửi yêu cầu RPC hoặc HTTP và nhận được thông tin mới nhất
Vì ngủ đông chỉ xảy ra sau một khoảng thời gian không hoạt động, nên khác với dự đoán, trường hợp ghi trở nên unavailable có lẽ chỉ là khi DO hoặc Worker bị crash ngay sau khi ghi
Bài blog của Cloudflare nói kỹ hơn về phần này. Nhờ đó họ vừa giữ được tốc độ ghi vừa đảm bảo độ bền dữ liệu
Máy đó luôn có góc nhìn nhất quán về cơ sở dữ liệu SQLite của chính nó
Có thể tạo ra hàng tỷ object, nhưng mỗi object có một cơ sở dữ liệu riêng
Không có cách nào đọc trực tiếp cơ sở dữ liệu đó từ một máy khác ngoài máy đang chạy DO
Mỗi DO là duy nhất trên toàn cầu, với một DO mang ID cụ thể chỉ chạy ở đúng một nơi nào đó, và chạy SQLite trên bộ nhớ lưu trữ cục bộ của trung tâm dữ liệu đó
Phần tôi vẫn chưa hiểu về Durable Objects là vị trí vật lý
Tôi muốn biết liệu nó có nằm ở khu vực đang host lệnh gọi API đã khởi tạo nó ban đầu hay không
Nếu đúng như vậy, tôi cũng muốn biết có cơ chế nào tự động chuyển DO sang vị trí khác khi ban đầu nó được tạo ở Bắc Mỹ nhưng sau đó toàn bộ lưu lượng đọc/ghi lại đến từ Úc hay không
locationHintnhư một phương án thay thế. Với Úc thì dùng"oc": https://developers.cloudflare.com/durable-objects/reference/...Cũng cần lưu ý rằng “việc tái phân bổ động cho Durable Objects hiện có là kế hoạch trong tương lai”
Chỉ khoảng 10–11% PoP của Cloudflare host Durable Objects
Các yêu cầu đi vào một PoP khác để tạo DO sẽ được chuyển tiếp đến một trong những PoP gần nhất có host tính năng này
Nếu tôi nhớ không nhầm thì Orleans(https://www.microsoft.com/en-us/research/wp-content/uploads/...) có thể di chuyển actor giữa các máy, và mô hình đó có vẻ rất phù hợp với cách chuyển DO giữa các vị trí
Tuy nhiên không có lệnh gọi API nào để di chuyển Durable Object
Nó phải không còn kết nối, và sẽ được tạo lại ở trung tâm dữ liệu gần nhất vào lần kết nối kế tiếp hoặc lần kết nối đầu tiên
Khi đó bộ nhớ sẽ mất nhưng dữ liệu lưu trữ vẫn còn
Gần đây đã có thêm một số chi tiết liên quan đến chế độ ngủ đông, nên phần mô tả này có thể đã hơi cũ
Có ai khác cũng thấy khó hiểu khi cố nắm bắt các công nghệ cloud mới kiểu này không?
Tôi có hơn 15 năm kinh nghiệm phát triển web và đã dùng stack Laravel / Postgres / Redis, nhưng đọc những bài như thế này thì chỉ thấy không hợp với mình
Những ứng dụng cộng tác thời gian thực hiện lên ngay trong đầu là Google Docs/Sheets, Notion, Miro, Figma
Tất cả đều là ứng dụng cộng tác quy mô toàn cầu, và tôi không chắc stack Laravel có thể hỗ trợ tốt các trường hợp sử dụng như vậy hay không
Có lẽ Google đã phải tự xây phần lớn mọi thứ và chắc hẳn đi đầu trong việc dùng CRDT
Nhưng khi các mẫu thiết kế dần lộ rõ và các thành phần được SaaS hóa, cộng tác thời gian thực ở quy mô lớn không còn là một bài toán kỹ thuật khổng lồ nữa, và điều đó mở ra khả năng cho nhiều sản phẩm thú vị hơn
Tôi thực sự thích thiết kế của Durable Objects. Đặc biệt là nó dễ hiểu về cách vận hành bên trong
Không giống nhiều giải pháp khác được thiết kế cho dữ liệu thời gian thực, Durable Objects có sự đơn giản kiểu như Redis và đồ ăn Ý. Bạn có thể nhìn thấy toàn bộ nguyên liệu
Nếu có đủ thời gian, tài nguyên và trung tâm dữ liệu, có vẻ như một lập trình viên giỏi có thể đọc tài liệu DO rồi tự triển khai lại một thứ tương tự
Vì vậy sẽ dễ đánh giá các đánh đổi liên quan hơn
Tuy nhiên tôi lo rằng DO rất tốt để tạo ra trải nghiệm thời gian thực nhanh, ít overhead, ví dụ như cho năm người cùng chỉnh sửa tài liệu theo thời gian thực, nhưng lại có thể khiến việc làm phân tích và tổng quan trở nên rất khó
Những câu hỏi như nhóm nào đã chỉnh sửa tài liệu nào trong tuần trước là ví dụ điển hình, và nếu dữ liệu nằm trong SQLite thì có thể còn khó hơn
Bạn sẽ phải truy vấn bằng cách nào đó qua vô số instance SQLite nhỏ rồi hợp nhất kết quả, nên tôi tự hỏi DO có sẵn thứ gì cho việc này không
Đây cuối cùng cũng là lý do tôi cứ quay lại với Postgres: có thể dùng nó cho cả tính năng cốt lõi của ứng dụng lẫn phần tổng quan, BI, v.v.
Đây thực sự là một thiết kế rất thú vị, nhưng cá nhân tôi luôn thấy những hệ thống thông minh kiểu này rơi vào thung lũng kỳ quái
Chỉ có đúng hai trường hợp cần đến nó: либо bạn phải mở rộng một hệ thống tải rất cao theo cách khéo léo, либо bạn đang làm một dự án đồ chơi cho vui
Nếu là dự án đồ chơi thì dùng gì cũng được
Nhưng nếu là production hay dùng cho công việc thì cần thứ đã được kiểm chứng
Nếu bạn không biết mình cần cái này, thì nghĩa là bạn không cần nó, cứ dùng một cơ sở dữ liệu Postgres nhàm chán cùng thứ như VM là được
Ngược lại, nếu bạn biết mình cần nó thì lại thành khó xử. Nó vẫn còn mới và chưa đủ trưởng thành, nên rất có thể bạn sẽ đụng nhiều edge case kỳ quặc, mà có lẽ bạn sẽ không muốn debug hay chấp nhận điều đó
Rốt cuộc tôi không rõ những hệ thống kiểu này thực sự dành cho ai
Nó quá ngách để có nhiều người chơi nghiêm túc sử dụng và nhanh chóng trưởng thành, đồng thời lại quá phức tạp và có quá nhiều đánh đổi để 99,9% công ty dùng
Có vẻ nhóm người dùng chắc chắn duy nhất là những lập trình viên nhìn thấy thứ lấp lánh rồi lập công ty, hoặc tệ hơn là xây công ty của người khác trên nó rồi sớm hối hận và chuyển sang thứ nhàm chán hơn
Nếu bạn muốn làm một thứ như Figma hay Google Docs tiếp theo, thì mô hình lập trình của Durable Objects rất tiện
Bài này nói kỹ hơn: https://digest.browsertech.com/archive/browsertech-digest-cl...
Bài cũ này cũng khá liên quan: http://ithare.com/scaling-stateful-objects/
Nếu bạn từng đọc bài về multiplayer của Figma và nghĩ “đại khái đây là thứ mình cần”, thì Durable Objects có lẽ rất hợp: https://www.figma.com/blog/rust-in-production-at-figma/
Cũng có những cách tiếp cận khác. Tôi từng thử dùng CRDT trên WebRTC, và nó thật sự cho cảm giác như công nghệ tương lai
Nhưng nó phức tạp hơn nhiều so với nền tảng là WebSocket ở đâu đó trên cloud và một instance của một lớp duy nhất
Việc đặt dữ liệu và hành vi cùng một chỗ thực sự làm giảm độ phức tạp một cách định lượng
Nó loại bỏ nỗi lo về độ trễ và băng thông, nên giảm cả mối lo vận hành lẫn mối lo phát triển. Tác động của bài toán N+1 nổi tiếng cũng giảm đi đáng kể
Bạn có thể lập luận rằng Postgres kết nối qua mạng tốt hơn vì những lý do khác, và điều đó thực sự có thể đúng
Nhưng SQLite thuộc hàng nhàm chán và dễ đoán bậc nhất, đồng thời các điểm mạnh đã biết của nó cũng rất rõ ràng
Vì vậy nó đang ngày càng phổ biến hơn ở phía server
Tuy nhiên tôi không thích lắm cách tạo ra thật nhiều cơ sở dữ liệu nhỏ như Durable Objects đề xuất
Nó gợi lại cơn ác mộng NoSQL, và có thể phá vỡ những bất biến quan trọng của cơ sở dữ liệu quan hệ
Tôi nghĩ SQLite phù hợp hơn nhiều khi dùng như một cơ sở dữ liệu đơn khối, như sản phẩm D1 của Cloudflare
Cloudflare cần những câu chuyện thành công tốt từ khách hàng, và rất có thể các kỹ sư giỏi của họ sẽ cùng bạn xem cách thứ này hoạt động ra sao, nó giúp được gì, và bug sẽ được xử lý như thế nào để đổi lấy một ca thành công
Khi đã được kiểm chứng thì mối quan hệ sẽ chuyển thành quan hệ dịch vụ, nhưng ở giai đoạn đầu nó gần với một quan hệ đối tác hơn
Có rất nhiều dịch vụ, như công cụ nội bộ, không cần tuning hiệu năng hay quan sát nội bộ sâu
Với những trường hợp như vậy, tôi nghĩ framework serverless rất phù hợp vì chúng giúp giảm rất nhiều thời gian dành cho triển khai
Nhanh thì tốt, nhưng hiếm khi là yêu cầu cốt lõi
Thường thì yêu cầu cốt lõi là có thể làm nhanh và ít phải bảo trì
Cloudflare có thể cung cấp trải nghiệm lập trình viên tốt ở đây, nhưng đó không phải điểm họ quảng bá chính, và cũng có nhiều dịch vụ cạnh tranh giúp kiểu phát triển này diễn ra nhanh hơn
Ngược lại, trong những trường hợp cần khả năng debug cao và quan sát nội bộ sâu, tôi không nghĩ các dịch vụ kiểu này là phù hợp
Điều quan trọng là bạn lấy được những chỉ số nào, có thông tin để hiểu vì sao một số Durable Objects bị chậm không, có sửa được không, logging ra sao và chi phí thế nào
Các dịch vụ kiểu này có thể tốt cho startup muốn ngay từ ngày đầu tạo ra một hệ thống phân tán thông minh để trì hoãn nỗi lo scale
Nhưng nếu là công ty ở quy mô scale-up thì có lẽ họ sẽ muốn chuyển sang thứ gì đó có thể nhìn sâu hơn, và quá trình chuyển đổi đó sẽ rất khó
Như những người khác đã nói, trường hợp sử dụng là multiplayer, vì để ứng dụng cho cảm giác tốt thì mọi người đều phải thấy thay đổi càng nhanh càng tốt
Nhìn rộng hơn, ngành lưu trữ đã từ lâu cố gắng tạo ra thứ gì đó vừa nhất quán, vừa có độ trễ thấp, vừa phù hợp cho nhiều người dùng
Ngay về mặt vật lý, thường tồn tại đánh đổi giữa tính nhất quán và độ trễ, nên đây là bài toán rất khó
Vì vậy người ta đang thử nhiều mô hình khác nhau, và phần lớn các thử nghiệm đó đang diễn ra xoay quanh SQLite. Không phải tất cả; vẫn có ngoại lệ như Yugabyte hay Cockroach
Tôi vẫn tiếp tục thấy kinh ngạc với thiết kế DO
Rất dễ có phản ứng bản năng rằng cách này có gì đó sai sai, nhưng thực ra tôi nghĩ nhiều sản phẩm ngoài đời đã ngầm được cấu trúc theo kiểu này
Mỗi đơn vị nguyên tử cần tính nhất quán giao dịch đều có rất nhiều thao tác phức tạp được thực hiện ở quy mô rất nhỏ
Nhìn lại, thứ chúng tôi xây ở Framer cho dự án hỗ trợ multiplayer cũng là một phiên bản ứng dụng hóa hơn của điều DO đang làm bây giờ
Nội dung chỉnh sửa được sao chép ở 60 FPS và áp dụng đúng thứ tự cho mọi client, và rốt cuộc chúng tôi cũng tạo ra một thứ giống WAL cho việc chỉnh sửa đối tượng JSON
Nhờ vậy, ngay cả khi instance của dự án bị crash thì bản sao lưu vẫn có thể tiếp quản như chưa từng có chuyện gì xảy ra
Ngay cả khi không có thời gian commit các JSON patch vào đối tượng dữ liệu dự án khổng lồ, nó vẫn được xử lý theo kiểu mỗi N lần cập nhật hoặc mỗi M giây, đúng như mô tả ở đây
Có thể là câu hỏi ngớ ngẩn, nhưng tôi tò mò việc xử lý schema migration trong kiểu cấu hình này sẽ như thế nào
Theo hiểu biết của tôi thì có vẻ họ đang nhắm tới cơ sở dữ liệu theo từng tenant, hoặc cấu trúc còn chia nhỏ hơn thế
Tôi muốn biết liệu có cách nào tỉnh táo để xử lý schema migration hay không, hay là kỳ vọng rằng những cơ sở dữ liệu này có vòng đời ngắn hơn nên phải hỗ trợ nhiều phiên bản DB/DO cho đến khi chúng bị xóa
Trong đầu tôi thấy sẽ khá thú vị nếu làm một dịch vụ bookmark với DO cho từng người dùng
Nhưng ngay khoảnh khắc muốn thêm một trường mới vào bảng hiện có, bạn sẽ gặp một vấn đề khá hóc búa là phải áp dụng thay đổi đó cho từng DO riêng lẻ
Có lẽ ví dụ đó là dữ liệu tồn tại quá lâu, nên có thể thiết kế này dành cho các trường hợp sử dụng tạm thời hơn
Nếu có ai đã thực sự làm việc này rồi thì tôi muốn biết họ đang xử lý ra sao
Có một phiên bản cho SQLite được viết bằng Python, nhưng tôi không rõ có chạy được trong Durable Objects hay không. Có thể làm được nếu dùng WASM và PyOdide
Hoặc phải port sang JavaScript
https://github.com/simonw/sqlite-migrate
Dạo này có vẻ Cloudflare đang thúc đẩy các lập trình viên dùng DO ở mọi nơi thay vì Worker
Kết nối WebSocket của Worker cũng timeout sau khoảng 30 giây, và cách làm được khuyến nghị là dùng DO
Bản demo chat gốc cũng đã dùng DO và WebSocket từ năm 2020: https://github.com/cloudflare/workers-chat-demo
Điều này có nghĩa là SQLite cho DO có thể làm mất tối đa 10 giây dữ liệu trong tình huống DO bị lỗi sao?
Ở đây có vẻ Simon định nói within chứ không phải “beyond”