Configuration
Client configuration (ConvergenceOptions)
Section titled “Client configuration (ConvergenceOptions)”Pass ConvergenceOptions to ConvergenceClient.ConnectAsync():
await using var client = await ConvergenceClient.ConnectAsync(new ConvergenceOptions{ Host = "127.0.0.1", Port = 3727, SourceId = 1, LivenessDeadlineMs = 30_000, ReconnectMaxDelayMs = 10_000, HeartbeatIntervalMs = 15_000, SubscriberBufferSize = 4_096,});// Coming soon| Property | Type | Default | Description |
|---|---|---|---|
Host | string | required | Server hostname or IP address. |
Port | int | required | Server port. |
SourceId | byte | required | Writer identity (0 to 63). Must be unique per logical service. See Limits. |
AuthToken | string? | null | Optional authentication token. |
LivenessDeadlineMs | uint | 30000 | If the server receives no frames from this client within this duration (milliseconds), the connection is considered dead. Entities with only this source’s bit are tombstoned. |
ReconnectMaxDelayMs | int | 10000 | Maximum exponential backoff between reconnection attempts (milliseconds). The first retry is 100ms, doubling on each failure up to this cap. |
HeartbeatIntervalMs | int | 15000 | How often the client sends keep-alive frames (milliseconds). Must be less than LivenessDeadlineMs to avoid spurious disconnections. |
SubscriberBufferSize | int | 4096 | Per-subscription bounded buffer capacity (number of notifications). During live notifications, if the subscriber falls behind and the buffer fills, the subscriber is disconnected. During bootstrap, the server applies backpressure instead (see Bootstrap). |
Tuning guidelines
Section titled “Tuning guidelines”Liveness deadline vs. heartbeat interval
Section titled “Liveness deadline vs. heartbeat interval”The heartbeat interval should be well under half the liveness deadline. With the defaults (15s heartbeat, 30s deadline), the client sends a heartbeat every 15 seconds, giving two chances to reach the server before the deadline expires.
If your network has higher latency or jitter, increase both values proportionally.
Subscriber buffer size
Section titled “Subscriber buffer size”The default of 4,096 handles most workloads. This buffer only limits throughput for live notifications — during bootstrap, the server applies backpressure so all entities are delivered regardless of buffer size.
Increase it if:
- Your entity kinds have very high live update rates (thousands per second).
- Your notification processing loop performs slow operations (database writes, HTTP calls).
Alternatively, offload expensive work to a background queue and keep the notification loop fast.
Reconnect max delay
Section titled “Reconnect max delay”The default of 10 seconds means the SDK retries at 100ms, 200ms, 400ms, 800ms, 1.6s, 3.2s, 6.4s, 10s, 10s, 10s… until the connection succeeds.
For latency-sensitive applications, decrease this value. For applications where the server may be down for extended periods, the default is usually fine.
Server configuration
Section titled “Server configuration”The server reads its configuration from three layered sources, in order of increasing precedence:
- Built-in defaults — every field has a sensible default; the server starts cleanly with no file and no env vars.
- TOML file — loaded only when
--config <PATH>is passed. Seeconfig/convergedb.tomlfor an annotated example. CONVERGEDB_*environment variables — override individual fields without touching the file.
The CLI surface is intentionally minimal: --config <PATH> is the only flag. Everything else is configured via the file or env vars.
Full reference
Section titled “Full reference”| TOML key | Env var | Type | Default | Valid range | Description |
|---|---|---|---|---|---|
bind | CONVERGEDB_BIND | string (host:port) | 127.0.0.1:3727 | parseable SocketAddr | TCP listener address. Use 0.0.0.0:3727 to accept connections from outside the host (Docker, k8s). |
partitions | CONVERGEDB_PARTITIONS | usize | number of CPU cores | 1..=256 | Number of single-threaded convergence engine partitions. |
coalescing_window_ms | CONVERGEDB_COALESCING_WINDOW_MS | u64 | 20 | 1..=10000 | Window over which writes to the same entity are merged before flushing (FR-11). |
tombstone_retention_secs | CONVERGEDB_TOMBSTONE_RETENTION_SECS | u64 | 300 | >= 1 | How long tombstones remain visible to late-joining subscribers (FR-22). |
initial_table_capacity | CONVERGEDB_INITIAL_TABLE_CAPACITY | usize | 1024 | >= 1 | Initial capacity for per-kind entity hash tables. |
subscriber_buffer_size | CONVERGEDB_SUBSCRIBER_BUFFER_SIZE | usize | 4096 | 64..=1048576 | Bounded notification buffer per subscriber (FR-20). |
default_liveness_deadline_ms | CONVERGEDB_DEFAULT_LIVENESS_DEADLINE_MS | u64 | 30000 | unbounded | Default liveness deadline used when a source does not request its own (FR-7). |
log_level | CONVERGEDB_LOG_LEVEL | string | "info" | trace, debug, info, warn, error | Default tracing level when RUST_LOG is unset. See note below. |
checkpoint_interval_secs | CONVERGEDB_CHECKPOINT_INTERVAL_SECS | u64 | 300 | >= 10 | Background compaction cadence (NFR-9). |
data_dir | CONVERGEDB_DATA_DIR | string | "data" | writable path | Base directory for WAL, checkpoints, and schemas. In Docker, mount a volume here. |
[admin].enabled | CONVERGEDB_ADMIN_ENABLED | bool | true | true / false | Whether the admin HTTP API is started. |
[admin].bind | CONVERGEDB_ADMIN_BIND | string (host:port) | main bind with port + 1 | parseable SocketAddr | Admin HTTP listener address. Auto-derives from bind when unset, so a single bind = "0.0.0.0:3727" exposes both ports. Set CONVERGEDB_ADMIN_BIND="" to clear an inherited value. |
[otel].endpoint | CONVERGEDB_OTEL_ENDPOINT | string (URL) | unset (OTel disabled) | starts with http:// or https:// | OTLP/gRPC collector endpoint. Leaving it unset disables OTel entirely with zero overhead. Set CONVERGEDB_OTEL_ENDPOINT="" to clear an inherited value. |
[otel].export_interval_secs | CONVERGEDB_OTEL_EXPORT_INTERVAL_SECS | u64 | 60 | >= 1 | Metrics export interval. |
[otel].trace_sample_rate | CONVERGEDB_OTEL_TRACE_SAMPLE_RATE | f64 | 0.01 | 0.0..=1.0 | Head-based trace sampling rate. |
Invalid values (parse errors or out-of-range) cause the server to print a clear error message naming the offending key and exit non-zero before binding any sockets.
Running in Docker or on a remote host
Section titled “Running in Docker or on a remote host”The default bind value is 127.0.0.1:3727 — loopback only. This is safe for local development but means a containerised server cannot accept connections from the host even with -p 3727:3727. Override it with CONVERGEDB_BIND:
docker run \ -e CONVERGEDB_BIND=0.0.0.0:3727 \ -p 3727:3727 \ -p 3728:3728 \ -v convergedb-data:/data \ -e CONVERGEDB_DATA_DIR=/data \ convergence-serverThe admin port (3728 by default) auto-derives from bind (port + 1) on the same interface, so a single CONVERGEDB_BIND=0.0.0.0:3727 exposes both. To put the admin API on a different interface or port, set CONVERGEDB_ADMIN_BIND explicitly.
For Kubernetes:
env: - name: CONVERGEDB_BIND value: "0.0.0.0:3727" - name: CONVERGEDB_DATA_DIR value: "/data" - name: CONVERGEDB_PARTITIONS value: "4"RUST_LOG vs. CONVERGEDB_LOG_LEVEL
Section titled “RUST_LOG vs. CONVERGEDB_LOG_LEVEL”Tracing is controlled by the standard tracing-subscriber EnvFilter:
RUST_LOG, if set, takes full precedence and accepts the rich filter syntax (info,convergence_server::net=debug, etc.).CONVERGEDB_LOG_LEVEL(orlog_levelin the TOML file) only sets the default level used whenRUST_LOGis unset. It accepts a single level name.
This matches the convention used by most Rust services — use CONVERGEDB_LOG_LEVEL for the deployment-wide default and RUST_LOG for ad-hoc per-module debugging.
Disk-backed storage
Section titled “Disk-backed storage”Disk-backed entity kinds store their state files in the stores/ subdirectory of each partition’s data directory (e.g., data/partition-0/stores/kind-1.dat). These files are append-only and grow over time as entities are updated. Compaction is planned but not yet implemented.
The storage mode is set per kind at registration time via the SDK’s [ConvergenceEntity] attribute (see Defining Entity Kinds). It cannot be changed after registration.
The LRU cache starts at the configured CacheCapacity (per partition) and grows adaptively if the hit rate drops below 70%. It will shrink back toward CacheCapacity when the hit rate exceeds 95%, but it will not shrink below the configured floor — and the underlying HashMap allocation is sized for that floor, so picking a CacheCapacity much larger than your per-partition working set wastes memory permanently. Each partition allocates an independent cache, so total cache memory is roughly CacheCapacity × partition_count slots per kind. Cache statistics (hit rate, size, capacity) are available via the engine’s stats output and the admin /memory/kinds endpoint.
CacheCapacity is part of the persisted schema and cannot be changed after a kind is registered. To change it, the kind must be re-created on a fresh data directory.