HeliosProxy Integration Guide
How to integrate HeliosProxy into your application stack.
Quick Start
1. Install HeliosProxy
HeliosProxy is a separate, Apache-2.0 product. Install it from crates.io or use the container image:
# Default build (connection pooling modes)cargo install heliosdb-proxy
# Every optional feature plus the PostgreSQL topology providercargo install heliosdb-proxy --features "all-features,postgres-topology"
# Container image (built with all-features,postgres-topology)# Pin a release tag in productiondocker pull ghcr.io/heliosdatabase/hdb-heliosdb-proxy:latest2. Create Configuration
listen_address = "127.0.0.1:5433"admin_address = "127.0.0.1:9090"tr_enabled = truetr_mode = "session"
[pool]min_connections = 2max_connections = 100idle_timeout_secs = 300max_lifetime_secs = 1800acquire_timeout_secs = 30test_on_acquire = true
[load_balancer]read_strategy = "least_connections"read_write_split = truelatency_threshold_ms = 100
[health]check_interval_secs = 5check_timeout_secs = 3failure_threshold = 3success_threshold = 2check_query = "SELECT 1"
[[nodes]]host = "127.0.0.1"port = 5432role = "primary"weight = 100enabled = true[pool], [load_balancer], [health] and at least one [[nodes]] entry (with weight and enabled) are required. [pool_mode] is optional and defaults to session pooling. See the configuration reference.
3. Start the Proxy
heliosdb-proxy --config /etc/heliosproxy/config.toml4. Connect Applications
# Connect via proxy instead of directly to databasepsql -h localhost -p 5433 -U myuser -d mydbApplication Integration
Python (psycopg2/asyncpg)
# Before: Direct connectionconn = psycopg2.connect( host="db.example.com", port=5432, database="mydb")
# After: Via HeliosProxyconn = psycopg2.connect( host="proxy.example.com", port=5433, # Proxy port database="mydb")Node.js (pg)
// Before: Direct connectionconst pool = new Pool({ host: 'db.example.com', port: 5432, database: 'mydb'});
// After: Via HeliosProxyconst pool = new Pool({ host: 'proxy.example.com', port: 5433, // Proxy port database: 'mydb'});Java (JDBC)
// BeforeString url = "jdbc:postgresql://db.example.com:5432/mydb";
// AfterString url = "jdbc:postgresql://proxy.example.com:5433/mydb";Go (pgx)
// Beforeconn, err := pgx.Connect(ctx, "postgres://user:pass@db.example.com:5432/mydb")
// Afterconn, err := pgx.Connect(ctx, "postgres://user:pass@proxy.example.com:5433/mydb")Rust (tokio-postgres)
// Beforelet (client, connection) = tokio_postgres::connect( "host=db.example.com port=5432 dbname=mydb", NoTls).await?;
// Afterlet (client, connection) = tokio_postgres::connect( "host=proxy.example.com port=5433 dbname=mydb", NoTls).await?;Connection Pooling Best Practices
Choosing a Pool Mode
HeliosDB Lite backends: use session pooling (the default). Transaction and statement pooling reset each connection with
DISCARD ALLwhen it returns to the pool, andDISCARD ALLis planned for Lite; until then the reset fails, HeliosProxy closes the backend connection instead of reusing it, and pooling gives no benefit.
| Use Case | Recommended Mode with Lite | Reason |
|---|---|---|
| Web applications | session | Transaction pooling needs DISCARD ALL, planned for Lite |
| Microservices | session | Transaction pooling needs DISCARD ALL, planned for Lite |
| Background jobs | session | Long-running operations |
| Prepared statements | session | Statements tied to connection |
| Simple SELECT queries | session | Statement pooling needs DISCARD ALL, planned for Lite |
Transaction Mode Guidelines
These apply once DISCARD ALL is available in Lite; until then keep session pooling. When using mode = "transaction":
# Good: Transaction boundaries are clearwith conn.cursor() as cur: cur.execute("BEGIN") cur.execute("INSERT INTO orders VALUES (%s)", (order_id,)) cur.execute("COMMIT") # Connection returned to pool here
# Bad: Implicit transaction holds connectionwith conn.cursor() as cur: cur.execute("SELECT * FROM orders") # Process results slowly... # Connection held until next statement or disconnectStatement Mode Guidelines
These apply once DISCARD ALL is available in Lite; until then keep session pooling. When using mode = "statement":
# Good: Simple, independent queriesresults = []for id in ids: cur.execute("SELECT * FROM items WHERE id = %s", (id,)) results.append(cur.fetchone())
# Bad: Prepared statements won't workcur.execute("PREPARE get_item AS SELECT * FROM items WHERE id = $1")cur.execute("EXECUTE get_item(1)") # May fail - different connectionQuery Routing with Hints
Use SQL comments to control routing:
-- Force to primary (for critical reads)SELECT /* helios:route=primary */ balance FROM accounts WHERE id = 1;
-- Force to standby (for read replicas)SELECT /* helios:route=standby */ * FROM analytics_data;
-- Skip cache (for real-time data)SELECT /* helios:cache=skip */ * FROM live_metrics;
-- Multiple hintsSELECT /* helios:route=standby helios:cache=skip */ * FROM events;Application Helper
def read_from_primary(query): """Execute query on primary node.""" return f"/* helios:route=primary */ {query}"
def read_from_replica(query): """Execute query on replica node.""" return f"/* helios:route=standby */ {query}"
# Usagecur.execute(read_from_primary("SELECT balance FROM accounts WHERE id = %s"), (user_id,))High Availability Setup
Multi-Node Configuration
listen_address = "0.0.0.0:5433"admin_address = "127.0.0.1:9090" # keep the admin API on loopback, or set admin_tokentr_enabled = truetr_mode = "session"
[pool_mode]mode = "session" # transaction pooling needs DISCARD ALL, planned for Litemax_pool_size = 50
[pool]min_connections = 2max_connections = 50idle_timeout_secs = 300max_lifetime_secs = 1800acquire_timeout_secs = 30test_on_acquire = true
[load_balancer]read_strategy = "least_connections"read_write_split = truelatency_threshold_ms = 100
[health]check_interval_secs = 5check_timeout_secs = 3failure_threshold = 3success_threshold = 2check_query = "SELECT 1"
# Primary node[[nodes]]host = "db-primary.internal"port = 5432role = "primary"weight = 100enabled = true
# Standby for failover[[nodes]]host = "db-standby.internal"port = 5432role = "standby"weight = 100enabled = true
# Read replicas for scaling[[nodes]]host = "db-replica-1.internal"port = 5432role = "replica"weight = 100enabled = true
[[nodes]]host = "db-replica-2.internal"port = 5432role = "replica"weight = 100enabled = trueFailover Behavior
- Primary fails: New writes wait up to
write_timeout_secsfor a healthy primary - Standby promoted: An external HA manager promotes the standby; with the default static topology, update the node roles (reload with
SIGHUP) or bring the primary back at the same address - Replica fails: Traffic redistributed to remaining replicas
- All replicas fail: Reads fall back to primary
Kubernetes Deployment
ConfigMap
apiVersion: v1kind: ConfigMapmetadata: name: heliosproxy-configdata: config.toml: | listen_address = "0.0.0.0:5433" # Non-loopback admin bind so kubelet probes can reach it: admin_token is then required. # /healthz and /readyz stay open for probes; every other admin route needs the token. admin_address = "0.0.0.0:9090" admin_token = "${HELIOS_ADMIN_TOKEN}" tr_enabled = true tr_mode = "session"
[pool_mode] mode = "session" # transaction pooling needs DISCARD ALL, planned for Lite max_pool_size = 100
[pool] min_connections = 2 max_connections = 100 idle_timeout_secs = 300 max_lifetime_secs = 1800 acquire_timeout_secs = 30 test_on_acquire = true
[load_balancer] read_strategy = "round_robin" read_write_split = true latency_threshold_ms = 100
[health] check_interval_secs = 5 check_timeout_secs = 3 failure_threshold = 3 success_threshold = 2 check_query = "SELECT 1"
[[nodes]] host = "heliosdb-primary.default.svc.cluster.local" port = 5432 role = "primary" weight = 100 enabled = true
[[nodes]] host = "heliosdb-standby.default.svc.cluster.local" port = 5432 role = "standby" weight = 100 enabled = trueDeployment
apiVersion: apps/v1kind: Deploymentmetadata: name: heliosproxyspec: replicas: 2 selector: matchLabels: app: heliosproxy template: metadata: labels: app: heliosproxy spec: containers: - name: heliosproxy image: ghcr.io/heliosdatabase/hdb-heliosdb-proxy:latest # pin a release tag in production args: ["--config", "/etc/heliosproxy/config.toml"] env: - name: HELIOS_ADMIN_TOKEN valueFrom: secretKeyRef: name: heliosproxy-admin key: token ports: - containerPort: 5433 name: postgres - containerPort: 9090 name: admin volumeMounts: - name: config mountPath: /etc/heliosproxy livenessProbe: httpGet: path: /healthz port: admin initialDelaySeconds: 5 readinessProbe: httpGet: path: /readyz port: admin initialDelaySeconds: 2 volumes: - name: config configMap: name: heliosproxy-configService
apiVersion: v1kind: Servicemetadata: name: heliosproxyspec: selector: app: heliosproxy ports: - name: postgres port: 5433 targetPort: 5433The Service exposes only the PostgreSQL port. Reach the admin API with kubectl port-forward rather than publishing it.
Docker Compose
HeliosDB Lite images are built from the licensed source distribution (no public image is published); see Docker Deployment. The HeliosProxy image is public on GHCR.
services: heliosproxy: image: ghcr.io/heliosdatabase/hdb-heliosdb-proxy:latest # pin a release tag in production command: ["--config", "/etc/heliosproxy/config.toml"] ports: - "5433:5433" volumes: - ./config.toml:/etc/heliosproxy/config.toml:ro depends_on: - heliosdb
heliosdb: image: heliosdb-lite:latest # built from the licensed source distribution command: ["start", "--data-dir", "/data", "--listen", "0.0.0.0", "--port", "5432"] environment: - HELIOSDB_JWT_SECRET=${HELIOSDB_JWT_SECRET:?set HELIOSDB_JWT_SECRET} volumes: - heliosdb_data:/data
volumes: heliosdb_data:In config.toml, point the primary [[nodes]] entry at host = "heliosdb". For a primary with standbys, see the HA example in Docker Deployment.
Monitoring
The admin API serves server metrics as JSON at GET /metrics, and as Prometheus exposition text at GET /metrics/prometheus. That text arrives wrapped in a JSON text field, so a scraper must unwrap it. Counters include heliosdb_proxy_connections_total, heliosdb_proxy_queries_total, heliosdb_proxy_failovers_total and the Transaction Replay counters such as heliosdb_proxy_tr_failovers_total. The Admin API reference lists every counter.
curl http://127.0.0.1:9090/metricscurl http://127.0.0.1:9090/metrics/prometheusTroubleshooting
Connection Issues
# Check proxy healthcurl http://localhost:9090/health
# Check pool statscurl http://localhost:9090/pools
# Check node statuscurl http://localhost:9090/nodesPool Exhaustion
Symptoms: connection acquire timeout errors
Solutions:
- Increase
max_pool_size - Switch to
transactionorstatementmode onceDISCARD ALLis available in Lite (until then these modes close a backend connection on every release) - Reduce application connection hold time
- Scale horizontally with multiple proxy instances
Query Routing Issues
# Enable debug logging and watch the routing decisions in the logheliosdb-proxy --config config.toml --log-level debug
# Check which node is the current primary and which nodes are healthycurl http://localhost:9090/topologySee Also
- Configuration Reference
- HeliosProxy documentation: configuration, admin API and feature flags
- Release Notes