{"id":13410140,"url":"https://github.com/maxpert/marmot","last_synced_at":"2026-01-28T04:59:30.696Z","repository":{"id":58194828,"uuid":"529504107","full_name":"maxpert/marmot","owner":"maxpert","description":"A distributed SQLite replicator built on top of NATS","archived":false,"fork":false,"pushed_at":"2024-08-12T16:41:11.000Z","size":863,"stargazers_count":1999,"open_issues_count":16,"forks_count":50,"subscribers_count":17,"default_branch":"master","last_synced_at":"2025-04-14T16:57:58.923Z","etag":null,"topics":["database","distributed","nats-streaming","raft-consensus-algorithm","replication","sqlite3"],"latest_commit_sha":null,"homepage":"https://maxpert.github.io/marmot/","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/maxpert.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-08-27T06:35:02.000Z","updated_at":"2025-04-11T22:47:15.000Z","dependencies_parsed_at":"2023-02-06T10:45:55.586Z","dependency_job_id":"410d6558-1535-4ce1-a336-eee93164a682","html_url":"https://github.com/maxpert/marmot","commit_stats":null,"previous_names":[],"tags_count":78,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxpert%2Fmarmot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxpert%2Fmarmot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxpert%2Fmarmot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maxpert%2Fmarmot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maxpert","download_url":"https://codeload.github.com/maxpert/marmot/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254328384,"owners_count":22052632,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","distributed","nats-streaming","raft-consensus-algorithm","replication","sqlite3"],"created_at":"2024-07-30T20:01:05.179Z","updated_at":"2026-01-26T03:05:31.551Z","avatar_url":"https://github.com/maxpert.png","language":"Go","readme":"# Marmot v2\n\n[![Go Report Card](https://goreportcard.com/badge/github.com/maxpert/marmot)](https://goreportcard.com/report/github.com/maxpert/marmot)\n[![Discord](https://badgen.net/badge/icon/discord?icon=discord\u0026label=Marmot)](https://discord.gg/AWUwY66XsE)\n![GitHub](https://img.shields.io/github/license/maxpert/marmot)\n\n## What \u0026 Why?\n\nMarmot v2 is a leaderless, distributed SQLite replication system built on a gossip-based protocol with distributed transactions and eventual consistency.\n\n**Key Features:**\n- **Leaderless Architecture**: No single point of failure - any node can accept writes\n- **MySQL Protocol Compatible**: Connect with any MySQL client (DBeaver, MySQL Workbench, mysql CLI)\n- **WordPress Compatible**: Full MySQL function support for running distributed WordPress\n- **Distributed Transactions**: Percolator-style write intents with conflict detection\n- **Multi-Database Support**: Create and manage multiple databases per cluster\n- **DDL Replication**: Distributed schema changes with automatic idempotency and cluster-wide locking\n- **Production-Ready SQL Parser**: Powered by rqlite/sql AST parser for MySQL→SQLite transpilation\n- **CDC-Based Replication**: Row-level change data capture for consistent replication\n\n## Why Marmot?\n\n### The Problem with Traditional Replication\n\nMySQL active-active requires careful setup of replication, conflict avoidance, and monitoring. Failover needs manual intervention. Split-brain scenarios demand operational expertise. This complexity doesn't scale to edge deployments.\n\n### Marmot's Approach\n\n- **Zero operational overhead**: Automatic recovery from split-brain via eventual consistency + anti-entropy\n- **No leader election**: Any node accepts writes, no failover coordination needed\n- **Direct SQLite access**: Clients can read the local SQLite file directly for maximum performance\n- **Tunable consistency**: Choose ONE/QUORUM/ALL per your latency vs durability needs\n\n### Why MySQL Protocol?\n\n- Ecosystem compatibility - existing drivers, ORMs, GUI tools work out-of-box\n- Battle-tested wire protocol implementations\n- Run real applications like WordPress without modification\n\n### Ideal Use Cases\n\nMarmot excels at **read-heavy edge scenarios**:\n\n| Use Case | How Marmot Helps |\n|----------|------------------|\n| **Distributed WordPress** | Multi-region WordPress with replicated database |\n| **Lambda/Edge sidecars** | Lightweight regional SQLite replicas, local reads |\n| **Edge vector databases** | Distributed embeddings with local query |\n| **Regional config servers** | Fast local config reads, replicated writes |\n| **Product catalogs** | Geo-distributed catalog data, eventual sync |\n\n### When to Consider Alternatives\n\n- **Strong serializability required** → CockroachDB, Spanner\n- **Single-region high-throughput** → PostgreSQL, MySQL directly\n- **Large datasets (\u003e100GB)** → Sharded solutions\n\n## Quick Start\n\n```bash\n# Start a single-node cluster\n./marmot-v2\n\n# Or run as daemon (background)\n./marmot-v2 -daemon -pid-file=/tmp/marmot/marmot.pid\n\n# Connect with MySQL client\nmysql -h localhost -P 3306 -u root\n\n# Or use DBeaver, MySQL Workbench, etc.\n```\n\n### Testing Replication\n\n```bash\n# Test DDL and DML replication across a 2-node cluster\n./scripts/test-ddl-replication.sh\n\n# This script will:\n# 1. Start a 2-node cluster\n# 2. Create a table on node 1 and verify it replicates to node 2\n# 3. Insert data on node 1 and verify it replicates to node 2\n# 4. Update data on node 2 and verify it replicates to node 1\n# 5. Delete data on node 1 and verify it replicates to node 2\n\n# Manual cluster testing\n./examples/start-seed.sh              # Start seed node (port 8081, mysql 3307)\n./examples/join-cluster.sh 2 localhost:8081  # Join node 2 (port 8082, mysql 3308)\n./examples/join-cluster.sh 3 localhost:8081  # Join node 3 (port 8083, mysql 3309)\n\n# Connect to any node and run queries\nmysql --protocol=TCP -h localhost -P 3307 -u root\nmysql --protocol=TCP -h localhost -P 3308 -u root\n\n# Cleanup\npkill -f marmot-v2\n```\n\n## WordPress Support\n\nMarmot can run **distributed WordPress** with full database replication across nodes. Each WordPress instance connects to its local Marmot node, and all database changes replicate automatically.\n\n### MySQL Compatibility for WordPress\n\nMarmot implements MySQL functions required by WordPress:\n\n| Category | Functions |\n|----------|-----------|\n| **Date/Time** | NOW, CURDATE, DATE_FORMAT, UNIX_TIMESTAMP, DATEDIFF, YEAR, MONTH, DAY, etc. |\n| **String** | CONCAT_WS, SUBSTRING_INDEX, FIND_IN_SET, LPAD, RPAD, etc. |\n| **Math/Hash** | RAND, MD5, SHA1, SHA2, POW, etc. |\n| **DML** | ON DUPLICATE KEY UPDATE (transformed to SQLite ON CONFLICT) |\n\n### Quick Start: 3-Node WordPress Cluster\n\n```bash\ncd examples/wordpress-cluster\n./run.sh up\n```\n\nThis starts:\n- **3 Marmot nodes** with QUORUM write consistency\n- **3 WordPress instances** each connected to its local Marmot node\n\n```\n┌─────────────┐  ┌─────────────┐  ┌─────────────┐\n│ WordPress-1 │  │ WordPress-2 │  │ WordPress-3 │\n│ :9101       │  │ :9102       │  │ :9103       │\n└──────┬──────┘  └──────┬──────┘  └──────┬──────┘\n       ▼                ▼                ▼\n┌─────────────┐  ┌─────────────┐  ┌─────────────┐\n│  Marmot-1   │◄─┤  Marmot-2   │◄─┤  Marmot-3   │\n│ MySQL: 9191 │  │ MySQL: 9192 │  │ MySQL: 9193 │\n└─────────────┘  └─────────────┘  └─────────────┘\n       └──────────────┴──────────────┘\n              QUORUM Replication\n```\n\n**Test it:**\n1. Open http://localhost:9101 - complete WordPress installation\n2. Open http://localhost:9102 or http://localhost:9103\n3. See your content replicated across all nodes!\n\n**Commands:**\n```bash\n./run.sh status   # Check cluster health\n./run.sh logs-m   # Marmot logs only\n./run.sh logs-wp  # WordPress logs only\n./run.sh down     # Stop cluster\n```\n\n### Production Considerations for WordPress\n\n- **Media uploads**: Use S3/NFS for shared media storage (files not replicated by Marmot)\n- **Sessions**: Use Redis or database sessions for sticky-session-free load balancing\n- **Caching**: Each node can use local object cache (Redis/Memcached per region)\n\n## Architecture\n\nMarmot v2 uses a fundamentally different architecture from other SQLite replication solutions:\n\n**vs. rqlite/dqlite/LiteFS:**\n- ❌ They require a primary node for all writes\n- ✅ Marmot allows writes on **any node**\n- ❌ They use leader election (Raft)\n- ✅ Marmot uses **gossip protocol** (no leader)\n- ❌ They require proxy layer or page-level interception\n- ✅ Marmot uses **MySQL protocol** for direct database access\n\n**How It Works:**\n1. **Write Coordination**: 2PC (Two-Phase Commit) with configurable consistency (ONE, QUORUM, ALL)\n2. **Conflict Resolution**: Last-Write-Wins (LWW) with HLC timestamps\n3. **Cluster Membership**: SWIM-style gossip with failure detection\n4. **Data Replication**: Full database replication - all nodes receive all data\n5. **DDL Replication**: Cluster-wide schema changes with automatic idempotency\n\n## Comparison with Alternatives\n\n| Aspect | Marmot | MySQL Active-Active | rqlite | dqlite | TiDB |\n|--------|--------|---------------------|--------|-------|------|\n| **Leader** | None | None (but complex) | Yes (Raft) | Yes (Raft) | Yes (Raft) |\n| **Failover** | Automatic | Manual intervention | Automatic | Automatic | Automatic |\n| **Split-brain recovery** | Automatic (anti-entropy) | Manual | N/A (leader-based) | N/A (leader-based) | N/A |\n| **Consistency** | Tunable (ONE/QUORUM/ALL) | Serializable | Tunabale (ONE/QUORUM/Linearizable) | Strong | Strong |\n| **Direct file read** | ✅ SQLite file | ❌ | ✅ SQLite file | ❌ | ❌ |\n| **JS-safe AUTO_INCREMENT** | ✅ Compact mode (53-bit) | N/A | N/A | ❌ 64-bit breaks JS |\n| **Edge-friendly** | ✅ Lightweight | ❌ Heavy | ✅ Lightweight | ⚠️ Moderate | ❌ Heavy |\n| **Operational complexity** | Low | High | Low | Low | High |\n\n## DDL Replication\n\nMarmot v2 supports **distributed DDL (Data Definition Language) replication** without requiring master election:\n\n### How It Works\n\n1. **Cluster-Wide Locking**: Each DDL operation acquires a distributed lock per database (default: 30-second lease)\n   - Prevents concurrent schema changes on the same database\n   - Locks automatically expire if a node crashes\n   - Different databases can have concurrent DDL operations\n\n2. **Automatic Idempotency**: DDL statements are automatically rewritten for safe replay\n   ```sql\n   CREATE TABLE users (id INT)\n   → CREATE TABLE IF NOT EXISTS users (id INT)\n\n   DROP TABLE users\n   → DROP TABLE IF EXISTS users\n   ```\n\n3. **Schema Version Tracking**: Each database maintains a schema version counter\n   - Incremented on every DDL operation\n   - Exchanged via gossip protocol for drift detection\n   - Used by delta sync to validate transaction applicability\n\n4. **Quorum-Based Replication**: DDL replicates like DML through the same 2PC mechanism\n   - No special master node needed\n   - Works with existing consistency levels (QUORUM, ALL, etc.)\n\n### Configuration\n\n```toml\n[ddl]\n# DDL lock lease duration (seconds)\nlock_lease_seconds = 30\n\n# Automatically rewrite DDL for idempotency\nenable_idempotent = true\n```\n\n### Best Practices\n\n- ✅ **Do**: Execute DDL from a single connection/node at a time\n- ✅ **Do**: Use qualified table names (`mydb.users` instead of `users`)\n- ⚠️ **Caution**: ALTER TABLE is less idempotent - avoid replaying failed ALTER operations\n- ❌ **Don't**: Run concurrent DDL on the same database from multiple nodes\n\n## CDC-Based Replication\n\nMarmot v2 uses **Change Data Capture (CDC)** for replication instead of SQL statement replay:\n\n### How It Works\n\n1. **Row-Level Capture**: Instead of replicating SQL statements, Marmot captures the actual row data changes (INSERT/UPDATE/DELETE)\n2. **Binary Data Format**: Row data is serialized as CDC messages with column values, ensuring consistent replication regardless of SQL dialect\n3. **Deterministic Application**: Row data is applied directly to the target database, avoiding parsing ambiguities\n\n### Benefits\n\n- **Consistency**: Same row data applied everywhere, no SQL parsing differences\n- **Performance**: Binary format is more efficient than SQL text\n- **Reliability**: No issues with SQL syntax variations between MySQL and SQLite\n\n### Row Key Extraction\n\nFor UPDATE and DELETE operations, Marmot automatically extracts row keys:\n- Uses PRIMARY KEY columns when available\n- Falls back to ROWID for tables without explicit primary key\n- Handles composite primary keys correctly\n\n## CDC Publisher\n\nMarmot can publish CDC events to external messaging systems, enabling real-time data pipelines, analytics, and event-driven architectures. Events follow the **[Debezium](https://debezium.io/) specification** for maximum compatibility with existing CDC tooling.\n\n### Features\n\n- **Debezium-Compatible Format**: Events conform to the [Debezium event structure](https://debezium.io/documentation/reference/stable/connectors/postgresql.html#postgresql-events), compatible with Kafka Connect, Flink, Spark, and other CDC consumers\n- **Multi-Sink Support**: Publish to multiple destinations simultaneously (Kafka, NATS)\n- **Glob-Based Filtering**: Filter which tables and databases to publish\n- **Automatic Retry**: Exponential backoff with configurable limits\n- **Persistent Cursors**: Survives restarts without losing position\n\n### Configuration\n\n```toml\n[publisher]\nenabled = true\n\n[[publisher.sinks]]\nname = \"kafka-main\"\ntype = \"kafka\"                    # \"kafka\" or \"nats\"\nformat = \"debezium\"               # Debezium-compatible JSON format\nbrokers = [\"localhost:9092\"]      # Kafka broker addresses\ntopic_prefix = \"marmot.cdc\"       # Topics: {prefix}.{database}.{table}\nfilter_tables = [\"*\"]             # Glob patterns (e.g., \"users\", \"order_*\")\nfilter_databases = [\"*\"]          # Glob patterns (e.g., \"prod_*\")\nbatch_size = 100                  # Events per poll cycle\npoll_interval_ms = 10             # Polling interval\n\n# NATS sink example\n[[publisher.sinks]]\nname = \"nats-events\"\ntype = \"nats\"\nformat = \"debezium\"\nnats_url = \"nats://localhost:4222\"\ntopic_prefix = \"marmot.cdc\"\nfilter_tables = [\"*\"]\nfilter_databases = [\"*\"]\n```\n\n### Event Format\n\nEvents follow the [Debezium envelope structure](https://debezium.io/documentation/reference/stable/connectors/postgresql.html#postgresql-change-events-value):\n\n```json\n{\n  \"schema\": { ... },\n  \"payload\": {\n    \"before\": null,\n    \"after\": {\"id\": 1, \"name\": \"alice\", \"email\": \"alice@example.com\"},\n    \"source\": {\n      \"version\": \"2.0.0\",\n      \"connector\": \"marmot\",\n      \"name\": \"marmot\",\n      \"ts_ms\": 1702500000000,\n      \"db\": \"myapp\",\n      \"table\": \"users\"\n    },\n    \"op\": \"c\",\n    \"ts_ms\": 1702500000000\n  }\n}\n```\n\n**Operation Types** (per [Debezium spec](https://debezium.io/documentation/reference/stable/connectors/postgresql.html#postgresql-create-events)):\n| Operation | `op` | `before` | `after` |\n|-----------|------|----------|---------|\n| INSERT | `c` (create) | `null` | row data |\n| UPDATE | `u` (update) | old row | new row |\n| DELETE | `d` (delete) | old row | `null` |\n\n### Topic Naming\n\nTopics follow the pattern: `{topic_prefix}.{database}.{table}`\n\nExamples:\n- `marmot.cdc.myapp.users`\n- `marmot.cdc.myapp.orders`\n- `marmot.cdc.analytics.events`\n\n### Use Cases\n\n- **Real-Time Analytics**: Stream changes to data warehouses (Snowflake, BigQuery, ClickHouse)\n- **Event-Driven Microservices**: Trigger actions on data changes\n- **Cache Invalidation**: Keep caches in sync with database changes\n- **Audit Logging**: Capture all changes for compliance\n- **Search Indexing**: Keep Elasticsearch/Algolia in sync\n\nFor more details, see the [Integrations documentation](https://maxpert.github.io/marmot/integrations).\n\n## Edge Deployment Patterns\n\n### Lambda Sidecar\n\nDeploy Marmot as a lightweight regional replica alongside Lambda functions:\n- Local SQLite reads (sub-ms latency)\n- Writes replicate to cluster\n- No cold-start database connections\n\n### Read-Only Regional Replicas\n\nScale reads globally with replica mode and transparent failover:\n\n```toml\n[replica]\nenabled = true\nfollow_addresses = [\"central-cluster-1:8080\", \"central-cluster-2:8080\", \"central-cluster-3:8080\"]\nreplicate_databases = []                    # Filter databases (empty = all, supports glob patterns)\ndatabase_discovery_interval_seconds = 10    # Poll for new databases (default: 10)\ndiscovery_interval_seconds = 30             # Poll for cluster membership (default: 30)\nfailover_timeout_seconds = 60               # Failover timeout (default: 60)\nsnapshot_concurrency = 3                    # Parallel snapshot downloads (default: 3)\nsnapshot_cache_ttl_seconds = 30             # Snapshot cache TTL (default: 30)\n```\n\n- Discovers cluster nodes automatically via `GetClusterNodes` RPC\n- Transparent failover when current source becomes unavailable\n- Automatic discovery of new databases with configurable polling\n- Per-database selective replication with glob pattern support\n- Parallel snapshot downloads with configurable concurrency\n- Snapshot caching for performance optimization\n- Zero cluster participation overhead\n- Auto-reconnect with exponential backoff\n\n### Hybrid: Edge Reads, Central Writes\n\n- Deploy full cluster in central region\n- Deploy read replicas at edge locations\n- Application routes writes to central, reads to local replica\n\n## SQL Statement Compatibility\n\nMarmot supports a wide range of MySQL/SQLite statements through its MySQL protocol server. The following table shows compatibility for different statement types:\n\n| Statement Type | Support | Replication | Notes |\n|---------------|---------|-------------|-------|\n| **DML - Data Manipulation** |\n| `INSERT` / `REPLACE` | ✅ Full | ✅ Yes | Includes qualified table names (db.table) |\n| `UPDATE` | ✅ Full | ✅ Yes | Includes qualified table names |\n| `DELETE` | ✅ Full | ✅ Yes | Includes qualified table names |\n| `SELECT` | ✅ Full | N/A | Read operations |\n| `LOAD DATA` | ✅ Full | ✅ Yes | Bulk data loading |\n| **DDL - Data Definition** |\n| `CREATE TABLE` | ✅ Full | ✅ Yes | Replicated with cluster-wide locking |\n| `ALTER TABLE` | ✅ Full | ✅ Yes | Replicated with cluster-wide locking |\n| `DROP TABLE` | ✅ Full | ✅ Yes | Replicated with cluster-wide locking |\n| `TRUNCATE TABLE` | ✅ Full | ✅ Yes | |\n| `RENAME TABLE` | ✅ Full | ✅ Yes | Replicated with cluster-wide locking |\n| `CREATE/DROP INDEX` | ✅ Full | ✅ Yes | Replicated with cluster-wide locking |\n| `CREATE/DROP VIEW` | ✅ Full | ✅ Yes | Replicated with cluster-wide locking |\n| `CREATE/DROP TRIGGER` | ✅ Full | ✅ Yes | Replicated with cluster-wide locking |\n| **Database Management** |\n| `CREATE DATABASE` | ✅ Full | ✅ Yes | Replicated with cluster-wide locking |\n| `DROP DATABASE` | ✅ Full | ✅ Yes | Replicated with cluster-wide locking |\n| `ALTER DATABASE` | ✅ Full | ✅ Yes | Replicated with cluster-wide locking |\n| `SHOW DATABASES` | ✅ Full | N/A | Metadata query |\n| `SHOW TABLES` | ✅ Full | N/A | Metadata query |\n| `USE database` | ✅ Full | N/A | Session state |\n| **Transaction Control** |\n| `BEGIN` / `START TRANSACTION` | ✅ Full | N/A | Transaction boundary |\n| `COMMIT` | ✅ Full | ✅ Yes | Commits distributed transaction |\n| `ROLLBACK` | ✅ Full | ✅ Yes | Aborts distributed transaction |\n| `SAVEPOINT` | ✅ Full | ✅ Yes | Nested transaction support |\n| **Locking** |\n| `LOCK TABLES` | ✅ Parsed | ❌ No | Requires distributed locking coordination |\n| `UNLOCK TABLES` | ✅ Parsed | ❌ No | Requires distributed locking coordination |\n| **Session Configuration** |\n| `SET` statements | ✅ Parsed | ❌ No | Session-local, not replicated |\n| `SET marmot_transpilation` | ✅ Full | ❌ No | Toggle MySQL→SQLite transpilation |\n| `LOAD EXTENSION` | ✅ Full | ❌ No | Load SQLite extension (requires config) |\n| **XA Transactions** |\n| `XA START/END/PREPARE` | ✅ Parsed | ❌ No | Marmot uses its own 2PC protocol |\n| `XA COMMIT/ROLLBACK` | ✅ Parsed | ❌ No | Not compatible with Marmot's model |\n| **DCL - Data Control** |\n| `GRANT` / `REVOKE` | ✅ Parsed | ❌ No | User management not replicated |\n| `CREATE/DROP USER` | ✅ Parsed | ❌ No | User management not replicated |\n| `ALTER USER` | ✅ Parsed | ❌ No | User management not replicated |\n| **Administrative** |\n| `OPTIMIZE TABLE` | ✅ Parsed | ❌ No | Node-local administrative command |\n| `REPAIR TABLE` | ✅ Parsed | ❌ No | Node-local administrative command |\n\n### Legend\n- ✅ **Full**: Fully supported and working\n- ✅ **Parsed**: Statement is parsed and recognized\n- ⚠️ **Limited**: Works but has limitations in distributed context\n- ❌ **No**: Not supported or not replicated\n- **N/A**: Not applicable (read-only or session-local)\n\n### Important Notes\n\n1. **Schema Changes (DDL)**: DDL statements are fully replicated with cluster-wide locking and automatic idempotency. See the DDL Replication section for details.\n\n2. **XA Transactions**: Marmot has its own distributed transaction protocol based on 2PC. MySQL XA transactions are not compatible with Marmot's replication model.\n\n3. **User Management (DCL)**: User and privilege management statements are local to each node. For production deployments, consider handling authentication at the application or proxy level.\n\n4. **Table Locking**: `LOCK TABLES` statements are recognized but not enforced across the cluster. Use application-level coordination for distributed locking needs.\n\n5. **Qualified Names**: Marmot fully supports qualified table names (e.g., `db.table`) in DML and DDL operations.\n\n## SQLite Extensions\n\nMarmot supports loading SQLite extensions to add custom functions, virtual tables, and other capabilities. This enables use cases like vector search (sqlite-vss), full-text search, and custom aggregations.\n\n### Configuration\n\n```toml\n[extensions]\n# Directory containing extension libraries (.so, .dylib, .dll)\ndirectory = \"/opt/sqlite/extensions\"\n\n# Extensions loaded automatically into every connection\nalways_loaded = [\"sqlite-vector\"]\n```\n\n### Dynamic Loading\n\nExtensions can be loaded at runtime via SQL:\n\n```sql\n-- Load an extension by name (resolved from extensions.directory)\nLOAD EXTENSION sqlite-vector;\n\n-- Verify the extension is loaded\nSELECT vec_version();\n```\n\n**Extension Resolution:**\n- Searches the configured `directory` for the extension\n- Tries multiple patterns: `name`, `name.so`, `name.dylib`, `libname.so`, `libname.dylib`\n- Platform-appropriate suffix is preferred (.dylib on macOS, .so on Linux, .dll on Windows)\n\n**Security:**\n- Extension names cannot contain path separators (no path traversal)\n- Resolved paths must stay within the configured directory\n- Only extensions in the configured directory can be loaded\n\n### Use Cases\n\n| Extension | Purpose |\n|-----------|---------|\n| **sqlite-vss** | Vector similarity search for AI/ML embeddings |\n| **sqlite-vec** | Lightweight vector search |\n| **fts5** | Full-text search (usually built-in) |\n| **json1** | JSON functions (usually built-in) |\n\n## Session-Level Transpilation Toggle\n\nMarmot normally transpiles MySQL syntax to SQLite. You can disable this per-session to send raw SQLite SQL directly.\n\n### Usage\n\n```sql\n-- Disable MySQL→SQLite transpilation (raw SQLite mode)\nSET marmot_transpilation = OFF;\n\n-- Now you can use SQLite-specific syntax\nSELECT sqlite_version();\nPRAGMA table_info(users);\n\n-- Re-enable transpilation\nSET marmot_transpilation = ON;\n\n-- Back to MySQL mode\nSELECT NOW();  -- Transpiled to SQLite's datetime functions\n```\n\n### When to Use\n\n- **Raw SQLite queries**: Use SQLite-specific syntax like PRAGMA, ATTACH, etc.\n- **Performance testing**: Compare transpiled vs raw query performance\n- **Debugging**: See exactly what SQLite receives\n- **Advanced SQLite features**: Access features not available through MySQL syntax\n\n**Note:** Transpilation is enabled by default. The setting is per-session and not persisted.\n\n## MySQL Protocol \u0026 Metadata Queries\n\nMarmot includes a MySQL-compatible protocol server, allowing you to connect using any MySQL client (DBeaver, MySQL Workbench, mysql CLI, etc.). The server supports:\n\n### Metadata Query Support\n\nMarmot provides full support for MySQL metadata queries, enabling GUI tools like DBeaver to browse databases, tables, and columns:\n\n- **SHOW Commands**: `SHOW DATABASES`, `SHOW TABLES`, `SHOW COLUMNS FROM table`, `SHOW CREATE TABLE`, `SHOW INDEXES`\n- **INFORMATION_SCHEMA**: Queries against `INFORMATION_SCHEMA.TABLES`, `INFORMATION_SCHEMA.COLUMNS`, `INFORMATION_SCHEMA.SCHEMATA`, and `INFORMATION_SCHEMA.STATISTICS`\n- **Type Conversion**: Automatic SQLite-to-MySQL type mapping for compatibility\n\nThese metadata queries are powered by the **rqlite/sql AST parser**, providing production-grade MySQL query compatibility.\n\n### Connecting with MySQL Clients\n\n```bash\n# Using mysql CLI\nmysql -h localhost -P 3306 -u root\n\n# Connection string for applications\nmysql://root@localhost:3306/marmot\n```\n\n## Recovery Scenarios\n\nMarmot handles various failure and recovery scenarios automatically:\n\n### Network Partition (Split-Brain)\n\n| Scenario | Behavior |\n|----------|----------|\n| **Minority partition** | Writes **fail** - cannot achieve quorum |\n| **Majority partition** | Writes **succeed** - quorum achieved |\n| **Partition heals** | Delta sync + LWW merges divergent data |\n\n**How it works:**\n1. During partition, only the majority side can commit writes (quorum enforcement)\n2. When partition heals, nodes exchange transaction logs via `StreamChanges` RPC\n3. Conflicts resolved using Last-Writer-Wins (LWW) with HLC timestamps\n4. Higher node ID breaks ties for simultaneous writes\n\n### Node Failure \u0026 Recovery\n\n| Scenario | Recovery Method |\n|----------|-----------------|\n| **Brief outage** | Delta sync - replay missed transactions |\n| **Extended outage** | Snapshot transfer + delta sync |\n| **New node joining** | Full snapshot from existing node |\n\n**Anti-Entropy Background Process:**\n\nMarmot v2 includes an automatic anti-entropy system that continuously monitors and repairs replication lag across the cluster:\n\n1. **Lag Detection**: Every 30 seconds (configurable), each node queries peers for their replication state\n2. **Smart Recovery Decision**:\n   - **Delta Sync** if lag \u003c 10,000 transactions AND \u003c 1 hour: Streams missed transactions incrementally\n   - **Snapshot Transfer** if lag exceeds thresholds: Full database file transfer for efficiency\n3. **Gap Detection**: Detects when transaction logs have been GC'd and automatically falls back to snapshot\n4. **Multi-Database Support**: Tracks and syncs each database independently\n5. **GC Coordination**: Garbage collection respects peer replication state - logs aren't deleted until all peers have applied them\n\n**Delta Sync Process:**\n1. Lagging node queries `last_applied_txn_id` for each peer/database\n2. Requests transactions since that ID via `StreamChanges` RPC\n3. **Gap Detection**: Checks if first received txn_id has a large gap from requested ID\n   - If gap \u003e delta_sync_threshold_txns, indicates missing (GC'd) transactions\n   - Automatically falls back to snapshot transfer to prevent data loss\n4. Applies changes using LWW conflict resolution\n5. Updates replication state tracking (per-database)\n6. Progress logged every 100 transactions\n\n**GC Coordination with Anti-Entropy:**\n- Transaction logs are retained with a two-tier policy:\n  - **Min retention** (2 hours): Must be \u003e= delta sync threshold, respects peer lag\n  - **Max retention** (24 hours): Force delete after this time to prevent unbounded growth\n- Config validation enforces: `gc_min \u003e= delta_threshold` and `gc_max \u003e= 2x delta_threshold`\n- Each database tracks replication progress per peer\n- GC queries minimum applied txn_id across all peers before cleanup\n- **Gap detection** prevents data loss if GC runs while nodes are offline\n\n### Consistency Guarantees\n\n| Write Consistency | Behavior |\n|-------------------|----------|\n| `ONE` | Returns after 1 node ACK (fast, less durable) |\n| `QUORUM` | Returns after majority ACK (default, balanced) |\n| `ALL` | Returns after all nodes ACK (slow, most durable) |\n\n**Conflict Resolution:**\n- All conflicts resolved via LWW using HLC timestamps\n- No data loss - later write always wins deterministically\n- Tie-breaker: higher node ID wins for equal timestamps\n\n## Limitations\n\n- **Selective Table Watching**: All tables in a database are replicated. Selective table replication is not supported.\n- **WAL Mode Required**: SQLite must use WAL mode for reliable multi-process changes.\n- **Eventually Consistent**: Rows may sync out of order. `SERIALIZABLE` transaction assumptions may not hold across nodes.\n- **Concurrent DDL**: Avoid running concurrent DDL operations on the same database from multiple nodes (protected by cluster-wide lock with 30s lease).\n\n## Auto-Increment \u0026 ID Generation\n\n### The Problem with Distributed IDs\n\nDistributed databases need globally unique IDs, but traditional solutions cause problems:\n\n| Solution | Issue |\n|----------|-------|\n| **UUID** | 128-bit, poor index performance, not sortable |\n| **Snowflake/HLC 64-bit** | Exceeds JavaScript's `Number.MAX_SAFE_INTEGER` (2^53-1) |\n| **TiDB AUTO_INCREMENT** | Returns 64-bit IDs that **break JavaScript clients** silently |\n\n**The JavaScript Problem:**\n\n```javascript\n// 64-bit ID from TiDB or other distributed DBs\nconst id = 7318624812345678901;\nconsole.log(id);  // 7318624812345679000 - WRONG! Precision lost!\n\n// JSON parsing also breaks\nJSON.parse('{\"id\": 7318624812345678901}');  // {id: 7318624812345679000}\n```\n\nTiDB's answer? \"Use strings.\" But that breaks ORMs, existing application code, and type safety.\n\n### Marmot's Solution: Compact ID Mode\n\nMarmot offers **two ID generation modes** to solve this:\n\n| Mode | Bits | Range | Use Case |\n|------|------|-------|----------|\n| `extended` | 64-bit | Full HLC timestamp | New systems, non-JS clients |\n| `compact` | 53-bit | JS-safe integers | **Legacy systems, JavaScript, REST APIs** |\n\n```toml\n[mysql]\nauto_id_mode = \"compact\"  # Safe for JavaScript (default)\n# auto_id_mode = \"extended\"  # Full 64-bit for new systems\n```\n\n**Compact Mode Guarantees:**\n- IDs stay under `Number.MAX_SAFE_INTEGER` (9,007,199,254,740,991)\n- Still globally unique across all nodes\n- Still monotonically increasing (per node)\n- No silent precision loss in JSON/JavaScript\n- Works with existing ORMs expecting integer IDs\n\n**With Marmot compact mode:**\n```javascript\nconst id = 4503599627370496;\nconsole.log(id);  // 4503599627370496 - Correct!\nJSON.parse('{\"id\": 4503599627370496}');  // {id: 4503599627370496} - Correct!\n```\n\n### How Auto-Increment Works\n\n\u003e **Note:** Marmot automatically converts `INT AUTO_INCREMENT` to `BIGINT` to support distributed ID generation.\n\n1. **DDL Transformation**: When you create a table with `AUTO_INCREMENT`:\n   ```sql\n   CREATE TABLE users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100))\n   -- Becomes internally:\n   CREATE TABLE users (id BIGINT PRIMARY KEY, name TEXT)\n   ```\n\n2. **DML ID Injection**: When inserting with `0` or `NULL` for an auto-increment column:\n   ```sql\n   INSERT INTO users (id, name) VALUES (0, 'alice')\n   -- Becomes internally (compact mode):\n   INSERT INTO users (id, name) VALUES (4503599627370496, 'alice')\n   ```\n\n3. **Explicit IDs Preserved**: If you provide an explicit non-zero ID, it is used as-is.\n\n**Schema-Based Detection:**\n\nMarmot automatically detects auto-increment columns by querying SQLite schema directly:\n- Single-column `INTEGER PRIMARY KEY` (SQLite rowid alias)\n- Single-column `BIGINT PRIMARY KEY` (Marmot's transformed columns)\n\nNo registration required - columns are detected from schema at runtime, works across restarts, and works with existing databases.\n\n## Configuration\n\nMarmot v2 uses a TOML configuration file (default: `config.toml`). All settings have sensible defaults.\n\n### Core Configuration\n\n```toml\nnode_id = 0  # 0 = auto-generate\ndata_dir = \"./marmot-data\"\n```\n\n### Transaction Manager\n\n```toml\n[transaction]\nheartbeat_timeout_seconds = 10  # Transaction timeout without heartbeat\nconflict_window_seconds = 10    # Conflict resolution window\nlock_wait_timeout_seconds = 50  # Lock wait timeout (MySQL: innodb_lock_wait_timeout)\n```\n\n**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`.\n\n### Connection Pool\n\n```toml\n[connection_pool]\npool_size = 4              # Number of SQLite connections\nmax_idle_time_seconds = 10 # Max idle time before closing\nmax_lifetime_seconds = 300 # Max connection lifetime (0 = unlimited)\n```\n\n### gRPC Client\n\n```toml\n[grpc_client]\nkeepalive_time_seconds = 10    # Keepalive ping interval\nkeepalive_timeout_seconds = 3  # Keepalive ping timeout\nmax_retries = 3                # Max retry attempts\nretry_backoff_ms = 100         # Retry backoff duration\n```\n\n### Coordinator\n\n```toml\n[coordinator]\nprepare_timeout_ms = 2000 # Prepare phase timeout\ncommit_timeout_ms = 2000  # Commit phase timeout\nabort_timeout_ms = 2000   # Abort phase timeout\n```\n\n### Cluster\n\n```toml\n[cluster]\ngrpc_bind_address = \"0.0.0.0\"\ngrpc_port = 8080\nseed_nodes = []                # List of seed node addresses\ncluster_secret = \"\"            # PSK for cluster authentication (see Security section)\ngossip_interval_ms = 1000      # Gossip interval\ngossip_fanout = 3              # Number of peers to gossip to\nsuspect_timeout_ms = 5000      # Suspect timeout\ndead_timeout_ms = 10000        # Dead timeout\n```\n\n### Security\n\nMarmot supports Pre-Shared Key (PSK) authentication for cluster communication. **This is strongly recommended for production deployments.**\n\n```toml\n[cluster]\n# All nodes in the cluster must use the same secret\ncluster_secret = \"your-secret-key-here\"\n```\n\n**Environment Variable (Recommended):**\n\nFor production, use the environment variable to avoid storing secrets in config files:\n\n```bash\nexport MARMOT_CLUSTER_SECRET=\"your-secret-key-here\"\n./marmot\n```\n\nThe environment variable takes precedence over the config file.\n\n**Generating a Secret:**\n\n```bash\n# Generate a secure random secret\nopenssl rand -base64 32\n```\n\n**Behavior:**\n- If `cluster_secret` is empty and `MARMOT_CLUSTER_SECRET` is not set, authentication is disabled\n- A warning is logged at startup when authentication is disabled\n- All gRPC endpoints (gossip, replication, snapshots) are protected when authentication is enabled\n- Nodes with mismatched secrets will fail to communicate (connection rejected with \"invalid cluster secret\")\n\n### Cluster Membership Management\n\nMarmot provides admin HTTP endpoints for managing cluster membership (requires `cluster_secret` to be configured):\n\n**Node Lifecycle:**\n- New/restarted nodes **auto-join** via gossip - no manual intervention needed\n- Nodes marked REMOVED via admin API **cannot auto-rejoin** - must be explicitly allowed\n- This prevents decommissioned nodes from accidentally rejoining the cluster\n\n```bash\n# View cluster members and quorum info\ncurl -H \"X-Marmot-Secret: your-secret\" http://localhost:8080/admin/cluster/members\n\n# Remove a node from the cluster (excludes from quorum, blocks auto-rejoin)\ncurl -X POST -H \"X-Marmot-Secret: your-secret\" http://localhost:8080/admin/cluster/remove/2\n\n# Allow a removed node to rejoin (node must then restart to join)\ncurl -X POST -H \"X-Marmot-Secret: your-secret\" http://localhost:8080/admin/cluster/allow/2\n```\n\nSee the [Operations documentation](https://maxpert.github.io/marmot/operations) for detailed usage and examples.\n\n### Replica Mode\n\nFor read-only replicas that follow cluster nodes with transparent failover:\n\n```toml\n[replica]\nenabled = true                                   # Enable read-only replica mode\nfollow_addresses = [\"node1:8080\", \"node2:8080\", \"node3:8080\"]  # Seed nodes for discovery\nsecret = \"replica-secret\"                        # PSK for authentication (required)\nreplicate_databases = []                         # Filter databases (empty = all, supports glob patterns like \"prod_*\")\ndatabase_discovery_interval_seconds = 10         # Poll for new databases (default: 10)\ndiscovery_interval_seconds = 30                  # Poll for cluster membership (default: 30)\nfailover_timeout_seconds = 60                    # Max time to find alive node during failover (default: 60)\nreconnect_interval_seconds = 5                   # Reconnect delay on disconnect (default: 5)\nreconnect_max_backoff_seconds = 30               # Max reconnect backoff (default: 30)\ninitial_sync_timeout_minutes = 30                # Timeout for initial snapshot (default: 30)\nsnapshot_concurrency = 3                         # Parallel snapshot downloads (default: 3)\nsnapshot_cache_ttl_seconds = 30                  # Snapshot cache TTL in seconds (default: 30)\n```\n\nYou can also specify follow addresses via CLI:\n```bash\n./marmot --config=replica.toml --follow-addresses=node1:8080,node2:8080,node3:8080\n```\n\n**Per-Database Selective Replication:**\n\nControl which databases are replicated using the `replicate_databases` filter:\n\n```toml\n[replica]\n# Replicate only specific databases\nreplicate_databases = [\"myapp\", \"analytics\"]\n\n# Replicate databases matching glob patterns\nreplicate_databases = [\"prod_*\", \"staging_*\"]\n\n# Replicate all databases (default)\nreplicate_databases = []\n```\n\nThe system database (`__marmot_system`) is never replicated - each replica maintains its own independent system database.\n\n**Snapshot Caching:**\n\nReplicas use an LRU cache to avoid redundant snapshot creation:\n- Cache TTL controlled by `snapshot_cache_ttl_seconds` (default: 30 seconds)\n- Cached snapshots served from temp storage until expiration\n- Background cleanup runs automatically\n- Improves performance when multiple replicas bootstrap simultaneously\n\n**Parallel Snapshot Downloads:**\n\nControl download concurrency with `snapshot_concurrency`:\n- Downloads multiple database snapshots in parallel (default: 3)\n- Uses worker pool pattern to limit resource usage\n- Partial failure handling: continues even if some databases fail\n- Failed databases retry in background with exponential backoff\n\n**Note:** Replica mode is mutually exclusive with cluster mode. A replica receives all data via streaming replication but cannot accept writes. It automatically discovers cluster nodes and fails over to another node if the current source becomes unavailable.\n\n### Replication\n\n```toml\n[replication]\ndefault_write_consistency = \"QUORUM\"      # Write consistency level: ONE, QUORUM, ALL\ndefault_read_consistency = \"LOCAL_ONE\"    # Read consistency level\nwrite_timeout_ms = 5000                   # Write operation timeout\nread_timeout_ms = 2000                    # Read operation timeout\n\n# Anti-Entropy: Background healing for eventual consistency\n# - Detects and repairs divergence between replicas\n# - Uses delta sync for small lags, snapshot for large lags\n# - Includes gap detection to prevent incomplete data after GC\nenable_anti_entropy = true                 # Enable automatic catch-up for lagging nodes\nanti_entropy_interval_seconds = 30         # How often to check for lag (default: 30s)\ngc_interval_seconds = 60                   # GC interval (MUST be \u003e= anti_entropy_interval)\ndelta_sync_threshold_transactions = 10000  # Delta sync if lag \u003c 10K txns\ndelta_sync_threshold_seconds = 3600        # Snapshot if lag \u003e 1 hour\n\n# Garbage Collection: Reclaim disk space by deleting old transaction records\n# - gc_interval must be \u003e= anti_entropy_interval (validated at startup)\n# - gc_min must be \u003e= delta_sync_threshold (validated at startup)\n# - gc_max should be \u003e= 2x delta_sync_threshold (recommended)\n# - Set gc_max = 0 for unlimited retention\ngc_min_retention_hours = 2   # Keep at least 2 hours (\u003e= 1 hour delta threshold)\ngc_max_retention_hours = 24  # Force delete after 24 hours\n```\n\n**Anti-Entropy Tuning:**\n- **Small clusters (2-3 nodes)**: Use default settings (30s AE, 60s GC)\n- **Large clusters (5+ nodes)**: Consider increasing AE interval to 60-120s and GC to 2x that value\n- **High write throughput**: Increase `delta_sync_threshold_transactions` to 50000+\n- **Long-running clusters**: Keep `gc_max_retention_hours` at 24+ to handle extended outages\n\n**GC Configuration Rules (Validated at Startup):**\n- `gc_min_retention_hours` must be \u003e= `delta_sync_threshold_seconds` (in hours)\n- `gc_max_retention_hours` should be \u003e= 2x `delta_sync_threshold_seconds`\n- Violating these rules will cause startup failure with helpful error messages\n\n### Query Pipeline\n\n```toml\n[query_pipeline]\ntranspiler_cache_size = 10000  # LRU cache for MySQL→SQLite transpilation\nvalidator_pool_size = 8        # SQLite connection pool for validation\n```\n\n### MySQL Protocol Server\n\n```toml\n[mysql]\nenabled = true\nbind_address = \"0.0.0.0\"\nport = 3306\nmax_connections = 1000\nunix_socket = \"\"              # Unix socket path (empty = disabled)\nunix_socket_perm = 0660       # Socket file permissions\nauto_id_mode = \"compact\"      # \"compact\" (53-bit, JS-safe) or \"extended\" (64-bit)\n```\n\n**Unix Socket Connection** (lower latency than TCP):\n```bash\nmysql --socket=/tmp/marmot/mysql.sock -u root\n```\n\n### CDC Publisher\n\n```toml\n[publisher]\nenabled = false  # Enable CDC publishing to external systems\n\n[[publisher.sinks]]\nname = \"kafka-main\"              # Unique sink name\ntype = \"kafka\"                   # \"kafka\" or \"nats\"\nformat = \"debezium\"              # Debezium-compatible JSON (only option)\nbrokers = [\"localhost:9092\"]     # Kafka broker addresses\ntopic_prefix = \"marmot.cdc\"      # Topic pattern: {prefix}.{db}.{table}\nfilter_tables = [\"*\"]            # Glob patterns for table filtering\nfilter_databases = [\"*\"]         # Glob patterns for database filtering\nbatch_size = 100                 # Events to read per poll cycle\npoll_interval_ms = 10            # Polling interval (default: 10ms)\nretry_initial_ms = 100           # Initial retry delay on failure\nretry_max_ms = 30000             # Max retry delay (30 seconds)\nretry_multiplier = 2.0           # Exponential backoff multiplier\n```\n\nSee the [Integrations documentation](https://maxpert.github.io/marmot/integrations) for details on event format, Kafka/NATS configuration, and use cases.\n\n### SQLite Extensions\n\n```toml\n[extensions]\ndirectory = \"/opt/sqlite/extensions\"  # Search path for extensions\nalways_loaded = [\"sqlite-vector\"]     # Auto-load into every connection\n```\n\n**Loading Extensions at Runtime:**\n```sql\nLOAD EXTENSION sqlite-vector;\nSELECT vec_version();\n```\n\n### Logging\n\n```toml\n[logging]\nverbose = false          # Enable verbose logging\nformat = \"json\"          # Log format: \"console\" or \"json\" (json is faster)\nfile = \"\"                # Log file path (empty = stdout only)\nmax_size_mb = 100        # Max size in MB before rotation\nmax_backups = 5          # Number of old log files to keep\ncompress = true          # Compress rotated files with gzip\n```\n\n**File Logging with Rotation:**\n\nWhen `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. Rotated files are optionally compressed with gzip.\n\n```toml\n[logging]\nfile = \"/var/log/marmot/marmot.log\"\nmax_size_mb = 100\nmax_backups = 5\ncompress = true\n```\n\n### Prometheus Metrics\n\n```toml\n[prometheus]\nenabled = true  # Metrics served on gRPC port at /metrics endpoint\n```\n\n**Accessing Metrics:**\n```bash\n# Metrics are multiplexed with gRPC on the same port\ncurl http://localhost:8080/metrics\n\n# Prometheus scrape config\nscrape_configs:\n  - job_name: 'marmot'\n    static_configs:\n      - targets: ['node1:8080', 'node2:8080', 'node3:8080']\n```\n\nSee `config.toml` for complete configuration reference with detailed comments.\n\n## Benchmarks\n\nPerformance benchmarks on a local development machine (Apple M-series, 3-node cluster, single machine):\n\n### Test Configuration\n\n| Parameter | Value |\n|-----------|-------|\n| Nodes | 3 (ports 3307, 3308, 3309) |\n| Threads | 16 |\n| Batch Size | 10 ops/transaction |\n| Consistency | QUORUM |\n\n### Load Phase (INSERT-only)\n\n| Metric | Value |\n|--------|-------|\n| Throughput | **4,175 ops/sec** |\n| TX Throughput | **417 tx/sec** |\n| Records Loaded | 200,000 |\n| Errors | 0 |\n\n### Mixed Workload\n\n| Metric | Value |\n|--------|-------|\n| Throughput | **3,370 ops/sec** |\n| TX Throughput | **337 tx/sec** |\n| Duration | 120 seconds |\n| Total Operations | 404,930 |\n| Errors | 0 |\n| Retries | 37 (0.09%) |\n\n**Operation Distribution:**\n- READ: 20%\n- UPDATE: 30%\n- INSERT: 35%\n- DELETE: 5%\n- UPSERT: 10%\n\n### Latency (Mixed Workload)\n\n| Percentile | Latency |\n|------------|---------|\n| P50 | 4.3ms |\n| P90 | 14.0ms |\n| P95 | 36.8ms |\n| P99 | 85.1ms |\n\n### Replication Verification\n\nAll 3 nodes maintained identical row counts (346,684 rows) throughout the test, confirming consistent replication.\n\n\u003e **Note**: These benchmarks are from a local development machine with all nodes on the same host. Production deployments across multiple machines will have different characteristics based on network latency. Expect P99 latencies of 50-200ms for cross-region QUORUM writes.\n\n## Backup \u0026 Disaster Recovery\n\n### Option 1: Litestream (Recommended)\n\nMarmot's SQLite files are standard WAL-mode databases, compatible with [Litestream](https://litestream.io/):\n\n```bash\nlitestream replicate /path/to/marmot-data/*.db s3://bucket/backup\n```\n\n### Option 2: CDC to External Storage\n\nEnable CDC publisher to stream changes to Kafka/NATS, then archive to your preferred storage.\n\n### Option 3: Filesystem Snapshots\n\nSince Marmot uses SQLite with WAL mode, you can safely snapshot the data directory during operation.\n\n## Stargazers over time\n[![Stargazers over time](https://starchart.cc/maxpert/marmot.svg?variant=adaptive)](https://starchart.cc/maxpert/marmot)\n\n## FAQs \u0026 Community\n\n - For FAQs visit [this page](https://maxpert.github.io/marmot/intro#faq)\n - For community visit our [discord](https://discord.gg/AWUwY66XsE) or discussions on GitHub\n","funding_links":[],"categories":["SQLite tools","Tools/Plugin","Go","backup and replicate","Built on top of NATS and JetStream"],"sub_categories":["Extended SQLite","Community Clients"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxpert%2Fmarmot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaxpert%2Fmarmot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaxpert%2Fmarmot/lists"}