---
title: "HeliosProxy Integration Guide"
---

# 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:

```bash
# Default build (connection pooling modes)
cargo install heliosdb-proxy

# Every optional feature plus the PostgreSQL topology provider
cargo install heliosdb-proxy --features "all-features,postgres-topology"

# Container image (built with all-features,postgres-topology)
# Pin a release tag in production
docker pull ghcr.io/heliosdatabase/hdb-heliosdb-proxy:latest
```

### 2. Create Configuration

```toml
# /etc/heliosproxy/config.toml
listen_address = "127.0.0.1:5433"
admin_address = "127.0.0.1:9090"
tr_enabled = true
tr_mode = "session"

[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 = "least_connections"
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 = "127.0.0.1"
port = 5432
role = "primary"
weight = 100
enabled = 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](https://www.heliosdb.com/docs/lite/guides/heliosproxy_configuration_reference/).

### 3. Start the Proxy

```bash
heliosdb-proxy --config /etc/heliosproxy/config.toml
```

### 4. Connect Applications

```bash
# Connect via proxy instead of directly to database
psql -h localhost -p 5433 -U myuser -d mydb
```

---

## Application Integration

### Python (psycopg2/asyncpg)

```python
# Before: Direct connection
conn = psycopg2.connect(
    host="db.example.com",
    port=5432,
    database="mydb"
)

# After: Via HeliosProxy
conn = psycopg2.connect(
    host="proxy.example.com",
    port=5433,  # Proxy port
    database="mydb"
)
```

### Node.js (pg)

```javascript
// Before: Direct connection
const pool = new Pool({
  host: 'db.example.com',
  port: 5432,
  database: 'mydb'
});

// After: Via HeliosProxy
const pool = new Pool({
  host: 'proxy.example.com',
  port: 5433,  // Proxy port
  database: 'mydb'
});
```

### Java (JDBC)

```java
// Before
String url = "jdbc:postgresql://db.example.com:5432/mydb";

// After
String url = "jdbc:postgresql://proxy.example.com:5433/mydb";
```

### Go (pgx)

```go
// Before
conn, err := pgx.Connect(ctx, "postgres://user:pass@db.example.com:5432/mydb")

// After
conn, err := pgx.Connect(ctx, "postgres://user:pass@proxy.example.com:5433/mydb")
```

### Rust (tokio-postgres)

```rust
// Before
let (client, connection) = tokio_postgres::connect(
    "host=db.example.com port=5432 dbname=mydb",
    NoTls
).await?;

// After
let (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 ALL` when it returns to the pool, and `DISCARD ALL` is 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"`:

```python
# Good: Transaction boundaries are clear
with 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 connection
with conn.cursor() as cur:
    cur.execute("SELECT * FROM orders")
    # Process results slowly...
    # Connection held until next statement or disconnect
```

### Statement Mode Guidelines

These apply once `DISCARD ALL` is available in Lite; until then keep session pooling. When using `mode = "statement"`:

```python
# Good: Simple, independent queries
results = []
for id in ids:
    cur.execute("SELECT * FROM items WHERE id = %s", (id,))
    results.append(cur.fetchone())

# Bad: Prepared statements won't work
cur.execute("PREPARE get_item AS SELECT * FROM items WHERE id = $1")
cur.execute("EXECUTE get_item(1)")  # May fail - different connection
```

---

## Query Routing with Hints

Use SQL comments to control routing:

```sql
-- 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 hints
SELECT /* helios:route=standby helios:cache=skip */ * FROM events;
```

### Application Helper

```python
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}"

# Usage
cur.execute(read_from_primary("SELECT balance FROM accounts WHERE id = %s"), (user_id,))
```

---

## High Availability Setup

### Multi-Node Configuration

```toml
listen_address = "0.0.0.0:5433"
admin_address = "127.0.0.1:9090"   # keep the admin API on loopback, or set admin_token
tr_enabled = true
tr_mode = "session"

[pool_mode]
mode = "session"   # transaction pooling needs DISCARD ALL, planned for Lite
max_pool_size = 50

[pool]
min_connections = 2
max_connections = 50
idle_timeout_secs = 300
max_lifetime_secs = 1800
acquire_timeout_secs = 30
test_on_acquire = true

[load_balancer]
read_strategy = "least_connections"
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"

# Primary node
[[nodes]]
host = "db-primary.internal"
port = 5432
role = "primary"
weight = 100
enabled = true

# Standby for failover
[[nodes]]
host = "db-standby.internal"
port = 5432
role = "standby"
weight = 100
enabled = true

# Read replicas for scaling
[[nodes]]
host = "db-replica-1.internal"
port = 5432
role = "replica"
weight = 100
enabled = true

[[nodes]]
host = "db-replica-2.internal"
port = 5432
role = "replica"
weight = 100
enabled = true
```

### Failover Behavior

1. **Primary fails**: New writes wait up to `write_timeout_secs` for a healthy primary
2. **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
3. **Replica fails**: Traffic redistributed to remaining replicas
4. **All replicas fail**: Reads fall back to primary

---

## Kubernetes Deployment

### ConfigMap

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: heliosproxy-config
data:
  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 = true
```

### Deployment

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: heliosproxy
spec:
  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-config
```

### Service

```yaml
apiVersion: v1
kind: Service
metadata:
  name: heliosproxy
spec:
  selector:
    app: heliosproxy
  ports:
  - name: postgres
    port: 5433
    targetPort: 5433
```

The 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](https://www.heliosdb.com/docs/lite/guides/docker_deployment/). The HeliosProxy image is public on GHCR.

```yaml
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](https://www.heliosdb.com/docs/lite/guides/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](/docs/proxy/admin-api/) lists every counter.

```bash
curl http://127.0.0.1:9090/metrics
curl http://127.0.0.1:9090/metrics/prometheus
```

---

## Troubleshooting

### Connection Issues

```bash
# Check proxy health
curl http://localhost:9090/health

# Check pool stats
curl http://localhost:9090/pools

# Check node status
curl http://localhost:9090/nodes
```

### Pool Exhaustion

Symptoms: `connection acquire timeout` errors

Solutions:
1. Increase `max_pool_size`
2. Switch to `transaction` or `statement` mode once `DISCARD ALL` is available in Lite (until then these modes close a backend connection on every release)
3. Reduce application connection hold time
4. Scale horizontally with multiple proxy instances

### Query Routing Issues

```bash
# Enable debug logging and watch the routing decisions in the log
heliosdb-proxy --config config.toml --log-level debug

# Check which node is the current primary and which nodes are healthy
curl http://localhost:9090/topology
```

---

## See Also

- [Configuration Reference](https://www.heliosdb.com/docs/lite/guides/heliosproxy_configuration_reference/)
- [HeliosProxy documentation](/docs/proxy/): configuration, admin API and feature flags
- [Release Notes](https://www.heliosdb.com/docs/lite/releases/heliosproxy_v100_release_notes/)
