Skip to content

Connection

Connection string format

/path/to/database.db?param1=value1&param2=value2

Parameters

Parameter Default Description
wal_checkpoint_threshold 1000 Auto-checkpoint after N WAL frames. Set to 0 to disable automatic checkpointing.
wal_write_buffer_size 65536 WAL write-buffer size in bytes. Set to 0 to flush every commit (lowest throughput, lowest exposure).
log_level warn Log verbosity: debug, info, warn, error
max_cached_pages 2000 Maximum pages to keep in the in-memory LRU page cache. Each page is 4 096 bytes; default ≈ 8 MB.
slow_query_threshold 0 (disabled) Log queries at WARN level when elapsed time meets or exceeds this value. Accepts Go duration strings: 50ms, 2s.
synchronous normal WAL fsync mode. See WAL durability modes.
parallel_scan off Enable concurrent leaf-page scanning for full table scans. See Parallel scan.
sort_mem_limit 4194304 Maximum bytes of row data held in memory before an ORDER BY sort spills to disk. Set to 0 to disable disk spill (all sorted rows stay in memory). See Disk-backed sort.
hnsw_vec_cache_size 4096 Maximum number of vector entries cached per HNSW index. Each slot holds the full float32 slice for one row (dims × 4 bytes). See HNSW vector cache.
encryption_key (none) Hex-encoded AES-256-CTR encryption key. See Encryption.

Examples

// Enable debug logging
db, err := sql.Open("minisql", "./my.db?log_level=debug")

// Larger page cache (16 000 pages ≈ 64 MB)
db, err := sql.Open("minisql", "./my.db?max_cached_pages=16000")

// Disable auto-checkpoint (run PRAGMA wal_checkpoint manually)
db, err := sql.Open("minisql", "./my.db?wal_checkpoint_threshold=0")

// Maximum write durability (fsync after every commit)
db, err := sql.Open("minisql", "./my.db?synchronous=full")

// Log queries taking more than 50 ms
db, err := sql.Open("minisql", "./my.db?slow_query_threshold=50ms")

// Enable parallel full table scans
db, err := sql.Open("minisql", "./my.db?parallel_scan=on")

// Raise ORDER BY sort memory to 64 MiB before spilling to disk
db, err := sql.Open("minisql", "./my.db?sort_mem_limit=67108864")

// Disable disk spill entirely (all sorted rows stay in memory)
db, err := sql.Open("minisql", "./my.db?sort_mem_limit=0")

// Raise HNSW vector cache to 16 384 entries for large high-dimensional tables
db, err := sql.Open("minisql", "./my.db?hnsw_vec_cache_size=16384")

// Encrypted database
import "encoding/hex"
key := []byte("my-32-byte-secret-key")
db, err := sql.Open("minisql", "./my.db?encryption_key="+hex.EncodeToString(key))

// Multiple parameters
db, err := sql.Open("minisql", "./my.db?log_level=info&max_cached_pages=4000&synchronous=full")

Connection pooling

Single connection required

MiniSQL requires exactly one open connection per database file. Multiple connections to the same file do not share page cache, WAL index, or transaction state, and will produce inconsistent results or corrupt the database.

Always configure the pool to use exactly one connection:

db, err := sql.Open("minisql", "./my.db")
if err != nil {
    log.Fatal(err)
}
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(1)

Read concurrency is still handled within the single connection. Concurrent goroutines running SELECT through the same *sql.DB each get their own MVCC snapshot and execute without blocking each other. The single-connection constraint does not reduce read throughput.

WAL durability modes

The synchronous parameter controls when fsync() is called on the WAL file, trading durability for write performance.

Mode Parameter Description
normal synchronous=normal Default. No fsync per commit. fsync only at checkpoint. Matches SQLite WAL default. Data loss possible only during OS crash/power failure in the narrow window between commit and next checkpoint.
full synchronous=full fsync after every WAL commit. Survives OS crash between commits. Significantly slower for write-heavy workloads.
off synchronous=off No fsyncs at all. Fastest, but uncommitted data may be lost on OS crash or power failure.

Read and change the current mode at runtime with PRAGMA:

PRAGMA synchronous;           -- returns 0 (off), 1 (normal), 2 (full)
PRAGMA synchronous = full;
PRAGMA synchronous = normal;
PRAGMA synchronous = off;

Parallel scan

When enabled, full table scans split the leaf-page chain across runtime.NumCPU() goroutines so multiple pages are decoded and filtered concurrently.

db, err := sql.Open("minisql", "./my.db?parallel_scan=on")

Or toggle at runtime:

PRAGMA parallel_scan = on;
PRAGMA parallel_scan;         -- returns 0 (off) or 1 (on)
PRAGMA parallel_scan = off;

Note

Parallel scan does not guarantee row-ID ordering. If your query depends on insertion order, always add an explicit ORDER BY.

Disk-backed sort

ORDER BY queries accumulate matching rows in memory to sort them. When the total size of those rows exceeds sort_mem_limit bytes (default 4 MiB), MiniSQL flushes the current sorted batch to a temporary file and continues accumulating. At the end of the scan all temp files are merged with a min-heap into a single sorted stream.

// Raise the threshold to 64 MiB for analytics workloads with large result sets
db, err := sql.Open("minisql", "./my.db?sort_mem_limit=67108864")

// Disable disk spill entirely (use when you know result sets are small)
db, err := sql.Open("minisql", "./my.db?sort_mem_limit=0")

Or adjust at runtime per-session with PRAGMA:

PRAGMA sort_mem_limit;              -- returns current limit in bytes
PRAGMA sort_mem_limit = 67108864;   -- set to 64 MiB
PRAGMA sort_mem_limit = 0;          -- disable disk spill

Note

Disk spill applies universally to all ORDER BY queries — sequential scans, joins, DISTINCT, and multi-column sorts — whenever the accumulated row data exceeds sort_mem_limit. The only exception is ORDER BY with LIMIT (and no DISTINCT), which uses a bounded min-heap sized to OFFSET + LIMIT rows and never needs to spill.

Parallel scan is most beneficial for large tables on multi-core machines running filter-heavy queries. For small tables or single-CPU environments the overhead typically outweighs the benefit.

HNSW vector cache

Each HNSW index keeps a per-index LRU cache of raw float32 vectors, keyed by row ID. During ANN search, the engine computes distances between candidate nodes and the query vector. Cached vectors avoid an overflow-page read per candidate on the hot search path.

Memory per cache entry: dims × 4 bytes. For example: - 128-dim vectors, 4096 entries: 2 MiB per index - 768-dim vectors, 4096 entries: 12 MiB per index

When to tune:

  • Large tables with high query throughput — increase the cache so more of the working set fits. HNSW search visits O(log N) nodes, and entry-point nodes at the top layers are accessed by almost every query. As long as those fit in cache, hit rates are high regardless of table size.
  • Many HNSW indexes on the same database — each index has its own independent cache. If you have four 768-dim indexes at the default 4096 entries each, that is 48 MiB total. Reduce per-index size if memory is constrained.
  • Small tables (< 4096 rows) — the default is already oversized; the cache will simply never be full. No tuning needed.
// 16 384 entries for a 768-dim index on a 100k-row table (~48 MiB)
db, err := sql.Open("minisql", "./my.db?hnsw_vec_cache_size=16384")

// Multiple parameters
db, err := sql.Open("minisql", "./my.db?hnsw_vec_cache_size=8192&max_cached_pages=4000")

The cache is populated lazily on first access and kept consistent with online DML: INSERT adds the new vector, DELETE evicts it, UPDATE replaces the old entry. The cache is never persisted to disk.