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

Event-Driven Architecture: Kafka, RabbitMQ, Apache Pulsar

Event-Driven Architecture là gì? Event-Driven Architecture (EDA) là kiến trúc phần mềm xây dựng xung quanh sự kiện — các thay đổi trạng thái trong hệ thống được phát hiện,…

Xem thêm

WebSocket thời gian thực trong web

WebSocket là gì? WebSocket là giao thức truyền thông hai chiều (full-duplex) qua một kết nối TCP duy nhất, cho phép server và client trao đổi dữ liệu thời gian…

Xem thêm
Polars DataFrame featured image comparison chart

Polars DataFrame: Thay thế Pandas cho xử lý dữ liệu lớn hiệu năng cao

Polars DataFrame là gì? Polars DataFrame là thư viện xử lý dữ liệu hiệu năng cao viết bằng Rust, được thiết kế như một sự thay thế nhanh hơn và…

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