the local speed of SQLite, with the convenience of a remote database.
Ever since launching the libSQL project and the Turso serverless database, we have strived to transform SQLite from an embedded-only database into a database that can work well for production-grade networked environments. But we also want to leverage the best capabilities users already know and love from SQLite, and working as an embedded database is a big part of that story.
Today we are releasing Embedded Replicas: it allows users using VMs or VPS to replicate a Turso database inside their application. It provides you with the ability to seamlessly transition between local and remote based on what's best for the specific situation for the same database, and makes Turso into a database that can work well anywhere.
Oh, and users can create unlimited Embedded replicas on any plan, including the free Starter plan.
Imagine being able to serve reads in microseconds while keeping your data in sync between your services. Makes for a great analytics platform that doesn't disrupt your main database, without the need for any ETL job.
Or how about an architecture so simple and fast that you don't even need external caches. Always have your reads with you, whether or not you have constant connectivity to the internet.
With embedded replicas, you can do that and more!
The advantages of local execution with an embedded database, are that the Database is:
It is important to note that the level of magnitude speed improvement delivered by local databases not only makes your application faster, but allows entirely different ways to interact with it. Concerned about N+1 query patterns? Well, that's a concern no more if you can do 1,000 queries, and still be faster than you would if you had to go over the network.
But dealing with a fully local embedded database comes with its own disadvantages:
Turso was designed to tackle the disadvantages of embedded mode, by making the database accessible over the network.
Local access as an embedded database was retained, but that was seen as a development-only feature: you could add files to your CI, and you could develop your application locally, with a file
URL:
import { createClient } from '@libsql/client';
const client = createClient({
url: process.env.DB, // will be 'file:localdev.db`
authToken: process.env.TOKEN, // stays empty
});
const res = await client.execute('SELECT * from users;');
for (const row of res.rows) {
console.log(`name: ${row['name']}, age ${row['age']}`);
}
but when it was time to handle live traffic, that was done over HTTP. This could be easily done by just making configuration changes, making sure the remote URL was used to create the client, and that the authentication token was present:
const client = createClient({
url: process.env.DB, // will be `libsql://your-db.turso.io`
authToken: process.env
.TOKEN`can be obtained with turso db tokens create your-db`,
});
Embedded replicas tear down the walls between local and remote modes of operation. It works similarly to other technologies that replicate SQLite databases, but with a twist: instead of having a local embedded database that can be copied somewhere else, the source of truth is the remote database. All writes are still done to the remote database. The writes are then synchronized into all of the replicas, allowing network-free local reads.
If you are deploying your API anywhere that has a filesystem, such as a VM or VPS, embedded replicas will provide you with the fastest possible reads, while still allowing you to scale out: need more requests? Bring a new server, sync your local instance with your main Turso copy, and start serving your microsecond-level requests. Only two changes are needed to your code.
First, when creating the client, you specify a local file and a synchronization URL:
import { createClient } from '@libsql/client';
const client = createClient({
url: 'file:local.db',
syncUrl: process.env.DB,
authToken: process.env.TOKEN,
});
And also, add calls to client.sync()
. You can add them periodically in the background, explicitly before important selects, or whenever your application wants
await client.sync(); // alternatively, do it every 1s in the background.
const res = await client.execute('SELECT * from users;');
Oftentimes, one of the concerns raised by organizations is that they want to run analytical workloads on the dataset. Analytical queries tend to be heavy, and the concern is that it may disrupt the main transactional databases. Standard Replicas or complex ETL jobs are often used to solve this issue, but long-lived replicas have a cost to run, and ETL jobs are no-one's idea of fun.
Embedded replicas allow you to create an ad-hoc replica locally, do whatever you want with it without disrupting the main database, and then let them go. You can keep the file alive, but shut down your compute, meaning the next synchronization point will only download the changes since the last job!
import { createClient } from '@libsql/client';
const client = createClient({
url: 'file:local.db',
syncUrl: process.env.DB,
authToken: process.env.TOKEN,
});
await client.sync();
const res = await client.execute(`
SELECT
o.customer_id,
SUM(oi.quantity * oi.price) AS total_revenue
FROM
orders o
JOIN
order_items oi ON o.order_id = oi.order_id
GROUP BY
o.customer_id;`);
console.log(res);
There are two hard problems in computer science: naming things, cache invalidation, and off-by-one errors. Embedded replicas may not solve the first one, but by allowing an in-process, you can rearchitecture your application in such a way that external caches are not needed. Embedded replicas have the following advantages over external caches:
Embedded replicas allow Turso to be used at, or closer to, the far edge. Have a device that has limited connectivity? You can always keep reading your database.
At launch date, embedded replicas are available for JavaScript/Typescript, and to get started, make sure your @libsql/client
package is version 0.3.5 or higher.
Go, Python and Rust packages are in beta state. Examples are available in our Github repository.
We haven't announced pricing for embedded replicas, and they are currently free to use for all users on all plans. We will give at least a few months of advanced notice if and when this changes.