Reference

Configuration Reference

Marmot v2 uses a TOML configuration file (default: config.toml). All settings have sensible defaults.

Core Configuration

node_id = 0  # 0 = auto-generate
data_dir = "./marmot-data"

Transaction Manager

[transaction]
heartbeat_timeout_seconds = 10  # Transaction timeout without heartbeat
conflict_window_seconds = 10    # Conflict resolution window
lock_wait_timeout_seconds = 50  # Lock wait timeout (MySQL: innodb_lock_wait_timeout)
ddl_implicit_commit = true      # DDL commits the open transaction, as MySQL does

ddl_implicit_commit (default true): MySQL has no transactional DDL. CREATE, ALTER, and DROP implicitly commit the transaction a session has open, then run on their own, leaving no transaction open. Marmot follows that by default, so DML can see a column added earlier in the same transaction — the pattern schema migration tools rely on.

Set it to false to keep DDL inside the transaction and replicate it atomically with the surrounding statements. Statements are then buffered until COMMIT, so DML in that transaction cannot see a schema change made earlier in it.

Note: Transaction log garbage collection is managed by the replication configuration to coordinate with anti-entropy. See replication.gc_min_retention_hours and replication.gc_max_retention_hours.

Connection Pool

[connection_pool]
pool_size = 4              # Number of SQLite connections
max_idle_time_seconds = 10 # Max idle time before closing
max_lifetime_seconds = 300 # Max connection lifetime (0 = unlimited)

gRPC Client

[grpc_client]
keepalive_time_seconds = 10    # Keepalive ping interval
keepalive_timeout_seconds = 3  # Keepalive ping timeout
max_retries = 3                # Max retry attempts
retry_backoff_ms = 100         # Retry backoff duration
compression_level = 1          # zstd compression (0=disabled, 1-4)

Compression Levels:

LevelSpeedRatioUse Case
0--Disabled
1~318 MB/s2.88xDefault - best for most deployments
2~134 MB/s3.0xBalanced speed/compression
3~67 MB/s3.2xBetter compression, slower
4~12 MB/s3.5xBandwidth-constrained networks

Decompression is always fast (~1600 MB/s) regardless of compression level. Uses zstd (opens in a new tab) via klauspost/compress (opens in a new tab).

Coordinator

[coordinator]
prepare_timeout_ms = 2000 # Prepare phase timeout
commit_timeout_ms = 2000  # Commit phase timeout
abort_timeout_ms = 2000   # Abort phase timeout

Cluster

[cluster]
grpc_bind_address = "0.0.0.0"
grpc_port = 8080
seed_nodes = []                # List of seed node addresses
cluster_secret = ""            # PSK for cluster authentication (see Security section)
gossip_interval_ms = 1000      # Gossip interval
gossip_fanout = 3              # Number of peers to gossip to
suspect_timeout_ms = 5000      # Suspect timeout
dead_timeout_ms = 10000        # Dead timeout
autoinc_merge_interval_ms = 2000       # Retry interval for merging narrow AUTO_INCREMENT claim bases while votes are held
autoinc_base_sync_interval_ms = 10000  # Interval for raising claim bases to every alive peer's

The two autoinc_* intervals drive narrow (TINYINT/SMALLINT/MEDIUMINT/INT) AUTO_INCREMENT id ranges. 0 selects the default and negative values fail validation. autoinc_base_sync_interval_ms bounds how long a claim committed before a membership change can stay unknown to a new quorum. See Compatibility and Operations.

Replication

[replication]
default_write_consistency = "QUORUM"      # Write consistency level: ONE, QUORUM, ALL
default_read_consistency = "LOCAL_ONE"    # Read consistency level
write_timeout_ms = 5000                   # Write operation timeout
read_timeout_ms = 2000                    # Read operation timeout
 
# Anti-Entropy: each round pulls every alive peer's local commit log from a
# durable per-(peer, database) cursor; falls back to a snapshot when a peer's
# log is truncated past that cursor
enable_anti_entropy = true                 # Enable anti-entropy log pull
anti_entropy_interval_seconds = 30         # Interval between rounds (default: 30s)
gc_interval_seconds = 60                   # GC interval (MUST be >= anti_entropy_interval)
delta_sync_threshold_transactions = 10000  # Read replica only: bootstrap snapshot if gap > this
delta_sync_threshold_seconds = 3600        # Only used by the GC validation rules below
 
# Garbage Collection: deletes committed log entries once every current member
# has consumed them and they are older than gc_min, or unconditionally once
# they are older than gc_max
gc_min_retention_hours = 2   # Minimum age before a consumed entry is deleted
gc_max_retention_hours = 24  # Entries older than this are deleted regardless; 0 = no maximum

Anti-Entropy Tuning:

  • Small clusters (2-3 nodes): Use default settings (30s AE, 60s GC)
  • Large clusters (5+ nodes): Consider increasing AE interval to 60-120s and GC to 2x that value
  • Long-running clusters: Keep gc_max_retention_hours at 24+ to handle extended outages. A node whose cursor falls behind a peer's truncated log restores that database by snapshot
  • gc_max_retention_hours = 0 sets no maximum: an entry is kept until every current member has consumed it, and row-version tombstones are never purged

Legacy delta_sync_* keys: cluster nodes no longer run a delta sync. delta_sync_threshold_transactions is read only by a read replica at bootstrap: a database more than this many transactions behind its source is restored by snapshot, a smaller gap is streamed with StreamChanges. delta_sync_threshold_seconds has no runtime effect; it only feeds the GC rules below.

GC Configuration Rules (Validated at Startup when enable_anti_entropy is true):

  • gc_min_retention_hours must be >= delta_sync_threshold_seconds (in hours)
  • gc_max_retention_hours must be >= 2x delta_sync_threshold_seconds (in hours), unless it is 0
  • gc_min_retention_hours must be < gc_max_retention_hours (when it is not 0)
  • gc_interval_seconds must be >= anti_entropy_interval_seconds
  • Violating these rules will cause startup failure with helpful error messages

Metastore

[metastore]
strict_prepare_sync = false  # Wait for CDC segment fsync before acknowledging PREPARE

strict_prepare_sync = true (or MARMOT_CDC_PREPARE_SYNC=strict) strengthens local crash durability at the cost of write latency. The default groups segment syncs asynchronously.

Query Pipeline

[query_pipeline]
transpiler_cache_size = 10000  # LRU cache for MySQL→SQLite transpilation

Vector Index

[vector_index]
enabled = false
data_dir = ""     # Reserved for a future explicit local vector-state root override

Settings:

  • enabled: Enables vector-index DDL, background workers, and query rewrite support.
  • data_dir: Reserved for a future explicit local vector-state root override. Current builds colocate .vecseg state next to the database file.

Runtime note:

  • Current builds store vector derived state in local .vecseg files next to the database and use the local segment store plus overlay journal for ANN reads, with exact rerank from the base table shortlist.
  • Vector index DML follows ordinary row-level CDC. Vector DDL replicates compact control metadata; segment files, rowmaps, centroids, PQ codebooks, and overlay journals are local derived state.
  • SQL tuning is through CREATE VECTOR INDEX ... WITH (nlist = ..., nprobe = ..., max_norm = ...) and per-connection @@marmot_vec_* session variables. See Vector Search for the startup guide and parameter details.

Batch Commit

Controls SQLite write batching for improved throughput. Batches multiple transactions into single SQLite commits, reducing fsync overhead.

[batch_commit]
enabled = true                       # Enable batch committing
max_batch_size = 128                 # Max transactions per batch
max_wait_ms = 10                     # Max wait before flush (ms)
 
# WAL Checkpoint: Manages WAL file size by checkpointing after batches
checkpoint_enabled = true            # Enable automatic checkpointing
checkpoint_passive_thresh_mb = 4.0   # PASSIVE checkpoint when WAL > 4MB
checkpoint_restart_thresh_mb = 16.0  # RESTART checkpoint when WAL > 16MB
 
# Incremental Vacuum: Reclaims freelist pages in time-limited background task
# Requires sqlite_vacuum_incr build tag (included in default build)
incremental_vacuum_enabled = true    # Enable incremental vacuum after checkpoint
incremental_vacuum_pages = 100       # Pages to vacuum per iteration
incremental_vacuum_time_limit_ms = 10 # Max time budget for vacuum (ms)

How Incremental Vacuum Works:

  1. After successful WAL checkpoint, vacuum is triggered in background goroutine
  2. Runs PRAGMA incremental_vacuum(N) in loop until time limit or freelist empty
  3. Non-blocking: doesn't delay commit responses to clients
  4. Prevents goroutine pileup with atomic flag (only one vacuum runs at a time)

Tuning Tips:

  • High write workload: Decrease incremental_vacuum_time_limit_ms to 5-10ms
  • Large deletes: Increase incremental_vacuum_pages to 500+ for faster reclamation
  • Disable: Set incremental_vacuum_enabled = false if using external vacuum scheduling

MySQL Protocol Server

[mysql]
enabled = true
bind_address = "0.0.0.0"
port = 3306
max_connections = 1000
local_infile_enabled = true         # Enable LOAD DATA LOCAL INFILE
auto_id_mode = "compact"  # "compact" (53-bit, default) or "extended" (64-bit)

Auto ID Mode:

  • "compact" (default): Generates 53-bit IDs that are safe for all clients including JavaScript (Number.MAX_SAFE_INTEGER). Format: 41 bits timestamp + 6 bits node + 6 bits sequence. Supports ~69 years from epoch (Jan 2, 2025) with 64K IDs/sec per node.
  • "extended": Generates full 64-bit HLC-based IDs for maximum uniqueness and ordering guarantees. Use when all clients support 64-bit integers natively.

LOAD DATA LOCAL INFILE Notes:

  • Supported variant is LOAD DATA LOCAL INFILE (client-uploaded bytes).
  • LOAD DATA INFILE (server-side file read) is not supported.
  • Client capability must be enabled (for example, mysql --local-infile=1 or driver-specific local infile flags).

Logging

[logging]
verbose = false          # Enable verbose logging (debug level)
format = "json"          # Log format: "console" or "json" (json is 21% faster)
file = ""                # Log file path (empty = stdout only)
max_size_mb = 100        # Max size in MB before rotation (default: 100)
max_backups = 5          # Number of old log files to keep (default: 5)
compress = true          # Compress rotated files with gzip (default: true)

File Logging with Rotation:

When file is set, logs are written to both stdout and the specified file. The file is automatically rotated when it reaches max_size_mb, keeping the last max_backups files.

[logging]
file = "/var/log/marmot/marmot.log"
max_size_mb = 100
max_backups = 5
compress = true

CLI Flags

Marmot supports the following command-line flags that override config file settings:

FlagDescription
-configPath to configuration file (default: config.toml)
-data-dirData directory (overrides data_dir in config)
-node-idNode ID (overrides node_id in config, 0=auto)
-grpc-portgRPC port (overrides cluster.grpc_port in config)
-mysql-portMySQL port (overrides mysql.port in config)
-follow-addressesComma-separated addresses for replica mode
-daemonRun as background daemon (detached from terminal)
-pid-filePID file path (used with -daemon)

Example:

./marmot -config=/etc/marmot/config.toml -data-dir=/var/lib/marmot -grpc-port=8081 -mysql-port=3307

Running as Daemon:

# Start as daemon with PID file
./marmot -config=/etc/marmot/config.toml -daemon -pid-file=/var/run/marmot.pid
 
# Check if running
cat /var/run/marmot.pid
 
# Stop daemon
kill $(cat /var/run/marmot.pid)

Note: When running as daemon, configure logging.file in config to capture logs since stdout/stderr are detached.