Local-First Software & CRDT: Xây dựng app offline-first, sync không cần server trung tâm

Local-first software là triết lý thiết kế ứng dụng mà dữ liệu chính lưu trữ trên thiết bị người dùng trước, không phụ thuộc server trung tâm. Sync tự động qua cloud khi có kết nối – nhưng app vẫn hoạt động đầy đủ offline. CRDT (Conflict-free Replicated Data Type) là “bộ não” cho local-first: đảm bảo dữ liệu đồng bộ đúng ngay cả khi hai người chỉnh sửa cùng lúc, không cần trung tâm quyết định.

Tại sao Local-First quan trọng?

Mô hình cloud-first hiện tại có ba vấn đề cốt lõi:

  • Vendor lock-in & shutdown risk: Evernote hạn chế free tier, Google Inbox shutdown, các service ngừng hoạt động = mất dữ liệu
  • Privacy & sovereignty: Dữ liệu nhạy cảm (health, finance, personal notes) gửi lên server bên thứ ba
  • Offline = useless: Máy bay, vùng núi, server maintenance – app trở thành brick

Local-first giải quyết cả ba: dữ liệu ở trong tay bạn, app hoạt động offline, sync khi muốn.

CRDT là gì?

Conflict-free Replicated Data Type (được đề xuất bởi Marc Shapiro, Nuno Preguiça, 2011) là cấu trúc dữ liệu đảm bảo eventual consistency mà không cần conflict resolution thủ công.

Ý tưởng cốt lõi: dữ liệu được thiết kế để merge một cách tự nhiên. Hai replica cùng sửa cùng lúc → merge → cả hai đều đồng thuận với kết quả → không cần server quyết định đúng/sai.

Hai họ CRDT chính

  • Op-based (operation-based): Gửi thao tác (add, delete, move) qua mạng. Cần reliable broadcast, định dạng operation thống nhất.
  • State-based (CvRDT): Gửi toàn bộ state hiện tại, merge bằng hàm merge(state_a, state_b). Đơn giản hơn nhưng bandwidth cao hơn.

Sơ đồ cơ chế merge CRDT giữa hai replica khi cùng sửa dữ liệu

Thực tế: CRDT trong ứng dụng

Text/Notes (Yjs, Automerge)

Google Docs, Notion dùng OT (Operational Transformation) – phụ thuộc trung tâm. YjsAutomerge dùng CRDT:

  • Yjs: Y.Text dùng RGA (Replicated Growable Array), hiệu năng cao, binding cho React, ProseMirror, Monaco
  • Automerge: immutable state, dễ debug, hỗ trợ nested objects, binary encoding hiệu quả

Ví dụ Yjs với React:

import * as Y from 'yjs'
import { WebsocketProvider } from 'y-websocket'

const ydoc = new Y.Doc()
const provider = new WebsocketProvider('wss://sync.example.com', 'room-1', ydoc)
const ytext = ydoc.getText('note')

ytext.insert(0, 'Hello local-first!')
// Cập nhật tự động sync qua WebSocket đến các peer khác

Lists & Todos (Y.Array)

Y.Array hỗ trợ insert/delete/move với định dạng Riak DT. Khi Alice xóa item 3, Bob thêm item mới → merge hoàn toàn tự nhiên, không ai mất data.

JSON & Structured Data

Y.Map, Y.Array, Y.Text cho phép lưu JSON phức tạp. Automerge còn hỗ trợ schema migration, undo/redo.

Kiến trúc local-first app: local storage, background sync, cloud backup với CRDT

Stack công nghệ Local-First

Thành phần Công nghệ Vai trò
CRDT Engine Yjs, Automerge Thao tác dữ liệu conflict-free
Local DB SQLite, IndexedDB, RxDB Lưu trữ persistent trên device
Transport WebSocket, WebRTC, WebTransport Đồng bộ real-time giữa devices
Cloud Backup WebDAV, S3, custom API Lưu snapshot, cross-device restore
Identity Ed25519 keypair, DID Peer ID không cần centralized auth
Encryption E2EE trước khi sync Server không đọc được data

Khi nào dùng Local-First?

  • Notes & knowledge base: Obsidian, Notion alternative, Obsidian Sync is cloud-first nhưng có plugins local-first
  • Task management: Todo app, project management – dữ liệu phải offline
  • Collaborative editing: Design tool, whiteboard, code editor – Google Docs nhưng local-first
  • IoT & Edge: Sensor data log, offline-first industrial app
  • Health & Finance: Privacy-sensitive data không muốn gửi cloud

Hạn chế & Lưu ý

  • Bandwidth: State-based CRDT gửi full state → không phù hợp large binary (dùng op-based hoặc delta sync)
  • Memory: Yjs history giữ toàn bộ operations → cần compaction định kỳ
  • Auth: E2EE key management phức tạp cho multi-device
  • Binary assets: Ảnh/video nên dùng file sync riêng (rsync, Syncthing) thay vì CRDT

Kết luận

Local-first không phải trend nhất thời – nó phản ứng lại mối quan hệ lệ thuộc mù mịt giữa người dùng và cloud vendor. Với CRDT, sync “just works”: merge tự nhiên, không cần central server quyết định đúng sai, offline vẫn full feature.

Stack hiện tại (Yjs + SQLite + WebSocket + WebDAV) đã đủ cho hầu hết use-case. Không cần chờ “perfect solution” – bắt build local-first ngay hôm nay, dữ liệu của bạn (và người dùng) sẽ cảm ơn bạn sau 5 năm.

Nguồn: Ink & Switch – Local-first Software, Yjs Documentation, Automerge, Yjs Research Paper, Kleppmann – CRDT Paper

Tôi là một lập trình viên IOS. Code chính là IOS nhưng thỉnnh thoảng vẫn đá sang Android hoặc web. Mặc dù không quá thông thạo nhưng tôi sẽ chia sẻ những kiến thức mà mình đã tìm hiểu, áp dụng qua.

Bài viết liên quan

Clean Code trong Python: nguyên tắc SOLID thực tế

Trong bối cảnh công nghệ phát triển nhanh chóng, clean code trong python: nguyên tắc solid thực tế trở thành chủ đề nóng hổi thu hút sự quan tâm của…

Xem thêm
Bar chart comparing Tauri and Electron application size memory usage and startup time metrics

Tauri v2: Khi Rust Gap Web De Xay Dung Ung Dung Desktop Nhe An Toan Va Hieu Qua

Tauri v2 biểu thị một bước tiến lớn trong phát triển ứng dụng desktop cross-platform, cho phép nhà phát triển sử dụng các công nghệ web quen thuộc (HTML, CSS,…

Xem thêm

HTMX: Giao Diện Web Đơn Giản Hơn Không Cần JavaScript

HTMX là thư viện JavaScript mã nguồn mở mở rộng HTML bằng các thuộc tính tùy chỉnh, cho phép sử dụng AJAX, CSS Transitions, WebSocket và Server-Sent Events trực tiếp…

Xem thêm
0 0 đánh giá
Article Rating
Theo dõi
Thông báo của
guest
0 Comments
Cũ nhất
Mới nhất Được bỏ phiếu nhiều nhất