147 lines
5.7 KiB
YAML
147 lines
5.7 KiB
YAML
# =============================================================================
|
|
# P4.7 — UMA instancia de Postgres para todos os tenants, com PgBouncer na
|
|
# frente.
|
|
#
|
|
# Hoje cada tenant tem o proprio pod de Postgres. Sao cinco instancias servindo
|
|
# bancos de poucos megabytes cada, e cada uma paga um processo, um volume e um
|
|
# ciclo de backup proprio.
|
|
#
|
|
# O modelo alvo ja estava decidido e preparado em P2.1: um DATABASE e um ROLE por
|
|
# tenant na mesma instancia (`athleticmap_<tenant>`, `r_<tenant>`), com schema por
|
|
# contexto DENTRO do database. Sao duas fronteiras diferentes — schema separa
|
|
# CONTEXTO, database separa CLIENTE (ADR-0002 §2).
|
|
#
|
|
# ── O QUE FOI VERIFICADO LOCALMENTE, E O QUE NAO FOI ─────────────────────────
|
|
#
|
|
# Verificado em 2026-09-18, com Postgres 16 + PgBouncer 1.25 em containers, dois
|
|
# tenants provisionados pelos scripts de P2.1:
|
|
#
|
|
# 1. role de um tenant no database de outro -> "permission denied" ✔
|
|
# 2. role certo com a senha do vizinho -> "SASL authentication failed" ✔
|
|
# 3. role certo no proprio database -> conecta ✔
|
|
# 4. enumeracao de tenants pelo catalogo -> enxerga 1, o proprio ✔
|
|
# 5. pools do PgBouncer -> um por (database, role),
|
|
# SEM mistura de credencial ✔
|
|
#
|
|
# TUDO ISSO ATRAVES DO PGBOUNCER, que e o ponto: transaction pooling reaproveita
|
|
# conexao de servidor entre clientes, e um pool compartilhado entre roles faria
|
|
# um tenant herdar a sessao do outro.
|
|
#
|
|
# NAO verificado: comportamento sob carga real, e a migracao dos dados dos
|
|
# tenants que ja existem. Os dois dependem de deploy.
|
|
#
|
|
# ── O DIMENSIONAMENTO, QUE E CONTA E NAO CHUTE ───────────────────────────────
|
|
#
|
|
# Alvo do card: ~18 tenants.
|
|
#
|
|
# por tenant: 1 database, 1 role, 1 pool
|
|
# por pool: default_pool_size 10 + reserve_pool_size 5 = ate 15
|
|
# 18 tenants: 18 x 15 = 270 conexoes de servidor no pior caso
|
|
# + superusuario: 3 reservadas (superuser_reserved_connections)
|
|
# + folga: ~27
|
|
# -----------------------------------------------------------------
|
|
# max_connections: 300
|
|
#
|
|
# `shared_buffers` em 512Mi = 25% do limite de 2Gi, que e a regra de bolso do
|
|
# proprio Postgres. `effective_cache_size` em 1536Mi (75%) diz ao planejador
|
|
# quanta pagina ele pode esperar encontrar em cache — nao reserva memoria, so
|
|
# muda a escolha entre varredura sequencial e indice.
|
|
#
|
|
# `work_mem` em 4MB, e nao mais: ele e POR OPERACAO de ordenacao, nao por
|
|
# conexao. Com 300 conexoes e consultas com dois sorts, 16MB viraria 9,6 GB no
|
|
# pior caso — e o limite do pod e 2Gi.
|
|
# =============================================================================
|
|
apiVersion: v1
|
|
kind: PersistentVolumeClaim
|
|
metadata:
|
|
name: pg-compartilhado
|
|
namespace: plataforma-prod
|
|
spec:
|
|
accessModes: [ReadWriteOnce]
|
|
storageClassName: local-path
|
|
resources:
|
|
requests:
|
|
storage: 50Gi
|
|
---
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: pg-compartilhado-conf
|
|
namespace: plataforma-prod
|
|
data:
|
|
# Passados por `-c` no comando do postgres, e nao por arquivo: o entrypoint da
|
|
# imagem oficial reescreve o postgresql.conf na primeira subida, e um arquivo
|
|
# montado seria ignorado ou sobrescrito, sem aviso.
|
|
parametros: >-
|
|
-c max_connections=300
|
|
-c superuser_reserved_connections=3
|
|
-c shared_buffers=512MB
|
|
-c effective_cache_size=1536MB
|
|
-c work_mem=4MB
|
|
-c maintenance_work_mem=128MB
|
|
-c wal_compression=on
|
|
-c log_min_duration_statement=1000
|
|
-c log_connections=off
|
|
-c log_disconnections=off
|
|
---
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: pg-compartilhado
|
|
namespace: plataforma-prod
|
|
spec:
|
|
replicas: 1
|
|
strategy:
|
|
# `Recreate`, e nao RollingUpdate: sao dois processos disputando o MESMO
|
|
# PersistentVolume. O segundo nao sobe — o Postgres recusa iniciar sobre um
|
|
# diretorio ja travado — e o rollout fica preso com o pod novo em erro e o
|
|
# velho ainda de pe, ate alguem olhar.
|
|
type: Recreate
|
|
selector:
|
|
matchLabels: { app: pg-compartilhado }
|
|
template:
|
|
metadata:
|
|
labels: { app: pg-compartilhado }
|
|
spec:
|
|
containers:
|
|
- name: postgres
|
|
image: postgres:16
|
|
args: ["postgres"]
|
|
command: ["sh", "-c", "exec docker-entrypoint.sh postgres $PARAMETROS"]
|
|
env:
|
|
- name: PARAMETROS
|
|
valueFrom:
|
|
configMapKeyRef: { name: pg-compartilhado-conf, key: parametros }
|
|
- name: POSTGRES_PASSWORD
|
|
valueFrom:
|
|
secretKeyRef: { name: pg-compartilhado-admin, key: password }
|
|
# `PGDATA` num subdiretorio: o local-path monta o volume na raiz, e o
|
|
# Postgres recusa inicializar num diretorio que ja tem `lost+found`.
|
|
- { name: PGDATA, value: /var/lib/postgresql/data/pgdata }
|
|
ports: [{ containerPort: 5432 }]
|
|
volumeMounts:
|
|
- { name: dados, mountPath: /var/lib/postgresql/data }
|
|
resources:
|
|
requests: { cpu: 500m, memory: 1Gi }
|
|
limits: { cpu: "2", memory: 2Gi }
|
|
readinessProbe:
|
|
exec: { command: ["pg_isready", "-U", "postgres"] }
|
|
initialDelaySeconds: 10
|
|
periodSeconds: 10
|
|
livenessProbe:
|
|
exec: { command: ["pg_isready", "-U", "postgres"] }
|
|
initialDelaySeconds: 60
|
|
periodSeconds: 30
|
|
volumes:
|
|
- name: dados
|
|
persistentVolumeClaim: { claimName: pg-compartilhado }
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: pg-compartilhado
|
|
namespace: plataforma-prod
|
|
spec:
|
|
selector: { app: pg-compartilhado }
|
|
ports: [{ port: 5432, targetPort: 5432 }]
|