<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Zak</title>
    <description>The latest articles on DEV Community by Zak (@zknill).</description>
    <link>https://dev.to/zknill</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1216241%2F952649c7-a795-445d-9254-df7f11be8f0c.jpeg</url>
      <title>DEV Community: Zak</title>
      <link>https://dev.to/zknill</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zknill"/>
    <language>en</language>
    <item>
      <title>Database generated events: LiveSync’s database connector vs CDC</title>
      <dc:creator>Zak</dc:creator>
      <pubDate>Thu, 20 Jun 2024 08:34:06 +0000</pubDate>
      <link>https://dev.to/ably/database-generated-events-livesyncs-database-connector-vs-cdc-lhk</link>
      <guid>https://dev.to/ably/database-generated-events-livesyncs-database-connector-vs-cdc-lhk</guid>
      <description>&lt;p&gt;&lt;a href="https://hubs.la/Q02CzvQ20"&gt;Ably LiveSync is a product we launched last month&lt;/a&gt; to help developers deliver live updates in their applications by automatically keeping their database and frontend clients in sync.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://hubs.la/Q02CzvRD0"&gt;LiveSync&lt;/a&gt; is made of two components, the &lt;a href="https://hubs.la/Q02CzvS_0"&gt;Models SDK&lt;/a&gt; that runs on the client, and the &lt;a href="https://hubs.la/Q02CzvSw0"&gt;database connector &lt;/a&gt;that listens to changes in your database and syncs those changes to your clients.&lt;/p&gt;

&lt;p&gt;When we talk about ‘listening to your database’, we’re often referring to Change Data Capture (or CDC). In CDC a component listens to your database and distributes events representing the changes that have happened. Normally the events that are created by CDC represent the changes to each individual underlying row. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In this post, we’re going to discuss how the LiveSync database connector works, and how and why it is different from CDC.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Change_data_capture"&gt;Change data capture&lt;/a&gt; (or CDC) is a mechanism of listening to the changes in your database and sharing these (typically over a queue) with other consumers. It allows other participants in your system to react to changes that have happened in one part of the system. A classic example is in some ‘event driven’ architecture where an email service will send a confirmation email when some e-commerce ‘order’ is created. Typically the changes that are shared as events by CDC represent changes to each individual underlying database table. For example, a change of email address for a user would have the user record’s ID, and email address before and after the change.&lt;/p&gt;

&lt;p&gt;LiveSync’s database connector is different from traditional CDC, and we made some intentional choices about how and why it should be different. Our database connector is based on the ‘outbox pattern’, where a specific table is used to share database changes with the connector. Rows written to the outbox table have a ‘data’ field where the content of that column will be the payload of the event that’s generated and shared over Ably channels by the LiveSync database connector. The rows in the outbox also let you choose which Ably channel a record should be sent to.&lt;/p&gt;

&lt;p&gt;So given both CDC and the LiveSync database connector generate events to be shared with some consumers, what’s the difference between the two? Well it’s all about encapsulation and control; two things we wanted to make sure LiveSync baked in, which you might not get with traditional CDC.&lt;/p&gt;

&lt;p&gt;In traditional CDC:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Joins are hard:&lt;/strong&gt; Typically CDC events are generated per-table, this means that for any frontend data model that’s made of more than one database table, where you’d normally be writing a join in your database query, it’s really hard to work with in CDC. Events representing changes to two different tables have to be glued back together after they have been generated by a CDC system (perhaps using transaction ID metadata attached to the events).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encapsulation is hard:&lt;/strong&gt; Because traditional CDC operates on the database table level, it’s far too easy to accidentally share some internal data model details with the consumers of your CDC events. Lots of CDC systems do allow you to filter the fields included in the CDC events, but it’s still an easy mistake to make.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access control is hard:&lt;/strong&gt; It’s hard to encode into CDC which consumers should get access to which pieces of data. Typically all changes for a specific table go into a specific queue. Some CDC products do allow the partitioning of events into separate queues or topics based on a field of the changed row, but this isn’t always enough. Especially when you need data from a different table to enforce your access requirements (see: ‘joins are hard’). You end up having to create some filtering or mapping somewhere that can be queried server-side to work out if a client is allowed to see a row. (This is exactly what Supabase Realtime does, where the database has to be re-queried on each change event to enforce Row Level Security).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When we built the LiveSync database connector we wanted to make it easy to work with database generated events, and solve the things that made it hard to work with CDC. LiveSync is different firstly because it uses the ‘&lt;a href="https://en.wikipedia.org/wiki/Inbox_and_outbox_pattern#The_outbox_pattern"&gt;outbox pattern&lt;/a&gt;’. Using the outbox table, you get to control the content of the events that are shared by the database connector. You also get to control which Ably channel those events are published on. These two differences solve a bunch of the problems with traditional CDC.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You can use channels to help with access control:&lt;/strong&gt; Ably’s &lt;a href="https://hubs.la/Q02CzvWW0"&gt;capabilities&lt;/a&gt; mechanism allows you to authorise clients to only perform specific operations (publish, subscribe, etc) on specific &lt;a href="https://hubs.la/Q02Czw6s0"&gt;channels&lt;/a&gt;. This allows you to publish only the information relevant to those clients on those channels. And because you get to control the channel a database event is written to at the time the event is created, you can dynamically control which clients get access to which data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can expose only the data the clients need:&lt;/strong&gt; Because you get to specify the content of the event that’s written to an Ably channel, you can publish the exact content that the frontend needs. This solves the problems that CDC has with joining events from multiple tables, and solves the oversharing that can happen with CDC. So enforcing the right encapsulation and joins on the data is much easier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Traditional CDC products can be faster to ‘plug and play’, as they will connect to your database without any changes to your application and start sharing database changes. But there’s a trade-off, enforcing the access control, encapsulation, and joins that your application needs is much harder.&lt;/p&gt;

&lt;p&gt;In LiveSync we make these problems easy, by giving you an outbox table allowing you to choose which events are published, where those events are published, and the content those events carry. You can write events to the outbox transactionally along with the other changes in your database to ensure that the publish of messages to Ably and changes in your database either both happen, or neither happen. The outbox table along with the database connector guarantees that events written to the outbox table will appear in Ably, and will retain their order within each channel you’re writing to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started with LiveSync now
&lt;/h2&gt;

&lt;p&gt;Sign-up for a free account and &lt;a href="https://hubs.la/Q02CzwyF0"&gt;dive into our docs&lt;/a&gt; to give LiveSync a try. If you are interested in being an alpha tester, or would like to provide feedback on the product, please &lt;a href="https://docs.google.com/forms/d/e/1FAIpQLSd00n1uxgXWPGvMjKwMVL1UDhFKMeh3bSrP52j9AfXifoU-Pg/viewform"&gt;get in touch&lt;/a&gt; - we’d love to collaborate!&lt;/p&gt;

</description>
      <category>database</category>
      <category>dataengineering</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Reliably syncing database and frontend state: A realtime competitor analysis</title>
      <dc:creator>Zak</dc:creator>
      <pubDate>Thu, 16 May 2024 07:57:25 +0000</pubDate>
      <link>https://dev.to/ably/reliably-syncing-database-and-frontend-state-a-realtime-competitor-analysis-164</link>
      <guid>https://dev.to/ably/reliably-syncing-database-and-frontend-state-a-realtime-competitor-analysis-164</guid>
      <description>&lt;p&gt;Ably’s &lt;a href="https://hubs.la/Q02xm9gL0"&gt;LiveSync&lt;/a&gt; product is now available as a public alpha release. LiveSync allows you to automatically keep data in sync across servers and clients, in realtime.&lt;/p&gt;

&lt;p&gt;This is a hard distributed systems problem. In this post, we’re going to have a look at four existing solutions to realtime data synchronization, the different technical designs, and the tradeoffs they make. Then, at the end of the post I’ll describe the set of design choices we’ve taken at Ably for our LiveSync product.&lt;/p&gt;

&lt;p&gt;But first, what are others up to?&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Supabase
&lt;/h2&gt;

&lt;p&gt;Supabase has a feature called &lt;a href="https://supabase.com/docs/guides/realtime/postgres-changes"&gt;Postgres Changes&lt;/a&gt;, which is part of their &lt;a href="https://github.com/supabase/realtime"&gt;realtime&lt;/a&gt; product offering. It allows you to listen to changes that happen in a single table, and send those changes to clients that are allowed to receive them.&lt;/p&gt;

&lt;p&gt;As you INSERT, UPDATE, and DELETE records in your Postgres instance, Supabase receives these changes via the &lt;a href="https://www.postgresql.org/docs/current/protocol-replication.html"&gt;Postgres Streaming Replication Protocol&lt;/a&gt;, and sends the changes to the subscribed clients over websockets.&lt;/p&gt;

&lt;p&gt;Supabase also bundles auth through Row Level Security (RLS). This is a feature where clients get individual tokens, and those tokens grant access to specific rows in a Postgres table. The Postgres Changes feature integrates with RLS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Things to consider:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Supabase WebSockets do not have history or guaranteed delivery:&lt;/strong&gt; So you’re &lt;a href="https://github.com/supabase/realtime?tab=readme-ov-file#does-this-server-guarantee-message-delivery"&gt;not guaranteed&lt;/a&gt; to receive all the changes made to your database, and you can’t ‘rewind’ to get those changes if you do notice you’ve missed them. Even temporary disconnections from clients can result in you missing messages, and you have to do quite a lot of work to protect against this.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Mis-managed replication slots can tank your whole database:&lt;/strong&gt; The &lt;a href="https://supabase.com/docs/guides/realtime/architecture"&gt;architecture&lt;/a&gt; used by Supabase Postgres Changes involves two components: your Postgres server and a Realtime server. The Realtime Server consumes database changes from the Postgres replication slot. The replication slot stores all the changes that are happening in the database and once those changes have been processed by the consumer of the replication slot the data representing that change can be deleted from the replication slot by Postgres. This means that if the Realtime server can’t consume from the slot fast enough (faster than Postgres is writing to the slot), then the data stored in that replication slot will continue to grow. This replication slot shares disk space with your database. Eventually, the replication slot can consume all the disk space of your database. Once this happens, Postgres will reject new connections and new writes to the database.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;RLS adds a lot of queries to your database when using Postgres changes for fan-out:&lt;/strong&gt; The Supabase RLS feature delegates authentication and access control to the built-in Postgres &lt;a href="https://www.postgresql.org/docs/current/ddl-rowsecurity.html"&gt;Row Security Policies&lt;/a&gt; feature. In most request-response queries to the database, a single user is making some queries and Postgres is checking if that user’s auth token determines that they can access those specific rows. However, one of the common uses of the Postgres Changes feature is to fan-out database changes to multiple clients over WebSockets. To implement RLS for these fan-out changes, &lt;a href="https://supabase.com/docs/guides/realtime/postgres-changes#database-instance-and-realtime-performance"&gt;Supabase will re-query the row&lt;/a&gt; that has changed using each client’s auth token. That means if you have 1000 clients listening for changes in your Postgres table, then a single INSERT, UPDATE, or DELETE operation on that table will result in Supabase generating 1000 queries against the database (one for each client) to fetch the changed row and check whether access is allowed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Postgres Changes only works for a single table at a time:&lt;/strong&gt; That is, you can subscribe to multiple tables, but events in different tables are distinct from one-another. The data that your clients need is probably spread across a bunch of normalized database tables, and probably relies on a set of joins to create the entity that the client uses. However, Supabase will &lt;a href="https://github.com/supabase/realtime/issues/303"&gt;only send the changes&lt;/a&gt; that are happening to individual database tables. One option is to implement logic in the client to materialize the result of a query from the stream of changes to individual tables, which is exceedingly difficult to implement. A simpler solution, sometimes known as a poke/pull pattern, is to use Postgres Changes to fan-out a change notification to clients who then individually query for the new data from the database. This causes all your clients to request data at the same time, creating a spike request load.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  2. Liveblocks
&lt;/h2&gt;

&lt;p&gt;Liveblocks’ products are based on a series of CRDT building blocks (LiveObject, LiveMap, LiveList). These objects are backed by some opaque storage managed by Liveblocks. The changes are shared between the server and clients over WebSockets, which handle automatic reconnection and fetching older data. Thanks to the properties of the underlying CRDT, the order of message delivery doesn’t matter.&lt;/p&gt;

&lt;p&gt;By building on CRDTs, Liveblocks ensures a high level of data integrity for multiplayer, collaborative applications where multiple users make changes to a shared object in realtime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Things to consider:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Liveblocks stores and controls your data but getting a copy to your server is hard:&lt;/strong&gt; There is no mechanism by which Liveblocks can &lt;em&gt;push&lt;/em&gt; changes into your datastore. In order to reflect a copy of the Liveblocks data into your own server or database, you can &lt;em&gt;pull&lt;/em&gt; the state of the object via the &lt;a href="https://liveblocks.io/docs/api-reference/rest-api-endpoints#Storage"&gt;Storage API&lt;/a&gt;. This can be used in conjunction with a &lt;a href="https://liveblocks.io/docs/platform/webhooks#StorageUpdatedEvent"&gt;Storage Updated Event&lt;/a&gt; webhook to obtain the new state when changes are made, which is sent at most every 5 seconds. Therefore, due to the additional network round trips and throttled change notifications, there may be significant latency in reflecting changes from Liveblocks to your own servers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Source-of-truth:&lt;/strong&gt; In order to implement the CRDT building blocks, Liveblocks maintains control over the source-of-truth copy of your data. Many applications may expect the database to represent the source-of-truth in order to provide other application functionality. Therefore Liveblocks is best suited for the truly collaborative parts of your application state in which eventual consistency with your database is acceptable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Limited cross-platform support:&lt;/strong&gt; Liveblocks ships a set of client libraries that allow you to interact with the underlying CRDT storage primitives. The libraries are open-source, but don’t cover a broad set of languages and currently only target the JavaScript ecosystem.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  3. ElectricSQL
&lt;/h2&gt;

&lt;p&gt;ElectricSQL is bi-directional database sync between a Postgres server and SQLite running on the clients. Changes made in your Postgres database are fanned out to the clients, and changes made in each client’s SQLite are reflected back into the Postgres Server. ElectricSQL makes this work with a series of client libraries, and a backend component called the &lt;a href="https://electric-sql.com/docs/usage/installation/service"&gt;Electric Sync Service&lt;/a&gt; that runs against your Postgres Server. The Sync Service, similar to Supabase Postgres Changes, also makes use of logical replication to receive outbound changes from the database, but additionally the database consumes changes inbound from the Sync Service. The SQLite databases running on the clients are kept in sync with the Sync Service over WebSockets.&lt;/p&gt;

&lt;p&gt;ElectricSQL offers a novel approach to data synchronization and provides some powerful tooling for building local-first applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Things to consider:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Tight coupling between the Sync Service and your database: ElectricSQL’s design means that the Sync Service is quite tightly coupled to your database. An implication of this, for example, is that migrations have to be applied to your database via the &lt;a href="https://electric-sql.com/docs/usage/data-modelling/migrations#migrations-proxy"&gt;Migrations Proxy&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Limited query language expressivity: ElectricSQL &lt;a href="https://electric-sql.com/docs/usage/data-access/queries"&gt;queries&lt;/a&gt; are table-scoped, which limits the complexity of the queries you can write.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sync lags for one-to-many relations: ElectricSQL queries allow you to request data from related tables, but only many-to-one relations will be synced at the same time as the parent row. Other relations can be ‘included’, but will be synced sometime after. ElectricSQL calls this ‘&lt;a href="https://electric-sql.com/docs/usage/data-access/shapes#move-in-lag"&gt;move-in lag&lt;/a&gt;’. For example in an app with projects and issues, if you sync ‘active’ projects and a project becomes active then the project row (and many-to-one relations like project author) will be synced at the same time, but the issues (one-to-many) won’t immediately appear. So your app would show a project with zero issues until the issues are synced by ElectricSQL.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Conflict resolution semantics: Local writes are subject to concurrent merge semantics of the &lt;a href="https://electric-sql.com/docs/reference/consistency"&gt;underlying CRDTs&lt;/a&gt; that are used under-the-hood, meaning that updates might be overridden by the semantics of the conflict resolution logic, which may not preserve the original intent of the operation performed by the user.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There’s no permissions control: At the time of writing, ElectricSQL doesn’t have an access control or permissions system, although it is on the &lt;a href="https://electric-sql.com/docs/reference/roadmap#ddlx-rules"&gt;roadmap&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  4. Replicache
&lt;/h2&gt;

&lt;p&gt;Replicache is a client library that can be integrated with your own backend to synchronize database changes to your frontend clients. It operates on the principle of &lt;a href="https://gabrielgambetta.com/client-side-prediction-server-reconciliation.html"&gt;Server Reconciliation&lt;/a&gt;, an approach which delegates the resolution of conflicting concurrent changes to the database (or server) such that all clients reflect the authoritative state on the server, rather than relying on the properties of CRDTs as Liveblocks and ElectricSQL do.&lt;/p&gt;

&lt;p&gt;To make Server Reconciliation work, the clients make requests to tell the server the change they want to make (and not the result of the change they have made, like you might in a REST API). The request to the server could be “increment by 1” rather than the result of the increment operation that a client might have applied to its own local state. The server then applies that change, and then sends a change notification to clients. The change notification (or “poke”) is delivered to the client over a WebSocket, and on receipt of the poke the client must then request any changes it hasn’t yet received from the server. To fulfill this request, the server must compute the set of changes on a per-client basis such that each client receives only the data that it is missing.&lt;/p&gt;

&lt;p&gt;Replicache has a hosted solution called Reflect that takes away the significant complexity of building and managing the associated backend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Things to consider:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Backend complexity:&lt;/strong&gt; There is significant complexity in correctly implementing your backend in order to satisfy the requirements of the client library. Depending on the needs of your application, Replicache offers guidance for the different &lt;a href="https://doc.replicache.dev/strategies/overview"&gt;strategies&lt;/a&gt; you can implement to enable server-client synchronization.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Storing the complete change history:&lt;/strong&gt; In order to send each client the specific set of changes that client hasn’t received, the backend needs to store the full change history that has happened. There’s no guarantee how up-to-date any specific client is, so the full change history is required so that lagging clients can be caught back up. For any reasonably fast-changing entity, the server will very quickly end up with a lot of changes it has to store. Additionally, some strategies require you to use soft deletes, meaning that entities cannot be fully removed from your database.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Storing per-client state:&lt;/strong&gt; Depending on the backend strategy used, the backend may need to store a pointer for each client that represents where in the change history the client thinks it is up to. The pointer is used to catch-back-up clients that have forgotten where they got to. Replicache’s design means you have to store a lot of extra data in the database.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Thundering herd:&lt;/strong&gt; On delivery of a poke to connected clients, all clients must request their missing changes from the backend. This creates a spike in load on your database and server which cannot be cached as each client requires a customized response based on where it is in the change history.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In each of these designs there are a series of technology and design trade-offs. Each one will be better or worse depending on the use case it’s applied to. With LiveSync, we’re trying to make the best choices for the most use cases, minimizing developer effort and offering a high degree of flexibility and data consistency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ably LiveSync
&lt;/h2&gt;

&lt;p&gt;LiveSync allows you to reliably synchronize changes that occur in your database to frontend clients. LiveSync is a flexible solution designed to work with your database of choice and supports incremental adoption in existing applications.&lt;/p&gt;

&lt;p&gt;LiveSync borrows concepts from &lt;a href="https://aws.amazon.com/event-driven-architecture/#:~:text=What%20is%20an%20Event%2DDriven,modern%20applications%20built%20with%20microservices."&gt;Event Driven Architectures&lt;/a&gt; to offer a flexible and reliable approach to data synchronization. It is built on top of &lt;a href="https://hubs.la/Q02xmb1V0"&gt;Ably Pub/Sub Channels&lt;/a&gt;, our highly available, low latency edge messaging product. This means that it automatically benefits from automatic &lt;a href="https://hubs.la/Q02xmb5Y0"&gt;connection recovery&lt;/a&gt; as well as features like &lt;a href="https://hubs.la/Q02xmb6-0"&gt;rewind&lt;/a&gt; and &lt;a href="https://hubs.la/Q02xmbdl0"&gt;history&lt;/a&gt; to make sure you don’t miss messages. You can also leverage Ably &lt;a href="https://hubs.la/Q02xmbmN0"&gt;capabilities&lt;/a&gt; to control what data changes clients can access.&lt;/p&gt;

&lt;p&gt;LiveSync provides a client library called the &lt;a href="https://hubs.la/Q02xmbvT0"&gt;Models SDK&lt;/a&gt; which makes it easy to define data models in your frontend that are automatically kept up to date with changes made on your backend. These changes are automatically distributed to clients via the &lt;a href="https://hubs.la/Q02xmbxk0"&gt;Database Connector&lt;/a&gt;, which is available as a hosted solution on the Ably platform or as a container image which you can operate yourself. The Models SDK helps merge those changes easily and provides hooks for optimistic updates allowing for a snappy user interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Synchronizing changes
&lt;/h3&gt;

&lt;p&gt;LiveSync uses Ably Pub/Sub Channels to deliver the stream of changes from the database to connected clients. Each change is assigned a number that describes its position in the sequence of changes, called a sequenceId. Ably stores a history of the stream of changes on the channel so your backend doesn’t need to. The Models SDK tracks the sequence of changes the client has received and automatically rewinds on the channel to ensure that it doesn’t miss any changes.&lt;/p&gt;

&lt;p&gt;Applications built on LiveSync make use of the &lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/transactional-outbox.html"&gt;transactional outbox pattern&lt;/a&gt; to reliably deliver an ordered sequence of changes from your database to clients. When you make a change in your database, you transactionally write an event to the outbox representing that change. You choose the content of the event, and the change will be reliably fanned out in-order to all clients and merged with your frontend application’s state via the Models SDK. Once the event is successfully published to a channel, the Database Connector will clean up the data it no longer needs from the outbox table, so your database doesn’t have to store change history or extraneous data.&lt;/p&gt;

&lt;p&gt;The outbox table uses a monotonic serial number to automatically assign each outbox event with a sequenceId. Since the event published to the outbox is written transactionally with the changes to your own database tables, all the changes end up with an exact and total order defined by the sequenceId. The Database Connector and Models SDK ensure that these changes are processed by your frontend clients exactly once in the correct order.&lt;/p&gt;

&lt;p&gt;You can integrate LiveSync with your existing backend API by extending your database writes to transactionally write change events to the outbox table. Since you determine what data is included on the change event and how it is interpreted by the client, you can make changes to models that span multiple tables and represent that data however you like on the client.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optimistic updates
&lt;/h3&gt;

&lt;p&gt;In LiveSync, your database is the source-of-truth: in effect, LiveSync is implementing a form of Server Reconciliation in which the server decides the resulting state of some updates and fans those changes out to clients.&lt;/p&gt;

&lt;p&gt;There can be a short delay between a client making a change and the client receiving the associated change event from the backend. To make this easy to work with, the Models SDK &lt;a href="https://hubs.la/Q02xmdC60"&gt;supports optimistic updates&lt;/a&gt;, where the frontend client can make an update optimistically and the result of that update is immediately reflected on that client only. When the update is confirmed by the backend the optimistic update is replaced with the confirmed result delivered to the client. If the backend decides to reject the optimistic update, then the optimistic update is automatically rolled back in the client that requested it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bootstrap
&lt;/h3&gt;

&lt;p&gt;The final piece of the puzzle is bootstrapping a brand new client from scratch. You probably already have a REST API that you use to fetch an entity from your backend. You can use your existing endpoint to fetch the data used to initialize the model in the frontend. This endpoint can be extended to return one additional piece of information: the sequenceId. The Models SDK automatically uses the sequenceId to rewind to the correct position in the change stream on the Ably channel, so that your frontend receives exactly the changes it’s missing and no more.&lt;/p&gt;

&lt;p&gt;You don’t have to manage the sequenceId, because it’s automatically managed by the monotonic serial on the outbox table. You can trivially obtain it by transactionally reading the largest sequenceId currently in the outbox table and return this along with your model data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;LiveSync delivers changes in your database to clients in realtime over WebSockets, reliably and in-order, over Ably’s global edge messaging infrastructure. It can be integrated with your existing application and database. Ably channel history allows you to avoid storing change history or per-client state in your database. Your database remains the source-of-truth for your data.&lt;/p&gt;

&lt;p&gt;LiveSync is now available in public alpha. If you would like to try it out, please check out the &lt;a href="https://hubs.la/Q02xmf4x0"&gt;documentation&lt;/a&gt;. If you have any feedback, please &lt;a href="https://forms.gle/YiQJBy3C9GKA2k4GA"&gt;get in touch&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>realtime</category>
      <category>database</category>
      <category>backenddevelopment</category>
      <category>webdev</category>
    </item>
    <item>
      <title>You don't need CRDTs for collaborative experiences</title>
      <dc:creator>Zak</dc:creator>
      <pubDate>Wed, 22 Nov 2023 14:13:42 +0000</pubDate>
      <link>https://dev.to/ably/you-dont-need-crdts-for-collaborative-experiences-emj</link>
      <guid>https://dev.to/ably/you-dont-need-crdts-for-collaborative-experiences-emj</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally published by Senior Engineer &lt;strong&gt;Zak Knill&lt;/strong&gt; on &lt;a href="https://zknill.io/posts/collaboration-no-crdts/"&gt;zknill.io&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  You don’t need CRDTs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;You don’t need CRDTs for collaborative experiences.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First lets get the ‘what-about-ery’ out the way…&lt;/p&gt;

&lt;p&gt;Things you do need CRDTs for:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Offline first&lt;/strong&gt; – this is wayy harder to get useful behaviour with out CRDTs. If you don’t use them, you’re pretty much destined to have LWW (which is actually a CRDT behaviour), and one user is likely to overwrite the changes of another. This isn’t a great experience for anyone involved.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Text editing&lt;/strong&gt; – everyone’s gonna say “but hey, Google docs uses operational transform not CRDTs”.. OK yes, &lt;em&gt;but you are not google.&lt;/em&gt; Martin Kleppmann has a great round-up of the various people who though they implemented OT correctly, but actually didn’t. The reason that you need CRDTs for text editing collaboration is that it’s a really extreme example of collaboration. The nature of text editing is that any tiny errors in the placement of characters by the convergence algorithm is going to create incorrect words, and incorrect words are incredibly obvious. Text editing has a high rate of edits (as you type), and the edits need to interleave perfectly or you get incorrect words, and errors in the interleaving are super obvious (incorrect words)!&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Hold, on.. that all sounds great, but..&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maybe I want to use a CRDT even if I don’t strictly need to?&lt;/strong&gt; OK, yes, maybe you do.&lt;/p&gt;

&lt;p&gt;But CRDTs are not without their downsides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ever-growing state:&lt;/strong&gt; for CRDTs to work well they need to keep a record of both what exists, and what has been deleted (so that the deletes aren’t accidentally added back in later). This means that CRDT state will continually expand. There’s a bunch of magic that CRDT library authors are doing with clever compression techniques to make this problem less-bad, but it’s basically in-escapable. The size of your CRDT state is not purely a function of the size of the state the CRDT represents, but also of the number of updates that state has gone through.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Complex implementations:&lt;/strong&gt; CRDTs are easy to implement wrong, so probably don’t roll your own. Instead you’re going to end up using one of the main libraries that are growing in maturity and popularity. These libraries solve the hard internal-CRDT problems for you, like converging updates, and compressing state, etc. But in using one of these, you’re locked into that technology (generally Javascript).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Opaque state:&lt;/strong&gt; Because the CRDT has to represent both the underlying state and the updates that led to that state, in order for it’s convergence algorithm to work properly, you’re generally left with an opaque blob of binary encoded data. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can’t inspect your model represented by the CRDT without using the CRDT library to decode the blob, and you can’t just store the underlying model state because the CRDT needs its change history also. You’re left with an opaque blob of data in your database. You can’t join on it, you can’t search it, you can’t do much without building extra features around that state blob.&lt;/p&gt;

&lt;p&gt;So maybe you are convinced that CRDTs are not the be-all-and-end-all of collaboration, and that you aren’t in one of the two categories where you probably should use a CRDT, and you’ve made it this far in the post.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need instead of CRDTs
&lt;/h2&gt;

&lt;p&gt;Now, let me explain why you don’t need CRDTs, and what you do need instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Contextual information&lt;/strong&gt; – the ability to show where a collaborator is in the collaborative environment; who is engaging with the page, which element they’ve selected, and where their cursor is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Locking for safety&lt;/strong&gt; – the ability to lock them smallest possible individual components or elements of the page to stop conflicting updates from happening.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small-scale updates&lt;/strong&gt; – the ability to update only the information that has changed, and no more.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Realtime fan-out of updates&lt;/strong&gt; – the ability to share the update a user has made, in realtime, with all the other collaborators in that environment.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RpzGfaDS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w1bb4dfhvw1tffuixpgj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RpzGfaDS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w1bb4dfhvw1tffuixpgj.png" alt="Colaboration features" width="800" height="564"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Remind me again why this is better? Well, you get realtime collaborative experiences in a data format/structure that you own and control, without suffering any of the downsides of CRDTs. TL;DR - because it works!&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples of collaboration without CRDTs
&lt;/h2&gt;

&lt;p&gt;I’ll run through a bunch of broad categories of applications, and describe how to make use of these features.&lt;/p&gt;

&lt;h3&gt;
  
  
  Form builders: Google Forms, Typeform, Attest, etc
&lt;/h3&gt;

&lt;p&gt;Typically lots of smaller individual inputs. You’re unlikely to want two users collaborating on the same form-box at once.&lt;/p&gt;

&lt;p&gt;How to make it collaborative:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lock the individual inputs/elements that a user is engaging with.&lt;/li&gt;
&lt;li&gt;Add Contextual information to show which user has locked which element.&lt;/li&gt;
&lt;li&gt;Add Contextual information to allow users to indicate to each other with their cursors.&lt;/li&gt;
&lt;li&gt;Have a mechanism to update only the form element that has changed, so that two users making updates at the same time do not overwrite each other&lt;/li&gt;
&lt;li&gt;Have a mechanism to fan-out the update that each user has made to the other user’s in realtime, so that each can see the other’s changes as soon as the input is unlocked.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Task management: Jira, Shortcut, Trello, Linear
&lt;/h3&gt;

&lt;p&gt;Typically lots of ‘cards’ or ’tasks’ with various properties: status, owner, title, description, project, epic, etc.&lt;/p&gt;

&lt;p&gt;How to make it collaborative:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lock the individual inputs/elements that a user is engaging with.&lt;/li&gt;
&lt;li&gt;Add Contextual information to show which user has locked which element.&lt;/li&gt;
&lt;li&gt;Have a mechanism to update only the form element that has changed, so that two users making updates at the same time do not overwrite each other&lt;/li&gt;
&lt;li&gt;Have a mechanism to fan-out the update that each user has made to the other user’s in realtime, so that each can see the other’s changes as soon as the input is unlocked.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Spreadsheets: Google sheets, Airtable
&lt;/h3&gt;

&lt;p&gt;Typically a ui of rows and columns. Sometimes the records can expand to show inner-details of that row, similar to the task management apps.&lt;/p&gt;

&lt;p&gt;How to make it collaborative:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lock the individual inputs/elements that a user is engaging with. For Google Sheets; the individual cells. For Airtable, the individual elements on the record.&lt;/li&gt;
&lt;li&gt;Add Contextual information to show which user has locked which element.&lt;/li&gt;
&lt;li&gt;Have a mechanism to update only the form element that has changed, so that two users making updates at the same time do not overwrite each other&lt;/li&gt;
&lt;li&gt;Have a mechanism to fan-out the update that each user has made to the other user’s in realtime, so that each can see the other’s changes as soon as the input is unlocked.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Picture drawing, presentation and whiteboards: Miro, Figma, Google Slides
&lt;/h3&gt;

&lt;p&gt;Typically free-form drawing combined with preset shapes, stickies, note and text boxes, and more.&lt;/p&gt;

&lt;p&gt;How to make it collaborative:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lock the individual elements that a user is engaging with, it doesn’t really matter if two users draw over the same part of the canvas. It only matters if two users try and change the same element at once.&lt;/li&gt;
&lt;li&gt;Add Contextual information to show which user has locked which element.&lt;/li&gt;
&lt;li&gt;Add Contextual information to allow users to indicate to each other with their cursors.&lt;/li&gt;
&lt;li&gt;Have a mechanism to update only the form element that has changed, so that two users making updates at the same time do not overwrite each other&lt;/li&gt;
&lt;li&gt;Have a mechanism to fan-out the update that each user has made to the other user’s in realtime, so that each can see the other’s changes as soon as the input is unlocked.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Design patterns
&lt;/h2&gt;

&lt;p&gt;You can start to see a few things here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The pattern for building collaborative experiences is almost exactly the same across all of these types of applications that don’t need CRDTs,&lt;/li&gt;
&lt;li&gt;Many of the applications that you know from these broad categories operate in exactly this way. A few examples; cell locking in Google Sheets, post-it locking in Miro and Figma, element locking in Google Slides.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>crdts</category>
      <category>webdev</category>
      <category>discuss</category>
    </item>
  </channel>
</rss>
