Skip to content

Configuration

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,
});
PropertyTypeDefaultDescription
HoststringrequiredServer hostname or IP address.
PortintrequiredServer port.
SourceIdbyterequiredWriter identity (0 to 63). Must be unique per logical service. See Limits.
AuthTokenstring?nullOptional authentication token.
LivenessDeadlineMsuint30000If 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.
ReconnectMaxDelayMsint10000Maximum exponential backoff between reconnection attempts (milliseconds). The first retry is 100ms, doubling on each failure up to this cap.
HeartbeatIntervalMsint15000How often the client sends keep-alive frames (milliseconds). Must be less than LivenessDeadlineMs to avoid spurious disconnections.
SubscriberBufferSizeint4096Per-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).

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.

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.

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.

The server reads its configuration from three layered sources, in order of increasing precedence:

  1. Built-in defaults — every field has a sensible default; the server starts cleanly with no file and no env vars.
  2. TOML file — loaded only when --config <PATH> is passed. See config/convergedb.toml for an annotated example.
  3. 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.

TOML keyEnv varTypeDefaultValid rangeDescription
bindCONVERGEDB_BINDstring (host:port)127.0.0.1:3727parseable SocketAddrTCP listener address. Use 0.0.0.0:3727 to accept connections from outside the host (Docker, k8s).
partitionsCONVERGEDB_PARTITIONSusizenumber of CPU cores1..=256Number of single-threaded convergence engine partitions.
coalescing_window_msCONVERGEDB_COALESCING_WINDOW_MSu64201..=10000Window over which writes to the same entity are merged before flushing (FR-11).
tombstone_retention_secsCONVERGEDB_TOMBSTONE_RETENTION_SECSu64300>= 1How long tombstones remain visible to late-joining subscribers (FR-22).
initial_table_capacityCONVERGEDB_INITIAL_TABLE_CAPACITYusize1024>= 1Initial capacity for per-kind entity hash tables.
subscriber_buffer_sizeCONVERGEDB_SUBSCRIBER_BUFFER_SIZEusize409664..=1048576Bounded notification buffer per subscriber (FR-20).
default_liveness_deadline_msCONVERGEDB_DEFAULT_LIVENESS_DEADLINE_MSu6430000unboundedDefault liveness deadline used when a source does not request its own (FR-7).
log_levelCONVERGEDB_LOG_LEVELstring"info"trace, debug, info, warn, errorDefault tracing level when RUST_LOG is unset. See note below.
checkpoint_interval_secsCONVERGEDB_CHECKPOINT_INTERVAL_SECSu64300>= 10Background compaction cadence (NFR-9).
data_dirCONVERGEDB_DATA_DIRstring"data"writable pathBase directory for WAL, checkpoints, and schemas. In Docker, mount a volume here.
[admin].enabledCONVERGEDB_ADMIN_ENABLEDbooltruetrue / falseWhether the admin HTTP API is started.
[admin].bindCONVERGEDB_ADMIN_BINDstring (host:port)main bind with port + 1parseable SocketAddrAdmin 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].endpointCONVERGEDB_OTEL_ENDPOINTstring (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_secsCONVERGEDB_OTEL_EXPORT_INTERVAL_SECSu6460>= 1Metrics export interval.
[otel].trace_sample_rateCONVERGEDB_OTEL_TRACE_SAMPLE_RATEf640.010.0..=1.0Head-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.

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:

Terminal window
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-server

The 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"

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 (or log_level in the TOML file) only sets the default level used when RUST_LOG is 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 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.