November 24, 2016·4 min read·engineeringrqliterestorereplication

rqlite bootstraps replicated databases from SQLite dumps

rqlite 3.7.0 adds a dump-loading endpoint that converts an existing SQLite database into commands for its replicated store. The import path respects the distinction between SQLite state and the authoritative Raft log.

Copying a SQLite database file into a replicated system does not establish the history that its consensus layer expects. rqlite replicates changes through Raft, and its local SQLite database is the result of applying that history. Version 3.7.0, released on 24 November, adds a way to initialize the system from an ordinary SQLite dump through its HTTP API.

The feature addresses a practical adoption problem. An existing database already contains the schema and rows that a new cluster needs. Requiring an application to reconstruct every insert request would add integration work before replication could help. A dump supplies SQL statements in an existing SQLite format, and the new endpoint accepts that input directly.

I value rqlite's proportional approach to distributed relational data. The import interface fits that approach by accepting an artifact SQLite can already produce. It also preserves the need to enter through the replicated store, which is the part that makes the resulting database a rqlite database.

Import enters through the leader

The released restore documentation demonstrates creating a SQLite database, producing a .dump file and posting it to /db/load. The request body contains the dump itself, rather than the JSON array used by the ordinary execute endpoint.

For an existing file named source.sqlite, the documented sequence can be expressed as follows. This assumes a node is already listening on the default local HTTP port.

sqlite3 source.sqlite .dump > source.dump
curl -XPOST localhost:4001/db/load --data-binary @source.dump

Using --data-binary sends the dump contents as the request body. The next useful operation is a query through rqlite, which checks the state produced by the import rather than continuing to inspect the original source file. The documentation illustrates this by selecting the imported row with the rqlite command-line client.

The 3.7.0 store implementation checks that the receiving store is the Raft leader before loading. It returns ErrNotLeader otherwise. The load handler in this release calls the store directly, so the useful target for the demonstrated operation is the leader's endpoint.

That check is architectural. A follower's local database is not an independent destination for new authoritative state. The leader controls the sequence of changes admitted to the replicated log. Loading SQL through that path keeps the imported contents within the system's replication rules.

A dump becomes a batch of commands

The loader scans the incoming reader for commands and accumulates them in a string slice. Empty commands are skipped, and a scanning error returns before execution. Once scanning finishes, it passes the collected commands to the store's Execute method. This is SQL import through the existing execution machinery, rather than installation of an opaque SQLite file.

The source also makes a resource characteristic visible. It collects the command strings before issuing the execution call. This implementation therefore needs memory for the parsed command collection. The documentation describes the process as fast and efficient, but it does not specify an upper database size or a streaming execution guarantee.

The code calls Execute with its transaction flag false. That detail is a reason to avoid describing the loader as a new general-purpose atomic replacement operation. Its stated purpose is bootstrapping or restoration from a dump, and the restore documentation explicitly leaves behavior undefined when data already exists on the node.

I find that boundary important for interpreting the feature. It supplies an initialization path for an existing dataset. It does not promise a merge policy for conflicts with a populated cluster, or a replacement policy for tables already present. Those would be additional database operations with their own semantics.

The log remains authoritative

The README at the release tag explains that modifications pass through the Raft log before execution against SQLite. The default local database is in memory, and rqlite can reconstruct it from its durable history. An on-disk option exists, but directly modifying that SQLite file bypasses replication and is explicitly unsupported.

This explains why the import endpoint matters even though SQLite already has a portable file representation. The cluster needs changes it can reproduce under its own execution model. A local file copied under one node would not supply the corresponding history to the other nodes.

The same model imposes a restriction on SQL statements. The release documentation says that only deterministic statements are safe, since each node executes the logged statement. A generated value that differs between executions can produce different local contents. Importing explicit values from a dump fits the goal of reconstructing an existing dataset, while arbitrary SQL still needs to respect the replicated-statement model.

Replication also has a cost. The README positions rqlite's replication as a fault-tolerance mechanism and notes network-round-trip overhead. The new loader reduces the work required to initialize that system. It does not convert replication into a throughput optimization or remove the consensus work associated with admitted changes.

Foreign-key settings are part of restoration

The restore documentation warns that SQLite dump files normally contain a command disabling foreign-key constraints. A successful load therefore does not by itself establish the constraint setting the application expects afterward. This is state associated with how the imported SQL executes, beyond whether the expected rows appear.

The release's foreign-key documentation describes enabling enforcement with PRAGMA foreign_keys=ON through rqlite. It says the setting persists across restarts until explicitly disabled. The status API exposes the current setting, giving callers a way to inspect it separately from a data query.

The HTTP load handler also checks the backup permission before accepting the POST. In this version, restoration uses that permission rather than introducing a separately named load permission. The endpoint therefore participates in the service's authorization model as well as its replication model.

The release adds an import path whose usefulness comes from fitting both systems involved. SQLite produces the dump that describes an existing database. rqlite parses that description and admits its commands through the leader's store, so the resulting data belongs to the replicated history instead of one node's private file.