0
0
mirror of https://github.com/tursodatabase/libsql.git synced 2024-12-15 05:09:50 +00:00
libsql/docs/USER_GUIDE.md
2024-08-09 12:47:44 +05:30

251 lines
8.9 KiB
Markdown

# `sqld` User Guide
Welcome to the `sqld` user guide!
## Table of Contents
* [Overview](#overview)
* [Replication](#replication)
* [TLS configuration](#tls-configuration)
* [Launching a primary server](#launching-a-primary-server)
* [Launching a replica server](#launching-a-replica-server)
* [Client Authentication](#clientauthentication)
* [Deployment](#deployment)
* [Deploying with Docker](#deploying-with-docker)
* [Deploying on Fly](#deploying-on-fly)
## Overview
The `sqld` program provides libsql over HTTP and supports transparent replication.
![libsql cluster overview.](sqld-overview.png)
<p align="center">
Figure 1. Overview of libsql clustering.
</p>
Figure 1 shows an overview of libsql cluster where clients execute SQL remotely over HTTP against `sqld` instances.
In the middle, there is the _primary_ instance, which is responsible for accepting writes and servicing replicas for write-ahead log (WAL) updates.
If a client performs a write operation such as `INSERT` statement in SQL, replicas delegate the write to a primary node.
Read operations, such as `SELECT` statements, however, are executed on the replica directly.
The replicas poll the primary instance for WAL updates periodically over a gRPC connection.
## Replication
In this section, we will walk you through how to set up a libsql cluster.
### TLS configuration
The nodes in a `sqld` cluster communicate over gRPC with TLS. To set up a `sqld` cluster, you need the following TLS configuration:
* Certificate authority (CA) certificate and private key
* Primary server certificate and private key
* Replica server certificates and private keys
In TLS speak, the primary server is the server and the replica servers are the clients.
For *development and testing* purposes, you can generate TLS keys and certificates with:
```console
python scripts/gen_certs.py
```
The script generates the following files:
* `ca_cert.pem` -- certificate authority certificate
* `ca_key.pem` -- certificate authority private key
* `server_cert.pem` -- primary server certificate
* `server_key.pem` -- primary server private key
* `client_cert.pem` -- replica server certificate
* `client_key.pem ` -- replica server private key
### Launching a primary server
To start a `sqld` server in primary mode, run:
```console
sqld \
--http-listen-addr 127.0.0.1:8081 \
--grpc-listen-addr 127.0.0.1:5001 \
--grpc-tls \
--grpc-ca-cert-file ca_cert.pem \
--grpc-cert-file server_cert.pem \
--grpc-key-file server_key.pem
```
You now have a `sqld` primary server listening to SQL over HTTP at `127.0.0.1:8081` and gRPC with TLS at `127.0.0.1:5001`.
### Launching a replica server
To start a a `sqld` server in replica mode, run:
```console
sqld \
--http-listen-addr 127.0.0.1:8082 \
--primary-grpc-url https://127.0.0.1:5001 \
--primary-grpc-tls \
--primary-grpc-ca-cert-file ca_cert.pem \
--primary-grpc-cert-file client_cert.pem \
--primary-grpc-key-file client_key.pem
```
You now have a `sqld` replica server listening to SQL over HTTP at `127.0.0.1:8082`, which is connected to a primary server at `127.0.0.1:5001`.
You can add more replicas to the cluster by just starting more `sqld` processes. However, it's recommended that you generate a different TLS configuration for every replica.
To test the cluster, you can, for example, create a table and insert rows in the replica:
```console
curl -d '{"statements": ["CREATE TABLE IF NOT EXISTS users (username)", "INSERT INTO users VALUES (\"alice\")"]}' 127.0.0.1:8082
```
and query the results from the primary:
```console
curl -d '{"statements": ["SELECT * FROM users"]}' 127.0.0.1:8081
```
## Client Authentication
You can configure client authentication by passing the `--auth-jwt-key-file FILENAME` command line option to `sqld`.
The key is either a PKCS#8-encoded Ed25519 public key in PEM, or just plain bytes of the Ed25519 public key in URL-safe base64.
## Deployment
### Deploying with Docker
You can pull the official Docker image for `sqld` with
```console
docker pull ghcr.io/libsql/sqld:main
```
You can find more information about the Docker image [here](./DOCKER.md).
### Deploying on Fly
You can use the existing `fly.toml` file from this repository.
Just run
```console
flyctl launch
```
... then pick a name and respond "Yes" when the prompt asks you to deploy.
You now have `sqld` running on Fly listening for HTTP connections.
Give it a try with this snippet, replacing `$YOUR_APP` with your app name:
```
curl -X POST -d '{"statements": ["create table testme(a,b,c)"]}' $YOUR_APP.fly.dev
curl -X POST -d '{"statements": ["insert into testme values(1,2,3)"]}' $YOUR_APP.fly.dev
curl -X POST -d '{"statements": ["select * from testme"]}' $YOUR_APP.fly.dev
```
```
[{"b":2,"a":1,"c":3}]
```
## Incremental snapshots
The `sqld` generates incremental snapshots of the database file, which you can apply to a local libSQL replica.
For example, suppose you have an application that is not always connected over the network and can't rely on the `sqld` gRPC replication method. In that case, you can configure `sqld` to notify of generated incremental snapshots, sync the snapshot files to another machine, and apply them.
You can use the `--snapshot-exec` command line option to specify a file, such as a shell script, to execute on snapshot generation. You can also use the `--max-log-duration SECS` command line option
on to control how often `sqld` generates the snapshot files to ensure the freshness of the data on local replicas.
To use incremental snapshots, first, create a shell script with the name `snapshot.sh`:
```bash
#!/bin/bash
SNAPSHOT_FILE="$1"
NAMESPACE="$2"
echo "Generated incremental snapshot $SNAPSHOT_FILE for namespace $NAMESPACE"
```
and then configure `sqld` to generate an incremental snapshot every 5 seconds and invoke the shell script when `sqld` generates a snapshot:
```console
sqld --snapshot-exec ./snapshot.sh --max-log-duration 5
```
When you write to the `sqld` database, you will eventually see log line such as:
```console
2023-08-11T08:21:04.183564Z INFO sqld::replication::snapshot: snapshot `e126f594-90f4-45be-9350-bc8a01160de9-0-2.snap` successfully created
Generated incremental snapshot data.sqld/dbs/default/snapshots/e126f594-90f4-45be-9350-bc8a01160de9-0-2.snap
```
The first line is logging from `sqld` and the second line is `sqld` executing `snapshot.sh` script.
You can now, for example, `rsync` the snapshot file to another machine, to apply the changes to a local replica with the `Database::sync_frames()` method of the `libsql` crate:
```rust
use libsql::Database;
use libsql_replication::{Frames, TempSnapshot};
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let opts = libsql::Opts::with_sync();
let db = Database::open_with_opts("test.db", opts).await.unwrap();
let conn = db.connect().unwrap();
let args = std::env::args().collect::<Vec<String>>();
if args.len() < 2 {
println!("Usage: {} <snapshot path>", args[0]);
return;
}
let snapshot_path = args.get(1).unwrap();
let snapshot = TempSnapshot::from_snapshot_file(snapshot_path.as_ref()).unwrap();
db.sync_frames(Frames::Snapshot(snapshot)).unwrap();
let rows = conn
.query("SELECT * FROM sqlite_master", ())
.unwrap()
.unwrap();
while let Ok(Some(row)) = rows.next() {
println!(
"| {:024} | {:024} | {:024} | {:024} |",
row.get::<&str>(0).unwrap(),
row.get::<&str>(1).unwrap(),
row.get::<&str>(2).unwrap(),
row.get::<&str>(3).unwrap(),
);
}
}
```
## Multitenancy
The `sqld` server supports more than one database. To create a database, send a create namespace request to the [admin API](ADMIN_API.md).
For example, to create a database named `db1`, send the following HTTP request:
```shell
curl -X POST http://localhost:8080/v1/namespaces/db1/create
```
The name of the database is determined from the `Host` header in the HTTP request.
For example, if you have the following entries in your `/etc/hosts` file:
```console
127.0.0.1 db1.local
127.0.0.1 db2.local
```
You can access `db1` with the `http://db1.local:8080`URL and `db2` with `http://db2.local:8080`.
The database files for the databases are stored in `<data dir>/dbs/db1` and `<data dir/dbs/db2`, respectively.
### Path based routing
For local development and testing, you can also connect to your databases by specifying the database namespace in the URL path.
You can access namespace `db1` with the `http://local:8080/dev/db1` URL and `db2` with `http://local:8080/dev/db2`, respectively.
### Wildcard domain for development
If you don't feel like editing `/etc/hosts` each time you want to test a new namespace, you can use any domain
that returns `127.0.0.1` for all subdomains. One of such domains is `*.db.sarna.dev`, which you can use to access
your local `db1` and `db2` as `http://db1.db.sarna.dev` and `http://db2.db.sarna.dev` respectively.