rqlite 6.1 separates read connections from its writer
A shared memdb database lets rqlite use separate read and write connection pools. The August release also enforces read-only query access and enables JSON1 and DBSTAT.
A single connection can make database access orderly while forcing unrelated reads to wait for the same resource. rqlite 6.1.0 changes that arrangement for in-memory databases. The release published on August 6 introduces concurrent in-memory reads through SQLite's memdb VFS and separates read-only connections from the write connection. It also enables JSON1 and the DBSTAT table.
The tagged changelog, dated August 5, describes the connection change as significant. It follows the July maintenance release that adopted database/sql but limited in-memory pooling to one connection. The new design uses the pool abstraction differently, read operations no longer need the same checkout used to execute writes.
I value the proportionality of rqlite, but a local bottleneck can still limit a small distributed database. This change is useful because it addresses the connection arrangement inside a node. It does not claim that adding replicas alone makes every kind of work concurrent.
A named in-memory database can have multiple connections
SQLite's 3.36.0 release notes from June document the enabling engine behavior. The memdb VFS can share an in-memory database among multiple connections in the same process when its name begins with /. That scope matters. The connections see one local database, while other nodes still need rqlite's replication protocol to obtain corresponding state.
The 6.1.0 database wrapper constructs a random path beginning with file:/. It then builds separate connection strings using that same name. Both select vfs=memdb, so their connections address the same in-memory database within the process.
The write side uses mode=rw and _txlock=immediate. The read side uses mode=ro and _txlock=deferred. These are implementation choices in rqlite's wrapper, not instructions for clients to open their own direct SQLite connections to the service. A remote application's supported interface remains the database service.
The shared name explains why simply increasing July's one-connection limit would not be a sufficient description of this release. Multiple connections need to address the intended same database. Version 6.1 gives them an explicit shared local identity and assigns separate roles to the resulting handles.
File-backed databases also receive separate read-write and read-only handles in this implementation. Their connection strings name the same file, with mode=ro on the read side. Opening the database pings the write handle to force creation of the file before using it. That sequence is specific to the file-backed path, while the in-memory path instead establishes its named memdb database through the retained connection.
The writer retains the database's lifetime
The read-write pool still permits only one open connection and one idle connection. Its maximum idle time and connection lifetime remain zero. The source comment gives the reason for retaining that connection, closing it could lose the in-memory database.
This is an ownership responsibility as well as a scheduling choice. Read connections can come and go while the writer's retained connection keeps the local database alive. The database object owns both pools and closes both when the object closes. Its lifetime is not accidentally determined by whichever short read happened to finish last.
The local writer remains singular under this pool configuration. Concurrent read support therefore does not imply multiple simultaneous writers or a different Raft write model. The release changes how readers obtain access to the local representation, while the write path still acquires its connection from the dedicated read-write handle.
I find the retained writer connection an especially useful implementation detail. It explains how pooling interacts with an in-memory database's existence. The connection is a resource needed to preserve state as well as a resource through which statements execute.
Query operations use read-only access
Execute obtains a connection from rwDB. Query obtains one from roDB. That division makes the intended access role part of the database connection itself. It also gives the service another defense against a write statement reaching a query endpoint.
The release's database tests explicitly exercise this behavior for both file-backed and in-memory databases. They create a table and insert through the execution method, then attempt another insert through the query method. The expected response contains an error about attempting to write a read-only database. A subsequent count confirms that the attempted query-path insert did not add a row.
This is stronger evidence than a method comment saying queries should not modify the database. The underlying access mode enforces the restriction for the tested statement. It connects endpoint intent with the capabilities of the selected SQLite connection.
That restriction also matters to replication. A local mutation performed outside the intended write path would not become valid merely because SQLite accepted it. The release notes explicitly say the new read-only connections ensure write SQL does not execute on the wrong endpoint. The service can retain a SQL query interface without giving every SQL-bearing request equal authority to modify state.
Concurrent reads have a concrete regression case
The same test file creates an in-memory table, inserts 5,000 rows, and starts 32 goroutines that each query its count. Every query expects 5,000. This is a correctness test of concurrent access through the wrapper. It is not a throughput benchmark or evidence of a particular speedup on production hardware.
The test is useful because its readers must all see the shared database populated by the execution path. A pool that accidentally gave readers separate empty in-memory databases would not satisfy that expectation. The shared memdb name and separate connection handles provide the mechanism the test exercises.
The announcement also says write requests no longer block those reads. That describes the intended improvement in the service's connection arrangement. It should not be expanded into a claim that SQLite locking, long-running operations, or all sources of waiting have disappeared. Separate pools remove one shared-checkout dependency. They do not turn every database operation into independent work.
Read consistency remains a separate request-level concern. A node can have several local readers while still being behind another node's replicated state. Local concurrency describes how requests access that node's database. It does not establish which cluster state a request must consult before returning its result.
The service takes responsibility for connection settings
The changelog removes support for specifying an arbitrary SQLite DSN and removes explicit controls for foreign-key constraints and journal mode. It says those controls are best left to rqlite. That is a meaningful tradeoff alongside the concurrency improvement, the service adopts an internal connection design and narrows some user configuration choices around it.
Connection parameters can no longer be understood as one unrestricted string applied to every operation. The wrapper now needs coordinated read-write and read-only handles sharing a database name. A user-supplied arrangement that changes one of those assumptions could conflict with the model the implementation requires.
Status output exposes the chosen read and write DSNs and separate pool statistics. It also adds SQLite compile-time options. Those observations let an operator inspect what the service actually opened and which engine options its build includes. They are particularly relevant when optional SQLite facilities become part of a release announcement.
JSON1 and DBSTAT add distinct query capabilities
The release enables JSON support and DBSTAT. These are separate additions from the connection design. JSON1 supplies SQLite's JSON functionality, while DBSTAT exposes database storage information through a queryable table. Their inclusion is a build choice the release now makes visible through compile-option reporting.
The tagged DBSTAT test queries the table and checks its column names. They include page number, page type, payload, unused space, and page size. Those fields concern SQLite's local storage representation. They do not describe Raft log replication or measure cluster-wide free capacity.
SELECT name, pageno, pagetype, payload, unused
FROM dbstat;
This example selects fields explicitly present in the release's test expectation. It is an inspection query, independent of whether an application stores JSON. Keeping the capabilities separate avoids treating every item in the announcement as a consequence of memdb.
The August release's central change remains local connection structure. A retained writer connection keeps the shared in-memory database alive, read-only connections can access the same database concurrently, and the query path refuses the tested write attempt. The optional engine capabilities and new diagnostics accompany that design, while cluster replication remains responsible for moving changes between nodes.