<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Sylvain Simao</title><description>Notes on web tech, product engineering and startups.</description><link>https://sylvainsimao.com/</link><language>en-AU</language><item><title>Fixing Turso Read-After-Write Consistency in Serverless</title><link>https://sylvainsimao.com/blog/fixing-turso-read-after-write-consistency-in-serverless/</link><guid isPermaLink="true">https://sylvainsimao.com/blog/fixing-turso-read-after-write-consistency-in-serverless/</guid><description>Using Turso in serverless setups like Vercel or Cloudflare can cause read-after-write consistency issues. Find out why these happen and explore solutions to keep your data consistent.</description><pubDate>Fri, 15 Nov 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;&lt;strong&gt;&lt;a href=&quot;https://turso.tech&quot; rel=&quot;noopener nofollow&quot; target=&quot;_blank&quot;&gt;Turso&lt;/a&gt; is a powerful and versatile database platform for SQLite. One of its key features is the ability to easily deploy database replicas close to your users, which helps boost response times.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;However, it can present challenges with web apps running in serverless setups, particularly with services like Vercel Edge and Cloudflare Workers, where unexpected data inconsistencies may arise if not managed properly.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;In fact, if you’re using Turso with replicas, these inconsistencies may already be affecting your app without your knowledge. Want to know why? Keep reading to find out.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;From Turso’s Docs&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Writes and Propagation:&lt;/strong&gt; All writes are forwarded to the primary database. Changes from the primary are eventually propagated to each replica, with a latency of 200-300ms.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Stale Data on New Connections:&lt;/strong&gt; New connections may encounter stale data until a write operation updates the replication index. Replicas do not guarantee immediate reflection of primary’s changes, leading to potential data differences across replicas.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Source: &lt;a href=&quot;https://docs.turso.tech/data-and-connections#data-consistency&quot; rel=&quot;noopener nofollow&quot; target=&quot;_blank&quot;&gt;Turso Docs&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Why This Could be an Issue?&lt;/h2&gt;
&lt;p&gt;In traditional server environments, a continuous connection can be maintained with Turso, allowing immediate consistency for read-after-write operations.&lt;/p&gt;
&lt;p&gt;However, in serverless setups like Vercel Edge or Cloudflare Workers, each time a function is called, it may create a new database connection. This stateless nature limits the ability of subsequent read operations to use Turso’s built-in replication index tracking, which relies on a continuous connection to ensure immediate consistency of data.&lt;/p&gt;
&lt;p&gt;As a result, users, particularly those connecting from locations far from the primary database, may encounter stale data due to the time it takes for updates to reach all replicas.&lt;/p&gt;
&lt;p&gt;While this delay is generally manageable, it can lead to significant challenges for critical operations like stock management or user authentication.&lt;/p&gt;
&lt;h2&gt;Example: Authentication&lt;/h2&gt;
&lt;p&gt;Imagine this scenario:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;User successfully authenticate &lt;strong&gt;from Australia&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Session data is written to the primary database, &lt;strong&gt;located in the US&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Subsequent requests read session data from a new connection, &lt;strong&gt;using the closest database replica in Australia&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;User session data may not be found due to &lt;strong&gt;replication latency (200-300ms)&lt;/strong&gt;. The data exists in the primary database, but not yet visible from the replica.&lt;/li&gt;
&lt;li&gt;As a result, the &lt;strong&gt;user is mistakenly logged out&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;How to Solve It?&lt;/h2&gt;
&lt;p&gt;To ensure read-after-write consistency for critical operations in serverless web apps, we can do the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create a short-lived cookie:&lt;/strong&gt; Upon completing a critical write operation, create a cookie with a short expiry (like 15 seconds). Attach this cookie to the user for whom you want to ensure read-after-write consistency.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Direct reads to Primary:&lt;/strong&gt; If the cookie is present, force subsequent read operations to use the primary database. This ensures you’re accessing the most current data and bypassing potential latency issues from replicas.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Applying the Solution: Authentication with SvelteKit + Drizzle Example&lt;/h2&gt;
&lt;p&gt;In your SvelteKit action for handling logins, set a short-lived cookie immediately after creating a new user session in the database:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// +page.server.ts
export const actions = {
  login: async ({ cookies }) =&amp;gt; {
    // ... login logic (create db session, etc.)

    // set a short-lived cookie after db session creation
    cookies.set(&apos;enforce_primarydb&apos;, &apos;true&apos;, { path: &apos;/&apos;, maxAge: 15 });
  }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Implement a server hook to manage sessions and ensure that subsequent reads, like fetching the session from the database, use the primary database:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// +hooks.server.ts
import { getDatabase } from &apos;$lib/server/database&apos;;

export const handle = async ({ event, resolve }) =&amp;gt; {
  // check for the cookie to decide on primary db usage
  const usePrimaryDB = !!event.cookies.get(&apos;enforce_primarydb&apos;);
  const db = getDatabase({ usePrimaryDB });

  // ... verify session logic (read from db, etc.)

  return await resolve(event);
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ensure primary database usage with Drizzle:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// lib/server/database.ts
import { type Client, createClient } from &apos;@libsql/client&apos;;
import { type LibSQLDatabase, drizzle } from &apos;drizzle-orm/libsql&apos;;
import * as schema from &apos;./schema&apos;;

let db: LibSQLDatabase&amp;lt;typeof schema&amp;gt; &amp;amp; { $client: Client };

const client = createClient({
  url: env.DATABASE_URL,
  authToken: env.DATABASE_AUTH_TOKEN
});
const clientPrimary = createClient({
  url: env.DATABASE_URL_PRIMARY,
  authToken: env.DATABASE_AUTH_TOKEN
});

export function getDatabase(ctx?: { usePrimaryDB?: boolean }) {
	if (ctx?.usePrimaryDB) db = drizzle({ client: clientPrimary });
	if (!db) db = drizzle({ client });
	return db;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: You can run &lt;code&gt;turso db show [database_name] --instance-urls&lt;/code&gt; to find your primary database url with the Turso CLI.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Wrapping Up&lt;/h2&gt;
&lt;p&gt;Using a short-lived cookie to direct reads to the primary database in Turso is a simple and effective way to solve read-after-write consistency issues in Serverless web apps.&lt;/p&gt;
&lt;p&gt;So if your app is experiencing data inconsistencies, this approach can help ensure your users always get the most current data.&lt;/p&gt;
&lt;p&gt;And if you haven’t tried Turso yet, definitely check it out!&lt;/p&gt;
</content:encoded></item></channel></rss>