EV-ClickHouse

 view release on metacpan or  search on metacpan

README.md  view on Meta::CPAN


- on\_trace => sub { my ($message) = @\_ }

    Debug trace callback. Called with internal state-machine messages
    (connect, dispatch, disconnect). Useful for diagnosing protocol issues.

- on\_failover => sub { my ($old\_host, $old\_port, $new\_host, $new\_port, $msg) = @\_ }

    Multi-host only. Fires after the failover wrapper rotates to the next
    host in the `hosts => [...]` list, with the old and new (host, port)
    pair plus the triggering error message. Use it for metrics ("which host
    am I on?") or to log host transitions. Fires before the user's `on_error`.

**Options:**

- compress => 0 | 1

    Enable compression: gzip on HTTP (request and response), LZ4 with CityHash
    checksums on the native protocol. Default: `0`. Native compression
    requires liblz4 at build time.

- session\_id => $id

    HTTP session id for stateful operations (temporary tables, SET, etc.).
    Native protocol has stateful sessions intrinsically; this option is HTTP-only.

- connect\_timeout => $seconds

    TCP/TLS connection timeout. `0` (default) means no timeout. Floating
    point allowed.

- query\_timeout => $seconds

    Default per-query timeout applied to every query and insert. The query
    callback receives a `timeout` error if exceeded. Override per-call via
    the `query_timeout` key in the settings hashref.

- max\_query\_size => $bytes

    Client-side guard: croak before sending any query whose SQL text exceeds
    this many bytes. `0` (default) disables the check. Useful as a
    last-resort defense against accidentally sending unbounded strings.

- max\_recv\_buffer => $bytes

    Defensive ceiling on the response. The cap applies to the raw recv
    buffer (every protocol), the chunked-decoded body (HTTP), and the
    gzip-decompressed body (HTTP), so the same upper bound applies to the
    user-visible payload regardless of transport encoding. On overflow the
    query callback receives an appropriate error ("recv buffer overflow",
    "chunked response too large", or "gzip body exceeds max\_recv\_buffer")
    and the connection is torn down so no subsequent query can slip past
    the cap on the same socket. `0` (default) keeps the historical
    no-cap behaviour (still bounded internally by a hard 128 MB ceiling
    on compressed paths). Recommended in production when the schema is
    constrained and you want a hard upper bound (e.g.
    `128 * 1024 * 1024` for 128 MB).

- http\_basic\_auth => 0 | 1

    HTTP only. When set, send credentials as
    `Authorization: Basic base64(user:password)` instead of the default
    `X-ClickHouse-User` / `X-ClickHouse-Key` header pair. Use this when
    the connection passes through an HTTP gateway (nginx, Envoy, ...) that
    strips the X-ClickHouse-\* headers but forwards Basic auth verbatim.
    Default: `0`.

- auto\_reconnect => 0 | 1

    Reconnect automatically on connection loss. Default: `0`. When enabled,
    queued (unsent) queries are preserved across reconnects; in-flight queries
    receive an error.

    The reconnect path covers TCP/TLS connect failures, `connect_timeout`
    or `query_timeout` expiry, and any clean server-side EOF (idle or
    mid-request). Mid-query I/O errors (ECONNRESET / EPIPE) and a malformed
    native ServerHello are **not** retried - they typically indicate a
    misconfigured peer or client-side bug that retry would only loop on.
    Combine with `reconnect_max_attempts` for an explicit ceiling.

- settings => \\%hash

    ClickHouse settings applied to every query and insert. Per-call settings
    (see ["query"](#query), ["insert"](#insert)) override these.

        settings => { async_insert => 1, max_threads => 4 }

- keepalive => $seconds

    Send a keepalive request every N seconds while the connection is idle:
    a native CLIENT\_PING on the native protocol or a `GET /ping` on HTTP
    (some load balancers / NATs drop idle HTTP connections after a few
    seconds; TCP-level keepalive is too coarse). Default: `0` (disabled).

- reconnect\_delay => $seconds

    Initial delay for the `auto_reconnect` exponential backoff. Each failed
    attempt doubles the delay, capped at `reconnect_max_delay`. Default:
    `0` (immediate retry, no backoff).

- reconnect\_max\_delay => $seconds

    Backoff ceiling. Default: `0`, meaning no explicit cap; the implementation
    still bounds the backoff exponent at 20 doublings, so with
    `reconnect_delay = 0.5` the worst case is roughly 6 days. Setting an
    explicit ceiling is recommended in production.

- reconnect\_jitter => $fraction

    Multiplicative jitter applied to each backoff delay: the actual sleep
    is uniformly random in `[delay, delay * (1 + jitter)]`. `0` (default)
    disables. Set to `0.1`-`0.5` when many clients reconnect against a
    shared cluster - without jitter, every replica restart causes a
    synchronised reconnect storm at the same backoff intervals. Jitter is
    applied _after_ `reconnect_max_delay` clamping, then re-clamped, so
    the ceiling is never exceeded.

- reconnect\_max\_attempts => $N

    Cap the total number of reconnect attempts before giving up. Once the
    cap is reached, `on_error` fires with the message

README.md  view on Meta::CPAN

    Without [EV::cares](https://metacpan.org/pod/EV%3A%3Acares), DNS resolution falls back to blocking
    `getaddrinfo`. Install [EV::cares](https://metacpan.org/pod/EV%3A%3Acares) for non-blocking lookup; otherwise
    use an IP literal or a local caching resolver (nscd / systemd-resolved).

- `connect_timeout` doesn't fire

    It does across TCP connect, TLS handshake, and native ServerHello. If
    the timer doesn't fire, the underlying issue is usually a synchronous
    DNS stall (see above) which happens before `start_connect` arms the
    timer; install [EV::cares](https://metacpan.org/pod/EV%3A%3Acares) to push DNS off the loop.

- Per-query `query_timeout` is ignored

    Set it inside the `\%settings` hashref, not as a top-level argument:
    `$ch->query($sql, { query_timeout => 5 }, $cb)`.

- Which host am I currently pointed at after failover?

    `$ch->current_host` and `$ch->current_port` reflect the
    live target after a multi-host rotation. Use `on_failover =>
    sub { ... }` to get notified at the moment of each rotation.

- How do I retry only on transient errors?

    `EV::ClickHouse->is_retryable_error($code)` returns true for the
    common transient codes (timeouts, network errors, replica catch-up,
    keeper exceptions, ...). Inspect `$ch->last_error_code` from
    inside your query callback and schedule a retry only when the predicate
    fires - permanent errors (auth failures, missing tables) won't qualify.

    Sample skeleton:

        $ch->query($sql, sub {
            my ($r, $err) = @_;
            if ($err && EV::ClickHouse->is_retryable_error($ch->last_error_code)) {
                schedule_retry($sql);
            } elsif ($err) { warn "permanent: $err" }
        });

- Idempotent insert silently drops some rows

    `idempotent => 1` auto-mints
    `insert_deduplication_token`; if your producer issues the SAME logical
    batch twice (e.g. retry after a transient network blip) only the first
    write lands, by design. To force two distinct logical batches through,
    either pass an explicit `idempotent => $token` per batch or
    omit the option for fresh inserts. See `eg/idempotent_insert.pl`.

- `on_data` vs `iterate` - which should I pick?

    `on_data => sub { }` in the per-query settings is the
    lowest-overhead streaming path: each native data block is delivered as
    soon as the parser has it, no per-row allocation overhead beyond the
    batch arrayref. `iterate` is a synchronous-feeling pull wrapper around
    the same machinery - useful when the surrounding code is procedural
    (ETL scripts, exporters) and a callback shape doesn't fit. Both are
    native-only.

- Connection in front of nginx / reverse proxy strips X-ClickHouse-\* headers

    Pass `http_basic_auth => 1` to send the credentials as
    `Authorization: Basic ...` instead. Most HTTP gateways forward
    Authorization verbatim while filtering proprietary headers.

# TUNING

- Native vs HTTP

    Native (port 9000) is typically 2-5x faster for insert and select-of-many-rows
    because rows ship as binary columns instead of TSV text. Use HTTP only when
    the network path requires HTTPS-only or when you need `raw => 1` CSV /
    JSONEachRow / Parquet bodies.

- `compress => 1`

    Enables LZ4 (native) or gzip (HTTP). LZ4 cost is small and saves ~50-70%
    on text-heavy columns. Gzip is heavier; turn on only if you're bandwidth-bound.

- `insert_streamer` batch\_size

    Default 10\_000 is a good baseline. Smaller (1k-2k) reduces memory pressure
    on the producer; larger (50k-100k) reduces server-side merge cost on
    MergeTree. Match to your row width: ~1 MB per batch is a sweet spot.

- `keepalive`

    Enable on long-lived idle connections (HTTP behind a load balancer or
    NAT, or a native connection that may sit minutes between queries). 15-30s
    is typical.

- `reconnect_max_attempts`

    Always set in production. Default is unlimited; a permanent failure
    (wrong host, wrong port, dead server) will spin `on_error` forever
    otherwise.

- `progress_period`

    Coalesce on\_progress packets to one fire per N seconds. Big SELECTs can
    emit hundreds per second; throttle to 1-5s for monitoring dashboards.

- Pull-iterator vs `on_data`

    `on_data` has lower per-block overhead. `iterate` trades that for a
    synchronous-feeling API; use it when the surrounding code is procedural.

- `EV::ClickHouse::Pool`

    A Pool fans concurrent queries across N independent connections, so a
    slow query on one doesn't head-of-line-block the others. Use it for
    read-mostly fan-out; do not use it for queries that depend on
    session-level state (temporary tables, `set`) since each query may
    land on a different connection.

## Performance tuning checklist

- 1. Pick the right protocol

    Native (port 9000) beats HTTP (port 8123) for almost all workloads.
    HTTP is only required for HTTPS-fronted ingress, the `raw` mode that
    returns `RowBinary` / `JSONEachRow` / `Parquet` bodies unparsed, or



( run in 1.451 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )