Syncing Data
The conceptual model: subscription queries, delta sync, and CRDT-based conflict resolution.
Managing Subscriptions
SDK reference for registering, retrieving, and canceling subscriptions in each language.
Stateful Subscription Performance Guidelines
How
LIMIT and ORDER BY clauses affect sync performance, and how to write subscription queries that scale.Mesh Networking
How peers discover each other and propagate updates across multiple transports.
Scope subscriptions to the data each peer needs
In a query-based sync model, a peer only receives documents that match the queries it has subscribed to. A well-scoped subscription reduces bandwidth, storage, and CPU on every device in the mesh, and lets the sync engine prioritize the data that matters to the user in front of the device. Subscribe to the narrowest set of documents the device actually needs — filter by tenant, region, role, or whatever dimensions partition your data — rather than syncing entire collections. When peers run the same subscription, Ditto’s flood-fill propagation works at full efficiency: any peer with matching documents can serve any other peer that wants them, regardless of who originally produced the data. Where practical, align subscription shapes across peers in the same role.Keep subscriptions stable
Each time a subscription is registered, canceled, or changed, peers across the mesh re-evaluate what they owe the local device and the sync engine reconciles state. Doing this frequently degrades sync throughput and can interrupt in-flight transfers. As a general guideline, avoid changing subscriptions more often than every fifteen minutes or so. Register subscriptions when the relevant data first becomes needed — at app start, after login, or when the user enters a workspace — and cancel them when that data is no longer relevant. To filter the data a user sees within a stable subscription, run a local query against the result set rather than re-registering the subscription with new arguments.When subscriptions are created dynamically, cancel those that are no longer needed as new ones are registered. Subscriptions remain active until explicitly canceled, so unreleased subscriptions can accumulate over time and affect sync performance.
Subscription lifecycle in C++ and Rust
In the C++ and Rust SDKs,register_subscription returns a SyncSubscription wrapped in a shared_ptr (C++) or an owned handle (Rust). The caller is the sole owner — Ditto does not retain an internal reference to the subscription object.
Always call cancel() explicitly when a subscription is no longer needed:
C++
Avoid stateful subscription patterns
Subscription queries that useLIMIT, or LIMIT combined with ORDER BY, maintain state about which documents currently fall within the limit boundary. When a document inside the boundary changes — is deleted, no longer matches the WHERE clause, or moves position under the ORDER BY — Ditto must invalidate its cache and re-evaluate the query. This is manageable when the relevant fields rarely change, but expensive when they change often.
For the full guide — query examples, cache-invalidation rules, and migration strategies — see Stateful Subscription Performance Guidelines.
Keep subscription queries simple
Subscription query complexity affects not just local performance but Big Peer stability. Queries with deeply nested predicates — multiple levels ofAND, OR, or nested object traversals in WHERE clauses — require more server-side processing to evaluate. In extreme cases, overly complex queries can degrade the subscription server’s ability to serve all connected peers.
Guidelines for subscription query complexity:
- Flatten predicates where possible. Instead of deeply nesting conditions, restructure your data model or break queries into separate subscriptions with simpler filters.
- Watch for programmatically generated queries. Code that dynamically builds
WHEREclauses by iterating over arrays or nested structures can produce queries with far more nesting than expected. Review the actual query strings in your debug logs. - Prefer simple field comparisons. Conditions like
field == valueorfield > valueare efficient. Deeply nested object paths and compound conditions add processing overhead.