(updated)
|
min. read

Ditto vs PowerSync

Conrad Hofmeyr

We’ve been asked how PowerSync compares to Ditto, especially since MongoDB has deprecated its Atlas Device Sync product and indicated both Ditto and PowerSync as potential migration paths. We’ve put together this overview to answer this question more thoroughly.

Note that this comparison was created by the PowerSync team based on public Ditto documentation as it existed around mid-January 2025. As such, there might be areas of Ditto we did not focus on or have inadvertently overlooked. Please feel free to get in touch or talk to us on Discord if you have any questions or comments, or would like to highlight any corrections or omissions.

Executive summary

Both Ditto and PowerSync are sync engines providing overlapping functionality (such as offline-first architecture, query-based partial syncing, real-time syncing, live queries on the client-side and consistency guarantees), but there are major differences:

  • Ditto enables peer-to-peer (P2P) syncing whereas PowerSync has a more traditional client-server architecture where data is always synced to clients via a server. Ditto’s P2P functionality is innovative and powerful but introduces complexities that might be unfamiliar to developers who are used to a client-server architecture.
  • PowerSync exposes a standard SQLite database to developers on the client-side whereas Ditto exposes a proprietary datastore interface [1] that lacks certain functionality compared to SQLite. SQLite is a widely-used open-source database and developers can use popular SQLite tools with PowerSync such as ORMs.
  • The source code for PowerSync is open, whereas Ditto is closed-source. This allows anyone to inspect and modify the core PowerSync system. Relatedly, PowerSync can be run locally and self-hosted, whereas Ditto cannot.
  • Ditto is a fully-fledged database whereas PowerSync is a pluggable “middleware” sync layer that acts as a bridge to existing backend databases (Postgres, MongoDB and MySQL) with bidirectional syncing. To sync data unidirectionally from Ditto to an external backend database requires integration using Kafka (an optional premium upgrade). For bidirectional syncing with an external database, Ditto’s MongoDB Connector could be used in the case of MongoDB (which is in preview and not yet production-ready at the time of writing) or a custom solution would be needed.
  • Ditto has some prescriptiveness and restrictions around how authentication and authorization can be accomplished, whereas PowerSync allows for more flexibility.
  • Ditto uses CRDTs to merge changes and resolve conflicts automatically. In comparison, PowerSync’s authoritative-server architecture makes conflict resolution behavior such as “last write wins” straightforward, while also allowing more customizability if needed.


Below, these similarities and differences are explored in more detail.

Similarities between Ditto and PowerSync

Here are some of the surface-level similarities between the two products:

Ditto PowerSync

Cloud/server environment

The Ditto Big Peer is described in parts of the Ditto documentation as a “server” although it is in most ways another peer in the distributed Ditto mesh network. Typically deployed in the cloud, it acts as a “trusted peer” in the network that provides services such as authentication, acting “as a conduit for mesh-generated data” (e.g. exposes an HTTP API) and allowing for integration with external databases. The Big Peer facilitates “device-to-cloud-to-device” syncing as opposed to device-to-device (P2P) syncing that is also supported by Ditto [2].

The PowerSync Service is the server-side component of PowerSync, and is in many ways analogous to the Big Peer in Ditto. It streams changes from the source backend database (Postgres, MongoDB, MySQL) and syncs them in real-time to clients based on Sync Rules, which also control authorization [2].

Client environment

Small Peers that embed the Ditto SDK are analogous to “clients” in a client-server architecture. Small Peers are typically end-user environments such as mobile, web, desktop or embedded apps.

PowerSync client SDKs are embedded into mobile, web, desktop or embedded apps.

Supported client SDKs

Ditto supports an impressive range of SDKs, including Swift, Kotlin, Flutter, React Native, JavaScript Web, C#/.NET, Java, Rust and C++ [3].

PowerSync currently supports Flutter, React Native, JavaScript Web, Swift and Kotlin client SDKs, with C#/.NET and Node.js under active development, and others on the roadmap.

Offline-first architecture

Provides for an offline-first architecture, enabling uninterrupted functioning of the app even if the network or cloud services become unavailable.

Ditto supports P2P syncing of data which allows Small Peers to sync with each other even if connectivity to the wide area network is unavailable.

Provides for an offline-first architecture, enabling uninterrupted functioning of the app even if the network or cloud services become unavailable.

While apps can continue to access their local database and read and write data while offline, PowerSync does not provide any built-in functionality to allow clients to sync with each other (P2P) when connectivity to the wide area network is unavailable.

Query-based partial syncing

Using Subscriptions defined on the client-side [4].

Using Sync Rules defined on the server-side, which can accept parameters from the client-side [4].

Real-time syncing

Provides for real-time syncing (P2P or via the Big Peer)

Provides for real-time syncing whenever clients are connected to the PowerSync Service

Reactivity on the client-side

Using Store Observers

Using live queries

Delta syncing for efficiency

Syncs deltas for efficiency, using CRDTs

Syncs deltas (field-level operations) for efficiency

Authentication

Conceptually similar to PowerSync, although technical details differ significantly:

Other advanced authentication schemes are also offered.

Conceptually similar to Ditto, although technical details differ substantially:

Attachments (binary files)

Attachments are “stored outside the main database” — only tokens/pointers to attachments are stored in the database. Attachments are not automatically synced and need to be explicitly fetched.

Support for attachments is built-in to the core Ditto product.

Recommends that attachments are stored outside the database — only tokens/pointers to attachments should be stored in the database. Attachments need to be fetched as needed.

Attachments are supported outside of the core product using helper packages.

Consistency guarantees

Causal consistency is enforced on the Big Peer. Note: Only eventual consistency is provided on the Small Peers — see below.

Causal consistency guarantee

Monitoring tools

Device dashboard showing per-device information

Monitoring & alerting tools including usage metrics dashboard, instance logs, issue alerts and metric alerts (deliverable as webhooks)

Key differences between Ditto and PowerSync

#1: P2P architecture vs. server-client architecture

Ditto PowerSync

Ditto has a decentralized P2P architecture and provides for P2P syncing (transfer directly between devices), with the exception of web browsers: P2P syncing is not available for web apps.

PowerSync has a more traditional client-server architecture, where clients sync data via the PowerSync Service.

Ditto Small Peers pull data from other peers whereas the Big Peer can also push data to Small Peers.

PowerSync always pushes data from the PowerSync Service to clients.

Ditto is an innovative and powerful product for use cases that require P2P syncing.

That being said, for many developers, the P2P/distributed architecture is a new and different paradigm to adapt to, and it comes with potential pitfalls and idiosyncrasies. We have listed some of these in an Annexure below (the security threat modeling is likely amongst the most important considerations.)

PowerSync’s client-server architecture is a familiar architecture pattern for many developers, and therefore the system is relatively easy to reason about.

PowerSync aims to provide a simple and robust architecture with comprehensive sync capabilities. It also aims to be extensible (developers can build on top of its simple yet capable core) and inspectable (developers are able to see what is happening within the system).

While Ditto enforces causal consistency on the Big Peer as mentioned above, only eventual consistency is provided on the Small Peer. This means that causally-related updates can appear out-of-order on Small Peers.

PowerSync provides causal consistency on the client. This means that updates will always appear in causal order.

#2: Open common technologies vs. proprietary technologies

Ditto PowerSync

Ditto exposes a proprietary datastore interface and query language (DQL) to developers [1].

PowerSync exposes a standard open-source SQLite database to developers on the client-side. SQLite is the most widely deployed database in the world.

Functionality such as aggregations, %%GROUP BY%%, joins, querying from multiple collections in the same query, and cross-collection transactions are not implemented in Ditto at the time of writing. Since Ditto is a proprietary database, the Ditto team has to build these features from the ground up.

PowerSync allows developers to leverage the mature capabilities that have existed in SQLite for many years, including its strong support for aggregations, joins, advanced indexing, transactions and JSON.

Ditto reportedly does not provide client-side database encryption.

PowerSync provides integrations with commonly-used SQLite libraries such as SQLCipher for client-side database encryption.

Ditto operates as a document database storing JSON-like documents.

PowerSync syncs schemaless JSON data and dynamically applies a client-side schema to the JSON data in SQLite using SQLite views. As a result, PowerSync adapts easily to working with both SQL and NoSQL backend source databases (e.g. Postgres or MySQL vs. MongoDB) and client-side schema migrations are generally not needed. Yet PowerSync still allows access to rich SQLite querying functionality.

Ditto introduces its own data types (e.g. %%REGISTER%%, %%MAP%%).

PowerSync leverages SQLite’s simple type system, type affinity, and its powerful JSON capabilities to work with JSON data, including embedded documents.

#3: Open vs. closed-source, running locally and self-hosting

Ditto PowerSync

Ditto is a closed-source proprietary product.

PowerSync client SDKs and supporting client-side packages are open-source (Apache 2.0 & MIT) while the server-side PowerSync Service is source-available under the Functional Source License (FSL).

Anyone can inspect the code for the core product, contribute to it, fork it, etc.

The Ditto Big Peer cannot run on the developer’s local machine.

PowerSync can freely be run locally for development / testing purposes and can be self-hosted anywhere.

#4: Vertically-integrated database vs. pluggable sync layer

Ditto PowerSync

Ditto is conceptually a full-stack database in and of itself. When it comes to data sync functionality, it is essentially a “syncing datastore” rather than a sync engine.

PowerSync is designed to be a sync engine that can be plugged into a developer’s existing preferred database stack. PowerSync can be thought of as a middleware layer. The PowerSync Service acts as a thin syncing layer between the primary source-of-truth backend database (Postgres, MongoDB, MySQL) and the client-side database (SQLite).

The datastore nature of Ditto introduces more complexity in certain areas, for example:

(1) To sync data unidirectionally from Ditto to an external backend database requires using Kafka change data capture (an optional premium upgrade). To sync data bidirectionally, Ditto’s MongoDB Connector can be used in the case of MongoDB (not yet production-ready at the time of writing), or a custom solution would need to be developed. There are potential pitfalls with using the Kafka integration. For example, when the replication falls too far behind, one has to use the HTTP API to requery the entire dataset and then resume incremental replication using the Kafka CDC stream.

(2) Migrating data between applications requires the help of the Ditto support team.

PowerSync’s architecture as a bidirectional sync layer between the source-of-truth backend database and the client-side database makes it relatively simple to deploy and manage.

Server-to-client sync path: The PowerSync Service pre-processes and pre-indexes data from the source-of-truth backend database (Postgres, MongoDB, or MySQL) for efficient syncing to clients, based on the Sync Rules configuration for the specific application. Therefore, the PowerSync Service is stateful in the sense that it maintains a “rebuildable cache” of data to be synced to clients. The data in the PowerSync Service can be cleared and recreated from the source backend database at any time.

PowerSync automatically takes care of snapshotting data from the source database and keeping it up-to-date using the CDC stream provided by the source database, as needed.

Client-to-server sync path: See details at #6 below.

#5: Ease of authentication & authorization

Ditto PowerSync

Ditto’s approach to authentication requires proxying an authentication request through the Ditto Big Peer to an “authorization endpoint” (webhook URL) provided by the developer. This authorization endpoint needs to conform to a certain implementation specification: it needs to accept arguments in a certain format and return a payload in a certain format.

PowerSync allows the developer to define their own client-side %%fetchCredentials()%% function that retrieves a JWT from their backend, which in turn allows the PowerSync client SDK to authenticate against the PowerSync Service. This allows for more flexibility and developer choice in implementation.

When it comes to authorization, there are restrictions that apply, which will likely require the developer to make system changes for proper implementation:

Ditto only supports specifying a permission query on the %%_id%% field of a document [6]. Permissions on mutable document properties are not supported. Therefore, in order to use other document fields in a permission query, they need to be duplicated into a compound %%_id%% field and treated as immutable.

These restrictions also have implications for Ditto’s MongoDB Connector.

Authorization for the read path is controlled using Sync Rules which are defined using SQL-like queries.

Sync Rules can make use of parameters from the JWT as well as client-provided parameters, and have flexibility to reference any of the columns/fields in tables/collections in the backend source database.

Writes are persisted to the backend database (Postgres, MongoDB, MySQL) via the developer’s backend of choice, where they can apply their own custom authorization rules. In most cases, developers would already have some kind of validation and authorization in place for writes (e.g. on their existing backend API), which they can reuse with PowerSync.

#6: CRDT distributed architecture vs. authoritative backend architecture

Ditto PowerSync

Ditto uses CRDTs to merge document changes and resolve conflicts.

This is a necessary byproduct of the fact that Ditto is a distributed system: since an authoritative server is not part of the system architecture, a deterministic distributed method of merging changes and conflict resolution is required, which is provided by CRDTs.

Since PowerSync has a more traditional server-client architecture, the server is considered authoritative and is used to merge changes and resolve conflicts:

The client-side application can perform writes directly on the local SQLite database. Those writes are also automatically placed into an upload queue by the PowerSync client SDK. The SDK then uses a developer-defined %%uploadData()%% function to manage the uploading of those writes to the backend. In the %%uploadData()%% function, the developer calls their backend API of choice. Because of the server-authoritative nature of PowerSync, whatever is written to the backend database will automatically be reflected on all clients (without overwriting local changes that have not yet been uploaded). The developer can either use their own backend completely separate from PowerSync, or use templatized turnkey cloud functions provided by PowerSync.

While PowerSync’s core protocol is not based on CRDTs, it is still possible to use CRDT data structures within PowerSync for applicable use cases.

The use of CRDTs means that conflict resolution is restricted to the predefined behavior provided by Ditto for its built-in data types (e.g. registers and maps).

The developer can customize the conflict resolution behavior according to their exact needs, including using CRDTs if desired. If they want simple behavior such as “last write wins”, this is straightforward and turnkey.

#7: Production-readiness for MongoDB

Ditto PowerSync

Ditto has a MongoDB Connector which is in preview status (pre-GA) at the time of writing, and is currently subject to various limitations that would prevent it from being used in production:

  1. The connector “only performs ongoing replication between Ditto and MongoDB” — it cannot yet sync any data already pre-existing in MongoDB.
  2. “The connector only supports the use of the REGISTER datatype within Ditto. Other datatypes such as MAP are not synchronized by the MongoDB connector”.
  3. “Filtering of data to synchronize with MongoDB is done at the collection level, filtering data within a collection to synchronize is not possible”.
  4. “Connection to a MongoDB Atlas cluster is routed via the internet rather than private networking options.”
  5. “It is not possible to change the configuration (e.g. add or remove a collection, change password) from a running connector. You must delete the connector then create it again with the new configuration”.

Taken together, limitations 1 and 5 mean that a new MongoDB database has to be created from scratch any time the configuration needs to be changed.

PowerSync’s support for MongoDB is currently in Beta status and a V1 release is planned before the end of Q1 2025.

PowerSync’s MongoDB connector is supported for production use and is currently in production use by customers.

  1. It supports initial replication of existing data in MongoDB as well as ongoing replication.
  2. It supports mapping between MongoDB types and SQLite types.
  3. It supports filtering data at the collection level as well as within collections — i.e. the standard Sync Rules functionality provided by PowerSync.
  4. It supports MongoDB Atlas private endpoints.
  5. The configuration can be changed on existing instances.

Annexure: P2P complexities

Ditto’s P2P functionality is innovative and powerful but introduces complexities that might be unfamiliar to developers who are used to a client-server architecture. Here are some examples:

  • Accessing data: If one tries to access data on the Big Peer using its HTTP API, the data associated with a specific transaction that happened somewhere in the mesh might not exist on the Big Peer yet, since it may require time to propagate through the mesh network (potentially through a multi-hop path)
  • Deleting data: There are various intricacies involved with deleting data, with soft-deleting patterns vs eviction of data vs hard-deleting data being applicable in different scenarios, each with its own considerations. For example:
    • Soft-Deletion: “In a distributed data system like Ditto, deletion follows a ‘soft delete’ pattern. This means that when you mark an item as deleted, it’s not immediately removed from the collection or local storage. Instead, the item is flagged as deleted to ensure that other devices in the network remain in sync. To fully remove the item and free up space, you’ll need to evict these ‘soft deleted’ items.” 
    • Eviction: “The EVICT method, once invoked, immediately removes the specified document from the local Ditto store, making it inaccessible by local queries. Although the document you evicted is removed from the local Ditto store, the document stored within remote Ditto stores persists. To prevent the evicted data from reappearing on the screen in a single flicker, make sure to stop subscriptions before you call EVICT”
    • Time-to-Live Eviction Strategy: This eviction strategy needs to be managed from the Big Peer to ensure that locally stored documents sync with the Big Peer before eviction (see also Device Storage Management).
    • Hard-deleting data from the Big Peer: This feature is currently available on request. Alternatively, deleting data from a Big Peer needs to be done using the legacy HTTP API, which creates Tombstones that cannot be removed themselves.
  • Data integrity: The complexities regarding deletion above also segue into complexities around data integrity:
    • Delete Considerations: Permanent data loss: “If documents are evicted from a local peer and don't exist on any other peer this data is lost and is unrecoverable.” 
    • Immutable ID fields: “Fields used in the id mapping must be immutable and always present to achieve convergence between the two systems. If these requirements are not met, it will result in undefined behavior, such as duplicate Ditto documents with different IDs, or failure to synchronize the document between MongoDB and Ditto.” 
    • Small-Peer-Based Eviction: “To maintain data integrity, you must cancel any active subscription that the eviction update may impact before executing your eviction process. Otherwise, an undesirable loop may result in which Ditto continuously evicts documents from the local Ditto store and then automatically re‑syncs them, leading to increased memory usage.”
      • This is also a consideration for creating new subscriptions: “You must declare your subscription object from the top-most scope of your app to ensure access throughout the life cycle of your app. Otherwise, you cannot modify or cancel your subscription from any part of your code, resulting in difficulty and potential errors when managing the subscription's lifecycle."
    • Conflicts with Tombstone and Modify: “If a tombstone (deletion flag) and a modification (INSERT) operation occur across two peers simultaneously, the resulting state depends on the causal order of these changes.”
  • Security: Additional complexities and considerations are involved in the security model:
    • Authorization: As mentioned above, permissions queries must use compound immutable %%_id%% fields.
    • Threat modeling: “Ditto peers are only able to synchronize data directly from a peer that has the authority to write that document. In other words, small peers are only able to trust writes from other peers that have the authority to make those writes. They cannot trust each other as a source of data that they aren’t authorized to write to, since they can’t tell the difference between an edit they made on their own and one which came from a further hop.”
      • "Today Ditto enforces that data which has to propagate peer-to-peer must have mutual write permissions. If integrity is at risk, you have to sign the payloads yourself at the application level."
    • App-level security vs. identity-level security trade-offs: “For apps with weaker security requirements, a developer may choose to relax the access rules inside the Ditto certificate, and instead restrict access in their application code. [...] The disadvantage is that an unprivileged user does have a device containing privileged data.”
  • Data size management: Ditto recommends careful management of the amount of data on the device:
    • Access Frequency and Relevance Considerations: “The fewer the number of documents replicated, the less the likelihood that peer devices run out of disk space and experience memory leaks, and the performance of the peer-to-peer mesh network that interconnects them degrades.”
    • Timing Subscriptions and Evictions: “Use Evict to manage local storage capacity and improve performance by routinely purging data stored locally.”
    • Wi-Fi Interference and Client Load: “Manage Data Size and Query Complexity: Large document syncs and verbose logging can slow down the network. It's important to manage the size of the data being synced and optimize queries to limit the amount of data processed at any given time. This can be done by evicting irrelevant data periodically and ensuring that only necessary data is queried.“
    • Device Storage Management: Eviction can slow down the Mesh: “Remember that before invoking the EVICT method, you must cancel relevant subscriptions and then restart them. This results in an increase in network usage, which can degrade sync performance. Call EVICT on a regular basis, but no more than once per day during periods of minimal disruption, such as after hours and at night.” This relates to the Time-to-Live Eviction Strategy 
    • Memory-usage caution: “A common issue we see in reactive apps is a failure to dispose of resources as conditions change. Your app could create a large accumulation of publishers that infinitely grow. Every liveQuery and subscription in ditto must be explicitly stopped using the stop or cancel API.”
    • See also: Troubleshooting: Synchronization seems slow, or comes to a halt over time
  • Bandwidth limitations: Bandwidth limitations of the P2P network (such as limited bandwidth on Bluetooth/BLE) as well as the inherent higher bandwidth usage of a mesh network should be considered. Note that Ditto allows disabling specific types of mesh network transports.
    • Wireless Infrastructure: “While Ditto does its best to be bandwidth-efficient, meshes are inherently more bandwidth-intensive than a typical hub-and-spoke server model. For larger meshes (e.g. more than 30 devices), LAN issues can start to really play a role in performance degradation.”
    • Embedded Maps are Slow: “Deeply nested objects result in slow replication and data store performance”
    • End-User Defined Peer Metadata: "Keep the size of peer metadata to a minimum, especially when syncing over Bluetooth LE or similar low-bandwidth transports. This is because peer metadata exceeding 128 KB, the maximum limit, results in the operation failing and Ditto throwing an error."
  • Battery life and CPU usage considerations:
    • Managing Redundant Bluetooth LE Connections: “Maintaining redundant Bluetooth LE connections can impact sync performance, especially in larger meshes with approximately 65 to 100 devices or more, where increased network overhead may cause a slowdown in the data replication process.
    • Transport Overview: Connections: “Each additional LAN connection consumes more CPU time but also depletes radio energy. Bluetooth Low Energy (LE) faces particularly stringent constraints — devices can only handle a few concurrent connections, and each connection initiation can take several seconds.” 
  • Local networking configuration complexities: Use of Ditto’s P2P syncing is dependent on network configuration and permissions considerations.
    • Ditto P2P syncing over the local network and/or Bluetooth is reliant on the user granting the necessary permissions.
    • Ditto P2P connectivity requires ports to be accessible on devices for external connections.
    • Ditto’s discovery protocol is dependent on the Client Isolation Mode feature on Wi-Fi routers or access points to be disabled.
    • IP multicasting entitlements and related permissions need to be enabled for connectivity between iOS and other devices.
    • Deployment Best Practices:When deploying applications that require multiple Bluetooth devices in a mesh network, it is crucial to prioritize connections to critical devices like payment readers. On iOS, where native prioritization may not be available, developers should implement custom logic to ensure that essential devices maintain a stable connection.” 
    • Direct WiFi connections can only be used between iOS devices or between Android devices, not cross-platform (see FAQ)
    • “Web browsers do not support peer-to-peer transports. That means a web app will only be able to connect to the Big Peer over WebSockets.” (see here)

[1] It appears that the Ditto datastore on Small Peers may use SQLite as its underlying persistence layer, but regardless, the developer interface to the data is Ditto’s proprietary datastore interface and DQL syntax.

[2] The Ditto Big Peer acts as a fully-fledged database (built on RocksDB). By contrast, the PowerSync Service acts more like a “rebuildable cache” that sits between the source-of-truth backend database (Postgres, MongoDB or MySQL) and client applications. The PowerSync Service pre-processes and pre-indexes data from the source backend database for efficient syncing to clients (organized into “buckets”). This pre-processed “bucket data” is stored in a pluggable storage layer (MongoDB and Postgres are currently supported as bucket storage databases).

[3] Ditto Tools (debugging and monitoring tools) are not available for all languages/frameworks — they are only available for Android (Kotlin), Swift and C# .NET

[4] The fact that subscriptions are defined on the client-side in Ditto result in additional considerations: “Start your subscription as early in the application lifecycle as possible. Keep a reference to the created subscription, so you can manage it and it doesn’t get garbage collected”. These considerations do not apply to PowerSync since its Sync Rules are defined on the server-side.

[5] In Ditto, enabling OnlinePlayground Authentication is an app-wide setting enabled on the Ditto cloud portal. The playground token also appears to be a global shared token for everyone testing a particular app. In PowerSync, Development Tokens are generated for a specific user and expire after a certain period of time.

[6] Another caveat is that permissions queries must be written using the legacy query syntax and not DQL.

Subscribe to receive PowerSync updates
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

By clicking "Subscribe" I agree to opt-in to PowerSync's mailing list
and accept the Privacy Policy.