HA logo

Highly available SQLite cluster with embedded NATS JetStream.

Overview

1. Installation

Download the latest release from the GitHub releases page.

1.1 Install from source

go install github.com/litesql/ha@latest

1.2 Install with Docker

docker run --name ha \
  -e HA_MEMORY=true \
  -p 5432:5432 -p 8080:8080 -p 4222:4222 \
  ghcr.io/litesql/ha:latest

Mount a volume at /data to persist NATS JetStream state.

Cluster example

Use the provided Docker Compose example:

cd examples/leader-based
docker compose up
Instance HTTP PostgreSQL Wire NATS MySQL Wire
node1 8080 5432 4222 3306
node2 8081 5433 4223 3307
node3 8082 5434 4224 3308

1.3 Install with Helm

helm repo add litesql https://litesql.github.io/helm-charts
helm repo update
helm install ha litesql/ha

Visit the litesql Helm charts repository for deployment options.

2. Quick Start

Start a local HA node

ha -n node1 -m --pg-port 5432

Start a second node that connects to the first node:

ha -n node2 -m --port 8081 --pg-port 5433 --nats-port 0 --replication-url nats://localhost:4222

Create a table

curl -d '[
  {
    "sql": "CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)"
  }
]' \
http://localhost:8080

Insert data using the HTTP API

curl -d '[
  {
    "sql": "INSERT INTO users(name) VALUES(:name)",
    "params": {"name": "HA User"}
  }
]' \
http://localhost:8080

Connect with PostgreSQL Wire Protocol

PGPASSWORD="ha" psql -h localhost -U ha
INSERT INTO users(name) VALUES('HA user from PostgreSQL Wire Protocol');
SELECT * FROM users;

Check another node

PGPASSWORD="ha" psql -h localhost -U ha -p 5433
SELECT * FROM users;

2.1 Load an existing database into memory

ha -m mydatabase.db

2.2 Use a database on disk

ha "file:mydatabase.db"

2.3 Load from the latest snapshot

ha --from-latest-snapshot

2.4 Load multiple databases

ha *.db

3. Local Replicas

3.1 Read/write replicas

  • Use the go-ha database/sql driver to create embedded read/write replicas.
  • Ideal for Go applications that require a local writable HA replica.

3.2 Read-only replicas

  • Use the ha-sync SQLite extension to create local read-only replicas from a remote HA database.
  • Works with any language that supports SQLite.

4. HA Client, PostgreSQL and MySQL Wire Protocol

  • Connect with any HA, PostgreSQL, or MySQL compatible client.
  • SQL is parsed by SQLite.
  • MySQL- and PostgreSQL-specific functions are not supported.
  • For GUI tools such as DBeaver, use the JDBC HA driver.

4.1 HA client mode

ha -r http://localhost:8080

The HA client accepts standard SQL plus these special commands:

Command Description
SHOW DATABASES; List all available databases
CREATE DATABASE <dsn>; Create a new database
DROP DATABASE <id>; Drop a database
SET DATABASE TO <id>; Run subsequent commands against a specific database
UNSET DATABASE; Reset to the default database
EXIT; Quit the client (Ctrl+D)

5. HTTP API

Access the OpenAPI definition at http://localhost:8080/openapi.yaml.

5.1 Bind parameters

curl -d '{
  "sql": "INSERT INTO users(name) VALUES(:name)",
  "params": {"name": "HA user"}
}' \
http://localhost:8080/query

Example response:

{
  "columns": ["rows_affected", "last_insert_id"],
  "rows": [[1, 3]]
}

5.2 Multiple commands in one transaction

curl -d '[
  {
    "sql": "INSERT INTO users(name) VALUES(:name)",
    "params": {"name": "new HA user"}
  },
  {
    "sql": "DELETE FROM users WHERE name = :name",
    "params": {"name": "new HA user"}
  },
  {
    "sql": "SELECT * FROM users"
  }
]' \
http://localhost:8080/query

Example response:

{
  "results": [
    {
      "columns": ["rows_affected", "last_insert_id"],
      "rows": [[1, 2]]
    },
    {
      "columns": ["rows_affected", "last_insert_id"],
      "rows": [[1, 2]]
    },
    {
      "columns": ["id", "name"],
      "rows": [[1, "HA user"]]
    }
  ]
}

5.3 Backup the database

curl -O -J http://localhost:8080/download

5.4 Take a snapshot

curl -X POST http://localhost:8080/snapshot

5.5 Download the latest snapshot

curl -O -J http://localhost:8080/snapshot

5.6 List replications

curl http://localhost:8080/replications

5.7 Replication status

curl http://localhost:8080/replications/{name}

5.8 Remove replication

curl -X DELETE http://localhost:8080/replications/{name}

6. Replication

  • Support writing to any server in leaderless mode.
  • Works with embedded or external NATS JetStream.
  • NATS JetStream delivers at least once.
  • DML operations (INSERT, UPDATE, DELETE) are idempotent.
  • Last writer wins.
  • DDL commands are replicated since v0.0.7.

6.1 CDC message format

{
  "node": "ha_node_name",
  "changes": [
    {
      "database": "main",
      "table": "users",
      "columns": ["id", "name"],
      "operation": "INSERT",
      "new_rowid": 2,
      "new_values": [2, "new HA user"]
    },
    {
      "database": "main",
      "table": "users",
      "columns": ["id", "name"],
      "operation": "DELETE",
      "old_rowid": 2,
      "old_values": [2, "new HA user"]
    }
  ],
  "timestamp_ns": 1758574275504509677
}

6.2 Replication limitations

  • Tables without ROWID are not replicated.
  • Replication is not triggered when conflicting rows are removed by ON CONFLICT REPLACE.
  • DDL idempotency is automatic for CREATE IF NOT EXISTS and DROP IF EXISTS, but ALTER TABLE replication is less predictable.
  • Writing to multiple nodes improves availability, but may reduce consistency in some edge cases. If consistency is required, route writes through a single node or use --leader-static / --leader-addr.

6.3 Conflict resolution

HA applies the following rules:

  1. Last Writer Wins: the most recent write is retained.
  2. Idempotent operations: supported DML and DDL commands can be replayed safely.
  3. Custom conflict handling: use --interceptor with a Go script to implement application-specific logic.

Example interceptor: ignore_alter_table_errors.go.

6.4 Proxy and source replication

HA can proxy reads and writes to an external MySQL or PostgreSQL source database while maintaining a local SQLite cache.

  • Use --mysql-proxied to connect to a source MySQL database.
  • Use --pg-proxied to connect to a source PostgreSQL database.
  • The local SQLite proxy file is configured with --proxy-local.
  • --proxy-disable-redirect forces all queries to run on the local SQLite database instead of redirecting them to the source.
  • --proxy-read-your-writes enables read-your-writes semantics for proxied queries.

For MySQL source replication, provide optional import settings:

  • --mysql-include and --mysql-exclude filter source MySQL tables by regexp.
  • --mysql-dump-bin - path to the mysqldump executable for initial data import.
  • --mysql-dump-db - database name used by mysqldump.
  • --mysql-dump-include - table filter passed to mysqldump.
  • --mysql-proxy-id - identifier for MySQL replication metadata.

For PostgreSQL source replication, configure logical replication:

  • --pg-publication - publication name in the source PostgreSQL database.
  • --pg-slot - replication slot name created on the source.

6.5 Debezium sink mode

HA can act as a Debezium sink for Kafka topics.

  • --debezium-brokers specifies Kafka brokers.
  • --debezium-group sets the Kafka consumer group.
  • --debezium-topics selects the Kafka topics to consume.
  • --debezium-source-dsn redirects writes back to the source database.

This mode is useful when HA is consuming Debezium change events and storing them locally while optionally forwarding writes back to the original source.

7. Cross-shard Queries

HA supports queries across multiple SQLite databases on the same node without ATTACH DATABASE.

Use the optimizer hint /*+ db=<regex> */ to select participating databases:

SELECT id, name /*+ db=.* */ FROM users;

To target a subset, use a narrower regex, for example db=users_.*.

  • Discover database IDs with SHOW DATABASES;
  • Refine shard selection with the regex pattern

8. Transaction Operations

HA provides transaction history and undo commands for committed changes.

Transaction History

HISTORY 100;
HISTORY '5m';

Undo Operations

  • UNDO n: revert transactions from sequence n onward.
  • UNDOE n: revert entity modifications from sequence n onward.
  • UNDOT n: revert transactions affecting entities modified by sequence n.

Examples:

UNDO 150;
UNDO '10m';
UNDOE 200;
UNDOT 250;

Note: Undo does not revert schema changes such as CREATE, ALTER, or DROP.

9. Configuration

Use ha --help for the full list of options.

Flag Environment Variable Default Description
-n, –name HA_NAME hostname Node name
-p, –port HA_PORT 8080 Server port for HTTP and gRPC endpoints
–token HA_TOKEN   API authentication token
-m, –memory HA_MEMORY false Store the database in memory
–db-params HA_DB_PARAMS default SQLite DSN parameters appended to each database file
–create-db-dir HA_CREATE_DB_DIR   Directory for new database files
–from-latest-snapshot HA_FROM_LATEST_SNAPSHOT false Load the latest snapshot from NATS JetStream Object Store if available
–snapshot-interval HA_SNAPSHOT_INTERVAL 0s Interval for automatic snapshots to NATS JetStream Object Store
–disable-ddl-sync HA_DISABLE_DDL_SYNC false Disable publishing DDL commands
–nats-logs HA_NATS_LOGS false Enable embedded NATS server logging
–nats-port HA_NATS_PORT 4222 Embedded NATS server port (0 disables embedded NATS)
–nats-store-dir HA_NATS_STORE_DIR   Embedded NATS server storage directory
–nats-user HA_NATS_USER   Embedded NATS server username
–nats-pass HA_NATS_PASS   Embedded NATS server password
–nats-config HA_NATS_CONFIG   Embedded NATS server configuration file
–leader-addr HA_LEADER_ADDR   Address used when this node becomes leader (enables leader election)
–leader-static HA_LEADER_STATIC   Static leader address (disables leader election)
–grpc-insecure HA_GRPC_INSECURE false Use plaintext gRPC for leader messages
–mysql-port HA_MYSQL_PORT 0 Port for MySQL wire protocol server
–mysql-user HA_MYSQL_USER ha MySQL authentication user
–mysql-pass HA_MYSQL_PASS   MySQL authentication password
–mysql-proxied HA_MYSQL_PROXIED   Source MySQL DSN to replicate into the local HA instance and redirect writes
–mysql-include HA_MYSQL_INCLUDE ^db.* Regexp to include tables from the proxied MySQL source
–mysql-exclude HA_MYSQL_EXCLUDE   Regexp to exclude tables from the proxied MySQL source
–mysql-dump-bin HA_MYSQL_DUMP_BIN   Path to mysqldump executable for proxied MySQL import
–mysql-dump-db HA_MYSQL_DUMP_DB   Database name used by mysqldump for proxied MySQL import
–mysql-dump-include HA_MYSQL_DUMP_INCLUDE   Table filter passed to mysqldump for proxied MySQL import
–mysql-proxy-id HA_MYSQL_PROXY_ID sqlite-ha Identifier for proxied MySQL replication metadata
–pg-port HA_PG_PORT 0 Port for PostgreSQL wire protocol server
–pg-user HA_PG_USER ha PostgreSQL authentication user
–pg-pass HA_PG_PASS ha PostgreSQL authentication password
–pg-cert HA_PG_CERT   TLS certificate file for PostgreSQL server
–pg-key HA_PG_KEY   TLS key file for PostgreSQL server
–pg-proxied HA_PG_PROXIED   Source PostgreSQL DSN to replicate from and proxy to
–pg-publication HA_PG_PUBLICATION ha_publication Publication name for source PostgreSQL logical replication
–pg-slot HA_PG_SLOT ha_slot Replication slot name for the source PostgreSQL database
–proxy-local HA_PROXY_LOCAL ha.db Local SQLite proxy database file path
–proxy-use-schema HA_PROXY_USE_SCHEMA false Create local tables based on source database schema
–proxy-disable-redirect HA_PROXY_DISABLE_REDIRECT false Disable redirecting queries to the source database
–proxy-read-your-writes HA_PROXY_READ_YOUR_WRITES false Enable read-your-writes behavior for proxied queries
–debezium-brokers HA_DEBEZIUM_BROKERS   Comma-separated Kafka brokers for Debezium sink mode
–debezium-group HA_DEBEZIUM_GROUP   Kafka consumer group for Debezium sink
–debezium-topics HA_DEBEZIUM_TOPICS   Kafka topics to consume in Debezium sink mode
–debezium-source-dsn HA_DEBEZIUM_SOURCE_DSN   Source DSN for Debezium write redirection
–concurrent-queries HA_CONCURRENT_QUERIES 50 Maximum number of concurrent queries
–async-replication HA_ASYNC_REPLICATION false Enable asynchronous replication message publishing
–async-replication-store-dir HA_ASYNC_REPLICATION_STORE_DIR   Directory for asynchronous replication outbox storage
–replicas HA_REPLICAS 1 Number of JetStream replicas for stream and object store
–replication-timeout HA_REPLICATION_TIMEOUT 15s Timeout for replication publisher operations
–replication-stream HA_REPLICATION_STREAM ha_replication Replication stream name
–replication-max-age HA_REPLICATION_MAX_AGE 24h Maximum age for messages in the replication stream
–replication-url HA_REPLICATION_URL   NATS URL for replication; defaults to embedded NATS when empty
–replication-policy HA_REPLICATION_POLICY   Replication subscriber delivery policy: all, last, new, by_start_sequence=X, or by_start_time=x
–row-identify HA_ROW_IDENTIFY pk Row identification strategy for replication: pk, rowid, or full
–extensions HA_EXTENSIONS   Comma-separated list of SQLite extensions to load
–config HA_CONFIG   Path to an optional config file
–version HA_VERSION   Print version information and exit