CRDT Overview
Conflict-Free Replicated Data Types for offline-first, multi-node, and eventually-consistent applications with Grove.
Grove includes an optional CRDT (Conflict-Free Replicated Data Type) layer that enables offline-first, multi-node, and eventually-consistent use cases. It tracks field-level changes with Hybrid Logical Clocks and merges them automatically during sync.
Why CRDTs?
Traditional databases rely on a single source of truth. When multiple nodes make concurrent edits, you need conflict resolution. CRDTs solve this mathematically: any two nodes that have seen the same set of updates will converge to the same state, regardless of the order updates arrive.
Grove CRDTs are useful for:
- Offline-first apps that queue changes locally and sync when connectivity returns
- Multi-node deployments where edge servers process writes independently
- Real-time collaboration where multiple users edit the same document
- Event sourcing with guaranteed convergence across replicas
Five CRDT Types
Grove supports five field-level CRDT types. Tag struct fields to opt in:
| Type | Tag | Merge Strategy | Use Case |
|---|---|---|---|
| LWW-Register | crdt:lww | Highest HLC timestamp wins | Strings, booleans, JSON objects |
| PN-Counter | crdt:counter | Per-node max of increments/decrements | View counts, scores, balances |
| OR-Set | crdt:set | Add-wins union with observed-remove | Tags, categories, member lists |
| RGA List | crdt:list | Union all nodes, DFS traversal sorted by HLC | Ordered collections, task lists, kanban boards |
| Nested Document | crdt:document | Per-path field merge via MergeEngine | JSON-like nested structures, forms, user profiles |
Fields without crdt: tags work normally with zero overhead.
How It Works
Model (struct tags)
|
Plugin (Grove hook)
|
Shadow Table (_<table>_crdt)
|
HLC Clock (Hybrid Logical Clock)
|
Syncer (pull/push protocol)
|
Transport (HTTP / SSE / WebSocket / custom)
|
Rooms / Presence ── Remote Node
|
Time-Travel (optional history)- Struct tags declare which fields are CRDT-enabled and their type
- Plugin intercepts mutations (insert/update/delete) via Grove hooks and records changes in shadow tables
- Shadow tables store per-field CRDT state alongside the primary table without modifying its schema
- HLC clock generates causally-ordered timestamps without coordination between nodes
- Syncer orchestrates two-phase sync: pull remote changes and merge, then push local changes
- Transport handles the network layer (HTTP, SSE streaming, WebSocket, or custom protocols)
- Rooms provide scoped collaborative spaces with participant management, cursor tracking, and typing indicators
- Time-Travel optionally records field-level history for audit trails, undo, and debugging
Architecture
grove/crdt/
crdt.go Core types: CRDTType, State, FieldState, ChangeRecord
clock.go HLC implementation
register.go LWW-Register merge
counter.go PN-Counter merge
set.go OR-Set merge (add-wins)
list.go RGA List merge (ordered collections)
document.go Nested Document merge (per-path heterogeneous)
merge.go MergeEngine: field + state merging
plugin.go Plugin (Grove hook integration)
plugin_hooks.go CRDTPlugin interface + interceptors
hooks.go PostMutation/PreQuery hooks
metadata.go Shadow table read/write
sync.go Syncer: push/pull orchestration + StreamSync
sync_hooks.go SyncHook interface for intercepting sync data
transport.go Transport interface, HTTPClient, StreamingTransport
transport_ws.go WebSocket transport (bidirectional multiplexed)
server.go SyncController + HTTPHandler (standalone)
presence.go PresenceManager: in-memory ephemeral state with TTL
presence_types.go Presence types: PresenceState, PresenceUpdate, PresenceEvent
room.go RoomManager: room CRUD, join/leave, participant limits
timetravel.go Time-travel: field history, state-at-time queries
validation.go Schema + field validation rules
metrics.go Sync/merge/presence metric collectors
options.go Functional options
inspect.go Debug/inspect utilities
migrations.go Shadow table DDLKey Concepts
- HLC (Hybrid Logical Clock) combines wall-clock time with a logical counter and node ID for total ordering without coordination
- Shadow tables (
_<table>_crdt) store CRDT metadata alongside primary data; your primary table schema is never modified - Tombstones mark deleted records so deletions propagate during sync; they expire after a configurable TTL (default: 7 days)
- Sync hooks intercept changes flowing between nodes for validation, filtering, transformation, or audit logging
- Transport is pluggable: use HTTP, SSE streaming, WebSocket, or implement your own for gRPC, NATS, etc.
- Presence provides ephemeral, real-time awareness (typing indicators, cursors, online users) with TTL-based cleanup, SSE broadcast, and React hooks
- Rooms provide scoped collaborative spaces with participant limits, cursor tracking, typing indicators, and lifecycle hooks
- Time-Travel optionally records field-level history for querying state at any point in time, audit trails, and undo
- WebSocket transport enables bidirectional multiplexed sync over a single persistent connection
- Plugins allow intercepting merge, metadata, presence, room, time-travel, and connection events for custom logic
- Validation enforces schema and field-level rules before merge operations
- Metrics collect sync, merge, and presence statistics for observability
Quick Example
import (
"github.com/xraph/grove/crdt"
"github.com/xraph/grove/hook"
)
// 1. Define a CRDT-enabled model
type Document struct {
grove.BaseModel `grove:"table:documents,alias:d"`
ID string `grove:"id,pk"`
Title string `grove:"title,crdt:lww"`
ViewCount int64 `grove:"view_count,crdt:counter"`
Tags []string `grove:"tags,type:jsonb,crdt:set"`
}
// 2. Create plugin and attach to DB
plugin := crdt.New(crdt.WithNodeID("node-1"))
db.Hooks().AddHook(plugin, hook.Scope{Tables: []string{"documents"}})
// 3. Sync with a remote node
syncer := crdt.NewSyncer(plugin,
crdt.WithTransport(crdt.HTTPTransport("https://cloud.example.com/sync")),
crdt.WithSyncTables("documents"),
)
report, err := syncer.Sync(ctx)Next Steps
- Getting Started for a complete setup walkthrough
- CRDT Types for deep dives into LWW, Counter, Set, List, and Document semantics
- Sync Protocol for transport, syncer, and hook details
- SSE Streaming for real-time change propagation
- Presence & Awareness for ephemeral real-time state (typing indicators, cursors, online users)
- Room Management for scoped collaborative spaces with participant limits and lifecycle hooks
- Time-Travel & History for querying state at any point in time
- Plugin System for intercepting merge, presence, room, and connection events
- WebSocket Transport for bidirectional multiplexed sync
- TypeScript Client for browser and React integration