Several people have asked us how PowerSync compares to ElectricSQL. We’ve put together this overview to answer this question more thoroughly. Please feel free to chat to us on Discord if you have any questions or comments.
Similarities between ElectricSQL (Legacy) and PowerSync
On the surface, the legacy ElectricSQL and PowerSync have similar goals and enable similar outcomes for developers: Both are designed to be a plug-in sync layer for existing application stacks — offering bi-directional syncing of data between standard Postgres [1] and client-side databases (e.g. SQLite).
Both ElectricSQL and PowerSync provide:
- Instant reactivity for your apps (by allowing your app front-end to work with a local database, with live queries)
- Offline operation of apps (read and write to a local database, and then data syncs in the background automatically when connectivity is available — both systems use some kind of upload queue on the client-side)
- Real-time multi-user collaboration (by keeping data in sync between users and devices in real-time)
Both can be used in greenfields and brownfields projects. Both use JWTs for authentication. Both use Postgres logical replication / the WAL to monitor the database for changes to be streamed to clients. Both provide causal consistency guarantees.
Key differences between ElectricSQL (Legacy) and PowerSync
While there are broad similarities between ElectricSQL and PowerSync, there are important differences in architecture.
Summary: ElectricSQL vs. PowerSync architecture
Here’s a brief overview of the fundamental differences in architecture. Some of these are explored in more detail below.
ElectricSQL’s direct-to-Postgres CRDT architecture vs. PowerSync’s authoritative backend architecture
As mentioned above, ElectricSQL writes directly to your Postgres database, using CRDTs to merge changes deterministically. At a high level, the architecture looks like this:
There are a few implications of this architecture that developers should take into consideration when weighing whether ElectricSQL is the right fit for their needs:
1. Access control & validation defined by the database
In the local-first (and offline-first) paradigm, you will typically implement much of your business logic in the client (see here), since you want the app to be fully functional regardless of network/cloud availability. However, the client-side is an untrusted environment, and therefore when writes are propagated to the server-side Postgres database, we need a way to enforce validation and authorization as a last line of defense.
With ElectricSQL there is no custom server/backend mediating the interfacing with Postgres, and ElectricSQL uses Postgres as the exclusive “control plane” for the system. This means that the Postgres database configuration is used exclusively by ElectricSQL for defining access control / validation — in other words, the database defines the line of defense for malicious client actors.
For example, user input validation has to be done using Postgres check constraints. Access control and permissions need to be defined using ElectricSQL’s DDLX which is executed in PostgreSQL as part of migrations. That being said, check constraints and DDLX access controls are not yet available in ElectricSQL at the time of publishing this blog post.
2. Server-side workflows using event sourcing
As a result of the ElectricSQL architecture, server-side workflows cannot be triggered by your backend application. Therefore, ElectricSQL recommends triggering server-side workflows using event sourcing based off of database change events. This means adding an additional system/component to your stack for database event sourcing, and integrating your backend framework with that system.
3. No custom conflict resolution
ElectricSQL applies predefined behavior to merging changes and resolving conflicts using CRDTs. For example, ElectricSQL uses the Rich-CRDT feature of ‘Compensations’ to guarantee referential integrity: If user A deletes an object while user B concurrently creates a reference to that object, the Compensation will re-create the object deleted by user A in order to preserve referential integrity.
In cases like this, the behavior of the CRDT might not be aligned with what the developer would prefer for a particular system or a particular business requirement. ElectricSQL does plan on eventually allowing the developer to select between different CRDTs / conflict-resolution policies, but this will still result in limited flexibility.
PowerSync’s authoritative backend architecture
With PowerSync, writes are sent through your own application backend [2], allowing you to customize how they’re handled: You can apply your own business logic, fine-grained authorization, validations and server-side integrations, you can resolve conflicts using the techniques/algorithms of your choosing, you can trigger server-side workflows, and you can reject changes from clients if needed. At a high level, the architecture looks like this:
Leveraging CRDTs’ strengths
At PowerSync, we believe CRDTs have great strengths: For example, they are an excellent solution for collaborative document/text editing. Although ElectricSQL’s internal merge logic uses CRDTs, ElectricSQL does not provide a built-in solution for document/text collaboration using CRDTs. It would be a similar effort to implement document/text collaboration with either PowerSync or ElectricSQL. For example, you could use Yjs and store its CRDT data structure in Postgres using blob columns, with each row in the Postgres table representing a user edit/update (or each row representing a whole document). We’ve provided an example implementation here.
Modification of your Postgres database: ElectricSQL vs. PowerSync
Another factor that developers should take into consideration when weighing whether ElectricSQL or PowerSync is the right fit for them, is the degree to which they require any changes to the Postgres database.
It should be noted that ElectricSQL makes various changes to your Postgres database:
- Adds additional “shadow tables” to your schema, which store CRDT data which are used to merge changes. This increases the data storage used by your database. At the very least it will double the size of the data accessed by ElectricSQL, and as the change history grows, it will gradually increase from there.
- Adds triggers, for example to perform merge logic.
- Typically subscribes to a logical replication stream from the ElectricSQL sync service — this is typically how changes are written into your database. ElectricSQL subsequently also added a ‘direct writes’ mode as an alternative to the logical replication subscription.
It is also worth noting that Electric requires [.inline-code-snipper]SUPERUSER[.inline-code-snipper] privileges/permissions to your Postgres database, unless you are using the ‘direct writes’ mode.
PowerSync does not modify your Postgres:
- Requires no modifications to your Postgres database except for enabling logical replication [3]
- Requires read-only permissions to your database (only the [.inline-code-snipper]SELECT[.inline-code-snipper] and replication privileges)
ElectricSQL’s tight coupling to Postgres vs. PowerSync’s loose coupling
Developers should also consider that ElectricSQL’s architecture makes it tightly-coupled to your Postgres schema and configuration:
- ElectricSQL places restrictions on what you can have in your Postgres schema. For example, Check and Unique constraints are not supported by ElectricSQL and need to be removed from a table’s schema before “electrifying” that table.
- Data transformations between Postgres and SQLite are not possible with ElectricSQL — the client-side schema will match the server-side schema (although it is possible to do basic filtering of columns).
- Only specific Postgres types are supported. If you are using custom types in your Postgres database (e.g. for PostGIS), you would need to look into whether this can be supported (refer to “Local migrations” in the ElectricSQL docs).
- The database configuration is exclusively used for defining validation and access controls e.g. using Check constraints and DDLX (as mentioned above)
- ElectricSQL performs automatic client-side migrations to keep the client-side schema in sync with the server-side Postgres schema. This may cause complications if your schema changes and users are still running older client versions.
- All Postgres migrations need to run through the ElectricSQL migrations proxy (which allows ElectricSQL to enforce its restrictions and make its required modifications to the Postgres schema/configuration). If you make changes to your Postgres schema without using the ElectricSQL proxy, ElectricSQL will malfunction [4]. This means that your migrations tooling must be configured to always use this ElectricSQL proxy. If you use a backend framework, you need to set it up to run its migrations (but not all database traffic) through the proxy.
PowerSync: Loose coupling to Postgres
By contrast, PowerSync is loosely coupled to Postgres, creating a layer of flexibility between your Postgres schema and configuration and the client-side SQLite schema.
- When defining Sync Rules, you can write custom SQL queries that can (optionally) perform transformations on the Postgres data/schema.
- You have control over defining the client-side schema. You can automatically generate a default schema based off of your Postgres schema, and/or you can customize it based on your needs. The Postgres and SQLite schemas can be relatively independent of each other. In fact, PowerSync does not actually enforce a hard schema on the SQLite database: PowerSync syncs “schemaless” data, and then applies your client-side schema on top of that using SQLite views.
ElectricSQL vs. PowerSync functionality and limitations
Something else to take into consideration is that ElectricSQL is currently in “open alpha”. The team has said that they do not currently recommend production use, and there are features missing that may be needed for production deployment.
Here’s a quick rundown of some differences in features and limitations, accurate as of the time of publishing this blog post:
Footnotes
[1] Support for additional databases is planned for PowerSync, including MySQL and Microsoft SQL Server / Azure SQL.
[2] Note: PowerSync is different from systems like WatermelonDB where you have to build your backend functionality for the read path, which can put a heavier burden on the developer in terms of scope and complexity. PowerSync provides the PowerSync Service which handles the read path of syncing to users, including the complexities of dynamic partial replication. PowerSync only relies on your backend for handling writes. On the client-side, PowerSync handles the complexity of queueing writes so that they can be retried if connectivity / the backend is not immediately available.
[3] In some cases denormalization is required to effectively partition the data to sync to different users.
[4] There are workarounds to do migrations without the proxy, but it requires more effort.