---
title: Postgres
slug: telemetry-pipeline/postgres
docTags: 
createdAt: 2025-04-21T01:02:28.107Z
---

# PostgreSQL Sink

The **PostgreSQL sink** delivers observability data (**logs, metrics, and traces**) into a PostgreSQL table using batched inserts. This guide focuses on **what each parameter does**, plus the operational implications you should consider in production.

## Important Warnings and Limitations

### PostgreSQL Column Defaults Are Not Applied

If the destination table has **default values** defined for columns, PostgreSQL will **not** automatically apply those defaults when the incoming event is missing that field. Instead, the sink will insert **NULL** for that column.

Operational impact:

- You may end up with unexpected NULLs even when the table defines defaults.&#x20;
- If downstream queries assume defaults, they may break or behave incorrectly.&#x20;

Common mitigations:

- Enforce NOT NULL constraints for fields that must always exist.&#x20;
- Use database-level logic (constraints/triggers) to set defaults when missing.&#x20;

### Batch Insert Failure Propagates to Entire Batch

Because insertion uses a recordset-style mechanism, **a single problematic event** (for example, a unique constraint violation) can cause the **entire batch** to fail.

Operational impact:

- One bad record can block ingestion and increase retry pressure.&#x20;
- You may observe repeated failures until the offending event(s) are removed or corrected.&#x20;

Mitigations:

- Use database-side exception-handling logic (triggers) for known constraint patterns.&#x20;
- Reduce batch size (in extreme cases insert one event per batch).&#x20;

### SQL Injection Risk in table

The **table name** is interpolated into SQL and **is not sanitized**.

Operational impact:

- Never construct the table name from untrusted input.&#x20;
- Treat the table value as static, controlled configuration.&#x20;

## Core Destination Parameters

### endpoint (required)

The PostgreSQL connection string used to reach the database.

What it controls:

- Database host/port and target database name&#x20;
- Potentially user credentials (if embedded in the string)&#x20;
- Connection behavior depending on supported connection string options&#x20;

Operational considerations:

- Connection failures cause retries and buffer growth.&#x20;
- Credential rotation strategy matters if credentials are embedded in the connection string.&#x20;

### table (required)

The name of the destination table where records are inserted.

What it controls:

- Where all incoming events land&#x20;
- Which schema constraints apply (NOT NULL, UNIQUE, FK, etc.)&#x20;

Operational considerations:

- Must match an existing table schema that aligns with your event structure.&#x20;
- Avoid dynamic/templated table names unless you fully control the input source.&#x20;

## Batching

### batch (optional)

Controls how events are grouped into insert operations.

Why it matters:

- Batch size affects throughput, latency, and failure blast radius.&#x20;

batch.max\_bytes (optional, default: 10,000,000 bytes)

Maximum uncompressed size of a batch before it is flushed.

Operational impact:

- Larger batches improve write efficiency but increase memory pressure and failure impact.&#x20;
- Very large batches can increase transaction time and lock contention.&#x20;

batch.max\_events (optional)

Maximum number of events in a batch before flushing.

Operational impact:

- Higher values improve throughput, but increase the number of events lost/retried when a batch fails.&#x20;
- Lower values reduce failure impact but can increase database request rate.&#x20;

batch.timeout\_secs (optional, default: 1 second)

Maximum age of a batch before it is flushed, even if size thresholds are not met.

Operational impact:

- Lower values reduce latency (faster visibility in DB).&#x20;
- Higher values improve batching efficiency at lower traffic volumes.&#x20;

**Failure-domain note:** Because one event can fail the entire batch, max\_events is a key reliability lever.

## Buffering

### buffer (optional)

Controls how events are temporarily stored when Postgres is slow or unavailable.

buffer.type (optional, default: memory)

- **memory:** higher performance, but buffered data is lost if worker crashes/restarts&#x20;
- **disk:** more durable (survives restarts), but slower and requires disk sizing&#x20;

buffer.max\_size (required)

Hard cap for how much memory/disk the buffer can consume.

Operational impact:

- Too small → frequent backpressure or drops (depending on when\_full)&#x20;
- Too large → can hide downstream issues and extend recovery time after outages&#x20;

Disk buffer note:

- Must meet minimum sizing requirements and needs sufficient IOPS for sustained backlog.&#x20;

buffer.max\_events (optional; relevant for memory buffers, default: 500)

Maximum number of buffered events (memory mode only).

Operational impact:

- Helps cap memory usage in event-count terms.&#x20;
- Useful when event size is unpredictable.&#x20;

buffer.when\_full (optional, default: block)

Behavior when the buffer is full:

- **block:** applies backpressure upstream (preferred when you want to avoid data loss)&#x20;
- **drop\_newest:** drops incoming events (preferred when system continuity is more important than completeness)&#x20;

## Connection Pooling

### pool\_size (optional, default: 5)

Number of database connections maintained in the sink’s connection pool.

Operational impact:

- Too low → throughput bottleneck, higher end-to-end latency&#x20;
- Too high → can overwhelm Postgres (connections, CPU, lock pressure), especially with many agents&#x20;

Sizing guidance:

- Match to Postgres capacity and expected write concurrency.&#x20;
- Consider total connections across all worker instances.&#x20;

## Request Behavior (Concurrency, Timeouts, Retries)

### request (optional)

Controls how worker schedules and retries outbound database operations. The retry backoff follows a Fibonacci pattern.

request.concurrency (optional, default: adaptive)

How many concurrent outbound requests are allowed:

- **none:** fixed concurrency of 1 (most predictable, lowest throughput)&#x20;
- **adaptive:** worker automatically adjusts concurrency based on observed performance&#x20;

Operational impact:

- Adaptive can maximize throughput but may oscillate if the DB performance is unstable.&#x20;
- Fixed (1) is safer for small databases or fragile schemas but may not meet throughput needs.&#x20;

### Adaptive Concurrency Fine-Tuning

These rarely need changes, but they shape stability under load:

- **request.adaptive\_concurrency.initial\_concurrency:** starting point after restart&#x20;
- **request.adaptive\_concurrency.max\_concurrency\_limit:** safety ceiling to prevent runaway concurrency&#x20;
- **request.adaptive\_concurrency.decrease\_ratio:** how aggressively concurrency is reduced when latency increases&#x20;
- **request.adaptive\_concurrency.ewma\_alpha:** how quickly the baseline adapts to new latency conditions&#x20;
- **request.adaptive\_concurrency.rtt\_deviation\_scale:** tolerance for latency variability before scaling back&#x20;

Operational caution:

- Incorrect values can create unstable behavior (oscillation, slow recovery, or overload).&#x20;

### Retry Controls

- **request.retry\_attempts:** maximum retries for failed operations
  &#x20;Very high values can cause prolonged retry storms during outages.&#x20;
- **request.retry\_initial\_backoff\_secs (default: 1 second):** delay before first retry
  &#x20;Subsequent delays follow Fibonacci growth.&#x20;
- **request.retry\_max\_duration\_secs (default: 30 seconds):** maximum wait between retries&#x20;
- **request.retry\_jitter\_mode (default: Full):** randomization strategy for backoff
  &#x20;Full jitter helps avoid synchronized retry spikes across many agents.&#x20;

### Timeouts

request.timeout\_secs (optional, default: 60 seconds)

Maximum time a request can run before being aborted.

Operational impact:

- If too low, you risk aborting operations that the DB might still complete, creating retries and extra pressure.&#x20;
- If too high, stalled operations consume concurrency and slow recovery.&#x20;

##

## Inputs

### inputs (required)

Defines which upstream sources/transforms feed this sink.

Operational impact:

- Mis-scoped inputs can unintentionally send high-volume telemetry to Postgres.&#x20;
- Wildcards are powerful but can increase blast radius of config mistakes.&#x20;

## Supported Data Types

- **Logs:** supported&#x20;
- **Metrics:** counter, gauge, histogram, distribution, summary, set&#x20;
- **Traces:** supported&#x20;

Operational note: ensure the destination schema and downstream queries can handle the variety of payload shapes implied by logs vs metrics vs traces.

## Built-in Telemetry You Should Monitor

Worker emits internal metrics that are especially useful for Postgres sink operations:

### Buffer Health

- **buffer\_byte\_size (gauge):** how much data is queued&#x20;
- **buffer\_events (gauge):** how many events are queued&#x20;
- **buffer\_discarded\_events\_total (counter):** events dropped due to buffer strategy&#x20;

### Reliability and Errors

- **component\_errors\_total (counter):** sink errors (connection, insert failures, etc.)&#x20;
- **component\_discarded\_events\_total (counter):** events dropped by the component (intentional or error-driven)&#x20;

### Throughput

- **component\_sent\_events\_total / component\_sent\_bytes\_total (counter):** delivery volume&#x20;
- **component\_received\_events\_total (counter):** ingress to the component&#x20;

Operational interpretation:

- Rising buffer gauges + rising errors usually indicates DB slowdowns/outages.&#x20;
- Rising discarded counters indicate loss (intentional drops or failure-driven discards) and should trigger incident investigation.
