Skip to content

Ingestion Tuning

How Bifract protects ClickHouse during ingest, and which settings to change when senders see 429 responses or ClickHouse sits idle.

How backpressure works

Request --> Token check --> Healthy? --> Parse --> Enqueue --> Workers --> ClickHouse
               |               |                      |
             401             429                    429

Accepted logs are queued in memory and written to ClickHouse in batches by a worker pool. Every rejection is a 429 with a Retry-After header, so a well-behaved sender just retries. Bifract rejects when any of these hold:

Check Trips at Clears at
Failed inserts 3 consecutive failed ClickHouse insert attempts 30 s after the last failure
ClickHouse CPU Above 80% for 6 polls (30 s) Below 60%
ClickHouse memory Above 90% for 3 polls (15 s); reads the pod's cgroup limit on Kubernetes Below 80%
ClickHouse disk Above 90% used Below 80%
Buffered bytes In-process buffer above BIFRACT_INGEST_BUFFER_BYTES Buffer drains
Queue depth Batch would take the queue past half its slots Queue drains
Archive spool 90% of spool capacity, when the Iceberg archive is on Below 70%
Fractal disk quota Fractal is over its quota and set to reject Space freed or quota raised
Rate limit More than BIFRACT_INGEST_RATE_LIMIT requests/second from one client IP Next second

The health checks (failures, CPU, memory, disk, spool) run before the body is read, so an overloaded server does not spend CPU parsing. Each queue slot holds at most 5,000 logs; larger requests are split across slots. The archive spool is fail-closed: a batch that cannot be spooled is rejected, never acknowledged.

What a 200 means

A 200 means the logs are in the ingest buffer (and in the archive spool, when it is on). They are written to ClickHouse within the flush interval (30 seconds by default, BIFRACT_INGEST_FLUSH_SECONDS). A failed insert is retried with backoff for about 2 minutes, which rides out a ClickHouse restart, and new requests get 429 meanwhile. Except on sharded clusters, every attempt carries the same deduplication token, so a retry after an insert that committed but reported an error writes no duplicate rows. An error a retry cannot fix, such as a type mismatch, gets 3 attempts. On shutdown, buffered logs are flushed; if ClickHouse is failing, the flush gives up after about 15 seconds. Logs are dropped only when retries run out or a batch cannot be encoded. Each drop is logged, counted in the insert error metrics and raises the Ingest Insert Errors notification. A crash or forced kill loses logs still in the buffer; with the archive on they remain in the spool.

Alerts and model state never read past logs that are still buffered or retrying. Each ingest and app process reports, every 5 seconds, the oldest receive time it has not yet written, and readers stop there. Normally that trails real time by at most the flush interval. While inserts are retrying it holds back, and alerts fire late rather than miss the logs. A process that stops reporting holds readers for up to 5 minutes, then is dropped. System > Alert engine shows how far it trails and which process is holding it.

Settings

Variable Default Change when
BIFRACT_INGEST_WORKERS 4 ClickHouse CPU is idle during bulk imports
BIFRACT_INGEST_QUEUE_SIZE 100 Frequent 429s during short spikes
BIFRACT_INGEST_RATE_LIMIT 10000 Legitimate senders hit the per-IP request limit
BIFRACT_INGEST_RATE_BURST 20000 Bursty senders need a higher peak
BIFRACT_MAX_BODY_SIZE 209715200 (200 MB) Requests larger than 200 MB; Caddy also caps the ingest port at 200 MiB
BIFRACT_INGEST_BUFFER_BYTES 268435456 (256 MB) Buffered-bytes rejections while memory is free

On Kubernetes, --install-k8s sets BIFRACT_INGEST_WORKERS and BIFRACT_INGEST_QUEUE_SIZE per resource profile. See the Sizing Guide.