# Connect string reference

Configuration knobs for QuestDB native clients (QWP over WebSocket). Drives ingress, egress, multi-host failover, and store-and-forward.

A single connect string configures the QuestDB native client. The same
string format drives QWP (QuestDB Wire Protocol) ingress, QWP egress,
multi-host failover, and the store-and-forward substrate. All language
implementations share one option vocabulary, so the same connect string can
configure both ingress and egress in any client. Where an implementation
deviates, the affected key section and client page call it out.

One `ws::` / `wss::` connect string serves both the ingress sender and the
egress query client. Each direction reads the keys relevant to it and
ignores keys meant only for the other direction, so the same string
configures both without edits. The *Applies to:* tag on each section below
marks which direction a key affects.

For legacy InfluxDB Line Protocol (ILP) transports (`http`, `https`, `tcp`,
`tcps`), see the [ILP overview](/docs/connect/compatibility/ilp/overview/).

**On this page:**

- [Syntax](#syntax)
- [Common patterns](#common-patterns)
- [Recipes](#recipes)
- [Protocols and transports](#protocols-and-transports)
- [Authentication](#auth)
- [TLS](#tls)
- [Auto-flushing](#auto-flush)
- [Buffer sizing](#buffer)
- [Multi-host failover](#failover-keys)
- [Store-and-forward](#sf-keys)
- [Reconnect and failover](#reconnect-keys)
- [Durable ACK](#durable-ack)
- [Query client keys](#egress-keys)
- [Error handling](#error-handling)
- [Key index](#key-index)

## Syntax {#syntax}

A connect string has the form:

```
schema::key1=value1;key2=value2;
```

The `schema` selects the wire protocol and transport. The remaining
`key=value` pairs configure it. The trailing semicolon is optional but
recommended.

For example:

```
ws::addr=localhost:9000;username=admin;password=secret;
```

This selects the QWP WebSocket transport, connects to `localhost:9000`, and
provides basic-auth credentials.

For the list of supported schemas, see
[Protocols and transports](#protocols-and-transports).

### Grammar

- **Schema** — alphanumeric ASCII characters and underscore. Terminated by
  `::`.
- **Key** — alphanumeric ASCII characters and underscore. Terminated by `=`.
  Keys are case-sensitive; the canonical form is lowercase `snake_case`.
- **Value** — any character except control characters
  (U+0000–U+001F, U+007F–U+009F). Terminated by `;`.
- **Escaping** — to include a literal `;` in a value, double it (`;;`).

Example with an escaped semicolon in a password (the actual password value
is `p;ssw;rd`):

```
ws::addr=localhost:9000;username=admin;password=p;;ssw;;rd;
```

### Loading a connect string

The Java client accepts a connect string in three ways:

- From a string literal:

  ```java
  Sender sender = Sender.fromConfig("ws::addr=localhost:9000;");
  ```

- From an environment variable (reads `QDB_CLIENT_CONF`):

  ```java
  Sender sender = Sender.fromEnv();
  ```

- From the builder, which accepts the same option keys programmatically:

  ```java
  Sender sender = Sender.builder(Transport.WS)
      .address("localhost:9000")
      .build();
  ```

Other language clients expose equivalent entry points; see each
[client library page](/docs/connect/overview/#client-libraries) for the
per-language syntax.

## Common patterns {#common-patterns}

Canonical shapes for typical deployments. Extend each with auth, failover,
or store-and-forward options from the sections below.

### Local development (no auth, no TLS)

```
ws::addr=localhost:9000;
```

### Production with basic auth (TLS)

```
wss::addr=questdb.example.com:443;username=admin;password=secret;
```

### Production with a custom trust store

```
wss::addr=questdb.example.com:443;username=admin;password=secret;tls_roots=/etc/questdb/ca-roots;tls_roots_password=changeit;
```

### Ingest with store-and-forward across multiple nodes

```
wss::addr=node-a:9000,node-b:9000;sf_dir=/var/lib/myapp/qdb-sf;sender_id=ingest-1;
```

### Query (egress) preferring a replica in your zone

```
wss::addr=node-a:443,node-b:443;target=replica;zone=eu-west-1a;
```

### Tolerate a slow or restarting server at startup

```
ws::addr=node-a:9000;reconnect_max_duration_millis=120000;
```

The 2-minute reconnect budget covers both the *first* connect and any
subsequent reconnect: setting any explicit `reconnect_*` key implicitly
turns on `initial_connect_retry`. See
[Ingress reconnect](#reconnect-keys).

## Recipes {#recipes}

Goal-to-keys mapping. For complete connect-string templates, see
[Common patterns](#common-patterns). For per-key details (type, default,
caveats), follow the section links from the [Key index](#key-index).

| Goal                                              | Direction | Required keys                          | Optional / related                                                                          |
| ------------------------------------------------- | --------- | -------------------------------------- | ------------------------------------------------------------------------------------------- |
| Minimal connect string                            | both      | `addr`                                 | —                                                                                           |
| Enable TLS                                        | both      | `addr` with `wss` schema               | `tls_verify`, `tls_roots`, `tls_roots_password`                                             |
| Basic-auth credentials                            | both      | `username`, `password`                 | `auth_timeout_ms`                                                                           |
| Bearer-token credentials                          | both      | `token`                                | `auth_timeout_ms`                                                                           |
| Multi-host failover                               | both      | `addr=h1,h2,…`                         | `target`, `zone`, `reconnect_*` (ingress), `failover_*` (egress)                            |
| Query only the primary (freshest data)            | egress    | `target=primary`                       | —                                                                                           |
| Query only replicas (offload primary)             | egress    | `target=replica`                       | —                                                                                           |
| Zone-aware routing with DR last-resort            | egress    | `zone=<id>`                            | `target`                                                                                    |
| Tune ingest batching                              | ingress   | —                                      | Clients with auto-flush: `auto_flush_rows`, `auto_flush_interval`, `auto_flush_bytes`       |
| Disable auto-flush (manual `flush()` only)        | ingress   | `auto_flush=off`                       | —                                                                                           |
| Memory-buffered ingest (no disk durability)       | ingress   | (omit `sf_dir`)                        | `init_buf_size`, `max_buf_size`                                                             |
| Durable store-and-forward ingest                  | ingress   | `sf_dir`                               | `sender_id`, `sf_max_segment_bytes`, `sf_max_total_bytes`, `sf_append_deadline_millis`              |
| Run multiple senders sharing one `sf_dir`         | ingress   | `sf_dir`, `sender_id`                  | unique `sender_id` per sender                                                               |
| Orphan recovery for crashed senders               | ingress   | `drain_orphans=on`                     | `max_background_drainers`                                                                   |
| End-to-end durable acknowledgement                | ingress   | `request_durable_ack=on`               | `durable_ack_keepalive_interval_millis`                                                     |
| Tune ingress reconnect backoff                    | ingress   | —                                      | `reconnect_initial_backoff_millis`, `reconnect_max_backoff_millis`, `reconnect_max_duration_millis` (any of these also implies `initial_connect_retry=on`) |
| Force fail-fast on initial connect                | ingress   | `initial_connect_retry=off`            | overrides the implicit promotion from any explicit `reconnect_*` key                        |
| Retry initial connect in background               | ingress   | `initial_connect_retry=async`          | `reconnect_*`                                                                               |
| Fast `close()` without drain                      | ingress   | `close_flush_timeout_millis=0`         | —                                                                                           |
| Disable per-query egress failover                 | egress    | `failover=off`                         | —                                                                                           |
| Tune per-query egress failover                    | egress    | —                                      | `failover_max_attempts`, `failover_backoff_initial_ms`, `failover_backoff_max_ms`, `failover_max_duration_ms` |
| Configure async error inbox                       | both      | —                                      | `error_inbox_capacity`                                                                      |

## Protocols and transports {#protocols-and-transports}

*Applies to: ingress and egress.*

The schema prefix selects the QWP transport.

| Schema | Transport       | Default port | Notes                                                                                                                |
| ------ | --------------- | ------------ | -------------------------------------------------------------------------------------------------------------------- |
| `ws`   | WebSocket       | `9000`       | QWP over plain WebSocket. Use for development or trusted networks.                                                   |
| `wss`  | WebSocket + TLS | `9000`       | QWP over TLS-secured WebSocket. Recommended for production.                                                          |
| `udp`  | UDP             | `9007`       | Fire-and-forget metrics ingest, single table per datagram. |

The client applies the default port when `addr` omits `:port`. Note that
`wss` does **not** default to `443`: both `ws` and `wss` use `9000` unless
overridden.

QWP negotiates its protocol version during the WebSocket upgrade — clients
do not need to configure it.

## Authentication {#auth}

*Applies to: ingress and egress.*

QWP runs over WebSocket and uses HTTP-style credentials sent on the
WebSocket upgrade request.

- `username` — username for HTTP basic authentication. `user` is an accepted
  alias.
- `password` — password for HTTP basic authentication. `pass` is an accepted
  alias.
- `token` — bearer token sent as `Authorization: Bearer <token>`. Mutually
  exclusive with `username` / `password`. Token auth avoids the per-request
  overhead of basic auth and is the recommended path for Enterprise
  deployments.
- `auth_timeout_ms` — per-host upper bound on the upgrade response read.
  Does not cover TLS handshake or post-upgrade frame reads, which use OS or
  hard-coded defaults. Default: `15000` (15 s).
- `connect_timeout` — integer milliseconds, must be `> 0`. Applies to ingress
  and egress. Bounds the TCP connect phase for each endpoint, so a black-holed
  host in a multi-host `addr` no longer stalls the
  [endpoint walk](#failover-keys) until the OS connect timeout. Unset by
  default.

**Mutual TLS (mTLS).** Not supported. The client validates the server's
certificate against a trust store but cannot present a client certificate;
the TLS handshake is server-authenticated only. `tls_roots` /
`tls_roots_password` configure server-cert trust, not client identity. Use
`token=<token>` or `username=` / `password=` for client authentication.

## TLS {#tls}

*Applies to: ingress and egress.*

Selecting the `wss` schema enables TLS.

- `tls_verify` — controls server certificate verification. Options: `on`,
  `unsafe_off`. Default: `on`. `unsafe_off` disables verification; **use
  only for testing** — bypassing verification makes the connection
  vulnerable to MITM attacks. **Mutually exclusive with `tls_roots`** — see
  below.
- `tls_roots` — path to a file of trusted root certificates, used instead
  of the system trust store. If omitted, the client uses the system default
  trust store. The accepted on-disk formats are client-specific:

  | Client | Formats accepted at `tls_roots` |
  |---|---|
  | Java | PEM (default, no password), JKS, PKCS#12 |
  | Rust, C, C++, Python | PEM (default), JKS, PKCS#12 |
  | .NET | PKCS#12 / PFX |
  | Go | none — OS trust store only, both keys rejected at parse time |

- `tls_roots_password` — password for the `tls_roots` file. Required only for
  a JKS or PKCS#12 trust store; PEM needs no password. Setting it without
  `tls_roots` is an error.

:::caution `tls_roots` and `tls_verify=unsafe_off` cannot be combined

Supplying both is rejected at connect time:

```
tls_roots cannot be combined with tls_verify=unsafe_off; remove tls_verify to
use custom roots, or remove tls_roots to disable certificate validation
```

The two express opposite intents — one pins a private CA, the other switches
verification off entirely. Pick one. For a self-signed certificate in a test
environment, `tls_verify=unsafe_off` alone is enough; to actually validate
against your own CA, supply `tls_roots` and leave `tls_verify` at its default.

:::

PEM is the passwordless default path on Java, Rust, C, C++ and Python, so a
private CA needs no `keytool` import:

```text
wss::addr=db.example.com:9000;tls_roots=/etc/ssl/ca.pem;
```

Existing JKS and PKCS#12 trust stores keep working through
`tls_roots_password`, and PKCS#12 now also loads on Java 8.

:::note Client support varies

The Go client verifies against the operating-system trust store only and
**rejects both keys at parse time**; to trust a private CA there, install it in
the host trust store. On Rust, C, C++ and Python, `tls_roots_password` switches
the file to a Java keystore and is QWP/WebSocket only: other transports keep
PEM as the sole format. Check the relevant
[client library page](/docs/connect/overview/#client-libraries) for
specifics.

QuestDB does not support mutual TLS (client certificates) — the server
does not negotiate client certificates regardless of client. See
[Authentication](#auth) for the supported credential paths.

:::

See also the [server-side TLS configuration](/docs/security/tls/).

## Auto-flushing {#auto-flush}

*Applies to: ingress.*

The client buffers rows in memory and flushes them to the server in batches.
For clients that implement auto-flushing, these keys control when the client
sends the buffer without an explicit `flush()` call. The three triggers below
act independently: whichever threshold trips first sends the batch.

- `auto_flush` — global enable. Options: `on`, `off`. Default where supported:
  `on`.
  When `off`, the application must call `flush()` explicitly to send
  buffered rows.
- `auto_flush_rows` — flush when the buffered row count reaches this
  threshold. Set to `off` to disable. Default where supported: `1000`.
- `auto_flush_interval` — flush when this many milliseconds have elapsed
  since the first buffered row. The client evaluates the interval on the
  next `at()` / `flush()` call, not on a wall-clock timer. Set to `off` to
  disable. Default where supported: `100` (100 ms).
- `auto_flush_bytes` — flush when the encode buffer reaches this byte
  size. Set to `off` to disable. Accepts
  [size suffixes](#size-suffixes). **The default differs by client**: Java
  ships it **disabled** (`0`), .NET defaults to `8m` (8 MiB), and Rust, C and
  C++ reject the key outright. A Java application that assumes an 8 MiB byte
  trigger is active will size batches expecting a flush that never fires.
  When set to a positive value, the
  client clamps the effective threshold down to 90% of the server-
  advertised `X-QWP-Max-Batch-Size` at handshake (one-way: the client
  keeps a configured value already below the advertised cap). The 10%
  margin absorbs encoding overhead such as schema and dict-delta bytes.
  Setting `off` opts out of byte-based auto-flush entirely — the
  handshake clamp does not re-enable it, and the application takes
  responsibility for not producing oversized batches. Older servers
  that do not advertise the header leave the configured value
  untouched.

:::note Rust support

The Rust client currently does not implement auto-flushing. It accepts
`auto_flush=off` for compatibility and rejects `auto_flush=on`,
`auto_flush_rows`, `auto_flush_interval`, and `auto_flush_bytes`. Rust
applications must call `flush()` explicitly; see the
[Rust client page](/docs/connect/clients/rust/#sending-data-column-major).

:::

## Buffer sizing {#buffer}

*Applies to: ingress (encode buffer).*

These keys control the in-memory row buffer that the client uses before
flushing.

- `init_buf_size` — initial buffer size in bytes. Default: `65536`
  (64 KiB). Accepts [size suffixes](#size-suffixes).
- `max_buf_size` — maximum buffer size; the buffer grows up to this cap.
  Default: `104857600` (100 MiB). Accepts size suffixes.
- `max_name_len` — maximum allowed length of a table or column name in
  bytes. Default: `127`.
- `max_datagram_size` — UDP only. Maximum datagram size; defaults to a
  value below typical Ethernet MTU.

### Size suffixes {#size-suffixes}

Size-typed values (`init_buf_size`, `max_buf_size`, `sf_max_segment_bytes`,
`sf_max_total_bytes`) accept JVM-style unit suffixes. Suffixes are
case-insensitive and 1024-based, matching `-Xmx` conventions:

| Suffix         | Meaning           | Example      |
| -------------- | ----------------- | ------------ |
| *(none)*       | bytes             | `65536`      |
| `k` or `kb`    | KiB (× 1024)      | `64k`        |
| `m` or `mb`    | MiB (× 1024²)     | `4m`, `4mb`  |
| `g` or `gb`    | GiB (× 1024³)     | `1g`, `10gb` |
| `t` or `tb`    | TiB (× 1024⁴)     | `1t`         |

## Multi-host failover {#failover-keys}

*Applies to: ingress and egress. The [Role filter and zone preference](#role-filter-and-zone-preference)
sub-section is egress only.*

:::note QuestDB Enterprise

Multi-host failover requires QuestDB Enterprise. OSS is single-node — there
is no secondary server to fail over to.

:::

The connect string accepts multiple `host:port` pairs in `addr`. The
parser accepts two syntaxes and accumulates entries across both:

```
wss::addr=node-a:9000,node-b:9000,node-c:9000;
```

```
ws::addr=node-a:9000;addr=node-b:9000;addr=node-c:9000;
```

The parser rejects empty entries (`,,`, or leading / trailing commas), and
also rejects a **duplicate** `host:port` — listing the same endpoint twice
fails at connect time with `duplicate addr entry: <host:port>`. This catches
the common case of templating an address list that collapses to the same node,
which would otherwise silently halve your effective failover breadth.

The I/O loop rotates through the endpoints on every reconnect attempt
within a single outage budget. When the server rejects the connection
because the current host is in the wrong role, the client treats it as
failover input and immediately tries the next endpoint without waiting for
backoff.

### Role filter and zone preference

Both `target` and `zone` apply to **egress only**. QuestDB is currently a
single-primary cluster: ingress automatically follows the primary across
the host list and adapts when the primary moves to another node. Ingress
silently accepts these keys and ignores them.

- `target` — server-role filter applied per endpoint after the upgrade
  reads `SERVER_INFO`. Options:
  - `any` (default) — no preference; route to any healthy endpoint.
  - `primary` — route only to the writer. Use when queries must see the
    most recent data; replicas are eventually consistent and may lag the
    primary.
  - `replica` — route only to replicas. Use for historical or analytical
    queries to avoid contending with the ingest traffic the primary is
    handling.

  The client skips endpoints whose role does not match the filter.

- `zone` — client zone identifier (opaque, case-insensitive — e.g.
  `eu-west-1a`, `dc-amsterdam`). When set, egress prefers endpoints whose
  server-advertised `zone_id` matches the client's. Mismatched-zone
  endpoints — typically a remote DR replica — drop to a lower priority
  tier; the client routes to them only as a last resort, when every
  same-zone endpoint is unhealthy. With `target=primary`, zone preference
  collapses: the writer is followed regardless of zone.

[Client failover](/docs/high-availability/client-failover/concepts/)
documents the full behavioural model — host picker policy, host-health
states, error classification, and backoff schedule. The
[High Availability section](/docs/high-availability/overview/) covers
server-side HA separately.

Related: [Reconnect and failover](#reconnect-keys),
[Store-and-forward](#sf-keys).

:::warning Enable DEDUP on tables ingested through failover

On unplanned failover — when the primary dies before issuing a durable
ACK — the client replays unacknowledged frames against the new primary.
Without [DEDUP](/docs/concepts/deduplication/) on the target table, those
replays can produce duplicate rows. Tables ingested through a multi-host
failover connect string **must** declare `DEDUP UPSERT KEYS(...)` covering
row identity. See [Delivery semantics](/docs/concepts/delivery-semantics/)
for the full at-least-once / exactly-once model.

:::

## Store-and-forward {#sf-keys}

*Applies to: ingress.*

Store-and-forward (SF) is an opt-in durability substrate available on QWP /
WebSocket. The client persists outgoing frames to disk before sending; the
server's cumulative ACK trims acknowledged segments. If the connection drops
or the client process restarts, the I/O thread silently reconnects and
replays whatever is still on disk.

To enable SF mode, set `sf_dir`. Without it, the client runs a memory-only
equivalent — same architecture, no durability across restarts.

### Storage

- `sf_dir` — parent directory under which the slot lives. The slot path is
  `<sf_dir>/<sender_id>/`. Required for SF mode; omit for memory-only mode.
  Path handling:
  - Taken verbatim. Absolute paths recommended for production; relative
    paths resolve against the process working directory.
  - The client does **not** expand shell-style syntax such as `~`.
  - The client creates the leaf directory if it is missing, but the parent
    must already exist — it does not create paths recursively.
- `sender_id` — slot identity. The slot lives at `<sf_dir>/<sender_id>/`,
  used verbatim as the directory name. Allowed characters: letters,
  digits, `_`, `-`. No path separators, no `.`, no spaces. Two senders
  sharing the same `sender_id` collide on the slot lock — the second one
  fails fast. Default: `default`.
  For pooled senders, `sender_id` is the slot **base** rather than a literal
  directory: the pool mints one directory per slot, and the un-suffixed path
  applies only to non-pooled senders. The minted name is client-specific:

  | Client | Minted slot directory |
  |---|---|
  | Java (`QuestDB` facade) | `<sf_dir>/<sender_id>-<index>/` |
  | Rust, C, C++ (`QuestDb` / `questdb::pool` / `questdb_db`) | `<sf_dir>/<sender_id>-ingest-<index>/` |

  The minted names belong to that pool's namespace, so pools sharing one
  `sf_dir` need distinct bases; the slot-in-use error covers both cases
  (another process or pool holds the slot).
- `sf_durability` — disk durability mode. `memory` (the default) and
  `periodic` both ship. `periodic` requires `sf_dir` and checkpoints published
  frames in the background at `sf_sync_interval_millis`. `flush` and `append`
  are reserved: they parse but are rejected at `build()`.

  Reach for `periodic` when you must survive host loss. `memory` mode is
  process-crash durable but **not** host-crash durable, because the page cache
  is lost on power failure.

  The .NET client is the exception: it accepts only `memory` and rejects
  anything else at parse time.
- `sf_sync_interval_millis` — cadence at which `sf_durability=periodic`
  checkpoints published frames to stable storage. Default: `5000`. Requires
  `sf_durability=periodic`; rejected otherwise. The configured interval is a
  floor, since scheduler and storage latency add to it.
- `sf_max_segment_bytes` — per-segment rotation threshold. Must be ≥ the largest
  single flushed frame. Default: `4 MiB` (`4m`). Accepts
  [size suffixes](#size-suffixes).
- `sf_max_total_bytes` — hard cap on per-slot storage. When the slot
  reaches the cap, `append()` blocks until ACKs trim space (see
  `sf_append_deadline_millis`). Defaults: `10 GiB` (`10g`) in SF mode,
  `128 MiB` (`128m`) in memory mode. Accepts size suffixes.

### Sender restart and replay

SF persists outgoing frames and the durable-ack watermark to disk under
`<sf_dir>/<sender_id>/`.

**Sender creation triggers recovery.** When the application
instantiates a new sender — `Sender.fromConfig(...)`, `Sender.fromEnv()`,
or the builder — the client analyses the on-disk state under `sf_dir`
before returning control. There is no background daemon; replay is part
of the Sender lifecycle.

To resume from the previous session's buffer after a restart — clean
exit, SIGKILL, host crash, or reboot — instantiate a new sender with the
**same** `sf_dir` and `sender_id`:

1. The new sender acquires the slot's POSIX `flock` (`LockFileEx` on
   Windows). If the previous process is still alive and holds the lock,
   the new sender fails fast with `sf slot already in use`. The kernel
   releases the lock on process exit, even after SIGKILL, so a crashed
   sender does not leave the slot stuck.
2. Recovery reads the persisted ack watermark and replays every on-disk
   segment past it against the server. Replay runs on the I/O thread in
   parallel with the application's new `append()` calls — it does not
   block the application.

If `sf_dir` is a relative path, ensure the process resolves it the same
way after restart (typically: use an absolute path).

For a *different* sender to pick up an **abandoned** slot — the original
is never coming back — see [Orphan recovery](#orphan-recovery) below.

**At-least-once delivery.** Replay can re-send frames the server already
accepted but did not durable-acknowledge before the previous sender died.
To prevent duplicate rows in the target table, declare
[DEDUP](/docs/concepts/deduplication/) `UPSERT KEYS(...)` covering row
identity. See [Delivery semantics](/docs/concepts/delivery-semantics/) for
the full model and recipe.

### Backpressure

- `sf_append_deadline_millis` — maximum time `append()` waits for trim to
  free space when the slot hits the cap. If the deadline fires, the call
  throws. Default: `30000` (30 s).

### Orphan recovery

When `drain_orphans=on`, the new sender scans `<sf_dir>/*` at startup for
sibling slots that are unlocked and contain unacked data. The scan runs
as part of Sender creation (alongside the same-slot recovery above). The
sender locks each orphan slot, drains it on its own dedicated connection,
and releases it — **multiple orphans drain in parallel**, up to
`max_background_drainers` concurrent drains.

- `drain_orphans` — `on` enables the orphan drainer pool. Default: `off`.
- `max_background_drainers` — maximum concurrent drainers. Default: `4`.

For delivery semantics, architecture, and tradeoffs (at-least-once
guarantees, DEDUP requirements, segment-granular trim), see
[Store-and-forward concepts](/docs/high-availability/store-and-forward/concepts/).

## Reconnect and failover {#reconnect-keys}

*Applies to: ingress and egress (separate key families).*

QWP / WebSocket has two distinct recovery loops, each with its own knob
family. The **ingress** cursor-engine reconnect loop runs continuously for
the lifetime of the sender. The **egress** per-`Execute()` failover loop
runs once per query.

### Ingress reconnect

These keys control the cursor-engine reconnect loop used by QWP ingest.
SF mode and memory-only mode share the same loop. A **running** sender
retries a transport outage indefinitely with capped exponential backoff —
there is no wall-clock give-up: the whole point of the buffering
architecture is that a producer survives an arbitrarily long outage.

- `reconnect_initial_backoff_millis` — initial wait between reconnect
  attempts. Backoff grows exponentially up to `reconnect_max_backoff_millis`.
  Default: `100`. Setting this enables `initial_connect_retry=on` implicitly;
  see below.
- `reconnect_max_backoff_millis` — cap on per-attempt backoff.
  Default: `5000` (5 s). Setting this enables
  `initial_connect_retry=on` implicitly; see below.
- `reconnect_max_duration_millis` — time budget for the **blocking sync
  initial connect** (`initial_connect_retry=on`): once exceeded, the
  constructor gives up and returns the error. The running loop and the
  `async` initial connect never consult it. Default: `300000` (5 min).
  Setting this enables `initial_connect_retry=on` implicitly; see below.
- `initial_connect_retry` — whether the client retries the initial connect
  attempt on failure.
  - `off` (default, alias `false`) — fail fast on initial connect failure.
  - `on` (aliases `sync`, `true`) — retry synchronously on the user
    thread, up to `reconnect_max_duration_millis`.
  - `async` — return the `Sender` immediately; the I/O thread retries in
    the background indefinitely, surfacing only genuine terminal failures
    (auth reject, durable-ack mismatch) via the error inbox.

  **Implicit promotion.** Setting any explicit `reconnect_*` key without
  also choosing an `initial_connect_retry` mode promotes
  `initial_connect_retry` to `on` automatically, so the budget also covers
  the *first* connect attempt — without the promotion,
  `reconnect_max_duration_millis` would be inert. To keep fail-fast
  behaviour on the first connect while still tuning the backoff, set
  `initial_connect_retry=off` explicitly; the explicit setting wins.
- `close_flush_timeout_millis` — `close()` blocks up to this many
  milliseconds waiting for buffered frames to drain. Set to `0` or `-1` for
  fast close (skip the drain). **The default differs by client**: `60000`
  (60 s) on Java and .NET, `5000` (5 s) on Rust, C, C++ and Python, which
  share the same Rust core.

  This is the shutdown data-loss window. Setting it to `0` skips the drain
  entirely and drops un-ACKed batches on every clean shutdown.

Auth failures during reconnect (authentication rejected, version mismatch,
durable-ack mismatch, non-101 upgrade without a role hint) are immediately
terminal — the loop does not retry them.

### Egress failover {#egress-failover}

These keys control the per-`Execute()` reconnect loop on the QWP query
client. Each query has its own budget; the loop resets between queries.
Requires QuestDB Enterprise (multi-host).

- `failover` — master switch. `on` (default) or `off`. When `off`,
  transport errors surface directly through `onError` without retry.
- `failover_max_attempts` — cap on reconnects per `Execute()` (initial
  attempt + `N − 1` failovers). Default: `8`.
- `failover_backoff_initial_ms` — first post-failure sleep. Default: `50`.
- `failover_backoff_max_ms` — cap on per-attempt sleep. Default: `1000`
  (1 s).
- `failover_max_duration_ms` — total wall-clock budget per `Execute()`.
  Default: `30000` (30 s). Set to `0` for unbounded.

## Durable ACK {#durable-ack}

*Applies to: ingress.*

:::note QuestDB Enterprise

Durable ACK requires QuestDB Enterprise. OSS is single-node and does not
ship WALs off-box, so the server-side durability-acknowledgement signal
that drives this protocol is enterprise-only.

:::

QuestDB Enterprise ships Write-Ahead Logs (WALs) from the primary to an
object store or another file system — typically over the network. After
durably shipping a WAL, the server emits a `STATUS_DURABLE_ACK` frame to
the store-and-forward client; the client marks that frame's FSN as durable
only after this acknowledgement arrives.

The benefit: if the primary dies before shipping a WAL, the client still
holds the corresponding frames in its SF buffer and replays them against
the new primary on failover — closing the data-loss window that a
transport-level OK ACK alone cannot close.

- `request_durable_ack` — when `on`, the client gates trim on
  `STATUS_DURABLE_ACK` frames from the server, suppressing OK-driven trim.
  Default: `off`.
- `durable_ack_keepalive_interval_millis` — interval at which the client
  emits keepalive PINGs while waiting for durable-ack frames. Required
  because the server only flushes pending durable acks on inbound recv
  events. Default: `200` (ms). Set to `0` or a negative value to disable.

See the [QWP Egress (WebSocket)](/docs/connect/wire-protocols/qwp-egress-websocket/)
wire protocol for the underlying mechanism.

## Query client keys {#egress-keys}

*Applies to: egress (query client).*

The QWP query client's connect string (the egress / `QwpQueryClient`
path) accepts these keys. The Sender (ingress) silently consumes the same
keys so that the Sender and the `QwpQueryClient` can share a single
connect string without an "unknown configuration key" error — the Sender
does not interpret the values. Range, enum, and type checks happen on the
egress side; the Sender silently accepts even a value the
`QwpQueryClient` parser would reject.

- `compression` — result-batch compression the client advertises. Options:
  `raw` (default — no compression; the client omits the accept-encoding
  header, so pre-compression servers see an unchanged handshake), `zstd`
  (demand zstd), `auto` (accept zstd if the server offers it).
- `compression_level` — zstd level hint. Range `1`–`22` (server clamps to
  `1`–`9`). Default `1` — the cheapest server-side CPU; raise it if you
  measure a meaningful ratio improvement on your payload and the server has
  the headroom. Ignored when `compression=raw`.
- `initial_credit` — byte-credit flow-control budget. `0` (default) means
  unbounded: the server streams as fast as the network allows. Set a
  non-zero budget to bound server push on a memory-constrained client.
- `max_batch_rows` — upper bound on rows per result batch. Range
  `1`-`1048576`; out-of-range values fail at parse time. Defaults to the
  server's own limit when unset.
- `client_id` — free-form client identifier sent on the upgrade as
  `X-QWP-Client-Id`, for example `java/1.0.2`. Default is client-specific.
- `query_close_timeout_ms` — bounds the close-path cleanup drain (closing a
  cursor mid-result-set, breaking out of iteration) before the client
  declares the connection desynced and discards it. Positive integer
  milliseconds; default `5000` (5 s).
- `buffer_pool_size` — number of decoded result-batch buffers the I/O
  thread keeps in rotation. Sets the in-flight window between the
  receive/decode loop and the user's `onBatch` callback: the I/O thread
  decodes at most this many batches ahead of the consumer. Default: `4`.
  Minimum: `1`
  (no read-ahead — the I/O thread waits for each `releaseBuffer()` call
  before decoding the next batch). Each buffer reserves about 64 KiB of
  native scratch, so raising the value grows pinned memory linearly. When
  the pool drains, the I/O thread parks and the TCP receive window closes,
  applying back-pressure to the server. Set it before `connect()`.

Equivalent options exist on the query client's builder API (for example,
`WithQwpQueryCompression`, `WithQwpQueryCompressionLevel`,
`WithQwpQueryInitialCredit` in the Go client). See the
[client library page](/docs/connect/overview/#client-libraries) for the
per-language names.

## Connection pool {#pool-keys}

*Applies to: the pooled facade (`QuestDB.connect`, `questdb::pool`,
`QuestDb::connect`, `questdb.connect`, `qdb.NewQuestDB`,
`QuestDBClient.Connect`).*

Every client now leads with a pooled facade, so these keys are a first-contact
concern. The `Sender` and query-client parsers accept and ignore them; the
facade reads them off the string. Each has an equivalent builder setter, and an
explicit setter always wins over the string.

- `sender_pool_min` — senders kept open even when idle. `0` lets the pool close
  them all. Default: `1`.
- `sender_pool_max` — maximum senders the pool opens. Default: `4`.
- `query_pool_min` — query clients kept open even when idle. Default: `1`.
- `query_pool_max` — maximum query clients the pool opens, which also caps
  total in-flight queries. Default: `4`.
- `acquire_timeout_ms` — how long a borrow waits for a free connection once the
  pool is at `max`, before throwing. Default: `5000`.
- `idle_timeout_ms` — how long an unused connection stays open before the
  housekeeper closes it, never going below `min`. `0` keeps idle connections
  forever. Default: `60000`.
- `max_lifetime_ms` — maximum age of a connection; the housekeeper closes and
  reopens older ones once idle. `0` means no age limit. Default: `1800000`
  (30 min).
- `housekeeper_interval_ms` — how often the housekeeper checks for idle and
  over-age connections. Default: `5000`.
- `lazy_connect` — when `on`, the pool defers opening its first connection
  until the first borrow, so construction succeeds against a server that is
  down. This is the supported way to tolerate a server that starts after your
  application. Default: `off`.

## Error handling {#error-handling}

*Applies to: ingress and egress.*

The QWP / WebSocket I/O loop reports errors via an asynchronous inbox
consumed by the application.

- `error_inbox_capacity` — bounded capacity for async error notifications.
  Must be ≥ `16`. Overflow drops the oldest entry and bumps a
  `droppedErrorNotifications` counter. Default: `256`.

:::caution Accepted, but not applied by every client

Every client's parser accepts the six `on_*_error` keys below, but only
clients that implement the policy layer act on them. **In the Java reference
client they are currently accepted no-ops** — setting
`on_write_error=retriable_other` parses cleanly and changes nothing. .NET does
implement them, via `SenderErrorPolicy` and `SenderErrorCategory`. The
category table and precedence model below describe the target contract.

:::

The following per-category keys select the **error policy** for each class
of server rejection. There is **no drop policy**: the client never silently
discards data. The client either replays a rejected batch — `retriable`
recycles the connection and replays from the acknowledged watermark;
`retriable_other` does the same but rotates to the next endpoint (the node
cannot serve writes at all) — or halts loudly with the bytes preserved in
the store-and-forward log (`terminal`). A frame that the server keeps
rejecting with no ack progress escalates to a terminal via the
poison-frame detector (`max_frame_rejections`, default `4`).

- `on_server_error` — global default for server-reject status frames.
  Accepts `auto` \| `terminal` \| `retriable` \| `retriable_other`.
  Default: `auto` (applies the built-in per-category defaults listed below).
- `on_schema_error` — schema-validation errors. Default: `terminal`
  (deterministic under byte-identical replay).
- `on_parse_error` — malformed-payload errors. Default: `terminal`.
- `on_internal_error` — unexpected server-side faults. Default: `retriable`.
- `on_security_error` — ACL denial on a writable node. Default: `terminal`.
- `on_write_error` — transient write failures (disk pressure, suspended
  table). Default: `retriable`.
- `max_frame_rejections` — poison-frame detector threshold: consecutive
  server rejections (retriable NACK, or non-orderly close after a send) of
  the same head-of-line frame, with no ack progress in between, before the
  sender latches a terminal instead of replaying forever. Integer ≥ 1;
  default `4`.

**Resolution precedence**, from highest to lowest (as documented on
`SenderError.Policy`):

1. Builder error-policy resolver — full programmatic control.
2. Builder per-category policy override.
3. The connect-string per-category key (e.g. `on_schema_error`) — overrides
   the global default when set.
4. `on_server_error` — the global default; when left at `auto` the built-in
   per-category defaults above apply.

`PROTOCOL_VIOLATION` is always terminal and `UNKNOWN` always retriable (fail
open: a status byte from a newer server degrades to retry, not to a dead
sender); neither can be overridden. Per-client wiring of the override surface
may lag the spec — check your client's documentation for which of the
resolver / per-category / connect-string layers it exposes. For the full
model see the
[NACK policy design](https://github.com/questdb/java-questdb-client/blob/main/design/qwp-nack-policy-v2.md)
and the `SenderError.Policy` docs in the client source.

## Key index {#key-index}

Alphabetical list of every option. The Section column links to the full
description and behaviour notes.

| Key                                     | Type                          | Default                       | Section                                                       |
| --------------------------------------- | ----------------------------- | ----------------------------- | ------------------------------------------------------------- |
| `acquire_timeout_ms`                    | int (ms)                      | `5000`                        | [Connection pool](#pool-keys)                                 |
| `addr`                                  | `host:port[,host:port…]`      | required                      | [Multi-host failover](#failover-keys)                         |
| `auth_timeout_ms`                       | int (ms)                      | `15000`                       | [Authentication](#auth)                                       |
| `auto_flush`                            | enum (`on` / `off`)           | `on` (Rust: only `off`)       | [Auto-flushing](#auto-flush)                                  |
| `auto_flush_bytes`                      | size                          | Java `0` (off) / .NET `8m` (Rust: rejected) | [Auto-flushing](#auto-flush)                    |
| `auto_flush_interval`                   | int (ms) / `off`              | `100` (Rust: rejected)        | [Auto-flushing](#auto-flush)                                  |
| `auto_flush_rows`                       | int / `off`                   | `1000` (Rust: rejected)       | [Auto-flushing](#auto-flush)                                  |
| `buffer_pool_size`                      | int (≥ 1)                     | `4`                           | [Query client keys](#egress-keys)                             |
| `catch_up_cap_gap_min_escalation_window_millis` | int (ms)              | `300000` (5 min)              | [Store-and-forward](#sf-keys)                                 |
| `client_id`                             | string                        | client-specific               | [Query client keys](#egress-keys)                             |
| `close_flush_timeout_millis`            | int (ms)                      | Java/.NET `60000` / Rust, C, C++, Python `5000` | [Ingress reconnect](#reconnect-keys)        |
| `compression`                           | enum (`raw` / `zstd` / `auto`) | `raw`                        | [Query client keys](#egress-keys)                             |
| `compression_level`                     | int (`1`–`22`)                | `1`                           | [Query client keys](#egress-keys)                             |
| `connect_timeout`                       | int (ms, `> 0`)               | unset                         | [Authentication](#auth)                                       |
| `connection_listener_inbox_capacity`    | int (≥ 1)                     | `64` (Java) · `256` (Go, .NET) · not supported by Rust, C/C++, Python | [Error handling](#error-handling)        |
| `drain_orphans`                         | enum (`on` / `off`)           | `off`                         | [Store-and-forward](#sf-keys)                                 |
| `durable_ack_keepalive_interval_millis` | int (ms)                      | `200`                         | [Durable ACK](#durable-ack)                                   |
| `error_inbox_capacity`                  | int (≥ 16)                    | `256`                         | [Error handling](#error-handling)                             |
| `failover`                              | enum (`on` / `off`)           | `on`                          | [Egress failover](#reconnect-keys)                            |
| `failover_backoff_initial_ms`           | int (ms)                      | `50`                          | [Egress failover](#reconnect-keys)                            |
| `failover_backoff_max_ms`               | int (ms)                      | `1000`                        | [Egress failover](#reconnect-keys)                            |
| `failover_max_attempts`                 | int                           | `8`                           | [Egress failover](#reconnect-keys)                            |
| `failover_max_duration_ms`              | int (ms)                      | `30000`                       | [Egress failover](#reconnect-keys)                            |
| `init_buf_size`                         | size                          | `65536` (64 KiB)              | [Buffer sizing](#buffer)                                      |
| `initial_connect_retry`                 | enum (`off` / `on` / `async`) | `off` (auto-promoted to `on` when any explicit `reconnect_*` key is set) | [Ingress reconnect](#reconnect-keys)                          |
| `initial_credit`                        | int (bytes)                   | `0` (unbounded)               | [Query client keys](#egress-keys)                             |
| `housekeeper_interval_ms`               | int (ms)                      | `5000`                        | [Connection pool](#pool-keys)                                 |
| `idle_timeout_ms`                       | int (ms)                      | `60000` (`0` ⇒ infinite)      | [Connection pool](#pool-keys)                                 |
| `lazy_connect`                          | enum (`on` / `off`)           | `off`                         | [Connection pool](#pool-keys)                                 |
| `max_background_drainers`               | int                           | `4`                           | [Store-and-forward](#sf-keys)                                 |
| `max_batch_rows`                        | int (`1`–`1048576`)           | server default                | [Query client keys](#egress-keys)                             |
| `max_lifetime_ms`                       | int (ms)                      | `1800000` (`0` ⇒ infinite)    | [Connection pool](#pool-keys)                                 |
| `max_buf_size`                          | size                          | `104857600` (100 MiB)         | [Buffer sizing](#buffer)                                      |
| `max_datagram_size`                     | size                          | (UDP) below typical MTU       | [Buffer sizing](#buffer)                                      |
| `max_name_len`                          | int                           | `127`                         | [Buffer sizing](#buffer)                                      |
| `max_frame_rejections`                  | int (≥ 1)                     | `4`                           | [Error handling](#error-handling)                             |
| `on_internal_error`                     | enum                          | `retriable`                   | [Error handling](#error-handling)                             |
| `on_parse_error`                        | enum                          | `terminal`                    | [Error handling](#error-handling)                             |
| `on_schema_error`                       | enum                          | `terminal`                    | [Error handling](#error-handling)                             |
| `on_security_error`                     | enum                          | `terminal`                    | [Error handling](#error-handling)                             |
| `on_server_error`                       | enum                          | `auto`                        | [Error handling](#error-handling)                             |
| `on_write_error`                        | enum                          | `retriable`                   | [Error handling](#error-handling)                             |
| `pass`                                  | string                        | unset                         | [Authentication](#auth) (alias of `password`)                 |
| `password`                              | string                        | unset                         | [Authentication](#auth)                                       |
| `poison_min_escalation_window_millis`   | int (ms)                      | `5000`                        | [Error handling](#error-handling)                             |
| `query_close_timeout_ms`                | int (ms)                      | `5000`                        | [Query client keys](#egress-keys)                             |
| `query_pool_max`                        | int                           | `4`                           | [Connection pool](#pool-keys)                                 |
| `query_pool_min`                        | int                           | `1`                           | [Connection pool](#pool-keys)                                 |
| `reconnect_initial_backoff_millis`      | int (ms)                      | `100`                         | [Ingress reconnect](#reconnect-keys)                          |
| `reconnect_max_backoff_millis`          | int (ms)                      | `5000`                        | [Ingress reconnect](#reconnect-keys)                          |
| `reconnect_max_duration_millis`         | int (ms)                      | `300000` (5 min)              | [Ingress reconnect](#reconnect-keys)                          |
| `request_durable_ack`                   | enum (`on` / `off`)           | `off`                         | [Durable ACK](#durable-ack)                                   |
| `sender_id`                             | string                        | `default`                     | [Store-and-forward](#sf-keys)                                 |
| `sender_pool_max`                       | int                           | `4`                           | [Connection pool](#pool-keys)                                 |
| `sender_pool_min`                       | int                           | `1`                           | [Connection pool](#pool-keys)                                 |
| `sf_append_deadline_millis`             | int (ms)                      | `30000` (30 s)                | [Store-and-forward](#sf-keys)                                 |
| `sf_dir`                                | path                          | unset (memory mode)           | [Store-and-forward](#sf-keys)                                 |
| `sf_durability`                         | enum (`memory` / `periodic`)  | `memory` (.NET: `memory` only) | [Store-and-forward](#sf-keys)                                |
| `sf_max_segment_bytes`                  | size                          | `4 MiB`                       | [Store-and-forward](#sf-keys)                                 |
| `sf_max_total_bytes`                    | size                          | `128 MiB` mem / `10 GiB` SF   | [Store-and-forward](#sf-keys)                                 |
| `sf_sync_interval_millis`               | int (ms)                      | `5000`                        | [Store-and-forward](#sf-keys)                                 |
| `target`                                | enum (`any` / `primary` / `replica`) | `any`                  | [Multi-host failover](#failover-keys)                         |
| `tls_roots`                             | path                          | system trust store            | [TLS](#tls)                                                   |
| `tls_roots_password`                    | string                        | unset (JKS / PKCS#12 only)    | [TLS](#tls)                                                   |
| `tls_verify`                            | enum (`on` / `unsafe_off`)    | `on`                          | [TLS](#tls)                                                   |
| `token`                                 | string                        | unset                         | [Authentication](#auth)                                       |
| `transaction`                           | enum (`on` / `off`)           | `off`                         | [Store-and-forward](#sf-keys)                                 |
| `user`                                  | string                        | unset                         | [Authentication](#auth) (alias of `username`)                 |
| `username`                              | string                        | unset                         | [Authentication](#auth)                                       |
| `zone`                                  | string                        | unset                         | [Multi-host failover](#failover-keys)                         |

:::note Per-client divergence

All clients share one option *vocabulary*, not one set of *defaults*. Where a
default differs it is split above; `auto_flush_bytes`,
`close_flush_timeout_millis` and `sf_durability` are the ones that bite most
often. Do not assume a value read here applies to your language without
checking its [client page](/docs/connect/overview/#client-libraries).

:::
