Install Ferrosa and run a single-node instance in minutes. Everything you need to connect existing CQL drivers and start querying.
# Start with defaults — CQL on :9042, web console on :9090 ./target/release/ferrosa
FERROSA_GRAPH_ENABLED=true ./target/release/ferrosa
FERROSA_CQL_BIND=127.0.0.1:9042 \ FERROSA_WEB_BIND=127.0.0.1:9090 \ FERROSA_AUTH_DISABLED=true \ FERROSA_GRAPH_ENABLED=true \ ./target/release/ferrosa
FERROSA_SEED configuration. Full cluster formation (3+ nodes)
uses Raft consensus and a Murmur3 token ring; there is no separate
FERROSA_CLUSTER_MODE switch.
| Service | Port | Protocol |
|---|---|---|
| CQL Server | 9042 | CQL native protocol v5 |
| Web Console | 9090 | HTTP (REST API + static UI + WebSocket) |
| Graph HTTP | 7474 | HTTP/JSON (Cypher queries) |
| Prometheus Metrics | 9090/metrics | HTTP (text exposition) |
| Internode (cluster) | 7000 | Custom binary protocol |
Ferrosa speaks CQL protocol v5 — the same wire protocol as Apache Cassandra. Any standard CQL driver works unchanged.
from cassandra.cluster import Cluster cluster = Cluster(['127.0.0.1']) session = cluster.connect() # Create a keyspace session.execute(""" CREATE KEYSPACE demo WITH replication = { 'class': 'SimpleStrategy', 'replication_factor': 1 } """) # Create a table session.execute(""" CREATE TABLE demo.users ( user_id uuid PRIMARY KEY, name text, email text ) """) # Insert and query session.execute(""" INSERT INTO demo.users (user_id, name, email) VALUES (uuid(), 'Alice', 'alice@example.com') """) rows = session.execute("SELECT * FROM demo.users") for row in rows: print(row.name, row.email)
// Same DataStax Java driver — just change the contact point CqlSession session = CqlSession.builder() .addContactPoint(new InetSocketAddress("127.0.0.1", 9042)) .withLocalDatacenter("datacenter1") .build(); ResultSet rs = session.execute("SELECT * FROM demo.users");
cluster := gocql.NewCluster("127.0.0.1") session, _ := cluster.CreateSession() defer session.Close() var name, email string session.Query("SELECT name, email FROM demo.users LIMIT 1").Scan(&name, &email)
# Standard cqlsh connects directly cqlsh 127.0.0.1 9042
There is no extra Ferrosa-only client bootstrap ritual beyond normal Cassandra-compatible CQL behavior. A standard driver connects like this:
OPTIONS and read SUPPORTED.STARTUP, including compression preferences if the driver uses them.AUTHENTICATE → AUTH_RESPONSE → AUTH_SUCCESS. If auth is disabled, the server replies with READY.READY or AUTH_SUCCESS, the driver introspects system.local, system.peers, and system.peers_v2 to learn tokens, schema version, and peer CQL endpoints.In other words, topology discovery happens after the normal CQL handshake. If a client authenticates successfully and then stalls while dialing bad peer addresses, that is a topology advertisement problem, not a missing handshake step.
OPTIONS payload, startup flag, or Ferrosa-only command to request public addresses. External versus internal peer addresses are chosen automatically from the server side based on the source IP of the connection that is querying system.local / system.peers / system.peers_v2.
Ferrosa supports strict serializable distributed transactions via the Accord protocol. Lightweight transactions (LWT) work with any CQL driver:
# Conditional insert — only succeeds if the row doesn't exist session.execute(""" INSERT INTO demo.users (user_id, name, email) VALUES (uuid(), 'Bob', 'bob@example.com') IF NOT EXISTS """) # Compare-and-set update session.execute(""" UPDATE demo.users SET email = 'newemail@example.com' WHERE user_id = ? IF email = 'bob@example.com' """, [user_id]) # Multi-partition transaction session.execute(""" BEGIN TRANSACTION UPDATE accounts SET balance = balance - 100 WHERE id = 'acct-1' IF balance >= 100; UPDATE accounts SET balance = balance + 100 WHERE id = 'acct-2'; COMMIT TRANSACTION """)
Ferrosa is configured via environment variables and an optional TOML file. Environment variables override file values. The tables below cover the customer-facing runtime variables currently honored by the server.
| Variable | Default | Description |
|---|---|---|
FERROSA_CONFIG | /etc/ferrosa/ferrosa.toml | Optional TOML config file loaded at startup |
FERROSA_MODE | development | Deployment mode used for production-safety checks |
FERROSA_HOST_ID | auto-generated | Stable node UUID persisted under the data directory |
FERROSA_CQL_BIND | 0.0.0.0:9042 | CQL server bind address |
FERROSA_CQL_BROADCAST | bind address/port | Public CQL address advertised to clients via system.local / system.peers_v2 |
FERROSA_CQL_INTERNAL_CLIENT_CIDRS | — | Comma-separated CIDRs whose clients should receive the internal topology view instead of the public CQL broadcast |
FERROSA_CQL_MAX_CONNECTIONS | 1024 | Maximum concurrent CQL connections |
FERROSA_CQL_MAX_CONNECTIONS_PER_IP | 64 | Per-client-IP connection cap |
FERROSA_CQL_TLS_CERT | — | PEM certificate path for CQL TLS |
FERROSA_CQL_TLS_KEY | — | PEM private-key path for CQL TLS |
FERROSA_CQL_REQUIRE_TLS | false | Fail startup if the CQL listener does not have TLS configured |
FERROSA_WEB_BIND | 0.0.0.0:9090 | Web console bind address |
FERROSA_AUTH_ENABLED | false | Enable CQL role auth and permission enforcement |
FERROSA_AUTH_WARN | false | Soak mode: log auth failures as warnings while still allowing requests |
FERROSA_AUTH_DISABLED | false | Development escape hatch that skips the CQL SASL handshake entirely |
FERROSA_SUPERUSER_PASSWORD | — | Optional override for the seeded ferrosa_admin password during bootstrap |
FERROSA_GRAPH_ENABLED | false | Enable graph engine + HTTP endpoint |
FERROSA_BOLT_PORT | 7687 | Bolt listener port when the graph engine is enabled |
FERROSA_SPARQL_ENABLED | false | Enable the SPARQL HTTP endpoint |
FERROSA_SPARQL_BIND | 0.0.0.0:8080 | SPARQL HTTP listen address |
RUST_LOG | info | Tracing filter (e.g. debug, ferrosa_cql=trace) |
| Variable | Default | Description |
|---|---|---|
FERROSA_CLUSTER_NAME | ferrosa | Cluster name (must match across nodes) |
FERROSA_SEED | — | Comma-separated seed addresses; each entry may be an IP:port or resolvable hostname:port |
FERROSA_INTERNODE_BIND | 0.0.0.0:7000 | Internode protocol bind address |
FERROSA_INTERNODE_BROADCAST | 127.0.0.1:7000 | Address other nodes use to reach this node; also forms the default internal topology view. May be an IP:port or resolvable hostname:port. |
FERROSA_INTERNODE_PSK | — | Pre-shared key for internode auth (HMAC-SHA256) |
FERROSA_INTERNODE_TLS_CERT | — | PEM certificate path for internode TLS |
FERROSA_INTERNODE_TLS_KEY | — | PEM private-key path for internode TLS |
FERROSA_INTERNODE_TLS_CA | — | CA certificate for mutual-TLS internode verification |
FERROSA_INTERNODE_REQUIRE_TLS | false | Fail startup if internode TLS is required but not configured |
FERROSA_HEARTBEAT_INTERVAL_MS | 500 | Heartbeat interval between peers |
FERROSA_HEARTBEAT_TIMEOUT_MS | 1500 | Timeout before marking a peer as suspected down |
FERROSA_MAX_INTERNODE_CONNECTIONS | 512 | Maximum concurrent internode connections |
FERROSA_HANDSHAKE_TIMEOUT_SECS | 5 | Internode handshake timeout |
FERROSA_MAX_FRAME_BODY_SIZE | 268435456 | Maximum internode frame size in bytes |
FERROSA_MAX_STREAMS_PER_LANE | 128 | Maximum concurrent streams per internode connection lane |
FERROSA_DATA_CENTER | datacenter1 | Data center name |
FERROSA_RACK | rack1 | Rack name within the data center |
FERROSA_NUM_TOKENS | 256 | Number of tokens on the ring |
FERROSA_DEFAULT_CL | QUORUM | Default consistency level |
FERROSA_AUTO_JOIN | true | Automatically join the ring on startup (dev mode) |
FERROSA_HINTED_HANDOFF_DIR | data/hints | Local directory for hinted handoff files |
FERROSA_HINTED_HANDOFF_MAX_MB | 1024 | Max disk space for hinted handoff per peer (MB) |
FERROSA_FORMATION_TIMEOUT_SECS | 60 | Maximum time to wait in forming state before falling back |
FERROSA_NODE_ROLE | both | Node role: data, indexer, or both |
FERROSA_RAFT_HEARTBEAT_MS | 300 | Raft heartbeat interval |
FERROSA_RAFT_ELECTION_MIN_MS | 3000 | Lower bound for Raft election timeout |
FERROSA_RAFT_ELECTION_MAX_MS | 6000 | Upper bound for Raft election timeout |
FERROSA_CLOCK_MAX_SKEW_SECS | 5 | Maximum tolerated clock skew for Accord timestamp validation |
| Variable | Default | Description |
|---|---|---|
FERROSA_DATA_DIR | /var/lib/ferrosa | Local data directory for SSTables and commit log |
FERROSA_CACHE_MAX_BYTES | 10737418240 | Local LRU cache size (10 GB) |
FERROSA_FLUSH_THRESHOLD_BYTES | 67108864 | Memtable flush threshold (64 MB) |
FERROSA_FLUSH_MAX_AGE_SECS | 30 | Flush memtables after this age even if the size threshold is not reached |
FERROSA_FLUSH_INTERVAL_SECS | 30 | Background maintenance interval for flush and schema sync |
FERROSA_WRITE_VERIFY | true | Verify committed writes during the storage pipeline |
FERROSA_S3_ENDPOINT | — | S3-compatible endpoint URL |
FERROSA_S3_BUCKET | — | S3 bucket for durable storage |
FERROSA_S3_REGION | us-east-1 | AWS region for S3 |
FERROSA_S3_ACCESS_KEY_ID | — | Access key (falls back to instance profile) |
FERROSA_S3_SECRET_ACCESS_KEY | — | Secret access key |
FERROSA_S3_ALLOW_HTTP | false | Allow non-TLS for local dev (MinIO) |
FERROSA_S3_PREFIX | — | Key prefix for multi-tenant separation |
FERROSA_S3_UPLOAD_QUEUE_DEPTH | 16 | Upload backpressure queue depth |
FERROSA_ARCHIVE_ENABLED | false | Enable commit-log archiving |
FERROSA_ARCHIVE_POLL_INTERVAL_SECS | 5 | Archive poll interval in seconds |
FERROSA_ARCHIVE_RETENTION_DAYS | 7 | Retention period for archived commit-log segments |
| Variable | Default | Description |
|---|---|---|
FERROSA_INDEX_BACKEND | local | Secondary-index backend: local, remote, or off |
FERROSA_INDEX_SIDECAR_ENDPOINTS | — | Comma-separated remote index sidecar URLs when FERROSA_INDEX_BACKEND=remote |
FERROSA_INDEX_SIDECAR_TIMEOUT_MS | 30000 | Per-request timeout for the remote index sidecar |
FERROSA_INDEX_SIDECAR_MAX_RETRIES | 2 | Retries per sidecar endpoint |
FERROSA_INDEX_CB_THRESHOLD | 5 | Circuit-breaker failure threshold for remote indexing |
FERROSA_INDEX_CB_RECOVERY_MS | 60000 | Circuit-breaker recovery interval in milliseconds |
| Variable | Default | Description |
|---|---|---|
FERROSA_TELEMETRY_ENABLED | false | Enable sampled tracing telemetry in the observability stack |
FERROSA_TELEMETRY_SAMPLE_RATE | 0.01 | Fraction of spans to sample when telemetry is enabled |
FERROSA_DEBUG_AUTH_TOKEN | — | Bearer token protecting the debug endpoints |
FERROSA_DEBUG_IP_WHITELIST | — | Comma-separated IP allowlist for debug endpoints |
Size-Tiered Compaction is the default strategy when no per-table configuration is specified.
| Variable | Default | Description |
|---|---|---|
FERROSA_COMPACTION_MIN_THRESHOLD | 4 | Min SSTables to trigger compaction |
FERROSA_COMPACTION_MAX_THRESHOLD | 32 | Max SSTables per compaction task |
FERROSA_COMPACTION_BUCKET_LOW | 0.5 | Lower size ratio for bucket membership |
FERROSA_COMPACTION_BUCKET_HIGH | 1.5 | Upper size ratio for bucket membership |
Unified Compaction Strategy (Cassandra 5.0) uses density-based levels with a configurable fan factor. Configure per-table via DDL:
CREATE TABLE ks.events (id uuid PRIMARY KEY, data text) WITH compaction = { 'class': 'UnifiedCompactionStrategy', 'fan_factor': '4' };
| Parameter | Default | Description |
|---|---|---|
fan_factor | 4 | SSTables per level before compaction triggers (W=2 aggressive, W=32 lazy) |
min_sstable_size | 104857600 | Minimum SSTable size (bytes) for density calculation |
Admin CLI for querying and monitoring a running Ferrosa node.
# Run a CQL query ferrosa-ctl query "SELECT * FROM demo.users" # Describe schema ferrosa-ctl describe # Show Prometheus metrics ferrosa-ctl metrics # Node health status ferrosa-ctl status # Active CQL connections ferrosa-ctl connections --sort client_addr # Currently running queries (long-running only) ferrosa-ctl queries --long-running # Storage engine stats ferrosa-ctl storage # Live TUI dashboard (ratatui) ferrosa-ctl monitor # Cluster management ferrosa-ctl add-node 10.0.0.5:7000 # Pre-approve a node for cluster join ferrosa-ctl decommission 10.0.0.3:7000 # Gracefully remove a node ferrosa-ctl ring # Show token ring distribution ferrosa-ctl rebalance # Trigger token rebalancing
The monitor command launches a terminal dashboard with 5 panels showing connections, active queries, storage stats, and more. Navigate with arrow keys, refresh is automatic. Connect to a specific host with --host 10.0.0.1:9042.
/metrics endpoint is available on the web console port (default 9090) without authentication. Scrape it directly from Prometheus or any compatible monitoring system.
Ferrosa is composed of 12 independent Rust crates that can be used together or embedded individually.
Shared types: Token, PartitionKey, DecoratedKey, CellValue, Murmur3 partitioner.
Read/write BTI SSTables. On-disk trie, Bloom filter, LZ4/Zstd compression.
Memtable, commit log with CDC, STCS + UCS compaction, S3 upload manager, NVMe pinning, local LRU cache.
Schema registry with ArcSwap, RBAC auth, column-level permissions, audit logging.
Secondary index framework: B-tree, hash, composite, phonetic, vector (HNSW + IVFFlat). Async builds, staleness tracking.
CQL v5 framing, LL(2) parser, prepared cache, LZ4/Snappy compression, SUBSCRIBE.
WebAssembly UDF executor: WIT contract, Wasmtime sandbox, per-function CPU/memory limits. In development.
Cypher parser, logical/physical planner, expand executor, HTTP/JSON endpoint.
Internode protocol: 3 priority lanes, PSK handshake, RPC, heartbeat failure detection.
Pair mode HA, Raft consensus (openraft + sled), token ring, coordinator with tunable CL.
CLI admin tool: query, describe, metrics, monitor (ratatui TUI dashboard).
Composes all crates. CQL on :9042, graph on :7474, web console on :9090 (auth + WebSocket).
When S3 is configured as the durable backend, Ferrosa writes asynchronously to S3 via an upload queue. If S3 becomes temporarily unreachable:
Ferrosa persists all schema changes (keyspaces, tables, indexes, types, functions) to local disk on every flush and to S3 during periodic sync. When a node restarts — even after a binary upgrade with preserved data — the schema is restored automatically. No manual DDL replay is needed.
The storage pipeline handles compaction end-to-end:
Three-node cluster formation uses Raft consensus for metadata and tunable consistency for data. Current limitations:
ferrosa-ctl
for manual recovery.Use two address families:
FERROSA_CQL_BROADCAST should point at the host- or externally-reachable CQL endpoint that off-node clients should dial after topology discovery.FERROSA_INTERNODE_BROADCAST should point at the address other Ferrosa nodes use to reach this node. Ferrosa also uses this address family as the internal topology view. Use either a stable IP:port or a hostname:port that resolves correctly from the other nodes.FERROSA_CQL_INTERNAL_CLIENT_CIDRS should list the container or pod CIDRs whose clients need the internal topology view.Clients whose source IP falls inside FERROSA_CQL_INTERNAL_CLIENT_CIDRS receive internal addresses from system.local and system.peers_v2. Other clients receive the public CQL broadcast address.
There is no client-side override for this selection. If an outside client is being handed internal addresses, the fix is to correct Ferrosa's advertised public endpoint or the CIDR classification, not to change the driver's handshake messages.
FERROSA_CQL_BIND=0.0.0.0:9042 FERROSA_CQL_BROADCAST=127.0.0.1:19042 FERROSA_INTERNODE_BROADCAST=10.89.1.48:7000 FERROSA_CQL_INTERNAL_CLIENT_CIDRS=10.89.0.0/16
In that setup, host-side drivers reconnect to 127.0.0.1:19042 after topology discovery, while clients running on the Podman network see 10.89.x.x:9042.
Not by itself. Cassandra-compatible topology tables expose inet columns, so drivers ultimately consume concrete IP addresses after topology discovery. Split DNS helps only if both environments can converge on the same reachable advertised IP, such as a shared VIP or proxy.
For the full architecture spec, see the the project repository.
Step-by-step walkthroughs for IoT, analytics, e-commerce, and 10 more use cases — with runnable CQL scripts.
Full reference of supported CQL statements, types, and functions.
Move from Apache Cassandra to Ferrosa without rewriting your application.