Overview
TimescaleDB Highly Available deploys TimescaleDB — the time-series database built as a PostgreSQL extension — as a multi-member PostgreSQL 18 cluster in one location. Patroni manages the members, elects a leader and fails over automatically when the leader is lost; a dedicated etcd cluster holds the consensus state. An HAProxy workload gives applications one stable address that always routes to the current leader, and an optional PgBouncer in front of it pools connections. The TimescaleDB extension is preloaded on every member and created automatically in your database, so hypertables, compression, continuous aggregates and retention policies work from the first connection. Scheduledpg_dumpall backups to S3, GCS or MinIO are optional.
Database credentials are not template values. The cluster reads its username, password and database name from a dictionary secret you create before installing, so no password passes through Helm or lands in the release. The template creates no credential secret of its own.
What Gets Created
Prerequisites
One secret must exist before you install. It holds the credentials your applications put in their connection strings. Secrets are org-level, so no GVC flag is involved.Create the database credentials secret
username, password and database. Patroni creates that user as the cluster superuser and that database the first time the cluster is initialized, runs CREATE EXTENSION timescaledb in it, and uses the same password for its internal replication role:postgres.credentialsSecretName to this name. Read it back later with cpln secret reveal CREDENTIALS_SECRET_NAME -o yaml — a bare cpln secret reveal prints only a summary table, not the values. Secret names are org-wide, so give each release its own.accessKey and secretKey — see Backing Up. Nothing else is required for a default install.
Installation
Install the chart from the marketplace registry, pointing it at the secret you created:UI
CLI
Terraform
Pulumi
Configuration
Each block below is the shipped default for that part ofvalues.yaml.
Cluster and Resources
replicas— Number of TimescaleDB members. Keep at least 3 for a cluster that survives the loss of one member; one member is the leader and accepts writes, the others stream from it.replicas: 1renders a single-member cluster with no failover.image— Thetimescale/timescaledb-haimage bundles PostgreSQL, the TimescaleDB extension and Patroni. Keep the PostgreSQL major at 18 unless you also changebackup.imageto match.multiZone— Spread the members across availability zones within the location, when the location supports it.
maxCpu to minCpu may not exceed 4:1. Raising maxCpu without raising minCpu can cross that limit and is rejected when the workload is applied.Credentials
CREATE EXTENSION IF NOT EXISTS timescaledb.
Storage
volumeset.capacity, so a member that stays down does not fill the leader’s disk; a member that exceeds the cap is re-cloned from the leader when it returns.
Internal Access
etcd Cluster
The bundled etcd template provides the consensus store. Its own values are passed through underetcd:
etcd.replicas— Must be an odd number of at least 3 (3, 5, 7); the chart refuses other values at render. Choose it at install time — the etcd template does not support changing the member count on a running release.etcd.internal_access— Must allow the TimescaleDB members to reach etcd on port2379. Withworkload-list, include//gvc/GVC_NAME/workload/RELEASE_NAME-timescaledb-haand//gvc/GVC_NAME/workload/RELEASE_NAME-etcd.
etcd History Compaction
Patroni renews its leader lease every few seconds and every renewal is a new etcd revision, so the etcd backend grows with time alone unless old revisions are compacted. Uncompacted, an otherwise idle cluster reaches etcd’s 2 GiB backend quota in a matter of months, etcd raises aNOSPACE alarm and goes read-only, and the TimescaleDB members can no longer renew their leases. The etcd.tuning defaults — periodic, 1h and 0 (etcd’s own 2 GiB limit) — keep the backend flat and should be left alone. The etcd template documents the mechanism, the accepted value formats and the read-only inspection commands.
Template version 1.0.0 did not compact — see Upgrading From 1.0.0.
HAProxy Leader Endpoint
Only the leader accepts writes; the other members are read-only. HAProxy checks every member’s Patroni REST API and sends port5432 traffic only to the one that reports itself primary, so applications keep one address through a failover.
pgbouncer.enabled: true. HAProxy reads the member list from replicas when it starts, so after changing replicas force a redeployment of RELEASE_NAME-timescaledb-ha-proxy.PgBouncer Connection Pooling
PgBouncer multiplexes many application connections into a smaller pool of real database connections. It connects to HAProxy, so leader routing and failover are handled for it, and it reads the same credentials secret — nothing extra to configure. When enabled it becomes the endpoint your applications connect to.minReplicas and maxReplicas.
Backup
A cron workload dumps every database and role withpg_dumpall through HAProxy on the schedule and uploads the compressed script to your bucket. Each provider’s bucket, cloud account and policy steps are under Backing Up.
backup.image— The backup container’spg_dumpallmust match the server major: tag18.1.0for the default PostgreSQL 18 image.backup.schedule— Standard five-field cron expression, evaluated in UTC.
Connecting
This template exposes no public endpoint. Everything below is reachable from inside the GVC, subject tointernal_access.type.
psql:
pg_is_in_recovery() returns f through HAProxy, because it always reaches the leader, and extversion confirms the TimescaleDB extension is installed in your database. The same query on a member directly returns t on a replica:
--replica to pick a member (RELEASE_NAME-timescaledb-ha-0 through RELEASE_NAME-timescaledb-ha-N); without it the CLI picks one for you. The superuser credentials are already present in the container’s environment, so psql does not prompt.
Applications should retry on connection errors with backoff. A failover, a rolling restart or a scale-down moves the leader, and connections open at that moment are reset while HAProxy re-routes to the new leader.
Using Hypertables
Any PostgreSQL client or ORM works unchanged. Turn a regular table into a hypertable, partitioned by time, and query it with time buckets:Operations
Checking Cluster State
Patroni’s own tool shows every member, its role, its state and its replication lag:/tmp/patroni_config.yml is the configuration the startup script writes in each member; patronictl needs it to find etcd. The same tool shows the cluster-wide settings Patroni keeps in etcd:
http://127.0.0.1:8405/stats in a browser:
Backing Up
Backups are off by default. Enable them withbackup.enabled: true, pick a backup.provider, and complete that provider’s setup below first. RELEASE_NAME-timescaledb-ha-backup runs on backup.schedule, connects through HAProxy with the username and password from your credentials secret, and uploads one postgres-TIMESTAMP.sql.gz object per run under backup.PROVIDER.prefix. The object is a gzip-compressed pg_dumpall script: every role and every database in the cluster, as plain SQL. HAProxy must be enabled.
Check the recent runs after enabling, and confirm the object actually landed in your bucket:
Backup Prerequisites
Complete the steps for your provider before enabling backups.AWS S3
-
Create an S3 bucket. Set
backup.aws.bucketto its name andbackup.aws.regionto its region. -
If you do not have one yet, create a Control Plane cloud account for the AWS account holding the bucket. Set
backup.aws.cloudAccountNameto its name. -
Create an IAM policy scoped to exactly that bucket, replacing
YOUR_BUCKET_NAME:
- Set
backup.aws.policyNameto that policy’s name. The template attaches it to the workload identity together withcpln-connectorand nothing else — the bucket in your policy is the only storage the backup container can reach. Upgrading from1.1.0or earlier narrows this; see Upgrading From 1.1.0.
GCS
-
Create a GCS bucket. Set
backup.gcp.bucketto its name. -
Create a Control Plane cloud account for the GCP project holding the bucket, and set
backup.gcp.cloudAccountNameto its name. Access is keyless — no credentials are stored. -
Grant the cloud account’s service account the Storage Admin role. The template additionally binds the identity to
roles/storage.objectAdminon exactly the bucket inbackup.gcp.bucket.
MinIO
No cloud account is needed — the backup container authenticates with a second prerequisite secret and uses path-style addressing, so any S3-compatible endpoint works.-
Create the bucket in MinIO. Set
backup.minio.bucketto its name. -
Set
backup.minio.endpointto the S3 API address including the port. For the minio template deployed in the same GVC that ishttp://WORKLOAD_NAME.GVC_NAME.cpln.local:9000. -
Create a dictionary secret holding exactly the keys
accessKeyandsecretKey, and setbackup.minio.credentialsSecretNameto its name. For theminiotemplate these are its admin username and password:
reveal on this secret as well as the database credentials secret. If the secret does not exist at install time the deployment wedges exactly as described in Prerequisites.
Restoring a Backup
Restoring hypertables is not the vanilla PostgreSQL procedure. The target must run the same TimescaleDB extension version as the source (2.28.3 for the default image — upgrade the extension after restoring, never before), and the data must be loaded with TimescaleDB’s background workers stopped and its timescaledb.restoring setting on, which is what timescaledb_pre_restore() and timescaledb_post_restore() do. Loading hypertable rows without that fails with invalid INSERT on the root table of hypertable.
Restoring the Scheduled pg_dumpall Backup
The scheduled backup is a whole-cluster plain SQL script. TimescaleDB’s documented restore wraps a single-databasepg_dump -Fc archive in timescaledb_pre_restore() / timescaledb_post_restore(); a pg_dumpall script does not fit that shape directly, because it reconnects to each database as it goes, so there is no single point at which to call timescaledb_pre_restore() for the database being loaded. Replaying the scheduled dump into a TimescaleDB database is therefore unverified on this template. Use it to recover roles, non-hypertable databases, and as the source for a per-database restore: extract the \connect DATABASE section of the script and load it as in Restoring a Single Database with the restoring setting on. Downloading the object is the same for every provider:
- AWS S3
- GCS
- MinIO
Restoring a Single Database
This is the procedure TimescaleDB documents, and the one to rehearse. It needs apg_dump custom-format archive of one database, taken with a PostgreSQL 18 client; the scheduled job does not produce one, so take it yourself through HAProxy with cpln port-forward RELEASE_NAME-timescaledb-ha-proxy 5432:5432 --gvc GVC_NAME open:
-j to pg_restore — parallel restore does not restore the TimescaleDB catalogs correctly. A must be owner of extension timescaledb error from pg_restore can be ignored when the extension already exists. Until timescaledb_post_restore() has run, background jobs (compression, retention, continuous aggregate refresh) stay stopped on that database.
Rotating the Password
Patroni writes its configuration from the secret each time a member starts and applies the password to the database only when the cluster is first initialized. After that the stored passwords live in PostgreSQL, so a rotation touches three places, and the order matters. This procedure has not been rehearsed on this template — practise it on a scratch release first.-
Update the
passwordkey in your credentials secret to the new value — for example withcpln apply -fof the full secret manifest — so any member that restarts from now on reads the new one. Nothing running changes yet: workloads resolve a secret only when a replica starts. -
On the leader (through HAProxy), change both roles that share the password. The replication role
replwas created with the same password as the superuser, and every member’s Patroni configuration will expect the new one after its next restart: -
Force a redeployment of the members, then of PgBouncer if it is enabled, so every process holds the new password. Expect a brief write outage while the members restart:
Upgrading From 1.0.0
Template version1.0.0 did not compact the bundled etcd cluster, so its backend grows with time alone and goes read-only once it reaches the 2 GiB quota — after which the TimescaleDB members restart-loop with exitCode: 0 and reason: Completed, which reads as healthy and is easily mistaken for a database fault. Upgrading to 1.0.1 or later turns compaction on and stops further growth, but it cannot shrink a backend that has already grown, and a cluster that has already raised a NOSPACE alarm needs operator recovery rather than an upgrade. Check first, with the read-only commands in If the Backend Quota Is Already Full, run against RELEASE_NAME-etcd.
Upgrading From 1.0.x
Template version1.1.0 removed postgres.username, postgres.password and postgres.database. They are now the prerequisite dictionary secret named by postgres.credentialsSecretName, and backup.minio.accessKey / backup.minio.secretKey moved the same way to backup.minio.credentialsSecretName. An upgrade that still carries any of the old keys is refused at render, before anything is applied, so your running cluster is left untouched:
Read the credentials the cluster already uses
Create the prerequisite secret with those same values
username, password and database. Different values here do not change the database — they leave every member unable to authenticate after its next restart.Remove the old keys from your values
postgres.username, postgres.password and postgres.database and set postgres.credentialsSecretName. If you back up to MinIO, replace backup.minio.accessKey and backup.minio.secretKey with backup.minio.credentialsSecretName.Upgrade, then rotate any default password
1.0.x shipped username: username and password: password as defaults, published in the public template repository. If your cluster still runs it, treat it as compromised and follow Rotating the Password after the upgrade.Upgrading From 1.1.0
Template version1.1.1 removes aws::ReadOnlyAccess from the backup identity. That AWS managed policy granted read access to every bucket in your AWS account and contains no write actions, so it never carried the backup itself — but it was silently supplying any read action your own bucket-scoped policy happened to omit. Update your IAM policy to the full action list in AWS S3 before upgrading; if it already matches, no action is needed. The identity now carries cpln-connector and your bucket-scoped policy only.
Upgrading From a Version Before 1.2.0
Template version1.2.0 changes how new clusters are initialized, and an upgrade does not apply those changes to a cluster that already exists:
-
Patroni’s consensus timeouts. The chart now initializes new clusters with
ttl: 45,loop_wait: 10andretry_timeout: 15. Earlier versions asked forttl: 30withretry_timeout: 30, a combination Patroni silently clamps rather than rejecting, so those clusters tolerated a shorter etcd outage than configured before demoting a healthy leader. Patroni reads these values only while the data directory is empty; afterwards they live in etcd, and an existing cluster keeps the ones it was created with. Move it withpatronictl— all three together, because Patroni requiresloop_wait + 2 × retry_timeout ≤ ttland quietly rewrites the others when one is changed alone:Confirm with theshow-configcommand in Checking Cluster State. -
Riding out an etcd outage. Clusters created from
1.2.0have Patroni’sfailsafe_modeon: when the leader loses etcd but every member still answers its REST API, it keeps serving instead of demoting. A cluster created earlier gains it only throughedit-config -s failsafe_mode=true.
1.2.0: backup.resources.memory rose from 128Mi to 512Mi because the smaller value OOM-killed the dump; passwords containing quotes or backslashes now work on both Patroni and PgBouncer. Before upgrading, confirm a recent backup actually completed — see Backing Up.
Scaling and Availability
- Leader loss fails over automatically. When the leader stops answering, Patroni promotes the most up-to-date replica and HAProxy’s health checks move port
5432to it. Writes are refused in between and connections open at that moment are reset — build retry with backoff into your applications. - Planned restarts hand leadership off first. Before a leader is stopped, the chart asks Patroni to switch over to a replica, so a rolling restart costs a few seconds of refused writes rather than a full failover. A
cpln helm upgradethat changes the member workload can restart members together; expect a brief write outage and plan upgrades as maintenance. - Losing etcd does not fail the cluster over. With
failsafe_modeon (clusters created from1.2.0), a leader that cannot reach etcd keeps serving as long as every other member still answers its REST API, and only a member Patroni recorded as eligible can win the next leader race. An etcd outage that also hides a replica still demotes the leader. - A member that stays down is re-cloned, not fatal. WAL retained for an absent member is capped at a quarter of
volumeset.capacity; past that, its slot is dropped and it re-clones from the leader when it returns. - Keep
replicasat 3 or more andetcd.replicasodd. Two members cannot fail over safely, and an even etcd count adds nothing to its fault tolerance. Chooseetcd.replicasat install time. - Changing
replicason a running release adds or removes members; a removed member that held the leader role triggers a failover. Afterwards force a redeployment ofRELEASE_NAME-timescaledb-ha-proxyso HAProxy picks up the new member list. - The first
cpln helm upgradeafter an install can re-apply resources that did not change and restart them. Plan it as a short outage. multiZone: trueon the members and on etcd spreads each across zones within the location, when the location supports it.
Troubleshooting
Workload never becomes ready and cpln logs returns nothing
Workload never becomes ready and cpln logs returns nothing
RELEASE_NAME-timescaledb-ha stays not ready indefinitely, and cpln logs '{gvc="GVC_NAME", workload="RELEASE_NAME-timescaledb-ha"}' --limit 50 --since 10m returns zero lines.Cause: postgres.credentialsSecretName (or, with MinIO backups, backup.minio.credentialsSecretName) names a secret that does not exist, so the container is never started. The message is only visible in status.versions[].message:cpln workload force-redeployment RELEASE_NAME-timescaledb-ha --gvc GVC_NAME.Upgrade fails with 'postgres.username, postgres.password and postgres.database were REMOVED'
Upgrade fails with 'postgres.username, postgres.password and postgres.database were REMOVED'
cpln helm upgrade stops before touching any resource:backup.minio.accessKey / backup.minio.secretKey produce the same kind of message under their own names.Cause: your values still carry keys that were removed in 1.1.0.Fix: follow Upgrading From 1.0.x. Your running cluster is unaffected until the upgrade succeeds.HAProxy logs 'waiting for patroni endpoints to answer before starting haproxy'
HAProxy logs 'waiting for patroni endpoints to answer before starting haproxy'
RELEASE_NAME-timescaledb-ha-proxy never becomes ready and cpln logs '{gvc="GVC_NAME", workload="RELEASE_NAME-timescaledb-ha-proxy"}' --limit 50 --since 10m repeats tsdb-ha proxy: waiting for patroni endpoints to answer before starting haproxy — pgN (...) not responding yet.Cause: HAProxy will not start until every member’s Patroni REST API answers on port 8008. Either the members are still starting (normal for the first minutes of an install), or internal_access.type: workload-list does not include the proxy workload, so it cannot reach them.Fix: wait for RELEASE_NAME-timescaledb-ha to become ready. If it is ready and HAProxy still waits, add //gvc/GVC_NAME/workload/RELEASE_NAME-timescaledb-ha-proxy to internal_access.workloads as described in Internal Access. The container gives up and restarts after ten minutes, so it recovers on its own once the members are reachable.Members restart-loop with exitCode 0 and reason Completed
Members restart-loop with exitCode 0 and reason Completed
cpln workload get-deployments RELEASE_NAME-timescaledb-ha --gvc GVC_NAME -o yaml, each exit reading exitCode: 0 and reason: Completed, while nothing in the database logs looks wrong.Cause: etcd has gone read-only. A Patroni member that cannot renew its lease exits cleanly, so the failure reads as healthy. The usual reason is an uncompacted etcd backend that has reached its 2 GiB quota and raised a NOSPACE alarm (template version 1.0.0).Fix: check etcd first, with two read-only commands:alarm list. NOSPACE confirms it; recovery is the operator procedure in If the Backend Quota Is Already Full, followed by an upgrade to 1.0.1 or later so it does not recur.Clients get 'remaining connection slots are reserved' through PgBouncer
Clients get 'remaining connection slots are reserved' through PgBouncer
RELEASE_NAME-pgbouncer fail with FATAL: remaining connection slots are reserved for roles with the SUPERUSER attribute (or ... for non-replication superuser connections) instead of waiting in the pool.Cause: pgbouncer.maxDbConnections is enforced per PgBouncer replica, so maxReplicas × maxDbConnections server connections can be opened against a cluster max_connections of 100 (97 usable).Fix: lower pgbouncer.maxDbConnections or pgbouncer.maxReplicas so their product stays under 97, and keep pgbouncer.defaultPoolSize within the same budget — see PgBouncer Connection Pooling.Queries fail with 'function create_hypertable does not exist'
Queries fail with 'function create_hypertable does not exist'
SELECT create_hypertable(...) or time_bucket(...) fails with ERROR: function create_hypertable(unknown, ...) does not exist, usually in a database other than the one in your credentials secret.Cause: the TimescaleDB extension is preloaded on every member but created only in the database named by the database key of your credentials secret, at first initialization. A database you created afterwards does not have it.Fix: connect to that database through HAProxy as the superuser and run CREATE EXTENSION IF NOT EXISTS timescaledb;. The replicas pick it up through streaming replication.Backup job exits with exitCode 137 and no object appears in the bucket
Backup job exits with exitCode 137 and no object appears in the bucket
cpln workload get-deployments RELEASE_NAME-timescaledb-ha-backup --gvc GVC_NAME -o yaml shows the backup container ending with exitCode: 137, cpln logs for it stops after Running pg_dumpall against ..., and no new postgres-TIMESTAMP.sql.gz lands under the prefix.Cause: the dump is being OOM-killed mid-run. Nothing is logged from inside the container when that happens; backup.resources.memory is below what pg_dumpall plus the upload needs (template versions before 1.2.0 shipped 128Mi).Fix: set backup.resources.memory to at least the shipped 512Mi and upgrade. The next scheduled run produces a backup.Apply rejected with 'The ratio between cpu and minCpu must be less than 4:1'
Apply rejected with 'The ratio between cpu and minCpu must be less than 4:1'
resources.maxCpu is more than four times resources.minCpu, which the platform rejects on a stateful workload.Fix: raise resources.minCpu or lower resources.maxCpu so the ratio is at most 4:1.Important Notes
- Create the credentials secret before installing. A reference to a secret that does not exist wedges the members with no log output; see Prerequisites for the one command that shows the reason.
- Connect through HAProxy (
RELEASE_NAME-timescaledb-ha-proxy), or PgBouncer when enabled. A member reached directly may be a read-only replica. - Credentials are applied only when the cluster is first initialized, and the
replreplication role shares the superuser password. Rotate in the order given in Rotating the Password. - The TimescaleDB extension is created only in the database your secret names. Run
CREATE EXTENSION IF NOT EXISTS timescaledbin any other database before creating hypertables there. - Keep
replicasat 3 or more andetcd.replicasodd, and chooseetcd.replicasat install time. - Do not change
etcd.tuningto disable compaction — an uncompacted etcd takes the whole cluster read-only months later. - With
workload-listaccess, list the release’s own workloads as well as your applications, or HAProxy cannot reach the members. - Backups are logical only — a scheduled
pg_dumpallwith no WAL archiving or point-in-time recovery. Keepbackup.resources.memoryat512Mior more. - Restore procedures are unverified on this template, and the scheduled whole-cluster dump does not fit TimescaleDB’s documented single-database restore directly — rehearse against a scratch release, and take a per-database
pg_dump -Fcfor anything you must be able to restore. cpln helm uninstalldeletes both volume sets and the data with them; the TimescaleDB volume set leaves a final snapshot for 7 days. Your credentials secrets are yours and are left alone.- Upgrading from 1.0.x is a breaking change — the removed keys stop the upgrade rather than resetting a password. See Upgrading From 1.0.x.
External References
TimescaleDB Documentation
Hypertables
Logical Backup and Restore
Patroni Documentation
patronictl reference