MCPcopy Create free account
hub / github.com/PostHog/duckgres

github.com/PostHog/duckgres @main

Chat with this repo
repository ↗ · DeepWiki ↗ · + Follow
6,704 symbols 26,706 edges 606 files ⚖ MIT 2,265 documented · 34% updated 3d agolatest · 2026-08-28★ 1735 open issues

Browse by type

Functions 5,901 Types & classes 803
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

Duckgres

Duckgres Mascot

A PostgreSQL wire protocol compatible server backed by DuckDB. Connect with any PostgreSQL client (psql, pgAdmin, lib/pq, psycopg2, etc.) and get DuckDB's analytical query performance.

Table of Contents

Features

  • PostgreSQL Wire Protocol: Compatibility with PostgreSQL clients for analytical workloads
  • Two-Tier Query Processing: Transparently handles both PostgreSQL and DuckDB-specific syntax
  • TLS Encryption: Required TLS connections with auto-generated self-signed certificates
  • Per-User Databases: Each authenticated user gets their own isolated DuckDB database file
  • Password Authentication: Cleartext password authentication over TLS
  • Extended Query Protocol: Support for prepared statements, binary format, and parameterized queries
  • COPY Protocol: Bulk data import/export with COPY FROM STDIN and COPY TO STDOUT
  • DuckDB Extensions: Configurable extension loading (ducklake enabled by default)
  • DuckLake Integration: Auto-attach DuckLake catalogs for lakehouse workflows
  • Rate Limiting: Built-in protection against brute-force attacks
  • Graceful Shutdown: Waits for in-flight queries before exiting
  • Control Plane Mode: Multi-process architecture with long-lived workers, zero-downtime deployments, and rolling updates
  • Flexible Configuration: YAML config files, environment variables, and CLI flags
  • Prometheus Metrics: Built-in metrics endpoint for monitoring

Metrics

Duckgres exposes Prometheus metrics on :9090/metrics. The metrics port is currently fixed at 9090 and cannot be changed via configuration.

See docs/metrics.md for exact request-path boundaries, labels, aggregation rules, PromQL examples, and admission metric migration.

Metric Type Description
duckgres_connections_open Gauge Process-wide number of currently open client connections, including native metadata-proxy sockets
duckgres_connection_duration_seconds{org} Histogram Worker-backed Duckgres connection lifetime, accept→disconnect (includes _count, _sum, _bucket); excludes native metadata-proxy connections, which use their dedicated duration family
duckgres_metadata_proxy_connections_open{org} Gauge Current admitted native metadata Postgres proxy connections; process-local, so sum across control-plane replicas
duckgres_metadata_proxy_connection_attempts_total{org,outcome} Counter Metadata proxy attempts by bounded terminal outcome
duckgres_metadata_proxy_connection_duration_seconds{org} Histogram Lifetime of admitted metadata proxy connections, including upstream bootstrap
duckgres_metadata_proxy_upstream_connect_duration_seconds{org,outcome} Histogram Internal metadata Postgres connect/auth latency; outcome is success or error
duckgres_metadata_proxy_bytes_total{org,direction} Counter Post-authentication pgwire bytes relayed in client_to_upstream or upstream_to_client direction
duckgres_metadata_proxy_cancel_requests_total{outcome} Counter Raw metadata-proxy CancelRequests handled as session_terminated on the owning control-plane replica or not_local on another replica
duckgres_query_total{org,status,reason} Counter Total non-empty query attempts. Valid status/reason pairs: success/none; failure/user, failure/canceled, failure/conflict; error/metadata_connection_lost, error/system.
duckgres_query_duration_seconds{org} Histogram Simple/extended query execution latency (includes _count, _sum, _bucket); use duckgres_query_total for attempt totals
duckgres_auth_failures_total Counter Process-wide authentication failures, including wrong-password metadata-proxy attempts; use duckgres_metadata_proxy_connection_attempts_total{outcome="auth_failed"} for the proxy-specific split
duckgres_rate_limit_rejects_total Counter Process-wide pre-TLS connection rejections due to rate limiting; these cannot be attributed to the worker or metadata endpoint because SNI is not available yet
duckgres_rate_limited_ips Gauge Number of currently rate-limited IP addresses
duckgres_control_plane_workers_active Gauge Number of active control-plane worker processes
duckgres_control_plane_worker_acquire_seconds Histogram Time spent acquiring a worker for a new session
duckgres_control_plane_worker_queue_depth Gauge Approximate number of session requests waiting on worker acquisition
duckgres_control_plane_worker_spawn_seconds Histogram Time spent spawning and health-checking a new worker
duckgres_session_admission_evaluation_duration_seconds{decision,reason} Histogram Latency of one DB-backed admission poll for the polling request
duckgres_session_admission_evaluations_total{decision,reason} Counter Admission request polls; repeated polls are distinct evaluations
duckgres_session_admission_wait_seconds{org,outcome,reason} Histogram End-to-end wait for one successfully enqueued admission request
duckgres_session_admission_requests_total{org,outcome,reason} Counter Exactly one terminal event per successfully enqueued admission request
duckgres_session_admission_queue_depth{org} Gauge Local callers waiting after successful durable enqueue; sum across replicas
duckgres_session_admission_active_vcpus{org} Gauge Requested vCPUs held by local live lease handles; cleanup-pending durable rows are excluded
duckgres_session_admission_limit_vcpus{org} Gauge Config-reconciled effective org cap for active org stacks; zero means unlimited, max across replicas
duckgres_session_admission_reclaim_pending Gauge Activated cleanup intents awaiting or executing exact database reclamation
duckgres_session_admission_reclaim_attempts_total{outcome} Counter Exact cleanup attempts by success or error outcome
duckgres_session_admission_reclaim_reservations_in_use Gauge Cleanup-ownership slots held before enqueue, while queued or live, and during pending cleanup
duckgres_session_admission_reclaim_reservation_capacity Gauge Cleanup-ownership slot capacity for this control-plane process (4096 per reclaimer by default)
duckgres_session_admission_reclaim_reservation_rejections_total{reason} Counter Reservations rejected because capacity was full, the reclaimer was closed, or the exact reference was a duplicate
duckgres_session_start_duration_seconds{org,protocol,outcome} Histogram Authenticated PostgreSQL session bootstrap through flushed ReadyForQuery
duckgres_postgres_session_start_total{org,outcome,reason} Counter Exactly one terminal result per authenticated PostgreSQL session start after server retries; outcome is success\|failure and bounded reasons distinguish operator-actionable failures from client/lifecycle noise

Testing Metrics

  • scripts/test_metrics.sh - Runs a quick sanity check (starts server, runs queries, verifies counts)
  • scripts/load_generator.sh - Generates continuous query load until Ctrl-C
  • scripts/perf_smoke.sh - Runs the golden-query perf harness and writes artifacts to artifacts/perf/<run_id>
  • scripts/perf_nightly.sh - Nightly wrapper with lock/timeout guards and optional artifact publisher
  • metrics-compose.yml - Starts Prometheus and Grafana locally for metrics (Prometheus at http://localhost:9091, Grafana at http://localhost:3000)

Query Log

When DuckLake uses a Postgres metadata store, Duckgres writes durable per-query history to the native Postgres table querylog.query_log_entries. The query log is queryable through ducklake.system.query_log, a live view over that native Postgres table. The view is not DuckLake snapshot data.

Rows record SQL user (user_name), org, query text, duration, row counts, errors, trace/span IDs, and profiling-derived resource usage. cpu_time_s is DuckDB cumulative CPU/thread time in seconds, and peak_buffer_memory_bytes is DuckDB's system_peak_buffer_memory in bytes, not process RSS.

query_id is a per-statement UUIDv7 minted when the query arrives. It is time-ordered, appears on the statement's OTEL span (duckgres.query_id) and its error logs, and is the key that correlates every query-log event for one statement. A batched simple query (SELECT 1; SELECT 2) runs each statement under its own query_id, with parent_query_id and statement_index identifying the Query message they arrived in.

Statements produce a pair of events, using ClickHouse's type vocabulary (QueryStart = 1, QueryFinish = 2, ExceptionBeforeStart = 3, ExceptionWhileProcessing = 4):

  • QueryStart is emitted when the statement begins executing.
  • One terminal event follows: QueryFinish, or ExceptionWhileProcessing if it failed after execution began, or ExceptionBeforeStart if it failed before execution began — auth or policy denial, a transpile error, a failure to obtain a worker, or an extended-protocol Describe whose prepare the engine rejected. ExceptionBeforeStart events have no QueryStart, by definition.

The boundary is execution began, not an engine saw it: Describe hands the statement to a worker to learn its result schema, so a binder error there is an ExceptionBeforeStart even though the engine did see the SQL. This is the same line ClickHouse draws — analysis-time failures are ExceptionBeforeStart. In practice this is the largest source of them, so when triaging, read ExceptionBeforeStart as "never ran", not as "never reached a worker".

A QueryStart with no terminal event is a query that never came back — a worker OOM-killed mid-statement, a pod evicted. That row is the only evidence such a query ever ran, so treat a sustained population of unpaired starts as an incident signal, allowing for queries still in flight.

The query_id travels to the worker on every statement RPC (x-duckgres-query-id), and the worker stamps it on its own logs — notably the "Query appears stuck" warning. That is what closes the loop on an unpaired QueryStart: the statement's own log row cannot exist, but the pod's last words about it carry the same ID.

event_time is the statement's start time on every event type, including terminal ones. This diverges from ClickHouse, where event_time is when the event was logged: pinning both rows of a pair to the same instant keeps them in one monthly partition and lets them join without a window function. A terminal row's finish time is event_time + query_duration_ms.

query_log.start_events selects which statements get a QueryStart:

  • data (default) — statements that touch data or change schema. Transaction control, SET/RESET/SHOW, and catalog introspection are skipped: they never hang, and they are the noisiest statements a driver sends.
  • all — every statement.
  • off — no start events.

Terminal events are always logged regardless of this setting, so nothing disappears from the log; cheap statements simply have no paired start row. Also settable via DUCKGRES_QUERY_LOG_START_EVENTS.

Each event also records what the statement touches, extracted from its parse tree (server/querymeta):

  • access_kinds — the access classes the statement needs, comma-separated: read, write, ddl, config, admin, transaction, metadata, unknown. A statement can be several at once: WITH x AS (INSERT …) SELECT is both a read and a write, which a classifier based on the command tag gets wrong.
  • query_metadata — JSON with the resolved detail: read_relations and write_relations (split, because grants are directional), columns, functions, and table_functions.
  • metadata_completefalse when extraction could not see the whole statement. DuckDB-native syntax (ATTACH, CREATE SECRET, PIVOT, SUMMARIZE) is not parseable as PostgreSQL and falls back to a coarse lexical classification.

That last column is load-bearing. These signals exist to let an authorization policy be evaluated against real traffic before it denies anything, so "referenced no relations" and "we could not tell what it referenced" must never be the same answer: a consumer that gates on query_metadata must treat metadata_complete = false as unknown, and deny.

table_functions is recorded alongside relations because `read_parquet('s3:/

Extension points exported contracts — how you extend this code

browse all types & interfaces →

Core symbols most depended-on inside this repo

browse all functions →

Shape

Function 3,720
Method 2,181
Struct 615
Interface 123
TypeAlias 34
Class 18
FuncType 13

Languages

Go93%
TypeScript4%
Python2%
Java1%
Rust1%
C1%

Modules by API surface

controlplane/k8s_pool_test.go152 symbols
server/flightsqlingress/ingress.go133 symbols
controlplane/session_mgr_drain_test.go127 symbols
server/conn_test.go101 symbols
transpiler/transpiler_test.go94 symbols
server/querylog_kafka_writer.go92 symbols
controlplane/admin/api_test.go84 symbols
controlplane/configstore/store.go83 symbols
server/flightsqlingress/ingress_test.go82 symbols
server/server.go79 symbols
controlplane/admin/api.go72 symbols
duckdbservice/service.go65 symbols

Datastores touched

duckgres_configDatabase · 1 repos
postgresDatabase · 1 repos
dbDatabase · 1 repos
duckgresDatabase · 1 repos
requested_dbDatabase · 1 repos

For agents

$ claude mcp add duckgres \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page