Redis Topology and pgBouncer¶
Overview¶
Two cost-reduction controls ship with the operator's managed-infrastructure layer:
- Redis topology (
redis.architecture) — single standalone pod (default) vs. master + replicas for HA. - pgBouncer connection pooling (
database.postgres.proxy.enabled) — off by default; opt in for production workloads that need connection multiplexing.
Both are configurable through all four CRDs — Nextcloud, NextcloudInstance,
NextcloudProfile, and NextcloudPool — following the existing four-CRD cascade:
Instance spec wins on conflict; profile or pool can set a shared baseline.
Managed Redis topology (spec.redis.architecture)¶
Applies only when Redis is managed by the operator (i.e., spec.redis.enabled: true
and no spec.redis.host set). When an external Redis host is provided the architecture
field is ignored — you control that cluster yourself.
Values¶
| Value | Pods deployed | Use case |
|---|---|---|
standalone |
redis-master-0 only |
Development, staging, cost-optimized single-tenant |
replication |
redis-master-0 + N redis-replica-* |
Production HA |
Default behavior¶
The operator applies a code-level default of standalone for new instances.
There is no CRD-level default; the choice is made in operator code after profile
resolution so profile overrides are not defeated by Kubernetes admission stamping.
Backfill-on-adopt: when a managed instance already has a HelmRelease deployed
and architecture is absent from the spec, the operator pins replication on the
first reconcile and persists it via a spec patch. This preserves the HA cache
configuration of instances created before this field existed, with no pod churn.
replicaCount¶
When architecture: replication, you may set redis.replicaCount to control how
many replica pods the bitnami Redis subchart deploys. When unset the bitnami chart
default applies (typically 3).
Nextcloud→Redis host¶
The Nextcloud application connects to {release}-redis-master in both
architectures. Switching topology does not change the Redis host, so no Nextcloud
configuration update is needed when changing architecture.
Examples¶
Minimal — single Redis (cost-optimized)¶
apiVersion: k8s.bnerd.com/v1alpha1
kind: NextcloudInstance
metadata:
name: nextcloud-cost-optimized
spec:
database:
managed: true
type: postgresql
redis:
enabled: true
# architecture unset → operator applies standalone default for new instances
admin:
username: admin
password: "change-me"
ingress:
host: nextcloud.example.com
HA Redis — explicit replication¶
Via a NextcloudProfile (applies to all instances using the profile)¶
apiVersion: k8s.bnerd.com/v1alpha1
kind: NextcloudProfile
metadata:
name: ha-profile
spec:
defaults:
redis:
architecture: replication
replicaCount: 3
Production profile¶
The built-in production profile sets:
Instances using profile: production inherit HA Redis by default.
Managed pgBouncer (database.postgres.proxy.enabled)¶
Applies only to managed PostgreSQL clusters (database.managed: true,
database.type: postgresql).
When enabled, the Percona PG Operator provisions pgBouncer pods alongside the
PostgreSQL cluster. The operator routes Nextcloud to the pgBouncer service endpoint
(-pgbouncer) instead of the primary PostgreSQL endpoint (-primary).
When disabled (the default), no pgBouncer pods are created and Nextcloud connects directly to the PostgreSQL primary service. This is correct and fully supported; pgBouncer is an optional optimization for workloads with high connection churn.
Fields¶
| Field | Type | Default | Description |
|---|---|---|---|
database.postgres.proxy.enabled |
boolean | false (operator default) |
Enable pgBouncer |
database.postgres.proxy.replicas |
integer | 2 |
pgBouncer pod count (only when enabled) |
database.postgres.proxy.poolMode |
string | transaction |
PgBouncer pool mode (session, transaction, statement) |
database.postgres.proxy.poolSize |
integer | 25 |
Default pool size per database-user pair |
There is no CRD-level default on proxy.enabled; the false default is applied in
operator code after profile resolution.
DB host wiring¶
The operator bakes the resolved host into the <instance>-nextcloud-db Secret
(db-host key) and into the externalDatabase.host Helm value. The behavior
differs depending on direction and instance state:
- Disabling (enabled true→false) on an existing instance: fully automatic on
reconcile — the operator removes the bouncer block from the live
PerconaPGClusterand rewrites the DB Secret to point at<cluster>-primary. No manual step. - Enabling (false→true) on an existing instance: the operator adds the bouncer
block to the live cluster immediately. Once pgBouncer is Ready, the 30-second
status timer detects the host drift and surgically re-points the HelmRelease
(
externalDatabase.host) so Flux rolls Nextcloud onto the bouncer — typically within ~30 seconds of bouncer readiness. No manual step required (as of v0.19.1). Thek8s.bnerd.com/reconcileannotation triggers the flip immediately if you cannot wait for the next timer tick. - New instances with pgBouncer enabled (including the
productionprofile): the host is resolved at create time; the wait-for-DB initContainer gates pod start. No manual step.
Production profile¶
The built-in production profile sets:
Instances using profile: production with a managed database get pgBouncer by default.
Example¶
apiVersion: k8s.bnerd.com/v1alpha1
kind: NextcloudInstance
metadata:
name: nextcloud-ha
spec:
profile: production
database:
managed: true
type: postgresql
postgres:
replicas: 3
version: "16"
proxy:
enabled: true
replicas: 2
poolMode: transaction
poolSize: 25
redis:
enabled: true
architecture: replication
replicaCount: 3
admin:
username: admin
password: "change-me"
ingress:
host: nextcloud.example.com
Configuring via all four CRDs¶
Both fields follow the same cascade as all other spec fields.
Transition runbook¶
HA Redis (replication) to single Redis (standalone)¶
This is safe. Redis holds only transient Nextcloud cache and distributed file locks. A brief lock-release (Redis restart) is acceptable.
-
Update the spec:
-
The operator renders
redis.architecture: standaloneinto the HelmRelease on the next reconcile. The Helm upgrade removes theredis-replicasStatefulSet. - The
redis-master-0pod restarts; the Nextcloud application reconnects to{release}-redis-master(unchanged hostname). -
Orphaned PVCs: the bitnami Redis chart does not garbage-collect StatefulSet PVCs on scale-down. The
redis-data-redis-replicas-*PVCs remain after the transition and must be deleted manually if storage reclamation is needed:kubectl get pvc -n <namespace> | grep redis-replicas kubectl delete pvc redis-data-<release>-redis-replicas-0 \ redis-data-<release>-redis-replicas-1 \ redis-data-<release>-redis-replicas-2 \ -n <namespace>These PVCs carry no persistent application data (Redis is used as a transient cache/lock store). Deleting them is safe once the StatefulSet is gone.
pgBouncer: enable on an existing instance (false → true)¶
-
Update the spec:
-
The operator immediately adds the pgBouncer block to the live
PerconaPGCluster. PGO starts provisioning pgBouncer pods. - The operator gates the DB host flip on pgBouncer readiness. The DB Secret and
HelmRelease remain pointed at
<cluster>-primaryuntil the bouncer is Ready. Nextcloud continues to run correctly on the primary during this window. -
Once pgBouncer pods are Ready, the 30-second status timer detects the host drift and automatically re-points the HelmRelease (
externalDatabase.host) to<cluster>-pgbouncer. Flux re-renders and rolls Nextcloud onto the bouncer — typically within ~30 seconds of bouncer readiness. No manual step is required.To force the flip immediately rather than waiting for the next timer tick:
-
Monitor the HelmRelease and Nextcloud pod readiness:
Self-driving since v0.19.1¶
The off→on host flip is fully automatic as of v0.19.1. The 30-second status timer
is drift-gated: in steady state it does no writes and causes no HelmRelease churn;
only the transition tick (when the bouncer just became Ready and the current host
differs from the target host) triggers the surgical patch. The k8s.bnerd.com/reconcile
annotation is an optional fallback to force the flip immediately.
pgBouncer: disable on an existing instance (true → false)¶
This is fully automatic. No manual step required.
-
Update the spec:
-
On the next reconcile (within 30 seconds, or immediately via the annotation below) the operator removes the pgBouncer block from the live
PerconaPGClusterand rewrites the DB Secret to point at<cluster>-primary. - The Helm upgrade rolls Nextcloud to the primary endpoint. PGO deprovisions the pgBouncer pods once the cluster spec is updated — idle pgBouncer CPU/memory is reclaimed immediately.
pgBouncer: new instances¶
New instances with proxy.enabled: true (including those using the production
profile) are handled entirely at create time. The wait-for-DB initContainer gates
pod start until the primary is accepting connections; no manual step is needed.
Force immediate reconcile¶
After changing redis.architecture or database.postgres.proxy.enabled, you can
trigger an immediate reconcile rather than waiting for the 30-second timer:
For the pgBouncer off→on case this is optional: the status timer completes the host flip automatically once the bouncer is Ready (see above).
Connection-pool sizing note¶
When pgBouncer is disabled, Nextcloud's PHP worker pool connects directly to the
PostgreSQL primary. Each PHP worker holds one persistent connection. With the default
poolMode: transaction pgBouncer multiplexes connections efficiently; without it,
ensure PostgreSQL max_connections is sized for your PHP worker count
(pm.maxChildren × pod replicas + headroom). A mismatch shows as
FATAL: remaining connection slots are reserved in the Nextcloud log.
Troubleshooting¶
# Check Redis architecture currently deployed
kubectl get helmrelease <name>-nextcloud -n <namespace> \
-o jsonpath='{.spec.values.redis}' | jq .
# Check Redis pods
kubectl get pods -n <namespace> -l app.kubernetes.io/name=redis
# Check pgBouncer pods (Percona PGO)
kubectl get pods -n <namespace> \
-l postgres-operator.crunchydata.com/role=pgbouncer
# Check the DB Secret host to confirm proxy toggle took effect
kubectl get secret <name>-nextcloud-db -n <namespace> \
-o jsonpath='{.data.db-host}' | base64 -d && echo
# Force reconcile
kubectl annotate nci <name> k8s.bnerd.com/reconcile=$(date +%s) --overwrite
See the Operations & Annotations guide for the full list of operational annotations.