Skip to content

Configuration

A node is configured in three layers. Defaults, then the TOML file given by --config, then MINK_* environment variables, then mink serve flags. A later layer wins.

bash
mink serve \
    --config /etc/mink/mink.toml \
    --node-id 2 \
    --listen 0.0.0.0:9123 --advertise grpc://node2.internal:9123 \
    --kafka-listen 0.0.0.0:9092 --kafka-advertise node2.internal:9092 \
    --meta-url postgres://user:pass@pg:5432/mink \
    --storage-uri "-2@s3://bucket?region=us-east-1"

Environment variables map to top-level keys: MINK_NODE_ID, MINK_META_URL, MINK_STORAGE_URI. The [kafka] and [lake] tables are set in the file, and the two Kafka listener keys also have flags. Unknown keys fail startup. mink cluster config prints the effective result with secrets redacted.

Identity

KeyFlagDefaultPurpose
node_id--node-id1Stable identity in the cluster. Unique per node
cluster_id--cluster-idminkPrefix for WAL objects and the Kafka cluster id. The same on every node
data_dir--data-dir./dataLocal working directory. KV tablet stores and caches, all rebuildable from object storage

The node epoch is not configured. Every start takes a clock-derived epoch higher than the last, which fences whatever the previous process left in flight.

Listeners

KeyFlagDefaultPurpose
listen--listen127.0.0.1:9123Arrow Flight bind address
advertise--advertisegrpc://127.0.0.1:9123The address clients and other nodes use to reach this node
kafka.listen--kafka-listen127.0.0.1:9092Kafka bind address. The listener runs only when [kafka] is present or a Kafka flag is given
kafka.advertise--kafka-advertiseThe bind addresshost:port returned to Kafka clients in metadata

advertise and kafka.advertise are registered in the metadata log and handed to clients as is, in Flight redirects and in Kafka metadata responses. They have to be reachable addresses, not bind addresses. The defaults work for a single node on one host.

Note

There is no authentication. Listeners bound beyond 127.0.0.1 are expected on a private network.

Metadata

meta_url selects the SQL database that holds the metadata log.

bash
--meta-url sqlite::memory:                     # tests, throwaway
--meta-url sqlite:./data/meta.db               # single node
--meta-url postgres://user:pass@pg:5432/mink   # cluster

SQLite is a file, so one node. A cluster needs Postgres, and every node points at the same database. That is the membership mechanism. See Metadata.

Storage

storage_uri is the data bucket as id@uri. The id is the s3stream bucket id, any stable integer.

bash
--storage-uri "-2@file://./data/objects"
--storage-uri "-2@s3://bucket?region=us-east-1"
--storage-uri "-2@s3://mink?region=us-east-1&endpoint=http://rustfs:9000&pathStyle=true"
URI parameterPurpose
regionS3 region
endpointS3 compatible stores such as RustFS or MinIO
pathStyle=truePath-style addressing for stores that need it
s3Express=trueForce S3 Express One Zone session auth. Detected automatically from the --x-s3 bucket suffix

Credentials come from the standard AWS_* environment variables.

wal_uri (--wal-uri) puts the WAL in its own bucket. Unset, the WAL is the data bucket under id minus one. The WAL URI takes the batching parameters covered in Tuning.

Background work

KeyDefaultPurpose
wal_upload_interval10sHow often buffered WAL is packed into stream objects
kv_snapshot_interval10mHow often a primary-key bucket checkpoints its KV store to object storage
snapshots_retained1KV snapshots kept per bucket
log_retention_interval5mHow often bucket leaders trim logs past log_ttl
lease_ttl30sHeartbeat and coordinator lease TTL. Renewed every quarter of it
coordinator_tick5sCoordinator loop: re-lead orphans, prune snapshots, expire offsets, schedule tiering
tiering_poll_interval30sHow often an idle tiering worker asks for work
default_bucket_count1Buckets for a table created without --bucket-count
producer_offsets_ttl24hHow long a sink's registered offsets live
producer_offsets_cleanup_interval1hHow often expired producer offsets are swept

Durations are humantime: 500ms, 10s, 5m, 2h, 7d.

Kafka

toml
[kafka]
listen = "0.0.0.0:9092"
advertise = "node2.internal:9092"
database = "kafka"
default_partitions = 3
KeyDefaultPurpose
databasekafkaThe database topics live in as log tables
auto_create_topicstrueCreate a topic on first produce or fetch
default_partitions1Buckets for an auto-created topic
max_request_bytes100 MiBLargest request accepted
max_fetch_bytes50 MiBCap on one fetch response
max_in_flight64Requests in flight per connection
max_wait30sLongest a fetch waits for data
min_session_timeout6sConsumer group session bounds
max_session_timeout30m
group_offsets_ttl7dHow long committed group offsets outlive the group

The topic to table mapping and request coverage are in Protocols.

Lake

toml
[lake]
format = "iceberg"
catalog = "rest"
uri = "http://iceberg-rest:8181"
warehouse = "s3://mink-lake"

[lake.properties]
"s3.endpoint" = "http://rustfs:9000"
"s3.region" = "us-east-1"
"s3.path-style-access" = "true"
"s3.access-key-id" = "mink"
"s3.secret-access-key" = "minkminkmink"
KeyValuesPurpose
formaticebergLake format
catalogrest, memoryrest is the Iceberg REST catalog at uri. memory is for tests
uriCatalog endpoint
warehouseRoot location for table data
propertiesPassed to iceberg-rust as catalog and file IO properties

Without [lake] the node accepts no table with --lake. With it, only tables created with --lake iceberg are tiered. mink-query takes the same block in its own config file. See Tiering.

Example

The file the compose stacks mount at /etc/mink/mink.toml, with the intervals the harness shortens for tests:

toml
wal_upload_interval = "1s"
kv_snapshot_interval = "2s"
log_retention_interval = "1m"
lease_ttl = "10s"
coordinator_tick = "1s"
tiering_poll_interval = "2s"
default_bucket_count = 3

[lake]
format = "iceberg"
catalog = "rest"
uri = "http://iceberg-rest:8181"
warehouse = "s3://mink-lake"

[lake.properties]
"s3.endpoint" = "http://rustfs:9000"
"s3.region" = "us-east-1"
"s3.path-style-access" = "true"
"s3.access-key-id" = "mink"
"s3.secret-access-key" = "minkminkmink"

[kafka]
listen = "0.0.0.0:9092"
database = "kafka"
default_partitions = 3

Identity, listeners, metadata and storage come from the environment in that stack, so one file serves every node.