{"id":33905079,"url":"https://github.com/posthog/duckgres","last_synced_at":"2026-06-10T06:01:48.481Z","repository":{"id":327489610,"uuid":"1109463501","full_name":"PostHog/duckgres","owner":"PostHog","description":"DuckDB Postgres Server","archived":false,"fork":false,"pushed_at":"2026-06-05T12:09:14.000Z","size":40697,"stargazers_count":128,"open_issues_count":11,"forks_count":16,"subscribers_count":4,"default_branch":"main","last_synced_at":"2026-06-05T12:28:20.077Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/PostHog.png","metadata":{"files":{"readme":"README.md","changelog":"HISTORY.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-12-03T20:56:13.000Z","updated_at":"2026-06-05T12:07:04.000Z","dependencies_parsed_at":"2026-03-12T03:01:54.411Z","dependency_job_id":null,"html_url":"https://github.com/PostHog/duckgres","commit_stats":null,"previous_names":["posthog/duckgres"],"tags_count":644,"template":false,"template_full_name":null,"purl":"pkg:github/PostHog/duckgres","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PostHog%2Fduckgres","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PostHog%2Fduckgres/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PostHog%2Fduckgres/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PostHog%2Fduckgres/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/PostHog","download_url":"https://codeload.github.com/PostHog/duckgres/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/PostHog%2Fduckgres/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34139184,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-10T02:00:07.152Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-12-12T01:49:52.263Z","updated_at":"2026-06-10T06:01:48.469Z","avatar_url":"https://github.com/PostHog.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Duckgres\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"media/oh_duck.png\" alt=\"Duckgres Mascot\" width=\"200\"\u003e\n\u003c/p\u003e\n\nA 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.\n\n## Table of Contents\n\n- [Features](#features)\n- [Metrics](#metrics)\n- [Runbooks](#runbooks)\n  - [Perf Runbook](docs/perf-harness-runbook.md)\n  - [Worker Upgrades \u0026 Canaries](docs/runbooks/worker-upgrades.md)\n- [Quick Start](#quick-start)\n- [Configuration](#configuration)\n  - [YAML Configuration](#yaml-configuration)\n  - [Environment Variables](#environment-variables)\n  - [CLI Flags](#cli-flags)\n  - [PostHog Logging](#posthog-logging)\n- [DuckDB Extensions](#duckdb-extensions)\n- [DuckLake Integration](#ducklake-integration)\n  - [Quick Start with Docker](#quick-start-with-docker)\n  - [Object Storage Configuration](#object-storage-configuration)\n  - [Seeding Sample Data](#seeding-sample-data)\n- [COPY Protocol](#copy-protocol)\n- [Graceful Shutdown](#graceful-shutdown)\n- [Rate Limiting](#rate-limiting)\n- [Usage Examples](#usage-examples)\n- [Architecture](#architecture)\n  - [Standalone Mode](#standalone-mode)\n  - [Control Plane Mode](#control-plane-mode)\n  - [Remote Worker Backend](#remote-worker-backend)\n- [Two-Tier Query Processing](#two-tier-query-processing)\n- [Supported Features](#supported-features)\n- [Transaction Isolation](#transaction-isolation)\n- [Limitations](#limitations)\n- [SQL Client Compatibility](#sql-client-compatibility)\n- [Dependencies](#dependencies)\n- [License](#license)\n\n## Features\n\n- **PostgreSQL Wire Protocol**: Full compatibility with PostgreSQL clients\n- **Two-Tier Query Processing**: Transparently handles both PostgreSQL and DuckDB-specific syntax\n- **TLS Encryption**: Required TLS connections with auto-generated self-signed certificates\n- **Per-User Databases**: Each authenticated user gets their own isolated DuckDB database file\n- **Password Authentication**: Cleartext password authentication over TLS\n- **Extended Query Protocol**: Support for prepared statements, binary format, and parameterized queries\n- **COPY Protocol**: Bulk data import/export with `COPY FROM STDIN` and `COPY TO STDOUT`\n- **DuckDB Extensions**: Configurable extension loading (ducklake enabled by default)\n- **DuckLake Integration**: Auto-attach DuckLake catalogs for lakehouse workflows\n- **Rate Limiting**: Built-in protection against brute-force attacks\n- **Graceful Shutdown**: Waits for in-flight queries before exiting\n- **Control Plane Mode**: Multi-process architecture with long-lived workers, zero-downtime deployments, and rolling updates\n- **Flexible Configuration**: YAML config files, environment variables, and CLI flags\n- **Prometheus Metrics**: Built-in metrics endpoint for monitoring\n\n## Metrics\n\nDuckgres exposes Prometheus metrics on `:9090/metrics`. The metrics port is currently fixed at 9090 and cannot be changed via configuration.\n\n| Metric | Type | Description |\n|--------|------|-------------|\n| `duckgres_connections_open` | Gauge | Number of currently open client connections |\n| `duckgres_query_duration_seconds` | Histogram | Query execution duration (includes `_count`, `_sum`, `_bucket`) |\n| `duckgres_query_errors_total` | Counter | Total number of failed queries |\n| `duckgres_auth_failures_total` | Counter | Total number of authentication failures |\n| `duckgres_rate_limit_rejects_total` | Counter | Total number of connections rejected due to rate limiting |\n| `duckgres_rate_limited_ips` | Gauge | Number of currently rate-limited IP addresses |\n| `duckgres_flight_auth_sessions_active` | Gauge | Number of active Flight auth sessions on the control plane |\n| `duckgres_control_plane_workers_active` | Gauge | Number of active control-plane worker processes |\n| `duckgres_control_plane_worker_acquire_seconds` | Histogram | Time spent acquiring a worker for a new session |\n| `duckgres_control_plane_worker_queue_depth` | Gauge | Approximate number of session requests waiting on worker acquisition |\n| `duckgres_control_plane_worker_spawn_seconds` | Histogram | Time spent spawning and health-checking a new worker |\n| `duckgres_flight_rpc_duration_seconds{method}` | Histogram | Flight ingress RPC duration by method |\n| `duckgres_flight_ingress_sessions_total{outcome}` | Counter | Flight ingress session outcomes (`created|reused|auth_failed|rate_limited|create_failed|token_invalid`) |\n| `duckgres_flight_sessions_reaped_total{trigger}` | Counter | Number of Flight auth sessions reaped (`trigger=periodic|forced`) |\n| `duckgres_flight_max_workers_retry_total{outcome}` | Counter | Max-worker retry outcomes for Flight session creation (`outcome=attempted|succeeded|failed`) |\n\n### Testing Metrics\n\n- `scripts/test_metrics.sh` - Runs a quick sanity check (starts server, runs queries, verifies counts)\n- `scripts/load_generator.sh` - Generates continuous query load until Ctrl-C\n- `scripts/perf_smoke.sh` - Runs the golden-query perf harness and writes artifacts to `artifacts/perf/\u003crun_id\u003e`\n- `scripts/perf_nightly.sh` - Nightly wrapper with lock/timeout guards and optional artifact publisher\n- `metrics-compose.yml` - Starts Prometheus and Grafana locally for metrics (Prometheus at http://localhost:9091, Grafana at http://localhost:3000)\n\n## Runbooks\n\n- [Worker Upgrades \u0026 Canaries](docs/runbooks/worker-upgrades.md): Process for upgrading DuckDB/DuckLake versions, canarying builds for a subset of tenants, and global version management.\n- [Performance Harness](docs/perf-harness-runbook.md): Local smoke and nightly operations for performance testing.\n- [Control Plane Rollout](docs/runbooks/control-plane-rollout.md): Zero-downtime deployment process for the control plane itself.\n- [Managed Warehouse Deprovision](docs/runbooks/managed-warehouse-deprovision.md): Destructive teardown process for managed warehouse infrastructure and org cleanup.\n- [Lakekeeper Iceberg Catalog](docs/runbooks/lakekeeper-iceberg-catalog.md): Per-org Lakekeeper Iceberg REST catalog backend — architecture, the no-vending credential model, activation, and the `ACCESS_DELEGATION_MODE 'none'` gotcha.\n\n## Quick Start\n\nThe project uses [just](https://github.com/casey/just) as a command runner. Run `just` to see all available recipes.\n\n### Build \u0026 Run\n\n```bash\njust build    # Build the binary\njust run      # Run in standalone mode\n```\n\nThe server starts on port 5432 by default with TLS enabled. Database files are stored in `./data/`. Self-signed certificates are auto-generated in `./certs/` if not present.\n\n### Connect\n\n```bash\njust psql     # Connect via psql (port 5432)\njust psql 35437  # Connect on a different port\n```\n\n### Docker\n\n```bash\njust docker   # Build image (tagged duckgres:dev)\ndocker run --rm -p 5432:5432 -p 9090:9090 duckgres:dev\n```\n\nMount a config file and persist data:\n\n```bash\ndocker run --rm \\\n  -p 5432:5432 -p 9090:9090 \\\n  -v ./duckgres.yaml:/app/duckgres.yaml \\\n  -v ./data:/app/data \\\n  duckgres:dev\n```\n\n## Configuration\n\nDuckgres supports three configuration methods (in order of precedence):\n1. CLI flags (highest priority)\n2. Environment variables\n3. YAML config file\n4. Built-in defaults (lowest priority)\n\n### YAML Configuration\n\nCreate a `duckgres.yaml` file (see `duckgres.example.yaml` for a complete example):\n\n```yaml\nhost: \"0.0.0.0\"\nport: 5432\nflight_port: 8815\nflight_session_idle_ttl: \"10m\"\nflight_session_reap_interval: \"1m\"\nflight_handle_idle_ttl: \"15m\"\nflight_session_token_ttl: \"1h\"\ndata_dir: \"./data\"\nsession_init_timeout: \"10s\"\n\ntls:\n  cert: \"./certs/server.crt\"\n  key: \"./certs/server.key\"\n\nusers:\n  postgres: \"postgres\"\n  alice: \"alice123\"\n\nextensions:\n  - ducklake\n  - httpfs\n\nducklake:\n  metadata_store: \"postgres:host=localhost user=ducklake password=secret dbname=ducklake\"\n  # Default: true. Disables postgres_scanner thread-local caching for the\n  # hidden DuckLake metadata pool to reduce retained metadata connections.\n  # Set to false to opt back into warm connection reuse.\n  disable_metadata_thread_local_cache: true\n  # Default: false. Also attach a Delta Lake catalog/table on worker boot.\n  # Without delta_catalog_path, defaults to a sibling top-level delta/ prefix\n  # beside the configured DuckLake object_store prefix.\n  delta_catalog_enabled: false\n  # delta_catalog_path: \"s3://bucket/delta/\"\n\nprocess:\n  min_workers: 0\n  max_workers: 0\n  retire_on_session_end: false\n\nrate_limit:\n  max_failed_attempts: 5\n  failed_attempt_window: \"5m\"\n  ban_duration: \"15m\"\n  max_connections_per_ip: 100\n```\n\nRun with config file:\n\n```bash\n./duckgres --config duckgres.yaml\n```\n\n### Environment Variables\n\n| Variable | Description | Default |\n|----------|-------------|---------|\n| `DUCKGRES_CONFIG` | Path to YAML config file | - |\n| `DUCKGRES_HOST` | Host to bind to | `0.0.0.0` |\n| `DUCKGRES_PORT` | Port to listen on | `5432` |\n| `DUCKGRES_FLIGHT_PORT` | Control-plane Flight SQL ingress port (`0` disables) | `0` |\n| `DUCKGRES_FLIGHT_SESSION_IDLE_TTL` | Flight auth session idle TTL | `10m` |\n| `DUCKGRES_FLIGHT_SESSION_REAP_INTERVAL` | Flight auth session reap interval | `1m` |\n| `DUCKGRES_FLIGHT_HANDLE_IDLE_TTL` | Flight prepared/query handle idle TTL | `15m` |\n| `DUCKGRES_FLIGHT_SESSION_TOKEN_TTL` | Flight issued session token absolute TTL | `1h` |\n| `DUCKGRES_DATA_DIR` | Directory for DuckDB files | `./data` |\n| `DUCKGRES_CERT` | TLS certificate file | `./certs/server.crt` |\n| `DUCKGRES_KEY` | TLS private key file | `./certs/server.key` |\n| `DUCKGRES_MEMORY_LIMIT` | DuckDB memory_limit per session (e.g., `4GB`) | Auto-detected |\n| `DUCKGRES_THREADS` | DuckDB threads per session | `runtime.NumCPU()` |\n| `DUCKGRES_PROCESS_ISOLATION` | Enable process isolation (`1` or `true`) | `false` |\n| `DUCKGRES_PROCESS_RETIRE_ON_SESSION_END` | Retire a process worker immediately after its last session ends instead of keeping it warm for reuse | `false` |\n| `DUCKGRES_IDLE_TIMEOUT` | Connection idle timeout (e.g., `30m`, `1h`, `-1` to disable) | `24h` |\n| `DUCKGRES_SESSION_INIT_TIMEOUT` | Session startup metadata initialization and catalog probe timeout | `10s` |\n| `DUCKGRES_WORKER_QUEUE_TIMEOUT` | Max time to wait for worker acquisition and per-org connection-limit queue admission; the managed K8s queue TTL uses this value | `60s` |\n| `DUCKGRES_HANDOVER_DRAIN_TIMEOUT` | Max time to drain planned shutdowns and upgrades before forcing exit | `24h` in process mode, `15m` in remote K8s mode |\n| `DUCKGRES_SNI_ROUTING_MODE` | Multi-tenant managed-hostname routing: `off`, `passthrough`, or `enforce`. Postgres uses the requested dbname first; managed SNI must resolve to the same org, and SNI supplies the database only when dbname is empty. | `off` |\n| `DUCKGRES_MANAGED_HOSTNAME_SUFFIXES` | Comma-separated managed hostname suffixes such as `.dw.us.postwh.com` | - |\n| `DUCKGRES_K8S_MAX_WORKERS` | Global cap for shared K8s workers (`0` means Duckgres does not impose a cap) | `0` |\n| `DUCKGRES_DUCKLAKE_METADATA_STORE` | DuckLake metadata connection string | - |\n| `DUCKGRES_DUCKLAKE_DELTA_CATALOG_ENABLED` | Attach a Delta Lake catalog/table during worker boot/activation | `false` |\n| `DUCKGRES_DUCKLAKE_DELTA_CATALOG_PATH` | Delta Lake catalog/table path; defaults to sibling `delta/` prefix at the DuckLake object-store root when enabled | Derived |\n| `POSTHOG_API_KEY` | PostHog project API key (`phc_...`); enables log export | - |\n| `POSTHOG_HOST` | PostHog ingest host | `us.i.posthog.com` |\n| `ADDITIONAL_POSTHOG_API_KEYS` | **(Experimental)** Comma-separated list of additional PostHog API keys to publish logs to. Requires `POSTHOG_API_KEY` to be set. | - |\n| `DUCKGRES_IDENTIFIER` | Suffix appended to the OTel `service.name` in PostHog logs (e.g., `duckgres-acme`); only used when `POSTHOG_API_KEY` is set | - |\n\n### PostHog Logging\n\nDuckgres can optionally export structured logs to [PostHog Logs](https://posthog.com/docs/logs) via the OpenTelemetry Protocol (OTLP). Logs are always written to stderr regardless of this setting.\n\nTo enable, set your PostHog project API key:\n\n```bash\nexport POSTHOG_API_KEY=phc_your_project_api_key\n./duckgres\n```\n\nFor EU Cloud or self-hosted PostHog instances, override the ingest host:\n\n```bash\nexport POSTHOG_API_KEY=phc_your_project_api_key\nexport POSTHOG_HOST=eu.i.posthog.com\n./duckgres\n```\n\n### CLI Flags\n\n```bash\n./duckgres --help\n\nOptions:\n  -config string           Path to YAML config file\n  -host string             Host to bind to\n  -port int                Port to listen on\n  -flight-port int         Control-plane Arrow Flight SQL ingress port, 0=disabled\n  -flight-session-idle-ttl string      Flight auth session idle TTL (e.g., '10m')\n  -flight-session-reap-interval string Flight auth session reap interval (e.g., '1m')\n  -flight-handle-idle-ttl string       Flight prepared/query handle idle TTL (e.g., '15m')\n  -flight-session-token-ttl string     Flight issued session token absolute TTL (e.g., '1h')\n  -data-dir string         Directory for DuckDB files\n  -cert string             TLS certificate file\n  -key string              TLS private key file\n  -memory-limit string     DuckDB memory_limit per session (e.g., '4GB')\n  -threads int             DuckDB threads per session\n  -process-isolation       Enable process isolation (spawn child process per connection)\n  -idle-timeout string     Connection idle timeout (e.g., '30m', '1h', '-1' to disable)\n  -mode string             Run mode: standalone (default), control-plane, or duckdb-service\n  -process-min-workers int Pre-warm process worker count at startup (control-plane mode, default 0)\n  -process-max-workers int Max process workers, 0=auto-derived (control-plane mode)\n  -process-retire-on-session-end\n                          Retire a process worker immediately after its last session ends instead of keeping it warm for reuse (control-plane mode)\n  -memory-budget string    Total memory for all DuckDB sessions (e.g., '24GB')\n  -socket-dir string       Unix socket directory (control-plane mode)\n  -handover-socket string  Handover socket for graceful deployment (control-plane mode)\n  -sni-routing-mode string Hostname routing: off, passthrough, or enforce\n  -managed-hostname-suffixes string\n                          Comma-separated managed tenant hostname suffixes\n  -k8s-max-workers int    Max K8s workers in the shared pool, 0=unbounded\n```\n\n## DuckDB Extensions\n\nExtensions are automatically installed and loaded when a user's database is first opened. The `ducklake` extension is enabled by default.\n\n```yaml\nextensions:\n  - ducklake    # Default - DuckLake lakehouse format\n  - httpfs      # HTTP/S3 file system access\n  - parquet     # Parquet file support (built-in)\n  - json        # JSON support (built-in)\n  - postgres    # PostgreSQL scanner\n```\n\n## DuckLake Integration\n\nDuckLake provides a SQL-based lakehouse format. When configured, the DuckLake catalog is automatically attached on connection:\n\n```yaml\nducklake:\n  # Full connection string for the DuckLake metadata database\n  metadata_store: \"postgres:host=ducklake.example.com user=ducklake password=secret dbname=ducklake\"\n\n  # Default: true. Disables postgres_scanner thread-local caching for the\n  # hidden DuckLake metadata pool before ATTACH creates it.\n  # Set to false to opt back into warm connection reuse.\n  disable_metadata_thread_local_cache: true\n\n  # Also attach a Delta Lake catalog/table as catalog \"delta\" during worker\n  # boot/activation. If delta_catalog_path is omitted, Duckgres derives\n  # s3://\u003cbucket\u003e/delta/ from ducklake.object_store. Prefer that isolated\n  # prefix over the bucket root so DuckLake and Delta files do not collide.\n  delta_catalog_enabled: false\n  # delta_catalog_path: \"s3://my-bucket/delta/\"\n```\n\nThis runs the equivalent of:\n```sql\nATTACH 'ducklake:postgres:host=ducklake.example.com user=ducklake password=secret dbname=ducklake' AS ducklake;\n-- when delta_catalog_enabled=true:\nATTACH 's3://my-bucket/delta/' AS delta (TYPE delta);\n```\n\nSee [DuckLake documentation](https://ducklake.select/docs/stable/duckdb/usage/connecting) for more details.\n\n`ducklake.disable_metadata_thread_local_cache` defaults to `true`. This applies a\npre-attach workaround for the hidden DuckLake metadata postgres pool so idle\nworker threads do not retain metadata connections indefinitely. Set it to\n`false` only if you explicitly want the older warm-reuse behavior and accept the\nlarger steady-state metadata connection footprint.\n\n### Quick Start with Docker\n\nThe easiest way to get started with DuckLake is using the included Docker Compose setup:\n\n```bash\n# Start PostgreSQL (metadata) and MinIO (object storage)\ndocker compose up -d\n\n# Wait for services to be ready\ndocker compose logs -f  # Look for \"Bucket ducklake created successfully\"\n\n# Start Duckgres with DuckLake configured\n./duckgres --config duckgres.yaml\n\n# Connect and start using DuckLake\nPGPASSWORD=postgres psql \"host=localhost port=5432 user=postgres sslmode=require\"\n```\n\nThe `docker-compose.yaml` creates:\n\n**PostgreSQL** (metadata catalog):\n- Host: `localhost`\n- Port: `5433` (mapped to avoid conflicts)\n- Database: `ducklake`\n- User/Password: `ducklake` / `ducklake`\n\n**MinIO** (S3-compatible object storage):\n- S3 API: `localhost:9000`\n- Web Console: `http://localhost:9001`\n- Access Key: `minioadmin`\n- Secret Key: `minioadmin`\n- Bucket: `ducklake` (auto-created on startup)\n\nThe included `duckgres.yaml` is pre-configured to use both services.\n\n### Object Storage Configuration\n\nDuckLake can store data files in S3-compatible object storage (AWS S3, MinIO, etc.). Two credential providers are supported:\n\n#### Option 1: Explicit Credentials (MinIO / Access Keys)\n\n```yaml\nducklake:\n  metadata_store: \"postgres:host=localhost port=5433 user=ducklake password=ducklake dbname=ducklake\"\n  object_store: \"s3://ducklake/data/\"\n  delta_catalog_enabled: true       # attaches s3://ducklake/delta/ by default\n  s3_provider: \"config\"            # Explicit credentials (default if s3_access_key is set)\n  s3_endpoint: \"localhost:9000\"    # MinIO or custom S3 endpoint\n  s3_access_key: \"minioadmin\"\n  s3_secret_key: \"minioadmin\"\n  s3_region: \"us-east-1\"\n  s3_use_ssl: false\n  s3_url_style: \"path\"             # \"path\" for MinIO, \"vhost\" for AWS S3\n```\n\n#### Option 2: AWS Credential Chain (IAM Roles / Environment)\n\nFor AWS S3 with IAM roles, environment variables, or config files:\n\n```yaml\nducklake:\n  metadata_store: \"postgres:host=localhost user=ducklake password=ducklake dbname=ducklake\"\n  object_store: \"s3://my-bucket/ducklake/\"\n  s3_provider: \"credential_chain\"  # AWS SDK credential chain\n  s3_chain: \"env;config\"           # Which sources to check (optional)\n  s3_profile: \"my-profile\"         # AWS profile name (optional)\n  s3_region: \"us-west-2\"           # Override auto-detected region (optional)\n```\n\nThe credential chain checks these sources in order:\n- `env` - Environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`)\n- `config` - AWS config files (`~/.aws/credentials`, `~/.aws/config`)\n- `sts` - AWS STS assume role\n- `sso` - AWS Single Sign-On\n- `instance` - EC2 instance metadata (IAM roles)\n- `process` - External process credentials\n\nSee [DuckDB S3 API docs](https://duckdb.org/docs/stable/core_extensions/httpfs/s3api#credential_chain-provider) for details.\n\n#### Environment Variables\n\nAll S3 settings can be configured via environment variables:\n- `DUCKGRES_DUCKLAKE_OBJECT_STORE` - S3 path (e.g., `s3://bucket/path/`)\n- `DUCKGRES_DUCKLAKE_DELTA_CATALOG_ENABLED` - attach Delta catalog (`true`/`false`)\n- `DUCKGRES_DUCKLAKE_DELTA_CATALOG_PATH` - Delta catalog/table path (e.g., `s3://bucket/delta/`)\n- `DUCKGRES_DUCKLAKE_S3_PROVIDER` - `config` or `credential_chain`\n- `DUCKGRES_DUCKLAKE_S3_ENDPOINT` - S3 endpoint (for MinIO)\n- `DUCKGRES_DUCKLAKE_S3_ACCESS_KEY` - Access key ID\n- `DUCKGRES_DUCKLAKE_S3_SECRET_KEY` - Secret access key\n- `DUCKGRES_DUCKLAKE_S3_REGION` - AWS region\n- `DUCKGRES_DUCKLAKE_S3_USE_SSL` - Use HTTPS (true/false)\n- `DUCKGRES_DUCKLAKE_S3_URL_STYLE` - `path` or `vhost`\n- `DUCKGRES_DUCKLAKE_S3_CHAIN` - Credential chain sources\n- `DUCKGRES_DUCKLAKE_S3_PROFILE` - AWS profile name\n\n### Seeding Sample Data\n\nA seed script is provided to populate DuckLake with sample e-commerce and analytics data:\n\n```bash\n# Seed with default connection (localhost:5432, postgres/postgres)\n./scripts/seed_ducklake.sh\n\n# Seed with custom connection\n./scripts/seed_ducklake.sh --host 127.0.0.1 --port 5432 --user postgres --password postgres\n\n# Clean existing tables and reseed\n./scripts/seed_ducklake.sh --clean\n```\n\nThe script creates the following tables:\n- `categories` - Product categories (5 rows)\n- `products` - E-commerce products (15 rows)\n- `customers` - Customer records (10 rows)\n- `orders` - Order headers (12 rows)\n- `order_items` - Order line items (20 rows)\n- `events` - Analytics events with JSON properties (15 rows)\n- `page_views` - Web analytics data (15 rows)\n\nExample queries after seeding:\n\n```sql\n-- Top products by price\nSELECT name, price FROM products ORDER BY price DESC LIMIT 5;\n\n-- Orders with customer info\nSELECT o.id, c.first_name, c.last_name, o.total_amount, o.status\nFROM orders o JOIN customers c ON o.customer_id = c.id;\n\n-- Event funnel analysis\nSELECT event_name, COUNT(*) FROM events GROUP BY event_name ORDER BY COUNT(*) DESC;\n```\n\n## COPY Protocol\n\nDuckgres supports PostgreSQL's COPY protocol for efficient bulk data import and export:\n\n```sql\n-- Export data to stdout (tab-separated)\nCOPY tablename TO STDOUT;\n\n-- Export as CSV with headers\nCOPY tablename TO STDOUT WITH CSV HEADER;\n\n-- Export query results\nCOPY (SELECT * FROM tablename WHERE id \u003e 100) TO STDOUT WITH CSV;\n\n-- Import data from stdin\nCOPY tablename FROM STDIN;\n\n-- Import CSV with headers\nCOPY tablename FROM STDIN WITH CSV HEADER;\n```\n\nThis works with psql's `\\copy` command and programmatic COPY operations from PostgreSQL drivers.\n\n## Graceful Shutdown\n\nDuckgres handles shutdown signals (SIGINT, SIGTERM) gracefully:\n\n- Stops accepting new connections immediately\n- Waits for in-flight queries to complete (default 30s timeout)\n- Logs active connection count during shutdown\n- Closes all database connections cleanly\n\nThe shutdown timeout can be configured:\n\n```go\ncfg := server.Config{\n    ShutdownTimeout: 60 * time.Second,\n}\n```\n\n## Rate Limiting\n\nBuilt-in rate limiting protects against brute-force authentication attacks:\n\n- **Failed attempt tracking**: Bans IPs after too many failed auth attempts\n- **Connection limits**: Limits concurrent connections per IP and, when configured, total concurrent sessions. In K8s multi-tenant mode, org `max_connections` is enforced cluster-wide through runtime-store leases.\n- **Auto-cleanup**: Expired records are automatically cleaned up\n\n```yaml\nrate_limit:\n  max_failed_attempts: 5        # Ban after 5 failures\n  failed_attempt_window: \"5m\"   # Within 5 minutes\n  ban_duration: \"15m\"           # Ban lasts 15 minutes\n  max_connections_per_ip: 100   # Max concurrent connections\n  max_connections: 16           # Max total concurrent sessions (0 = unlimited)\n```\n\n## Usage Examples\n\n```sql\n-- Create a table\nCREATE TABLE events (\n    id INTEGER,\n    name VARCHAR,\n    timestamp TIMESTAMP,\n    value DOUBLE\n);\n\n-- Insert data\nINSERT INTO events VALUES\n    (1, 'click', '2024-01-01 10:00:00', 1.5),\n    (2, 'view', '2024-01-01 10:01:00', 2.0);\n\n-- Query with DuckDB's analytical power\nSELECT name, COUNT(*), AVG(value)\nFROM events\nGROUP BY name;\n\n-- Use prepared statements (via client drivers)\n-- Works with lib/pq, psycopg2, JDBC, etc.\n```\n\n## Architecture\n\nDuckgres supports three run modes: **standalone** (single process, default), **control-plane** (multi-process with worker pool), and **duckdb-service** (worker process mode used by the control plane).\n\n### Standalone Mode\n\nThe default mode runs everything in a single process:\n\n```\n┌─────────────────┐\n│  PostgreSQL     │\n│  Client (psql)  │\n└────────┬────────┘\n         │ PostgreSQL Wire Protocol (TLS)\n         ▼\n┌─────────────────┐\n│    Duckgres     │\n│    Server       │\n└────────┬────────┘\n         │ database/sql\n         ▼\n┌─────────────────┐\n│    DuckDB       │\n│  (per-user db)  │\n│  + Extensions   │\n│  + DuckLake     │\n└─────────────────┘\n```\n\n### Control Plane Mode\n\nFor production deployments, control-plane mode splits the server into a **control plane** and a pool of long-lived **worker processes**. The control plane owns client connections end-to-end (TLS, authentication, PostgreSQL wire protocol, SQL transpilation), while workers are thin DuckDB execution engines reachable via Arrow Flight SQL over Unix sockets. Optional control-plane Flight ingress (`flight_port`) also exposes Arrow Flight SQL directly with HTTP Basic auth (`Authorization: Basic ...`), compatible with Duckhog clients.\n\n```\n                    CONTROL PLANE (duckgres --mode control-plane)\n                    ┌──────────────────────────────────────────────┐\n  PG Client ──TLS──\u003e│ PG TCP Listener                              │\n Flight SQL Client ─\u003e│ Flight SQL TCP Listener (Basic Auth)         │\n                    │ TLS Termination + Password Auth              │\n                    │ PostgreSQL Wire Protocol                     │\n                    │ SQL Transpilation (PG → DuckDB)              │\n                    │ Rate Limiting                                │\n                    │ Session Manager + Connection Router           │\n                    │   │ Arrow Flight SQL (Unix socket)           │\n                    │   ▼                                          │\n                    └──────────────────────────────────────────────┘\n                                                           │\n                                                Flight SQL (UDS)\n                                                           │\n                    WORKER POOL                            ▼\n                    ┌──────────────────────────────────────────────┐\n                    │ Worker 1 (duckgres --mode duckdb-service)    │\n                    │   Arrow Flight SQL Server (Unix socket)      │\n                    │   Bearer Token Auth                          │\n                    │   DuckDB Instance (long-lived)               │\n                    │   ├── Session 1                               │\n                    │   ├── Session 2                               │\n                    │   └── Session N ...                           │\n                    ├──────────────────────────────────────────────┤\n                    │ Worker 2 ...                                  │\n                    └──────────────────────────────────────────────┘\n```\n\nStart in control-plane mode:\n\n```bash\n# Start in control-plane mode (workers spawn on demand, 1 per connection)\n./duckgres --mode control-plane --port 5432\n\n# Enable Flight SQL ingress for Duckhog-compatible clients\n./duckgres --mode control-plane --port 5432 --flight-port 8815\n\n# Pre-warm 2 process workers and cap at 10\n./duckgres --mode control-plane --port 5432 --process-min-workers 2 --process-max-workers 10\n\n# Connect with psql (identical to standalone mode)\nPGPASSWORD=postgres psql \"host=localhost port=5432 user=postgres sslmode=require\"\n\n# Flight SQL clients use Basic auth headers (user/password)\n# Example endpoint: grpc+tls://localhost:8815\n```\n\n**Zero-downtime deployment** using the handover protocol:\n\n```bash\n# Start the first control plane with a handover socket\n./duckgres --mode control-plane --port 5432 --handover-socket /var/run/duckgres/handover.sock\n\n# Deploy a new version - it takes over the listener and workers without dropping connections\n./duckgres-v2 --mode control-plane --port 5432 --handover-socket /var/run/duckgres/handover.sock\n```\n\nWhen running under **systemd** with `RuntimeDirectory`, ensure `RuntimeDirectoryPreserve=yes` is set in your unit file. This prevents systemd from cleaning up or remounting the socket directory as read-only when the old process exits during a handover.\n\n**Rolling worker updates** via signal:\n\n```bash\n# Replace workers one at a time (drains sessions before replacing each worker)\nkill -USR2 \u003ccontrol-plane-pid\u003e\n```\n\n### Remote Worker Backend\n\nIn Kubernetes environments, `--worker-backend remote` is the multitenant path. It requires `--config-store`. Control-plane replicas coordinate through durable runtime rows in the config-store Postgres DB, spawn worker pods via the Kubernetes API, and communicate with them over gRPC (Arrow Flight SQL). Planned rolling deploys mark old replicas draining, fail readiness, and wait up to `handover_drain_timeout` before forcing shutdown. Unplanned control-plane failure still drops live pgwire connections; Flight may reconnect with a durable session token if the worker survives and the token is still valid.\n\nManaged-hostname routing is controlled by `--sni-routing-mode` and `--managed-hostname-suffixes`. For Postgres, an explicit startup `database`/`dbname` takes priority, but when SNI matches a managed suffix the hostname prefix and requested database must resolve to the same org. If the startup database is empty, the managed SNI prefix is used as the database fallback. Unknown `--sni-routing-mode` values behave like `off`.\n\nWorkers are spawned on demand: when an org opens a session with no reusable worker, the control plane creates a worker pod (sized from the connection's `duckgres.worker_cpu`/`worker_memory` request, or a default), activates it over the worker control RPC, and it becomes hot for that org. When its last session ends, the worker moves to `hot_idle` instead of being retired immediately: it keeps the org assignment and DuckLake attachment so any control-plane replica can reclaim it for the same org (by exact worker shape) without full reactivation, until its `duckgres.worker_ttl` expires. Hot-idle reuse is image/version strict; a worker whose image no longer matches the org target is retired instead of reused. The janitor retires hot-idle workers at their TTL. The main lifecycle is: idle → reserved → activating → hot → hot_idle → retired. Workers can also move through `draining` during shutdown, rollout, or cleanup. (Spawn latency is hidden by the node-headroom controller, which keeps placeholder pods ready for real workers to preempt.)\n\n```bash\n# Local multitenant K8s workflow\njust run-multitenant-kind\n```\n\nSee [`k8s/README.md`](k8s/README.md) for the full architecture, configuration reference, manifest details, and the default local kind workflow via `just run-multitenant-kind`. The older OrbStack path remains available through `just run-multitenant-local` for manual macOS iteration.\n\nOn the multi-tenant path, the config store now keeps per-team managed-warehouse metadata in addition to team/user auth and limits. That team-scoped contract is intended to become the source of truth for the tenant warehouse DB, the tenant DuckLake metadata store (which may live on shared Aurora or a dedicated RDS instance), object-store settings, worker identity, secret references, and provisioning state. The older config-store `DuckLakeConfig` singleton remains only as a legacy cluster-wide setting and should not be treated as authoritative for multi-tenant runtime wiring.\n\nThe shared K8s pool keeps workers neutral at startup, reserves them per org, activates tenant runtime over the control-plane RPC channel, and keeps idle activated workers briefly available for same-org hot-idle reuse before janitor retirement.\n\nManaged-warehouse contract notes:\n\n- At most one managed-warehouse row exists per team. The row may be absent before first provisioning or after cleanup, but there is never more than one active warehouse contract for a team.\n- The admin API exposes that contract at `GET /api/v1/teams/:name/warehouse` and `PUT /api/v1/teams/:name/warehouse`. Team list/get responses also include a nested `warehouse` object when present.\n- User rows support an optional `default_catalog` field on `POST /api/v1/users` and `PUT /api/v1/orgs/:id/users/:username`. The default is empty, which preserves the standard DuckLake-first session behavior. Set `default_catalog` to `iceberg` for users whose sessions should resolve schema-qualified names and compatibility metadata through the Iceberg catalog by default; a client-supplied startup `search_path` still takes precedence.\n- The typed sections are `warehouse_database`, `metadata_store`, `s3`, `worker_identity`, and structured secret refs for `warehouse_database_credentials`, `metadata_store_credentials`, `s3_credentials`, and `runtime_config`. In shared warm mode, every non-empty secret ref must store an explicit `namespace`, and it must match `worker_identity.namespace`.\n- Secret references only are stored in the config store. Secret material remains outside the database.\n- The provisioning fields are stored directly on the warehouse row as overall `state` / `status_message`, per-resource `*_state` / `*_status_message`, plus `ready_at` and `failed_at`.\n- Those state fields are open strings. Canonical values are `pending`, `provisioning`, `ready`, `failed`, `deleting`, and `deleted`, but callers may persist other values while workflows evolve.\n\n## Two-Tier Query Processing\n\nDuckgres uses a two-tier approach to handle both PostgreSQL and DuckDB-specific SQL syntax transparently:\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│                        Incoming Query                           │\n└─────────────────────────────┬───────────────────────────────────┘\n                              ▼\n┌─────────────────────────────────────────────────────────────────┐\n│                  Tier 1: PostgreSQL Parser                      │\n│                   (pg_query_go / libpg_query)                   │\n└──────────────┬─────────────────────────────────┬────────────────┘\n               │                                 │\n          Parse OK                          Parse Failed\n               │                                 │\n               ▼                                 ▼\n┌──────────────────────────┐    ┌─────────────────────────────────┐\n│   Transpile PG → DuckDB  │    │   Tier 2: DuckDB Validation     │\n│   (type mappings, etc.)  │    │   (EXPLAIN or direct execute)   │\n└──────────────┬───────────┘    └──────────────┬──────────────────┘\n               │                               │\n               ▼                               ▼\n┌─────────────────────────────────────────────────────────────────┐\n│                     Execute on DuckDB                           │\n└─────────────────────────────────────────────────────────────────┘\n```\n\n### How It Works\n\n1. **Tier 1 (PostgreSQL Parser)**: All queries first pass through the PostgreSQL parser. Valid PostgreSQL syntax is transpiled to DuckDB-compatible SQL (handling differences in types, functions, and system catalogs).\n\n2. **Tier 2 (DuckDB Fallback)**: If PostgreSQL parsing fails, the query is validated directly against DuckDB using `EXPLAIN`. If valid, it executes natively. This enables DuckDB-specific syntax that isn't valid PostgreSQL.\n\n### Supported DuckDB-Specific Syntax\n\nThe following DuckDB features work transparently through the fallback mechanism: `FROM`-first queries, `SELECT * EXCLUDE/REPLACE`, `DESCRIBE`, `SUMMARIZE`, `QUALIFY` clause, lambda functions, positional joins, `ASOF` joins, struct operations, `COLUMNS` expression, and `SAMPLE`.\n\n## Supported Features\n\n### SQL Commands\n- `SELECT` - Full query support with binary result format\n- `INSERT` - Single and multi-row inserts\n- `UPDATE` - With WHERE clauses\n- `DELETE` - With WHERE clauses\n- `CREATE TABLE/INDEX/VIEW`\n- `DROP TABLE/INDEX/VIEW`\n- `ALTER TABLE`\n- `BEGIN/COMMIT/ROLLBACK` (DuckDB transaction support)\n- `COPY` - Bulk data loading and export (see below)\n\n### PostgreSQL Compatibility\n- Extended query protocol (prepared statements)\n- Binary and text result formats\n- MD5 password authentication\n- Basic `pg_catalog` system tables for client compatibility\n- `\\dt`, `\\d`, and other psql meta-commands\n\n## Transaction Isolation\n\nDuckDB provides **snapshot isolation** (MVCC), which is stricter than PostgreSQL's default `read committed`. In practice this means:\n\n| Behavior | PostgreSQL (default) | Duckgres (DuckDB) |\n|----------|---------------------|-------------------|\n| Default isolation level | Read Committed | Snapshot (≈ Serializable) |\n| Non-repeatable reads | Possible | Not possible |\n| Phantom reads | Possible | Not possible |\n| Write conflicts | Last writer wins | Second writer gets a conflict error |\n\nClients that issue `SET transaction_isolation` or `SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL ...` will succeed silently — the setting is accepted but DuckDB always operates at snapshot isolation. `SHOW transaction_isolation` returns `read committed` for client compatibility.\n\nSince DuckDB's isolation is strictly stronger than PostgreSQL's default, applications that work correctly under read committed will also work correctly here. The only observable difference is write-write conflicts: DuckDB will reject a concurrent write that PostgreSQL would silently accept under read committed.\n\n## Limitations\n\n- **Single Node**: No built-in replication or clustering\n- **Limited System Catalog**: Some `pg_*` system tables are stubs (return empty)\n- Unmapped DuckDB types (MAP, STRUCT, UNION, ENUM, BIT) fall back to OidText\n\n## SQL Client Compatibility\n\nDuckgres implements a subset of PostgreSQL's system catalog to satisfy introspection queries from common SQL clients, ORMs, and BI tools. The tables below document current coverage.\n\n### pg_catalog Views\n\n| View | Status | Notes |\n|------|--------|-------|\n| `pg_class` | Implemented | `pg_class_full` wrapper adding `relforcerowsecurity`; DuckLake variant sources from `duckdb_tables()`/`duckdb_views()` |\n| `pg_namespace` | Implemented | Maps `main` → `public`; DuckLake variant derives from `duckdb_tables()`/`duckdb_views()` |\n| `pg_attribute` | Implemented | Maps DuckDB internal type OIDs to PG OIDs via `duckdb_columns()` JOIN; fixes `atttypmod` for NUMERIC |\n| `pg_type` | Implemented | Fixes NULLs + adds synthetic entries for missing OIDs (json, jsonb, bpchar, text, record, array types) |\n| `pg_database` | Implemented | Hardcoded: postgres, template0, template1, testdb |\n| `pg_stat_user_tables` | Implemented | Uses `reltuples` from pg_class; zeros for scan/tuple stats |\n| `pg_roles` | Stub (empty) | Single hardcoded `duckdb` superuser |\n| `pg_constraint` | Stub (empty) | |\n| `pg_enum` | Stub (empty) | |\n| `pg_collation` | Stub (empty) | |\n| `pg_policy` | Stub (empty) | |\n| `pg_inherits` | Stub (empty) | |\n| `pg_statistic_ext` | Stub (empty) | |\n| `pg_publication` | Stub (empty) | |\n| `pg_publication_rel` | Stub (empty) | |\n| `pg_publication_tables` | Stub (empty) | |\n| `pg_rules` | Stub (empty) | |\n| `pg_matviews` | Stub (empty) | |\n| `pg_partitioned_table` | Stub (empty) | |\n| `pg_stat_activity` | Stub (empty) | Intercepted at query time for live data |\n| `pg_statio_user_tables` | Stub (empty) | |\n| `pg_stat_statements` | Stub (empty) | |\n| `pg_indexes` | Stub (empty) | |\n| `pg_settings` | Missing | `current_setting()` macro handles `server_version` and `server_encoding` only |\n| `pg_proc` | Missing | DuckDB has native `pg_catalog.pg_proc` but no wrapper |\n| `pg_description` | Missing | Handled via `obj_description()`/`col_description()` macros returning NULL |\n| `pg_depend` | Missing | |\n| `pg_am` | Missing | |\n| `pg_attrdef` | Missing | |\n| `pg_tablespace` | Missing | |\n\n### information_schema Views\n\n| View | Status | Notes |\n|------|--------|-------|\n| `tables` | Implemented | Filters internal views, normalizes `main` → `public` |\n| `columns` | Implemented | DuckDB → PG type name normalization, optional metadata overlay |\n| `schemata` | Implemented | Adds synthetic entries for `pg_catalog`, `information_schema`, `pg_toast` |\n| `views` | Implemented | Filters internal views |\n| `key_column_usage` | Missing | Used by ORMs for relationship discovery |\n| `table_constraints` | Missing | Used by ORMs for relationship discovery |\n| `referential_constraints` | Missing | Used by ORMs for FK introspection |\n\n### Functions \u0026 Macros\n\n| Function | Status | Notes |\n|----------|--------|-------|\n| `format_type(oid, int)` | Implemented | Comprehensive OID → name mapping |\n| `pg_get_expr(text, oid)` | Implemented | Returns NULL |\n| `pg_get_indexdef(oid)` | Implemented | Returns empty string |\n| `pg_get_constraintdef(oid)` | Implemented | Returns empty string |\n| `pg_get_serial_sequence(text, text)` | Implemented | Returns NULL |\n| `pg_table_is_visible(oid)` | Implemented | Always true |\n| `pg_get_userbyid(oid)` | Implemented | Maps OID 10 → `postgres`, 6171 → `pg_database_owner` |\n| `obj_description(oid, text)` | Implemented | Returns NULL |\n| `col_description(oid, int)` | Implemented | Returns NULL |\n| `shobj_description(oid, text)` | Implemented | Returns NULL |\n| `has_table_privilege(text, text)` | Implemented | Always true |\n| `has_schema_privilege(text, text)` | Implemented | Always true |\n| `pg_encoding_to_char(int)` | Implemented | Always `UTF8` |\n| `version()` | Implemented | Returns PG 15.0 compatible string |\n| `current_setting(text)` | Implemented | Handles `server_version` and `server_encoding` |\n| `pg_is_in_recovery()` | Implemented | Always false |\n| `pg_backend_pid()` | Implemented | Returns 0 |\n| `pg_size_pretty(bigint)` | Implemented | Full human-readable formatting |\n| `pg_total_relation_size(oid)` | Implemented | Returns 0 |\n| `pg_relation_size(oid)` | Implemented | Returns 0 |\n| `pg_table_size(oid)` | Implemented | Returns 0 |\n| `pg_indexes_size(oid)` | Implemented | Returns 0 |\n| `pg_database_size(text)` | Implemented | Returns 0 |\n| `quote_ident(text)` | Implemented | |\n| `quote_literal(text)` | Implemented | |\n| `quote_nullable(text)` | Implemented | |\n| `txid_current()` | Implemented | Epoch-based pseudo ID |\n| `current_schema()` | Missing | |\n| `current_schemas(bool)` | Missing | |\n\n### Startup Parameters\n\n| Parameter | Value |\n|-----------|-------|\n| `server_version` | `15.0 (Duckgres)` |\n| `server_encoding` | `UTF8` |\n| `client_encoding` | `UTF8` |\n| `DateStyle` | `ISO, MDY` |\n| `TimeZone` | `UTC` |\n| `integer_datetimes` | `on` |\n| `standard_conforming_strings` | `on` |\n| `IntervalStyle` | Missing |\n\n## Dependencies\n\n- [DuckDB Go Driver](https://github.com/duckdb/duckdb-go) - DuckDB database engine\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fposthog%2Fduckgres","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fposthog%2Fduckgres","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fposthog%2Fduckgres/lists"}