Server configuration reference

The laminardb binary reads a TOML configuration file, constructs streaming pipelines, and serves a REST API with Prometheus metrics. Server and checkpoint defaults are checked against server config and engine config. This page describes the v0.33.0 release. Last updated: 19 October 2026.

Install

v0.33.0 is the current release. Release assets are named laminardb-server-<target>-v0.33.0.tar.gz (.zip for x86_64-pc-windows-msvc) for the targets x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, x86_64-unknown-linux-musl, x86_64-apple-darwin, aarch64-apple-darwin and x86_64-pc-windows-msvc, with a SHA256SUMS file. The commands below link to the v0.33.0 tag so that they keep working after later releases.

Install & run
BASE=https://github.com/laminardb/laminardb/releases/download/v0.33.0
curl -fLO "$BASE/SHA256SUMS"

# Linux x86_64
ASSET=laminardb-server-x86_64-unknown-linux-gnu-v0.33.0.tar.gz
curl -fLO "$BASE/$ASSET"
grep " $ASSET\$" SHA256SUMS | sha256sum -c -
tar xzf "$ASSET"

# macOS Apple silicon (instead of the Linux lines above)
ASSET=laminardb-server-aarch64-apple-darwin-v0.33.0.tar.gz
curl -fLO "$BASE/$ASSET"
grep " $ASSET\$" SHA256SUMS | shasum -a 256 -c -
tar xzf "$ASSET"

# Run the binary (the archive contains the laminardb executable)
./laminardb --config laminardb.toml

# Or build from a source checkout (laminar-server is not published to crates.io)
git clone https://github.com/laminardb/laminardb.git
cd laminardb && git checkout v0.33.0
cargo build --release -p laminar-server
./target/release/laminardb --config laminardb.toml

# Or Docker. The image's built-in config binds 0.0.0.0:8080 and reads
# console_token from LAMINAR_CONSOLE_TOKEN; the server exits without it.
export LAMINAR_CONSOLE_TOKEN="$(openssl rand -hex 32)"
docker run -d -p 8080:8080 -e LAMINAR_CONSOLE_TOKEN \
  -v laminardb-data:/var/lib/laminardb \
  laminardb/laminardb-server:0.33.0

The image runs laminardb --config /etc/laminardb/laminardb.toml. To add pipelines, mount your own file there (for example -v "$PWD/laminardb.toml:/etc/laminardb/laminardb.toml:ro"); keep a console_token, because a container needs a non-loopback bind. Its built-in checkpoint directory is file:///var/lib/laminardb/checkpoints. The image sets LAMINAR_DISABLE_FILE_WATCH=1, so call POST /api/v1/reload after editing a mounted config. The :latest and :main tags are rolling builds of the main branch, not releases.

CLI Options

laminardb --help (v0.33.0, options)
  -c, --config <CONFIG>            [default: laminardb.toml]
      --log-level <LOG_LEVEL>      [default: info]
      --admin-bind <ADMIN_BIND>
      --pgwire-bind <PGWIRE_BIND>  Postgres wire bind address (e.g. `127.0.0.1:5433`). Wildcard binds rejected
FlagDefaultDescription
-c, --config <CONFIG>laminardb.tomlTOML configuration file path
--log-level <LOG_LEVEL>infotrace, debug, info, warn, error
--admin-bind <ADMIN_BIND>Overrides [server].bind from the config file
--pgwire-bind <PGWIRE_BIND>Postgres wire bind address (e.g. 127.0.0.1:5433); overrides [server].pgwire_bind, and an empty value disables the listener. A non-loopback address (including 0.0.0.0) is refused unless pgwire_users, pgwire_tls_cert/pgwire_tls_key and pgwire_allow_remote = true are configured.

[server]

The [server] section configures standalone server behaviors, control plane security, and pgwire network parameters.

FieldTypeDefaultDescription
mode String "single" Run mode. "single" (standalone) or "cluster" (multi-node deployment).
key_groups u16 256 Stable hash partitions for keyed aggregates and interval joins. A single-node runtime owns all of them; a cluster distributes them across nodes.
bind String "127.0.0.1:8080" HTTP API and Console UI control-plane bind address.
delivery String "at_least_once" Pipeline-wide contract: best_effort, at_least_once, or exactly_once. Connector protocols are derived from this value; sinks do not have an independent delivery setting.
incremental_emit bool true Query execution policy: emit dirty-only changelog updates for keyed non-windowed running aggregates.
datafusion_memory_limit_bytesusize268435456 (256 MiB)Shared participating DataFusion reservations per DB/node. Positive value required; direct Arrow allocations and process RSS are outside this limit.
source_queue_max_bytesusize67108864 (64 MiB)Shared connector-to-coordinator Arrow queue, including parked input. Each source's waiting batch must fit; value must be positive and at most the engine's MAX_SOURCE_QUEUE_BYTES.
pipeline_max_input_buf_batchesusize256Batch count per graph input port. Set to 0 to disable the count limit.
pipeline_max_input_buf_bytesusizeunsetOptional retained Arrow-byte limit per graph input port; must be positive when set. Fan-out is charged at each port.
pipeline_max_managed_state_bytesusize268435456 (256 MiB)Pipeline-wide charged-byte budget for managed operator state (keyed aggregates, windows, joins). Must be positive when set.
temporal_join_idle_history_retentionDurationunsetRight-side version history kept while a temporal join input is idle. Required when a pipeline contains a temporal join; at least 1ms.
source_idle_timeoutDurationunsetMarks inactive watermarked sources and input channels idle after this duration. Unset disables idle detection; at least 1ms when set.
event_time_max_future_skewDuration"5m"Event timestamps further ahead of the wall clock than this do not advance source watermarks. 0 disables the guard.
drain_timeoutDurationcheckpoint.timeout + 30sCluster graceful-shutdown allowance for the leader to checkpoint and reassign this node's key groups. Must be at least checkpoint.timeout.
reference_table_max_rowsusize1000000Positive live-row limit for each local reference table.
reference_table_max_bytesusize268435456 (256 MiB)Positive retained-memory charge limit for each local reference table. Cluster reference-table plans are restricted.
materialized_view_max_rowsusize1000000Positive live-row limit for each local materialized view.
materialized_view_max_bytesusize268435456 (256 MiB)Positive retained-memory charge limit for each local materialized view. Cluster materialized views are rejected.
console_token String none Bearer token for the /api/v1/* and /ws/{name} routes; at least 8 characters. Required when bind is not a loopback address; omitting it on a loopback bind leaves those routes open for local development.
diagnostic_read_token String none Cluster only. Read-only bearer token for the local diagnostic routes. Requires a loopback bind and a console_token; both tokens must then be different, unpadded base64url encodings of exactly 32 bytes (43 characters).
console_cors_allowed_origins Array<String> none CORS origins allowed to call the control-plane REST/WS endpoints. When unset, the CORS policy is permissive (development only).
pgwire_bind String none Postgres wire protocol listener bind address. Disabled if omitted.
pgwire_allow_remote bool false Must be true to bind pgwire on a non-loopback address. A remote bind also requires pgwire_users (trust auth never leaves loopback) and pgwire_tls_cert + pgwire_tls_key.
pgwire_users Map {} Usernames mapped to a plain-text password (at least 12 characters) or a pre-hashed md5 + 32 lowercase hex value. Non-empty enables MD5 auth; empty means trust auth on loopback only.
pgwire_tls_cert String none Path to PEM certificate file for pgwire TLS; set together with pgwire_tls_key. The listener watches the cert, key and client-CA files and reloads them on change.
pgwire_tls_key String none Path to PEM private key (PKCS#8/RSA) for pgwire TLS.
pgwire_tls_client_ca String none Path to PEM CA bundle; every client must present a certificate chained to it (mTLS). Requires pgwire_tls_cert + pgwire_tls_key.
pgwire_max_connections usize 256 Concurrent pgwire session cap; extra connections are closed immediately. Must be greater than 0.
pgwire_max_auth_failures_per_min u32 10 Failed authentications per IP per minute before temporary ban. 0 disables.
pgwire_tls_min_version String "1.2" Minimum TLS protocol version ("1.2" or "1.3").

Top-Level Config Keys:

FieldTypeDefaultDescription
node_id String none Unique node identity. Required when mode = "cluster": startup fails without it (there is no hostname fallback). Changing it requires a restart.
sql String none Raw SQL DDL executed before the pipeline starts, as an alternative to the structured sections. Changing it requires a restart.

Other top-level sections: [supervision] (see Supervision), [[process_function]] (supervised Python workers; see the server README), and [ai] / [models.<name>] (see AI functions). Unknown keys in [server], [checkpoint], [discovery], [[source]], [[sink]] and at the top level are rejected at startup.

laminardb.toml
[server]
mode = "single"
bind = "0.0.0.0:8080"
console_token = "${LAMINAR_CONSOLE_TOKEN}"   # required for a non-loopback bind
pgwire_bind = "127.0.0.1:5433"

Cluster Config

When mode = "cluster", the server requires a unique top-level node_id, a [discovery] section with seeds, and a [checkpoint] url on shared object storage (s3://, gs:// or az://). Cluster coordination uses that checkpoint store; there is no separate [coordination] section or embedded Raft service.

Cluster mode rejects best_effort delivery, local paths and file:// checkpoints (peers cannot recover them), and a checkpoint.interval below 100ms. At-least-once requires replayable input and a durable sink. Cluster exactly-once supports Kafka input with either an S3/S3A append-mode Delta Lake sink or a REST-catalog Iceberg append sink backed by S3/S3A storage.

[discovery]

FieldTypeDefaultDescription
strategy String required Discovery mode. Either "gossip" (Chitchat p2p membership) or "static".
seeds Array<String> [] Gossip address of every node, including this one (e.g. ["10.0.0.1:7946", "10.0.0.2:7946"]). Required for both strategies; expected membership is derived from it.
gossip_port u16 7946 Gossip communications port.
advertise_host String none Host or IP this node advertises to peers. If unset: the host part of [server].bind; for a wildcard bind (0.0.0.0, ::) the machine hostname; 127.0.0.1 only if the hostname is empty.
failure_domain String none Locality gossiped to peers for placement: a flat label ("rack17") or coarsest-first ;-separated tiers ("region=...;zone=...;rack=...").
placement_isolation_tier usize 0 Locality tier the placement and blast-radius metrics group by (0 = coarsest).
cluster_tls_cert Path none PEM certificate for control-plane (barrier/shuffle) mTLS.
cluster_tls_key Path none PEM private key paired with cluster_tls_cert.
cluster_tls_client_ca Path none CA that signed every peer certificate; verifies both directions.
cluster_tls_server_name String none DNS SAN present in every node certificate, checked on connect (peers dial by IP).

The four cluster_tls_* keys must be set together and each path must exist. They do not protect the HTTP API; terminate HTTP TLS at a trusted proxy.

laminardb.toml (Cluster sections)
node_id = "node-1"   # unique per node

[server]
mode = "cluster"
bind = "0.0.0.0:8080"
console_token = "${LAMINAR_CONSOLE_TOKEN}"   # required for a non-loopback bind
delivery = "at_least_once"
key_groups = 256

[checkpoint]
url = "s3://my-bucket/laminardb/cluster-checkpoints"   # shared by every node

[discovery]
strategy = "gossip"
gossip_port = 7946
advertise_host = "10.0.0.1"
seeds = ["10.0.0.1:7946", "10.0.0.2:7946"]

[checkpoint]

FieldTypeDefaultDescription
url String OS temp/laminardb Checkpoint storage URL: absolute file://, s3:///s3a://, gs:///gcs://, or az:///abfs[s]:///wasb[s]://. R2 and MinIO use s3:// with an endpoint option. Single-node at_least_once and exactly_once need a local file:// directory; an object-store URL there is rejected with [LDB-0014]. Cluster mode needs an object-store URL. Use an explicit durable location in production. On Windows, keep a file:// directory's path short (for example file:///C:/laminardb/checkpoints): checkpoint files sit several levels below it, and a path over the 260-character Windows limit stops the pipeline at its first checkpoint.
interval Duration "10s" Checkpoint interval; must be greater than zero. Supports humantime such as "10s", "1m", or "500ms". Cluster mode has a 100ms minimum.
timeout Duration "120s" One absolute end-to-end deadline spanning admission, alignment, capture, durable publication, and completion delivery.
storage Map {} Object-store options and credentials, for example aws_region, aws_access_key_id, aws_secret_access_key. Credentials can also come from the provider's standard environment.
max_node_data_bytes u64 536870912 (512 MiB) Maximum participant checkpoint data object and in-flight captured-state admission. Positive value required; separate from live-state limits.
Single-node checkpoint (at_least_once / exactly_once)
[checkpoint]
url = "file:///var/lib/laminardb/checkpoints"
interval = "30s"
S3 checkpoint (cluster mode, or single-node best_effort)
[checkpoint]
url = "s3://my-bucket/laminardb/checkpoints"
interval = "30s"

[checkpoint.storage]
aws_region = "us-east-1"
aws_access_key_id = "${AWS_ACCESS_KEY_ID}"
aws_secret_access_key = "${AWS_SECRET_ACCESS_KEY}"

A single-node server with replay-capable delivery and an s3://, gs:// or az:// checkpoint fails with [LDB-0014]: a shared cloud checkpoint namespace is not admitted for one node until its writer lease is term-fenced. A file:// checkpoint directory is locked by one process at a time.

Memory Limits & Production Tuning

The limits above are independent admission budgets. DataFusion reservations are shared per DB/node; source queue bytes are shared per DB/node; graph input limits apply to each port; reference-table and materialized-view limits apply to each local table or view. They do not sum to a process RSS cap. Connector decoding, direct Arrow allocations, managed state, query results, checkpoint encoding, restore staging and allocator overhead need separate headroom.

Managed operator working state has a default 256 MiB charged-byte budget. Set [server].pipeline_max_managed_state_bytes to change it (embedded users set the same field through LaminarConfig or the builder). Its charge is a lower bound, not total resident memory. Local reference tables and materialized views are unavailable in cluster plans that require distributed ownership.

Size a deployment

  1. Run representative peak input, bursts and a fault/restart cycle with the intended SQL and connectors. Measure peak process or container RSS, source lag and checkpoint duration.
  2. Observe /metrics: laminardb_cycles_backpressured_total, laminardb_input_buf_bytes, laminardb_managed_state_accounted_bytes, laminardb_checkpoint_size_bytes, laminardb_checkpoints_failed_total and laminardb_checkpoint_duration_seconds. Use OS or container RSS (on Linux, laminardb_process_resident_memory_bytes) for the actual memory envelope.
  3. Set container memory requests and limits above the measured peak with room for Arrow and connector scratch, simultaneous old/new state on recovery, and checkpoint capture. The Helm chart defaults to resources: {}; supply workload-specific values.
  4. If admission saturates, examine batch size and downstream throughput before raising queue or graph-port limits. A result too large for a port after execution faults the pipeline. Size checkpoint.max_node_data_bytes above the largest expected participant artifact and verify recovery at that setting.

checkpoint.interval defaults to 10s and trades replay work against checkpoint traffic. checkpoint.timeout defaults to 120s; keep it above observed attempt duration. Changes to [server] and [checkpoint] require a restart (see Hot Reload). The main I/O runtime uses TOKIO_WORKER_THREADS (logical CPU count by default), while streaming compute uses one dedicated thread. On the default non-MSVC build, jemalloc can be tuned with MALLOC_CONF at process start; see the allocator guidance.

Console UI & Control API

The Console UI is a web client that lets you inspect your streaming SQL nodes, view lineage dependency graphs, and author queries. You can access the hosted console at laminardb.github.io/laminardb-console-ui.

The console connects to the control-plane APIs exposed by the server. When server.console_token is configured, every /api/v1/* route and /ws/{name} requires it. /health, /ready and /metrics are always public so probes and scrapers work without a token; restrict network access to them.

Security & Gating

  • HTTP Authentication: Send the configured token in the header: Authorization: Bearer <token>. Duplicate or comma-joined bearer values are refused.
  • WebSocket Authentication: Browsers cannot set headers on the upgrade request, so /ws/{name} also accepts ?token=<token>. The query parameter is accepted only on /ws/ routes.
  • CORS Gating: Restrict client origins using the server.console_cors_allowed_origins array in TOML. Without it the CORS policy is permissive.
  • TLS: The HTTP listener has no TLS settings; terminate TLS at a trusted proxy.

Lineage Dependency Graph

GET /api/v1/graph returns the pipeline lineage (sources → streams → sinks). node_type is "Source", "Stream" or "Sink"; sql is the node's SQL or null.

GET /api/v1/graph Response Format
{
  "nodes": [
    { "name": "trades", "node_type": "Source", "sql": null },
    { "name": "vwap", "node_type": "Stream", "sql": "SELECT ..." },
    { "name": "trade_archive", "node_type": "Sink", "sql": null }
  ],
  "edges": [
    { "from": "trades", "to": "vwap" },
    { "from": "vwap", "to": "trade_archive" }
  ]
}

WebSocket Subscription Protocol

Connect to GET /ws/{name}, where name is a stream; add ?as_of_epoch=N to start from a committed epoch instead of the tail. Each text frame is a JSON object with a type. subscription_id is the stream name, and all counters are sent as decimal strings. Data frames carry at most 1 MiB, so a large batch is split across frames using row_offset/row_count:

{
  "type": "data",
  "subscription_id": "vwap",
  "data": [ { "symbol": "AAPL", "vwap": 178.5 } ],
  "sequence": "42",
  "log_sequence": "17",
  "row_offset": "0",
  "row_count": "1"
}

Other frame types: progress (epoch, checkpoint_id, log_sequence, through_log_sequence, sequence), gap (the subscriber lagged; code = subscription_lagged, skipped_messages) and error (code, message). In cluster mode data frames also carry stream_generation, partition, partition_sequence and committed_epoch. The server sends a WebSocket ping every 15 seconds and closes the connection after two unanswered pings. The server accepts at most 10,000 WebSocket connections.

Cluster Management Endpoints

  • GET /api/v1/cluster/nodes — Cluster members: id, name, rpc_address, state (Joining, Active, Suspected, Draining, Left), metadata and last_heartbeat_ms.
  • GET /api/v1/cluster/vnodes — The durable key-group (vnode) assignment snapshot.
  • GET /api/v1/cluster/leader — The current leader's node record (if known) and whether this node is the leader.
  • GET /api/v1/cluster/checkpoints — Latest checkpoint status; also available in single-node mode.
  • GET /api/v1/cluster/topology and /api/v1/cluster/topology/* — Durable catalog version, topology validation, migration submission, preparation, status and legacy-catalog adoption (see the REST table below).

/api/v1/cluster, /nodes, /vnodes and /leader return 404 when the server is not in cluster mode.

REST API

The server exposes an HTTP API on the configured bind address. All responses are JSON except /metrics (Prometheus text format). The first three routes are public; the rest require the console token when one is configured. Until startup completes, and in cluster mode during coordinated recovery, routes other than the three probes return 503.

Probes and metrics (public)

GET /health Liveness: 200 unless the pipeline is Stopped (then 503); includes version and pipeline state
GET /ready Readiness: 200 only while the pipeline is Running
GET /metrics Prometheus text metrics

Catalog, SQL and operations

GET /api/v1/sources List sources
GET /api/v1/sinks List sinks
GET /api/v1/streams List streams
GET /api/v1/streams/{name} Stream detail by name
GET /api/v1/mvs List materialized views
GET /api/v1/process-functions List the process functions bound at startup
GET /api/v1/connectors Registered source and sink connector types and their config keys
GET /api/v1/graph Lineage graph (nodes and edges)
POST /api/v1/sql Execute SQL or DDL
POST /api/v1/checkpoint Trigger a checkpoint; returns success, checkpoint_id, epoch, duration_ms
POST /api/v1/reload Re-read the config file and apply source/lookup/pipeline/sink changes
GET /ws/{name} WebSocket subscription to a stream

POST /api/v1/sql takes {"sql": "..."} and returns at most 1000 rows, stopping after a 5 second collection budget; a capped result sets "truncated": true. Use a WebSocket or pgwire subscription for unbounded results.

Pipeline control

POST /api/v1/pipeline/start Start the server's pipeline
POST /api/v1/pipeline/stop Stop the server's pipeline
GET /api/v1/pipeline/status pipeline_state, plus last_error after a fault

These routes act on the whole server, not on one named pipeline. In cluster mode, start and stop run locally first and are then forwarded to every peer; add ?local=true to act on this node only.

Cluster

GET /api/v1/cluster Mode, node ID and pipeline state (cluster mode only)
GET /api/v1/cluster/nodes Members and their state
GET /api/v1/cluster/vnodes Durable key-group assignment snapshot
GET /api/v1/cluster/leader Current leader and whether this node leads
GET /api/v1/cluster/checkpoints Latest checkpoint status (single-node too)
GET /api/v1/cluster/topology Durable catalog version and this process's activation evidence
POST /api/v1/cluster/topology/validate Validate a topology candidate without applying it
POST /api/v1/cluster/topology/operations Submit one atomic migration (operation UUID and expected parent version)
GET /api/v1/cluster/topology/operations/{operation_id} Migration status
POST /api/v1/cluster/topology/operations/{operation_id}/prepare Prepare an admitted migration on this participant
POST /api/v1/cluster/topology/adopt Adopt an existing legacy catalog after a coordinated upgrade
GET /api/v1/cluster/local-evidence Bounded authority evidence retained by this process
GET /api/v1/cluster/local-checkpoint-barrier-timings Bounded local checkpoint-barrier pause records

The two local-* diagnostic routes accept the console token or the read-only server.diagnostic_read_token, allow one request at a time (at most 8 starts per second), and are outside the console CORS policy. See the server README for the topology migration workflow.

Hot Reload

Edit the TOML config file while the server is running. A file watcher detects changes (500ms debounce, content-hash comparison), diffs the old and new configs, and applies incremental DDL. Or trigger manually via POST /api/v1/reload; a second reload while one is running returns 409.

What reloads without restart: [[source]], [[lookup]], [[pipeline]] and [[sink]] entries (add, remove, or change).

What requires restart: [server], [checkpoint], [discovery], [supervision], [[process_function]], [ai], [models], the top-level sql string and node_id. The reload response carries a warning for each of these that changed.

Reload does not touch certificates. The pgwire listener watches its own pgwire_tls_cert, pgwire_tls_key and pgwire_tls_client_ca files and swaps them in when they change; a failed reload keeps the previous certificate.

Reload order: remove changed/deleted sinks → pipelines → lookups → sources, then recreate sources → lookups → pipelines → sinks. Disable file watcher with LAMINAR_DISABLE_FILE_WATCH=1.

Prometheus Metrics

GET /metrics returns Prometheus text format (version 0.0.4) and needs no token. Every metric name carries the laminardb_ prefix and the constant labels instance (hostname) and pipeline (the first [[pipeline]] name, or default). A representative set:

MetricTypeDescription
laminardb_events_ingested_totalcounterEvents ingested from sources
laminardb_events_emitted_totalcounterEvents emitted to streams
laminardb_events_dropped_totalcounterEvents dropped
laminardb_events_null_timestamp_totalcounterSource rows dropped because the event-time column was null
laminardb_window_late_dropped_totalcounterWindow assignments dropped past allowed lateness
laminardb_cycles_totalcounterNormal processing cycles completed
laminardb_cycles_backpressured_totalcounterCycles skipped by backpressure
laminardb_batches_totalcounterBatches processed
laminardb_cycle_duration_secondshistogramNormal processing-cycle duration
laminardb_operator_process_duration_secondshistogramPer-operator processing time (labels: operator, mode)
laminardb_pipeline_watermarkgaugeGlobal pipeline watermark
laminardb_source_watermark_msgaugePer-source watermark in epoch ms (label: source)
laminardb_source_idlegauge1 if the source is idle and excluded from the watermark minimum (label: source)
laminardb_stream_watermark_msgaugePer-stream watermark in epoch ms (label: stream)
laminardb_input_buf_bytesgaugePer-stream input buffer bytes (label: stream)
laminardb_managed_state_accounted_bytesgaugeOperator-reported retained-state charge, a lower bound (labels: operator, phase)
laminardb_mv_updates_totalcounterMaterialized view updates
laminardb_mv_bytes_storedgaugeApproximate materialized view bytes stored
laminardb_checkpoints_completed_totalcounterCompleted checkpoints
laminardb_checkpoints_failed_totalcounterFailed checkpoints
laminardb_checkpoint_epochgaugeCurrent checkpoint epoch
laminardb_checkpoint_size_bytesgaugeLast checkpoint size
laminardb_checkpoint_duration_secondshistogramCheckpoint cycle duration
laminardb_sink_write_failures_totalcounterSink write errors
laminardb_sink_write_timeouts_totalcounterSink write timeouts
laminardb_sink_precommit_duration_secondshistogramSink pre-commit latency
laminardb_lookup_cache_hits_totalcounterOn-demand lookup cache hits (label: table)
laminardb_lookup_cache_misses_totalcounterOn-demand lookup cache misses (label: table)
laminardb_lookup_source_errors_totalcounterOn-demand lookup source fetch errors (label: table)
laminardb_pipeline_faults_totalcounterCompute-thread crashes that faulted the pipeline
laminardb_pipeline_restarts_totalcounterAutomatic recover-from-checkpoint restarts after a fault
laminardb_reload_totalcounterConfig reload count
laminardb_uptime_secondsgaugeServer uptime in seconds
laminardb_ws_connectionsgaugeActive WebSocket connections

On Linux the standard process collector is registered under the same prefix (for example laminardb_process_resident_memory_bytes). Cluster nodes add laminardb_cluster_subscription_* and placement metrics, and connectors register their own metrics when they start (for example laminardb_kafka_source_records_polled_total).

[[source]]

Source: config/mod.rs, DDL generation in server/mod.rs

Each [[source]] becomes CREATE SOURCE name (columns, PRIMARY KEY, WATERMARK) FROM CONNECTOR (properties) [FORMAT fmt]. Unknown fields are rejected.

FieldTypeDefaultDescription
name String required Unique source name. Referenced in SQL and sinks.
connector String required "kafka", "postgres-cdc", "mongodb-cdc", "nats", "websocket", "files", "delta-lake", "iceberg", "otel", "generator"
format String none Emitted as the FORMAT clause. When omitted there is no FORMAT clause and the connector's own default applies (Kafka, NATS and WebSocket use JSON). A format key under properties is rejected.
properties Table {} Connector options; see the connector sections below. Values must be flat (strings, numbers, booleans, or arrays, which are joined with commas); quote dotted keys such as "bootstrap.servers".
schema Array [] Column definitions: {name, type, nullable}; nullable defaults to true. Can be omitted for connectors with a fixed or resolvable schema.
primary_key Array<String> [] Emitted as PRIMARY KEY (...), for keyed sources such as CDC document mode or the right side of a temporal join.
watermark Section none Event-time watermark: {column, max_out_of_orderness} (default "5s"). The generated DDL uses whole seconds, so sub-second values round down. The column must be a TIMESTAMP.
Source with schema and watermark
[[source]]
name = "trades"
connector = "kafka"
format = "json"

[source.properties]
"bootstrap.servers" = "localhost:9092"
topic = "market-trades"
"group.id" = "laminar-analytics"
"startup.mode" = "earliest"   # required under at_least_once / exactly_once

[[source.schema]]
name = "symbol"
type = "VARCHAR"
nullable = false

[[source.schema]]
name = "price"
type = "DOUBLE"

[[source.schema]]
name = "ts"
type = "TIMESTAMP"

[source.watermark]
column = "ts"
max_out_of_orderness = "5s"

[[pipeline]]

Each [[pipeline]] becomes CREATE STREAM name AS sql. It has two fields; other keys are ignored.

FieldTypeDefaultDescription
name String required Unique pipeline (stream) name
sql String required The SELECT query. Multi-line supported with triple-quoted strings.

[[sink]]

Each [[sink]] becomes CREATE SINK name FROM pipeline INTO CONNECTOR (properties) [FORMAT fmt]. Unknown fields are rejected.

FieldTypeDefaultDescription
name String required Unique sink name
pipeline String required The [[pipeline]] (or process-function output) this sink reads from. Must exist.
connector String required "kafka", "postgres-sink", "mongodb-sink", "nats", "delta-lake", "iceberg", "websocket", "files"
format String none Emitted as the FORMAT clause; required by the files sink, rejected by the WebSocket sink. A format key under properties is rejected.
properties Table {} Connector-specific properties (flat values, as for sources)

Delivery is configured once at [server].delivery. A per-sink delivery field is rejected, and a delivery.guarantee property is owned by the runtime and cannot be set, so a pipeline cannot silently mix checkpoint semantics.

[[lookup]]

Each [[lookup]] becomes CREATE LOOKUP TABLE name (columns, PRIMARY KEY (...)) WITH ('connector' = ..., 'strategy' = ..., properties).

FieldTypeDefaultDescription
name String required Unique lookup table name. Used in SQL JOINs.
connector String required "postgres", "delta-lake", "iceberg" (replicated or on-demand), "mongodb" (on-demand only), "static" (replicated only)
strategy String set explicitly "replicated" (load the table and keep it in memory) or "on-demand" (fetch keys from the connector through a bounded cache). Always set it: in v0.33.0 a [[lookup]] without strategy fails at startup. On-demand is rejected under exactly_once.
schema Array required At least one [[lookup.schema]] column {name, type, nullable}
primary_key Array<String> required Exactly one column, declared NOT NULL (nullable = false)
properties Table {} Connector options, plus pushdown: "auto" (default), "enabled" or "disabled"
cache.size_bytes u64 104857600 [lookup.cache]; on-demand only. A non-default value is passed as cache.memory.
cache.ttl Duration "300s" [lookup.cache]; on-demand only. A non-default value is passed as cache.ttl in whole seconds.

With strategy = "replicated", a non-default [lookup.cache] value is rejected ("cache.memory and cache.ttl require strategy = 'on-demand'"). There is no cache.hybrid option, and cache.disk is rejected. Keys placed directly under [[lookup]] other than the fields above are ignored, so put pushdown under [lookup.properties].

Replicated PostgreSQL lookup table
[[lookup]]
name = "instruments"
connector = "postgres"
strategy = "replicated"
primary_key = ["symbol"]

[lookup.properties]
host = "db.internal"
database = "reference_data"
user = "reader"
password = "${PG_PASSWORD}"
table = "public.instruments"

[[lookup.schema]]
name = "symbol"
type = "VARCHAR"
nullable = false

[[lookup.schema]]
name = "exchange"
type = "VARCHAR"

Connectors

In Rust builds, include the connectors you need with Cargo features, for example cargo add laminar-db --features kafka,delta-lake. These are build options, not runtime approval gates. The v0.33.0 server binaries and Docker image include every connector below.

Set delivery once at [server].delivery (or LaminarDbBuilder::delivery_guarantee when embedded). The runtime injects the selected protocol and owns checkpoint/committer identities; connector properties do not require public writer IDs.

Set the serialization format with the SQL FORMAT clause (... FROM KAFKA (...) FORMAT JSON) or the TOML format field. A 'format' connector option is rejected: "option 'format' is unsupported; declare the format with the FORMAT clause". Format-specific options go in FORMAT fmt WITH (...).

RabbitMQ, IBM MQ, FIX and FpML connectors are optional build features and are not in default builds or the Docker image. The v0.33.0 x86_64 Linux (glibc) binary includes all four, the x86_64 Linux (musl) binary none, and the other binaries FIX and FpML.

Kafka

Source + Sink
kafka

NATS

Source + Sink
nats

PostgreSQL CDC

Source
postgres-cdc

MongoDB CDC

Source + Sink
mongodb-cdc

PostgreSQL Sink

Sink
postgres-sink

Delta Lake

Source + Sink
delta-lake

Apache Iceberg

Source + Sink
iceberg

OpenTelemetry

Source (OTLP/gRPC)
otel

WebSocket

Source + Sink
websocket

File Auto-Loader

Source + Sink
files

Generator

Source
built in

Kafka Source

Formats (FORMAT clause / TOML format): json (used when omitted), csv, raw, debezium, avro. Avro needs schema.registry.url; the registry supports Avro only.

Required

PropertyDescription
bootstrap.serversKafka broker addresses (comma-separated)
group.idConsumer group ID
topic or topic.patternComma-separated topic list, or a regex pattern. If both are set, topic.pattern wins. A pattern requires best_effort delivery and is rejected in cluster mode; guaranteed delivery needs an explicit topic list.

Startup position

PropertyDefaultDescription
startup.modegroup-offsetsgroup-offsets, earliest, latest. at_least_once and exactly_once reject group-offsets (another group member could move that cursor) and reject latest on a fresh start, so set earliest or one of the two keys below.
startup.specific.offsetsExplicit start positions, partition:offset,... (e.g. 0:100,1:200). Takes precedence over startup.mode; needs an explicit topic list.
startup.timestamp.msStart from a timestamp (ms since epoch). Takes precedence over startup.mode.
auto.offset.resetearliestearliest, latest, none; used with group-offsets when the group has no committed offset

Schema Registry

PropertyDefaultDescription
schema.registry.urlConfluent Schema Registry URL; required for avro, rejected for other formats. Must not embed credentials.
schema.registry.usernameBasic auth; set together with the password
schema.registry.passwordBasic auth; set together with the username
schema.registry.ssl.ca.locationCA bundle for the registry (also schema.registry.ssl.certificate.location, schema.registry.ssl.key.location)

Security

PropertyDefaultDescription
security.protocolplaintextplaintext, ssl, sasl_plaintext, sasl_ssl
sasl.mechanismPLAIN, SCRAM-SHA-256, SCRAM-SHA-512, GSSAPI (only where librdkafka reports sasl_gssapi; Linux builds do not include Cyrus SASL). Required for the sasl_* protocols. OAUTHBEARER is rejected.
sasl.usernameSASL username (PLAIN, SCRAM)
sasl.passwordSASL password (PLAIN, SCRAM)
ssl.ca.locationCA certificate path
ssl.certificate.locationClient certificate path (with ssl.key.location and ssl.key.password)

Consumer Tuning

PropertyDefaultDescription
latency.profilebalancedlow_latency, balanced, throughput; supplies batching defaults that explicit options override
max.poll.records1000Records per poll batch (256 with low_latency, 8192 with throughput)
isolation.levelread_committedread_committed, read_uncommitted. Guaranteed delivery requires read_committed.
partition.assignment.strategyrangerange, roundrobin, cooperative-sticky
metadata.columnsAppend any of: key, topic, partition, offset, timestamp, timestamp_type, headers

Properties prefixed with kafka. are passed to librdkafka with the prefix removed, except protected keys, which fail with "overrides a setting the Kafka connector owns; use the connector option for it instead". Protected keys are everything the connector sets itself (for example kafka.bootstrap.servers, kafka.group.id, kafka.enable.auto.commit, kafka.isolation.level), security and TLS settings, transaction and idempotence settings, kafka.partitioner, and sasl.kerberos.* / sasl.oauthbearer.*.

Kafka Sink

Formats: json (used when omitted), csv, raw, avro. debezium is source-only.

PropertyDefaultDescription
bootstrap.serversrequiredKafka broker addresses
topicrequiredTarget topic
key.columnColumn(s) for the record key; required for envelope = 'upsert'
key.formatnativenative, text, kafka, json
envelopeappendappend or upsert. Upsert writes each key to murmur2(key) % partition.count and a NULL value on delete; it needs key.column, partition.count and the key-hash partitioner.
partition.countTopic partition count; required for upsert
partitionerkey-hashkey-hash, round-robin, sticky
commit.topicCompacted topic for commit markers. Required for exactly_once and rejected otherwise; must differ from topic. Exactly-once Kafka output is embedded/single-node only; cluster Kafka output stays at-least-once.
schema.registry.urlRequired for avro; rejected for other formats
latency.profilebalancedlow_latency, balanced, throughput
compression.typenonenone, gzip, snappy, lz4, zstd (default lz4 under throughput). zstd needs the kafka-zstd build feature, which default builds (including the Docker image) do not include; the v0.33.0 release workflow builds the x86_64 Linux (gnu) binary with --all-features, which does. snappy is not built on Windows.
linger.ms5Batch linger time (0 with low_latency, 50 with throughput)
batch.size16384Max batch bytes (1048576 with throughput)
delivery.timeout.ms120000Per-record delivery deadline, 1 to 300000
transaction.timeout.ms60000Exactly-once transaction timeout, up to 900000

The sink always uses idempotent production with acks=all; acks is not configurable. Under exactly_once, output becomes visible to read_committed readers once per committed checkpoint.

NATS Source

Formats (FORMAT clause / TOML format): json (used when omitted), csv, raw. The source is ephemeral, so it needs best_effort delivery; it is rejected under at_least_once, the server default.

PropertyDefaultDescription
serversrequiredNATS server URLs (comma-separated)
modejetstreamjetstream, core
auth.modenonenone, user_pass, token, creds_file. Credentials with none are rejected.
userAuth username (required for user_pass)
passwordAuth password (required for user_pass)
tokenAuth token (required for token)
creds.filePath to creds file (required for creds_file)
tls.enabledfalseEnable TLS
tls.ca.locationCA cert path
tls.cert.locationClient certificate path
tls.key.locationClient key path
streamNATS JetStream stream name (required in jetstream mode)
subjectSubscription subject. Required in core mode; in jetstream mode set subject or subject.filters.
subject.filtersComma-separated JetStream subject filters; used instead of subject when both are set
consumerPull consumer durable name (required in jetstream mode; ephemeral consumers are not supported)
deliver.policyallall, new, by_start_sequence, by_start_time
start.sequenceStart sequence (required if deliver.policy=by_start_sequence)
start.timeStart RFC3339 timestamp (required if deliver.policy=by_start_time)
ack.policyexplicitexplicit, none
ack.wait.ms60000Ack wait timeout in milliseconds
max.deliver5Max deliveries; -1 for unlimited
max.ack.pending10000Max outstanding unacknowledged messages; -1 for unlimited
fetch.batch500Batch size for fetch operations
fetch.max.wait.ms500Max wait for fetch operations in milliseconds
queue.groupQueue group (core mode only)

JetStream messages receive confirmed acknowledgements asynchronously after successful deserialization; acknowledgement failures and timeouts leave them eligible for broker redelivery. NATS source progress is not checkpoint-coupled, and both JetStream and Core modes are admitted only as ephemeral/best-effort sources.

NATS Sink

Source: nats/sink/

Formats: json (used when omitted), csv, raw. Core mode is ephemeral (best_effort only); JetStream mode supports at_least_once. Neither supports exactly_once.

PropertyDefaultDescription
serversrequiredNATS server URLs (comma-separated)
modejetstreamjetstream, core
auth.modenonenone, user_pass, token, creds_file
userAuth username (required for user_pass)
passwordAuth password (required for user_pass)
tokenAuth token (required for token)
creds.filePath to creds file (required for creds_file)
tls.enabledfalseEnable TLS
tls.ca.locationCA cert path
tls.cert.locationClient certificate path
tls.key.locationClient key path
streamJetStream target, required in JetStream mode and forbidden in Core mode; file storage, three replicas, and publish acknowledgements are validated at startup
subjectLiteral publishing subject. Exactly one of subject and subject.column is required.
subject.columnPer-row subject column name. Exactly one of subject and subject.column is required.
dedup.id.columnColumn used for JetStream message deduplication IDs; jetstream mode with stream only
min.duplicate.window.ms120000Minimum duplicate window in milliseconds
max.pending4096Maximum pending publish acknowledgements (1 to 4096)
ack.timeout.ms30000Ack timeout in milliseconds (at most 300000)
header.columnsComma-separated column names to map to NATS headers

PostgreSQL CDC Source

Reads one table through logical replication (pgoutput, PostgreSQL 17+) as typed keyed upserts or a retractable __weight changelog, with an optional initial snapshot handed off at the slot's consistent point. The table needs a primary key and REPLICA IDENTITY FULL, and the publication must be FOR TABLE exactly that table. The slot advances only after a checkpoint commits. Embedded and single-node server mode, at-least-once; cluster mode and exactly-once reject it. TRUNCATE and table or publication changes stop the source with reset guidance.

PropertyDefaultDescription
hostrequiredPostgreSQL hostname
port5432Port
databaserequiredDatabase name
usernamepostgresAuth username
passwordAuth password
slot.namerequiredPrefix (at most 46 bytes) of the replication slot the source creates after its first checkpoint commits, named <slot.name>_<claim id>; other slots under the prefix are reported, never used or dropped
publicationrequiredPublication name
ssl.modeverify-fullverify-full or explicit plaintext disable
ssl.ca.cert.pathOptional PEM CA bundle for verify-full; web PKI roots are used by default
tablerequiredCaptured table as schema.table
snapshot.modeinitialinitial copies the table, then streams every later change; never streams changes after the slot is created
output.modeupsertupsert: current rows and key-only deletes for keyed sinks. changelog: Z-set rows with a trailing __weight BIGINT NOT NULL for filters and COUNT/SUM/AVG with EMIT CHANGES; needs snapshot.mode=initial
max.buffered.bytes268435456Total connector-owned payload budget across raw WAL, decoded transactions, and Arrow construction (1 MiB to 4 GiB)

PostgreSQL Sink

Unknown properties are rejected. The sink is durable at-least-once in every mode; exactly_once rejects it.

PropertyDefaultDescription
hostnamerequiredPostgreSQL hostname
port5432Port
databaserequiredDatabase name
usernamerequiredAuth username
passwordAuth password
table.namerequiredTarget table
schema.namepublicPostgreSQL schema
write.modeappendappend (binary COPY) or upsert (INSERT ... ON CONFLICT DO UPDATE)
primary.keyPK columns for upsert (comma-separated). Required if write.mode=upsert.
changelog.modefalsetrue applies Z-set records (split by _op) as inserts and deletes; requires write.mode = upsert
ssl.modeverify-fullverify-full or explicit plaintext disable
ssl.ca.cert.pathPEM CA bundle for verify-full; web PKI roots are used by default
flush.interval.ms250Maximum time to buffer before flushing
connect.timeout.ms10000Connection deadline
statement.timeout.ms30000Per-query deadline (at least 1000)
auto.create.tablefalseCreate the table if missing

MongoDB CDC Source

Reads one collection's change stream and delivers it straight to sinks, with no broker in between. Byte-bounded backpressure, classified bounded reconnects, and deployment- and collection-bound resume-token checkpoints. Embedded and single-node server mode, at-least-once; cluster mode and exactly-once reject it.

PropertyDefaultDescription
connection.urirequiredMongoDB connection string (replica set or sharded cluster)
databaserequiredDatabase name
collectionrequiredFixed collection name; the collection must exist before source startup
output.modehistoryhistory: one immutable record per change event (append-only). document: post-image puts and key-only deletes for keyed mirrors
full.document.modedeltadelta or required; document mode needs required
snapshot.modeneverinitial copies the collection at a committed point in time, then streams every later change (replica sets only)
objectid.columnsdocument mode: VARCHAR columns that hold ObjectIds as lowercase hex
document.json.columndocument mode: VARCHAR column receiving the whole document as canonical Extended JSON
pipeline[]JSON array of at most 64 $match stages, bounded to 256 KiB; history mode without a snapshot only
max.buffered.bytes67108864Retained BSON-byte limit across the reader queue and poll buffer (1 MiB to 4 GiB)

Full document modes: delta sends the update description only (default). required needs changeStreamPreAndPostImages enabled on the collection; a missing image stops the source and is never treated as a delete.

History schema (version 1): event_id, event_version, operation, database, collection, collection_uuid, document_key, full_document, update_description, event_details, resume_token, cluster_time_seconds, cluster_time_increment, wall_time, txn_number, lsid, snapshot_id. Document values are canonical Extended JSON v2. Operation names are data: no history column makes a sink delete rows.

Document mode: the PRIMARY KEY must be the document key (_id, plus shard-key fields); non-key columns are nullable and map exactly or fail. Sinks must be keyed on the same columns: PostgreSQL upsert with changelog.mode, or Delta write.mode=upsert. Filters, streams, joins and aggregates over a document source are rejected.

MongoDB Sink

PropertyDefaultDescription
connection.urirequiredMongoDB connection string
databaserequiredTarget database name
collectionrequiredTarget collection name
write.modeinsertinsert, upsert, cdc_replay
write.mode.key_fieldsComma-separated top-level keys; required for upsert
replay.source.namespacecdc_replay: the source database.collection whose events apply to this target
flush.interval.ms250Max time between flushes (ms)
sink.write.timeout.ms30000End-to-end write deadline

Write modes: insert is append-only. upsert replaces documents by the fields listed in write.mode.key_fields. cdc_replay applies MongoDB CDC history records: insert, replace and snapshot replace by document key, update applies the post-image (or the update description), delete deletes by key, metadata events write nothing, and destructive lifecycle events stop the sink. Writes are ordered and majority-journaled; these correctness settings are not user-tunable. Time series collections only support insert.

Delta Lake Source

Replayable, so it works with at_least_once. It is a singleton source; cluster mode rejects it.

PropertyDefaultDescription
table.pathrequiredDelta table path (local, s3://, az://, gs://)
read.modechangeschanges (snapshot, then each later version's changes) or snapshot (latest version only)
starting.versionA non-negative version number or latest; only with read.mode = changes. Unset emits the current snapshot first.
poll.interval.ms1000Version poll interval; must be greater than 0
catalog.typenonenone, glue (needs catalog.database), unity (needs catalog.name and catalog.schema)

partition.filter and cdf.enabled are rejected.

Delta Lake Sink

PropertyDefaultDescription
table.pathrequiredDelta table path
write.modeappendappend, overwrite (each flush replaces the table), upsert (alias merge)
merge.key.columnsKey columns for upsert (comma-separated); required for upsert
partition.columnsPartition columns (comma-separated)
target.file.size134217728Target Parquet file size (128 MiB)
max.buffer.records100000Records before flush
max.buffer.duration.ms60000Time before flush
schema.evolutionfalseAuto-merge new columns
auto.createfalseCreate the table if missing (defaults to true when catalog.storage.location is set)
write.timeout.ms30000Write deadline; at least 5000
parquet.compressionzstdzstd, snappy, lz4, gzip, none
catalog.typenonenone, glue, unity (with catalog.* keys)

Cloud storage options are passed with a storage. prefix, for example storage.aws_access_key_id, storage.aws_secret_access_key, storage.aws_region, storage.aws_endpoint; each falls back to the provider's environment variable. Delivery comes from the pipeline-wide setting. exactly_once is supported only for write.mode = append with the atomic-create DefaultLogStore; custom S3 endpoints must pass an atomic conditional-create probe at sink startup. Cluster exactly-once also requires an S3/S3A table URI.

Apache Iceberg Source & Sink

Connector name iceberg for both directions. Only the REST catalog is enabled in v0.33.0 builds; glue, hms, s3tables and sql are rejected at startup. Default builds include S3 and local filesystem storage; GCS and Azure need the iceberg-gcs / iceberg-azure build features.

Common

PropertyDefaultDescription
catalog.urirequiredREST catalog endpoint; must not embed credentials
catalog.warehouserequiredWarehouse (legacy alias warehouse)
namespacerequiredIceberg namespace
table.namerequiredTable name
catalog.typerestOnly rest works in this release
catalog.auth.typenonenone, bearer (token in catalog.property.token), oauth2 (credential in catalog.property.credential, plus catalog.oauth2.*)
storage.typeinferreds3, gcs, azure, fs
storage.endpointObject-store endpoint (e.g. MinIO); with storage.region and storage.path_style
table.refmainThe sink can publish only to main

Other catalog and storage settings pass through as catalog.property.* and storage.property.*.

Source

PropertyDefaultDescription
read.modesnapshotsnapshot or append (alias incremental); changelog is rejected
read.bootstrapinitialinitial or none; only with read.mode = append
poll.interval60sSnapshot poll interval (alias poll.interval.ms)
start.snapshot.idStart from this snapshot instead of the current one
projectionColumns to read (alias select.columns)
filterScan filter

Sink

PropertyDefaultDescription
write.modeappendOnly append; merge-on-read and copy-on-write are rejected
auto.createfalseCreate the table; format.version (default 2), partition.spec, sort.order and table.property.* require it
target.file.size.bytes134217728Target data file size (alias target.file.size)
parquet.compressionzstdzstd, snappy, lz4, uncompressed
max.buffer.rows65536Rows buffered before a flush
max.buffer.bytes67108864Bytes buffered before a flush
max.flush.age300sMaximum age of buffered data
schema.evolution.modestrictOnly strict; safe is rejected

Cluster exactly_once is admitted only for an append sink on a REST catalog with no or bearer auth and S3 storage with a direct S3 warehouse.

WebSocket Source

Note: the WebSocket source is a client and is ephemeral (non-replayable), so it requires pipeline-wide best_effort; it is rejected under at_least_once, the server default. Reconnects can create gaps or duplicates. Formats: json (used when omitted), csv (comma-delimited, no header) or binary. Unknown properties are rejected.

PropertyDefaultDescription
urlrequiredws:// or wss:// URL (comma-separated in failover order)
subscribe.messageMessage sent after each handshake
reconnect.enabledtrueAuto-reconnect
reconnect.initial.delay.ms100Initial reconnect delay
reconnect.max.delay.ms30000Max backoff delay
reconnect.max.retriesMaximum reconnect attempts; empty means unlimited
on.backpressureblockblock or drop_newest
max.message.size67108864Maximum accepted message size in bytes (at most 64 MiB)

WebSocket Sink

Ephemeral, so best_effort only. The wire format is always JSON: any FORMAT clause (or TOML format) is rejected. Unknown properties are rejected.

PropertyDefaultDescription
modeserverserver (accept clients) or client (push to remote)
bind.addressListen address, e.g. 0.0.0.0:9090. Required in server mode; not allowed in client mode.
urlRemote ws:///wss:// URL. Required in client mode; not allowed in server mode.
max.connections10000Max concurrent clients (server mode, 1 to 65536)
ping.interval.ms30000Ping interval (server mode, at least 1000)
ping.timeout.ms10000Pong timeout (server mode, at least 1000)

Client mode sends one schema-encoded JSON row per text frame. Server-mode clients must first send {"action":"subscribe"}; the server replies with a subscription ID and then emits {"type":"data","data":[...],"sequence":N} text frames. Server fan-out uses a shared 32-frame ring with 256 KiB frames; an oversized row or burst is rejected before publication, and a lagging subscriber is disconnected so sequence gaps remain visible. The built-in server does not terminate TLS or authenticate clients; expose it only on a trusted network or behind an authenticated TLS reverse proxy.

File Source (Auto-Loader)

Watches a local directory and loads new files. Formats: csv, tsv, json (aliases jsonl, ndjson), text, parquet, arrow. Without a FORMAT clause the format is taken from the extension in path (e.g. /data/in/*.csv); a bare directory needs an explicit format. CSV and JSON sources need declared columns or schema.inference = true. Replayable; cluster mode rejects it.

PropertyDefaultDescription
pathrequiredLocal directory, or a glob in the last path component; an absolute file:// URL is accepted. Cloud URLs are rejected in this release. Not recursive.
glob_patternFile-name glob; overrides a glob in path
poll_interval10sDuration (e.g. "5s", "500ms"; a bare number means seconds). Used when the directory is watched by polling (network or FUSE mounts); local directories use filesystem events.
stabilisation_delay1sWait after the last modify event before a file is treated as complete
max_files_per_poll100Files per poll cycle
max_file_bytes268435456Largest file accepted (256 MiB)
csv.delimiter,CSV delimiter (first byte used; tab for tsv)
csv.has_headertrueCSV has a header row
schema.inferencefalseInfer CSV/JSON columns from sample files
include_metadatafalseAdd a _metadata struct column

Known limitations in v0.33.0:

  • A json file must contain exactly one JSON object. Files with several JSON Lines records, or a top-level JSON array, fail to decode ("trailing characters" or "JSON value must be an object"), despite the jsonl/ndjson aliases.
  • A file that fails to decode is retried continuously, with a warning in the log each time, and stays at the head of the queue, so later files are not loaded. Fixing or deleting the file does not clear it while the server runs (the source then reports that the file changed size or is missing): restart the server after fixing or removing it.
  • The CSV reader cannot decode TIMESTAMP columns; declaring one fails with "CSV reader ... cannot decode". Use BIGINT, DOUBLE, BOOLEAN, DATE or VARCHAR columns, or another format such as Parquet.

File Sink

Writes immutable rolling files to a local directory. Durable at-least-once; exactly_once rejects it. The format (FORMAT clause / TOML format) is required: csv, json, text, parquet or arrow.

PropertyDefaultDescription
pathrequiredLocal output directory
prefixpartFile name prefix
compressionsnappyParquet only: snappy, gzip, zstd, lz4, none/uncompressed (brotli is rejected)
max_file_sizeBytes before rotation (row formats only)
max_buffered_batches10000Maximum batches buffered between bulk-format (Parquet, Arrow) flushes

The mode option was removed and is rejected; files are always published as immutable rolling files.

OpenTelemetry Source

Source: otel/config/

Receives OTLP/gRPC traces, metrics or logs. Each source handles one signal type on its own port and has a fixed schema for that signal, so there is no FORMAT clause. The source is ephemeral and node-local: it needs best_effort delivery and cannot run in cluster mode.

PropertyDefaultDescription
port4317gRPC listen port
bind.address0.0.0.0Listen address
signalstracesOne of traces, metrics, logs; create one source per signal
batch_size1024Max rows per batch
channel_capacity64Bounded batch channel capacity
OTel traces source (SQL)
CREATE SOURCE traces FROM OTEL (port = '4317', signals = 'traces');

Generator Source

Emits a deterministic sequence with no external system, for demos and tests. It is always built in. Row i is (seq = i, ts_ms = i * 1000 / rows.per.second, value = "v{i}") with the fixed schema seq BIGINT NOT NULL, ts_ms BIGINT NOT NULL, value VARCHAR NOT NULL. Because output depends only on the offset, the source is replayable and admitted for single-node at_least_once and exactly_once. It is a singleton source, so cluster mode rejects it.

PropertyDefaultDescription
rows.per.second1000Emission rate; must be greater than 0
batch.max.size1024Max rows per batch
max.rowsunboundedStop after this many rows
Generator source (TOML)
[[source]]
name = "gen"
connector = "generator"

[source.properties]
"rows.per.second" = "100"

Join Semantics

Bounded interval joins are the stateful stream-stream joins. Temporal joins look up the version of a keyed source that was valid at the probe row's event time. Lookup joins enrich a stream from a [[lookup]] table. Admission rejects unsupported join shapes before connector I/O. The rules below follow the SQL reference.

Join TypeStateScopeMatch
Bounded interval Keyed stream state Single node and cluster All pairs within the time bound
Temporal Versioned right-side history, keyed Single node and cluster Point-in-time lookup
Lookup Lookup table (replicated or on-demand cache) Single node (cluster rejects it) Key equality

Interval Join (Stream-Stream)

Matches pairs of rows from two direct sources whose equality keys match and whose event times satisfy the directional predicate right.ts BETWEEN left.ts AND left.ts + bound, where the bound is a positive, finite interval. Both inputs need watermarks on TIMESTAMP NOT NULL event-time columns. Equality keys are one or more VARCHAR/BIGINT columns of matching types; NULL keys do not match.

Supported types: INNER, LEFT, RIGHT, FULL, LEFT/RIGHT SEMI, LEFT/RIGHT ANTI

Interval join — match orders to payments within 1 hour
CREATE STREAM matched AS
SELECT o.order_id AS order_id,
       o.amount AS amount,
       p.status AS status
FROM orders o
INNER JOIN payments p
    ON o.order_id = p.order_id
    AND p.ts BETWEEN o.ts AND o.ts + INTERVAL '1' HOUR;
  • Every projected expression needs an explicit alias, and every projected or filtered column must use its left/right qualifier.
  • Outer and anti unmatched rows are emitted only when the opposite input's watermark closes their possible match interval.
  • Joining and aggregating in one statement is not supported: name the join output and aggregate it in a separate stream.
  • Cross, unbounded, general non-equality, intermediate-input and multi-way joins are rejected on this path.

Temporal Join (FOR SYSTEM_TIME AS OF)

For each left row, looks up the right-side row that was valid at the left row's event time, using SQL:2011 FOR SYSTEM_TIME AS OF. Runs in single-node and cluster mode for one direct two-input join. ASOF JOIN is not an alias.

Supported types: INNER, LEFT

Temporal join — currency rate valid at order time
CREATE STREAM converted_orders AS
SELECT o.order_id AS order_id,
       o.amount AS amount,
       r.rate AS rate,
       o.amount * r.rate AS converted
FROM orders o
JOIN currency_rates FOR SYSTEM_TIME AS OF o.order_time AS r
    ON o.currency = r.currency;
  • The left source must be append-only. The right source must declare its equality keys as its PRIMARY KEY; it may be append-only or keyed-upsert.
  • Both sides need watermarks on their event-time columns. A result becomes final only after the right watermark passes its probe time.
  • Set [server].temporal_join_idle_history_retention; a pipeline containing a temporal join requires it. It bounds right-side version history while an input is idle.

Lookup Join

Enriches stream rows from a lookup table by equality on its single-column primary key, with no time bound. A replicated table is held in memory; an on-demand table fetches missing keys from the connector and keeps them in a bounded memory cache (cache.memory, cache.ttl). With pushdown set to auto or enabled, predicates are pushed to the lookup connector. Cluster mode rejects lookup joins.

Supported types: INNER, LEFT

Lookup join — enrich orders with product info
SELECT o.order_id, o.amount, p.name, p.category
FROM orders o
LEFT JOIN products p
    ON o.product_id = p.id;

[supervision]

Single-node only. After a fatal pipeline fault, the server restarts the pipeline from the last checkpoint with exponential backoff, up to max_restarts times within the window; after that the pipeline stays faulted. Supervision is not enabled when [[process_function]] entries are configured. Changes require a restart.

FieldTypeDefaultDescription
max_restartsusize5Restarts allowed within window_secs
window_secsu6460Sliding window for counting restarts; must be greater than 0
initial_backoff_msu64500Backoff before the first restart in a window
max_backoff_secsu6430Cap on the exponential backoff

AI Functions ([ai] and [models])

SQL functions such as ai_classify, ai_sentiment, ai_embed and ai_complete run a named model from a registry configured with [ai.providers.<name>], [models.<name>] and [ai.defaults]. See the server README AI section for the keys and an example.

  • AI functions are single-node only; cluster mode rejects them ([LDB-4007]).
  • Local (ONNX) models need ONNX Runtime 1.24 or newer installed on the host, on the library search path or via ORT_DYLIB_PATH. The Docker image does not include ONNX Runtime. Remote providers do not need it.
  • Remote providers read their API key from the environment variable named in api_key_env; the key is not stored in the config.
  • Changes to [ai] or [models] require a restart.

Environment Variables

Config file: before parsing, the server replaces ${VAR} and ${VAR:-default} anywhere in the TOML text with environment values, including inside [[pipeline]] SQL and the top-level sql string. A missing variable without a default is a startup error. Write $${VAR} to keep a literal ${VAR} in the loaded config so that it is resolved later, when the DDL runs.

Connector and format options in DDL: when a CREATE SOURCE, CREATE SINK or lookup-table statement runs, ${VAR} and ${VAR:-default} in its connector and format option values are resolved from the database's config variables (set with LaminarDbBuilder::config_var when embedded), then from the process environment. This also applies to DDL sent through POST /api/v1/sql or pgwire. An unresolved name without a default fails the statement. Other parts of SQL submitted at runtime are not substituted.

Environment variable examples
[source.properties]
"bootstrap.servers" = "${KAFKA_BROKERS:-localhost:9092}"
"sasl.username" = "${KAFKA_USER}"
"sasl.password" = "$${KAFKA_PASSWORD}"   # kept literal; resolved when the DDL runs

[checkpoint]
url = "s3://${S3_BUCKET}/checkpoints"   # cluster mode (single-node at_least_once needs file://)

[checkpoint.storage]
aws_access_key_id = "${AWS_ACCESS_KEY_ID}"
aws_secret_access_key = "${AWS_SECRET_ACCESS_KEY}"

Other variables read by the server: LAMINAR_DISABLE_FILE_WATCH=1 (or true) disables the config watcher, TOKIO_WORKER_THREADS sizes the I/O runtime, RUST_LOG overrides --log-level, and MALLOC_CONF tunes jemalloc.

Delivery Guarantees

GuaranteeHow it worksRequirements
best_effort No replay promise. This is the low-latency mode for inputs or outputs that cannot survive a failure. Accepts ephemeral connectors; failure may lose accepted events.
at_least_once Server default. Recovery resumes replayable sources from the finalized checkpoint cut, so some records may be processed again. Checkpointing, replayable sources, and durably acknowledged sinks. Single-node mode needs a local file:// checkpoint directory (an object-store URL is rejected with [LDB-0014]). Cluster mode needs a cluster-shared s3://, gs:// or az:// checkpoint URL.
exactly_once Barrier-aligned state and source positions are sealed before checkpoint-committable sink output becomes externally visible. Single-node requires an exclusively locked local file:// checkpoint directory ([LDB-0014] otherwise), a replayable source with exact recovery, and a coordinated sink. Cluster mode requires a shared checkpoint URL and Kafka input with either an S3/S3A append-mode Delta Lake sink or a REST-catalog Iceberg append sink backed by S3/S3A storage. Kafka output is exactly-once in embedded and single-node mode through checkpoint-staged transactions and a fenced committer; cluster Kafka output remains at-least-once. Unsupported combinations fail before connector I/O.

Delivery is one pipeline-wide setting. Per-connector delivery fields are rejected, and incompatible connectors fail before external I/O. For example, the WebSocket, OpenTelemetry and NATS (Core and JetStream) sources are non-replayable and therefore require best_effort; they fail under the server default at_least_once. Cluster mode rejects best_effort. Cluster exactly_once admits Kafka input with an S3/S3A append-mode Delta Lake sink or a REST-catalog Iceberg append sink backed by S3/S3A storage.

Full Config Example

A single-node, at-least-once pipeline: a Kafka source read from the earliest offset, a one-minute VWAP window, and two sinks. Set LAMINAR_CONSOLE_TOKEN (at least 8 characters) and KAFKA_BROKERS before starting; a missing variable is a startup error. To add enrichment, define a [[lookup]] table and join it in a separate pipeline.

laminardb.toml
[server]
mode = "single"
bind = "0.0.0.0:8080"
console_token = "${LAMINAR_CONSOLE_TOKEN}"   # required for a non-loopback bind
delivery = "at_least_once"

[checkpoint]
# Single-node at_least_once / exactly_once need a local file:// directory.
url = "file:///var/lib/laminardb/checkpoints"
interval = "30s"

# --- Sources ---

[[source]]
name = "trades"
connector = "kafka"
format = "json"

[source.properties]
"bootstrap.servers" = "${KAFKA_BROKERS}"
topic = "market-trades"
"group.id" = "laminar-vwap"
"startup.mode" = "earliest"   # group offsets are rejected under at_least_once

[[source.schema]]
name = "symbol"
type = "VARCHAR"
nullable = false

[[source.schema]]
name = "price"
type = "DOUBLE"

[[source.schema]]
name = "volume"
type = "BIGINT"

[[source.schema]]
name = "ts"
type = "TIMESTAMP"
nullable = false

[source.watermark]
column = "ts"
max_out_of_orderness = "5s"

# --- Pipelines ---

[[pipeline]]
name = "vwap"
sql = """
SELECT symbol,
       tumble(ts, INTERVAL '1' MINUTE) AS window_start,
       SUM(price * CAST(volume AS DOUBLE)) / SUM(CAST(volume AS DOUBLE)) AS vwap,
       SUM(volume) AS volume
FROM trades
GROUP BY symbol, tumble(ts, INTERVAL '1' MINUTE)
EMIT ON WINDOW CLOSE
"""

# --- Sinks ---

[[sink]]
name = "kafka_output"
pipeline = "vwap"
connector = "kafka"
format = "json"

[sink.properties]
"bootstrap.servers" = "${KAFKA_BROKERS}"
topic = "vwap-1m"
"key.column" = "symbol"

[[sink]]
name = "archive"
pipeline = "vwap"
connector = "files"
format = "parquet"

[sink.properties]
path = "/var/lib/laminardb/archive/vwap"