10 điểm bởi GN⁺ 2025-09-23 | 1 bình luận | Chia sẻ qua WhatsApp
  • Ứng dụng local-first hứa hẹn tốc độ phản hồi nhanhquyền riêng tư cơ bản được đảm bảo, nhưng trên thực tế việc triển khai hỗ trợ offline đúng nghĩa là cực kỳ khó
  • Lý do lớn nhất là độ phức tạp của việc đồng bộ, vì khi dữ liệu bị thay đổi đồng thời trên nhiều thiết bị thì cuối cùng hệ thống phải hội tụ về chính xác cùng một trạng thái
  • Có hai thách thức kỹ thuật lớn là sự bất định về thứ tự thời gianxung đột
  • Để giải quyết vấn đề này, cần áp dụng các thiết kế hệ thống phân tán như Hybrid Logical Clocks(HLCs)CRDTs
  • Bằng cách tận dụng các phần mở rộng dựa trên SQLite, có thể cung cấp một kiến trúc đồng bộ đơn giản và đáng tin cậy, và có thể sử dụng trên mọi nền tảng

Lời hứa và thực tế của ứng dụng offline-first

  • Ứng dụng offline-first quảng bá rằng có thể mang lại phản hồi tức thì, quyền riêng tư được đảm bảo mặc định, và sử dụng được ngay cả trong môi trường mạng không ổn định mà không phải chờ tải
  • Trên thực tế, phần lớn ứng dụng không triển khai hỗ trợ offline một cách đúng đắn; đa số chỉ chọn cách tạm lưu thay đổi ở máy cục bộ rồi gửi đi sau khi có kết nối mạng
  • Cách triển khai này kém tin cậy và cuối cùng dẫn đến những cảnh báo như "các thay đổi có thể không được lưu"

Khó khăn cốt lõi của việc đồng bộ

  • Khi xây dựng ứng dụng local-first, về bản chất bạn sẽ phải xây dựng một hệ thống phân tán
  • Nhiều thiết bị có thể độc lập thay đổi dữ liệu trong môi trường offline, và khi kết nối lại sau đó thì chúng phải hội tụ chính xác về cùng một trạng thái
  • Để làm được điều đó, có hai thách thức lớn
    • Sự bất định về thứ tự của các sự kiện
    • Xung đột trên cùng một dữ liệu

1. Sự bất định về thứ tự sự kiện

  • Các sự kiện phát sinh ở những thời điểm khác nhau trên nhiều thiết bị, và trạng thái có thể thay đổi tùy theo thứ tự
    • Ví dụ: thiết bị A đặt x=3, thiết bị B đặt x=5 → sau khi mỗi bên thay đổi trong trạng thái offline rồi đồng bộ, có thể xuất hiện các kết quả khác nhau
  • Cơ sở dữ liệu tập trung truyền thống giải quyết việc này bằng tính nhất quán mạnh, nhưng cách đó cần đồng bộ toàn cục nên không phù hợp với hệ thống local-first
  • Cuối cùng, cần xác định thứ tự phù hợp cho từng sự kiện ngay cả trong môi trường động và phân tán, tức là cần một cách xác định thứ tự mà không cần đồng hồ trung tâm

Đưa Hybrid Logical Clocks(HLCs) vào sử dụng

  • Hybrid Logical Clocks(HLCs) là một thuật toán đơn giản nhưng hiệu quả giúp các thiết bị riêng lẻ có thể đi đến đồng thuận thực tế về thứ tự sự kiện
  • HLC kết hợp thông tin thời gian vật lý với bộ đếm logic để sử dụng
  • Ví dụ:
    • Thiết bị A ghi lại sự kiện vào lúc 10:00:00.100, HLC là (10:00:00.100, 0)
    • Thiết bị B nhận được thông điệp, dù đồng hồ của nó chậm hơn thì vẫn nâng HLC lên thành (10:00:00.100, 1)
    • Nhờ vậy có thể xác định chính xác thứ tự sự kiện bất kể chênh lệch đồng hồ vật lý giữa hai thiết bị

2. Vấn đề xung đột

  • Chỉ áp dụng đúng thứ tự thôi là chưa đủ; nếu cùng một dữ liệu bị chỉnh sửa độc lập trên các thiết bị khác nhau thì xung đột là điều không thể tránh khỏi
  • Phần lớn hệ thống yêu cầu lập trình viên tự viết mã xử lý xung đột, nhưng điều này gây ra rủi ro lỗi và gánh nặng bảo trì

Ứng dụng CRDTs

  • Cách tốt nhất là áp dụng Conflict-Free Replicated Data Types(CRDTs)
  • CRDTs đảm bảo rằng dù đồng bộ theo thứ tự nào, hoặc dù áp dụng trùng lặp, trạng thái trên mỗi thiết bị cuối cùng vẫn luôn giống nhau
  • Chiến lược CRDT đơn giản nhất là Last-Write-Wins(LWW)
    • Gắn timestamp cho mỗi lần cập nhật
    • Khi đồng bộ, chọn giá trị có timestamp mới hơn

Ưu điểm của SQLite

  • Khi xây dựng ứng dụng local-first, một DB cục bộ nhẹ và đáng tin cậy là yếu tố bắt buộc, và SQLite là lựa chọn tối ưu
  • Nếu triển khai chức năng đồng bộ bằng phần mở rộng framework dựa trên SQLite, sẽ có các lợi ích sau
    • Việc áp dụng thông điệp rất đơn giản: truy vấn giá trị hiện tại → nếu timestamp mới hơn thì ghi đè → nếu không thì bỏ qua
    • Cách này đảm bảo trạng thái hội tụ trên mọi thiết bị bất kể thứ tự đồng bộ

Ý nghĩa của kiến trúc này

  • Cấu trúc này hiện thực hóa đồng bộ đơn giản và đáng tin cậy
    • Vẫn đáng tin cậy, không mất dữ liệu ngay cả khi offline trong nhiều tuần
    • Tính quyết định, luôn hội tụ về trạng thái cuối cùng
    • Giải quyết chỉ với phần mở rộng SQLite gọn nhẹ không có phụ thuộc nặng
    • Hỗ trợ mọi nền tảng chính như iOS, Android, macOS, Windows, Linux, WASM

Gợi ý cho lập trình viên

  • Cần tránh cách chỉ "giả lập" hỗ trợ chế độ offline bằng hàng đợi request đơn giản
  • Cần chấp nhận khái niệm eventual consistency và tận dụng các công nghệ hệ thống phân tán đã được kiểm chứng như HLC và CRDT
  • Thay vì framework lớn và phức tạp, nên theo đuổi cấu trúc nhỏ gọn, không phụ thuộc
  • Kết quả là ứng dụng có thể tận hưởng các lợi thế như khởi chạy tức thì, sử dụng được khi offline, quyền riêng tư được đảm bảo mặc định

Tham khảo mã nguồn mở SQLite-Sync

  • Nếu quan tâm đến một engine offline-first đa nền tảng, có thể dùng ngay trong production, bạn có thể tham khảo phần mở rộng mã nguồn mở SQLite-Sync

1 bình luận

 
GN⁺ 2025-09-23
Ý kiến trên Hacker News
  • CRDT (Conflict-Free Replicated Data Types) thường được coi là giải pháp, nhưng trên thực tế việc xây dựng một mô hình CRDT vừa khớp với kỳ vọng trực quan của người dùng vừa nhất quán với logic nghiệp vụ là cực kỳ khó, và việc biến mô hình dữ liệu thành một mớ thông điệp rồi liên tục tái dựng lại thành trạng thái thực sự là một cơn ác mộng lớn
    • Có một sáng kiến tên là BRAID như một tiêu chuẩn web mới. Nó hướng tới tiêu chuẩn đồng bộ trạng thái trên web, áp dụng kỹ thuật Operational Transformation (OT) và CRDT vào HTTP để về bản chất tạo ra một web đồng bộ thân thiện hơn với cả con người lẫn máy móc. Braid giúp tăng hiệu năng mạng và hỗ trợ P2P gốc, chỉnh sửa cộng tác và phát triển web app local-first. Các liên kết liên quan là cuộc họp BRAID, thảo luận Braid trên HN, thảo luận về RESTful API, bài giải thích về Braid HTTP
    • Câu chuyện về CRDT thường được nói như thể là lời giải vạn năng, nhưng thực tế tự động hợp nhất không hề dễ. Về mặt kỹ thuật, thuật toán người ghi cuối cùng thắng cũng có thể được xem là một dạng CRDT, nhưng trong việc hợp nhất văn bản phức tạp thì tôn trọng đầy đủ cả ý định lẫn kỳ vọng của người dùng là một bài toán gần như bất khả thi. Thậm chí trong một số tình huống, cố dùng CRDT để hợp nhất còn có thể là cách tiếp cận sai. Ví dụ khi đặt phòng họp mà hai người cùng lúc đặt cùng một phòng, thì đó nên là xung đột để người dùng tự nhận ra và tự giải quyết, chứ không phải việc cho thuật toán xử lý
    • Đồng ý rằng đây là lĩnh vực khó dấn thân nếu không đủ gan. Cũng có những lựa chọn thay thế “không dùng CRDT” cho những ai không muốn đi theo hướng đó, xem tại đây
    • Đội của chúng tôi khi làm app local-first dùng cách đơn giản là bỏ qua các tình huống xung đột. Thay đổi cuối cùng sẽ thắng. Phần lớn xung đột là chuyện nhỏ (hoặc có thể xử lý dễ qua audit log), hoặc vốn dĩ không thể giải quyết tự động. Ví dụ trong một task tracker có hỗ trợ offline, nếu hai người cùng lúc bắt đầu một công việc thì đó phải được xử lý ở tầng quy trình nghiệp vụ riêng
  • Trước đây hầu như mọi phần mềm đều là local-first, và điều đó là mặc định. Nhưng ngày nay thế giới vận hành hoàn toàn theo logic kiểm soát và tối ưu hóa lợi nhuận, và kết quả là một cấu trúc khiến con người thường xuyên chịu thiệt hơn. Dù có bất mãn cũng chẳng có mấy lựa chọn thay thế
    • Hồi còn làm các sản phẩm on-premise và self-hosted, phàn nàn lớn nhất từ khách hàng là không có tùy chọn cloud. Phần lớn công ty không muốn tự host, mà thích trả phí hàng tháng để giao cho nơi khác quản lý hơn. Có vẻ HN đang đánh giá thấp nhu cầu thực sự rất lớn đối với dịch vụ cloud
    • Lập luận kiểu “nếu mọi người muốn dịch vụ ít bóc lột hơn thì hẳn cũng có thể kiếm tiền từ đó” là không đúng về mặt kinh tế. Vấn đề là mọi người thực sự đang chấp nhận một mức độ thiệt hại nào đó. Có thể điều này sẽ khác nếu giáo dục hoặc nhận thức rủi ro cao hơn, nhưng tôi nghĩ đó là một bài toán cực khó
    • Một phương án thay thế là FOSS (phần mềm nguồn mở)
  • Tôi chỉ mong các app đừng phụ thuộc mọi nội dung vào online. GPS của Tesla thậm chí không cache các tile đã tải, nên khi offline bản đồ chẳng hiện gì cả. Các app như Peacock hay Kanopy cũng không giữ lại danh sách media hay chính các đối tượng đã render trên thiết bị. Trong khi thiết bị đã có sẵn 95% nội dung rồi, giá mà họ tận dụng tích cực hơn. Chỉ cần đánh dấu UI là dirty và chờ lưu bất đồng bộ thành công là được. Không cần thay đổi lớn trong thiết kế app offline thì chỉ với thói quen tốt hơn, phần lớn vấn đề đã có thể giải quyết dễ dàng
    • Dùng Cache-Control đúng cách trong phản hồi API và buộc tầng mạng tuân thủ nó thì giải quyết được rất nhiều vấn đề. Làm vậy cũng cho phép thay đổi thời gian sống của cache từ phía server mà không cần cập nhật app
    • Google Maps cho phép tải bản đồ offline bằng cách chọn thủ công khu vực, và cũng có thể cache nhiều vùng cùng lúc. Tôi đã dùng rất ổn khi đi thăm các công viên quốc gia
    • Với ý “không cần đổi thiết kế app”, tôi nghĩ các công ty thật ra muốn thu thập nhiều dữ liệu hơn. Ví dụ Apple cũng từng cung cấp bản đồ offline nhưng cố tình cho dữ liệu hết hạn để trói người dùng vào hệ sinh thái của mình. Tôi cũng đoán các tile bản đồ của Tesla (hoặc Google) có động cơ ẩn như vậy
  • Có nhiều trường hợp người ta mải tập trung vào các vấn đề “nóng về mặt chính trị” như local-first hay tính phân tán của app, mà lại bỏ lỡ giá trị cốt lõi thật sự mà người dùng muốn
    • Chuyển sang Immich, tôi tưởng mình sẽ phải hy sinh nhiều vì self-hosting, nhưng lại ngạc nhiên vì nó tốt hơn Apple hay Google rất nhiều. Đúng là một sản phẩm hiếm như kỳ lân
  • Tôi nghĩ việc app local-first không phổ biến cuối cùng vẫn là vấn đề kinh tế. Mô hình SaaS hay quảng cáo đã rất vững, còn app local-first thì lợi nhuận thấp hơn rõ rệt. Những người thích mô hình này thường coi trọng các đặc tính như chủ quyền dữ liệu, mã hóa đầu cuối, dùng offline... vốn xung đột với các mô hình kiếm tiền hiện có. Cuối cùng chỉ còn cách dựa vào nhiệt huyết của cộng đồng nguồn mở
    • Giờ đây dường như chỉ còn hai lựa chọn là trả “tiền + dữ liệu” hoặc “xem quảng cáo”, còn mô hình chỉ trả tiền mặt thật và dữ liệu vẫn được bảo vệ thì bị loại khỏi cuộc chơi
    • Tôi cũng đang làm một app local-first tên là Relay, mang kiểu cộng tác giống Google Docs vào Obsidian. Tôi nghĩ mô hình kinh doanh của nó khá đặc biệt. Dịch vụ được chia thành “global identity layer” và “Relay Server” (mã nguồn mở / self-hosted), để người dùng có toàn quyền kiểm soát nội dung tài liệu. Nó cung cấp SSO đơn giản và quản lý quyền, và đang nhận được nhiều quan tâm đặc biệt từ các công ty trong lĩnh vực AI, AI Safety hoặc những nơi compliance là yếu tố quan trọng. Liên kết là Relay.md
    • Điều tôi thường thấy quanh mình là người tiêu dùng không mua chỉ vì lúc bán chỉ có gói thuê bao. Họ muốn mua đứt trước rồi dùng sau, nhưng các điều kiện ràng buộc (thời gian giảm giá, giá tăng khi quay lại sau...) làm họ mất hẳn động lực mua. Tôi không nghĩ đó là mô hình kinh doanh tối ưu
    • Tôi không cho rằng cấu trúc dữ liệu nhân bản mới là vấn đề. Ngay cả game single-player hoàn toàn local-first mà vẫn thường bắt phải kết nối Internet qua launcher
    • App local-first cũng có vấn đề nghiêm trọng về độ phức tạp. Nó phải chạy được trên đủ loại thiết bị và môi trường, trong khi app cloud-first chỉ cần chạy theo một môi trường duy nhất ở phía server nên tương đối dễ hơn và chi phí bảo trì cũng thấp hơn
  • Trong quá khứ, phần mềm desktop và mobile thường bị đối xử như thể là ngoại lệ kỳ quặc, nhưng thật ra đó vẫn là cách phân phối phần mềm rất phổ biến. Ngược lại, nếu cố triển khai tính năng local-first trong trình duyệt thì chỉ phải gánh thêm nhược điểm của trình duyệt, như vấn đề tích hợp với hệ thống host
  • Có thể tôi đang bỏ sót điều gì đó, nhưng nhìn chung tôi vẫn nghĩ app “local-first” mới là điều bình thường. Phần lớn người dùng cũng dùng khá nhiều app hoạt động dựa trên offline. Nếu ý ở đây là “local-first web app” thì có lẽ diễn đạt như vậy đúng hơn. Thực ra “local-first web app” tự nó đã là một khái niệm mâu thuẫn
    • Tôi muốn hỏi lại xem liệu phần lớn app hiện nay có còn là loại local-first mà người dùng trả tiền trực tiếp để dùng hay không. Ngoài game ra thì có vẻ gần như không còn công ty nào như vậy, mà ngay cả game single-player giờ cũng đòi Internet với các lý do như DRM, chống gian lận, cập nhật...
    • “Local-first” ở đây nghĩa là app lấy dữ liệu local làm mặc định ngay từ đầu thay vì lưu trên cloud, rồi có hỗ trợ đồng bộ với cloud
  • App offline-first cũng chẳng khác mấy khi gặp kết nối mạng chập chờn vì vẫn cứ quay loading spinner. Ví dụ Google Docs bị kẹt lúc cố kiểm tra tài liệu có phải bản mới nhất hay không, và chỉ khi bật Airplane Mode để cắt hẳn thì mới mở ra ngay. Spotify cũng dừng lại vì cố tải thêm thông tin online ngay cả với playlist đã lưu sẵn. Rốt cuộc kết nối không ổn định là cơn đau đầu lớn nhất khi phát triển app offline, vì app luôn cố thêm một lần truy cập dữ liệu cloud nữa
  • Giải pháp trong bài viết cũng đang bị khóa vào một cloud offering đóng. Giống Firebase, bản thân nó không tệ, nhưng đáng tiếc là điều này không được ghi rõ mà lại được diễn đạt như kiểu “chỉ là một extension cho sqlite”, trong khi thực tế chỉ hỗ trợ cloud thương mại. Các nhà cung cấp như Powersync hay ElectricSQL ít nhất cũng minh bạch chuyện này, và Powersync còn cho phép self-host
    • Tôi hơi phân vân không biết khái niệm local-first có áp dụng được cho developer tools hay không. Có lẽ cần xem xét liệu có thể xây phần mềm dựa trên mô hình lưu trữ cục bộ bằng các công cụ kiểu SQLite-sync hay không. ElectricSQL có thể self-host, và có lẽ tôi cũng nên tiếp tục cập nhật danh sách công cụ “sqlite sync” của mình. Các liên kết liên quan là bài gốc về local-first, SQLSync, SQLiteSync, SQLite-Sync
  • Tôi nghĩ chúng ta cần nhiều app chỉ-local và self-host hơn nữa. Hoặc kiến trúc liên hợp (federated) cũng tốt. Tôi cảm thấy hạ tầng mạng trước đây là rào cản, nhưng với các giải pháp như Tailscale thì việc xây những app như vậy sẽ dễ hơn đáng kể
    • Tôi đang làm phần mềm trình chiếu, và nó bắt buộc phải chạy tốt ở local nhưng đồng thời cũng phải truy cập được từ mọi nơi. Đáp ứng cả hai điều này cùng lúc khó hơn tưởng tượng nhiều. Nhưng khi công nghệ tiến bộ, việc triển khai P2P như direct connection hay WebRTC đang trở nên dễ hơn. Dù vậy, đưa vào sản phẩm thực tế và kiểm thử vẫn còn là thử thách. Dẫu sao tôi nghĩ trong tương lai sẽ ngày càng có nhiều phần mềm vừa local-first vừa có khả năng mạng tốt. Nó là mã nguồn mở. Xem hồ sơ của tôi để biết thêm chi tiết
    • Dạo này tôi cũng đang khám phá hướng này rất hứng thú. Có thể đọc thêm tại đây. Xây app theo cách mới khá là vui