PowerSync: Designed for Causal+ Consistency
PowerSync is designed to have causal+ consistency, while providing enough flexibility for applications to perform their own data validations and conflict handling. PowerSync’s consistency properties have been tested and verified.How It Works: Checkpoints
A checkpoint is a single point-in-time on the server (similar to an LSN in Postgres) with a consistent state — only fully committed transactions are part of the state. The client only updates its local state when it has all the data matching a checkpoint, and then it updates the state to exactly match that of the checkpoint. There is no intermediate state while downloading large sets of changes such as large server-side transactions. Different tables and buckets are all included in the same consistent checkpoint, to ensure that the state is consistent over all data in the client.Client-Side Mutations
Client-side mutations are applied on top of the last checkpoint received from the , as well as being persisted into an upload queue. While mutations are present in the upload queue, the client does not advance to a new checkpoint. This means the client never has to resolve conflicts locally. Only once all the client-side mutations have been acknowledged by the server, and the data for that new checkpoint is downloaded by the client, does the client advance to the next checkpoint. This ensures that the operations are always ordered correctly on the client.There is one nuanced case here, which is buckets with Priority 0 if you are using Prioritized Syncing.
Types of Client-Side Mutations/Operations
The client automatically records mutations to the client-side database asPUT, PATCH or DELETE operations — corresponding to INSERT, UPDATE or DELETE statements in SQLite. These are grouped together in a batch per client-side transaction.
Since the developer has full control over how mutations are applied to the source database, more advanced operations can be modeled on top of these three. See Custom Conflict Resolution for examples.
Validation and Conflict Handling
With PowerSync offering full flexibility in how mutations are , it is also the developer’s responsibility to implement this correctly to avoid consistency issues. Some scenarios to consider: While the client was offline, a row was modified on the client-side. By the time the client is online again, that row has been deleted on the source database. Some options for handling the mutation in your backend:- Discard the mutation.
- Discard the entire transaction.
- Re-create the .
- Record the failed mutation elsewhere, potentially notifying the user and allowing the user to resolve the issue.
- In general, consider relaxing constraints somewhat on the backend where they are not absolutely required. It may be better to accept data that is somewhat inconsistent (e.g. a client not applying all expected validations), rather than discarding the data completely.
- If it is critical to preserve all client mutations and preserve the order of mutations:
- Block the client’s upload queue on unexpected errors (don’t in your backend API).
- Implement error monitoring to be notified of issues, and resolve the issues as soon as possible.
- If it is critical to preserve all client mutations, but the exact order may not be critical:
- On a constraint error, persist the transaction in a separate queue on your backend, and acknowledge the change.
- The backend queue can then be inspected and retried asynchronously, without blocking the client-side upload queue.
- If it is acceptable to lose some mutations due to constraint errors:
- Discard the mutation, or the entire transaction if the changes must all be applied together.
- Implement error notifications to detect these issues.