ADR-0003 · Persistencia: Postgres histórico + DynamoDB hot-path + S3 split¶
- Status: Accepted
- Date: 2026-04-30
- Deciders: Cristian Fernández (Zerviz Group)
- Related: ADR-0002 (tenancy), ADR-0004 (eventing),
docs/discovery/00-phase1-prerequisites.md3.1/3.3 + Anexo A-03/A-04.
Context¶
Decisión asentada en checklist: cada microservicio elige su store. RDS Postgres es exclusivamente para histórico, audit y reportería; DynamoDB para hot-path. Costo mínimo en dev; HA + Multi-AZ sólo en qa y prod. Aislamiento por tier (ADR-0002): tier lógico comparte RDS+DDB; tier físico tiene los suyos.
Decision¶
Postgres (ze-{env}-postgres)¶
- Engine: RDS for PostgreSQL 16 LTS (no 18.x; 16 tiene soporte largo y es estable). Migrar major version es decisión separada (ADR futuro).
- Dev:
db.t4g.micro, single-AZ, sin réplica, BackupRetentionPeriod = 1 día, DeletionProtection = false. ~USD 13/mes. - QA:
db.t4g.small, Multi-AZ, BackupRetentionPeriod = 14 días, DeletionProtection = true. ~USD 60/mes. - Prod:
db.t4g.mediumMulti-AZ con read replica + PITR 35 días, DeletionProtection = true, IAM auth ON, slow query log + audit log a CloudWatch. ~USD 250/mes baseline. - Bases de datos lógicas:
ze_app— datos de aplicación tier lógico (todas las tablas contenant_id+ RLS).ze_control_plane— registros transversales (catálogo de tenants, conectores instalados, audit transversal, billing). Sintenant_id(datos de plataforma).- Esquema multi-tenant tier lógico: RLS sobre tabla compartida + columna
tenant_id uuid NOT NULL. Middlewarepackages/tenant-contextejecutaSET LOCAL app.tenant_id = '<id>'en cada conexión. Roles: un rol app por servicio (ze_inbound_router,ze_reporting_api, ...) conBYPASSRLS = falsemandatorio. - Tier físico: cada cuenta enterprise tiene su
ze-postgrespropio sin RLS (innecesario). - Storage: GP3, autoscaling habilitado en qa/prod.
DynamoDB¶
- Patrón naming:
ze-{env}-<entity>(e.g.ze-dev-flow-state,ze-prod-dedup). - Modo: PAY_PER_REQUEST por defecto. Pasar a Provisioned + autoscaling si llegamos a > 100 RPS sostenidos.
- TTL obligatorio en tablas con datos efímeros:
dedup(24 h),flow-state(configurable por flow, default 7 días),five9-sessions(1 h, ver checklist 6.1). - Tier lógico:
PK = TENANT#<id>+ SK por entidad. IAM condynamodb:LeadingKeys = TENANT#${aws:PrincipalTag/tenant_id}. - Tier físico: PK directa por entidad (sin prefijo TENANT) en la cuenta del tenant.
- Streams: activos en tablas que requieren outbox pattern (ver ADR-0004 §Outbox).
- Encryption: KMS CMK por tenant (
alias/ze-{env}-tenant-<id>) para datos confidenciales; AWS-managed para neutros.
S3¶
- Topología split por env (asentado checklist 8.1):
ze-{env}-spa— bundles de SPA (apps/builder-spa,apps/widget-spa).ze-{env}-data— datos de negocio (encuestas, audit, exports). Prefijotenants/<id>/....ze-{env}-flows— catálogo de flows (sucesor des3://ze-engine/flujos/). Single source of truth (mitigación R-15).- Versionado: ON en todos los buckets de datos. Object Lock modo compliance en
flows/ytenants/<id>/audit/. - Encryption: SSE-KMS con CMK por tenant para
tenants/<id>/...; SSE-S3 (AES256) para SPA bundles. - Lifecycle: retención configurable por tenant (asentado checklist 8.3) vía tag
RetentionDaysaplicado a prefijotenants/<id>/. Default 730 días (2 años); enterprise puede subirlo a 2.555 días (7 años). - CloudFront OAC obligatorio sobre
ze-{env}-spa. Sin distribuciones huérfanas.
Asignación store ↔ servicio¶
| Servicio | Store(s) | Justificación |
|---|---|---|
tenant-mgmt |
RDS ze_control_plane.tenants + DDB ze-tenants-cache |
Datos transversales relacionales; cache de lookup hot |
auth-bff |
(sin estado propio) | Cognito es la fuente |
inbound-router |
DDB ze-dedup (TTL 24 h) |
Idempotencia hot-path |
flow-engine |
DDB ze-flow-state (TTL configurable) + Streams → outbox |
Hot-path por conversación |
connector-orchestrator |
SSM Parameter Store (CB state) + DDB ze-circuit-breaker |
Estado breaker compartido |
connectors-social |
(sin estado, secrets en SM) | Stateless |
connectors-cc |
DDB ze-five9-sessions (TTL 1 h) |
Cache de auth |
outbound-dispatcher |
RDS ze_app.campaigns + DDB ze-outbound-runs (TTL 30 d) |
Catálogo + locks |
reporting-api |
RDS ze_app (read-only) |
Queries analíticas |
encuesta-service |
DDB ze-encuesta-state (TTL 30 d) |
FSM efímera |
Consequences¶
Positive¶
- Costo dev mínimo (~USD 15/mes en persistencia).
- Postgres concentrado en lo que escala mal en NoSQL (audit, joins, reportería).
- DDB concentrado en lo que escala bien (sesiones, hot-path).
- S3 split termina con la mezcla
ze-enginelegacy.
Negative¶
- 2 stores ⇒ los devs deben elegir bien. Mitigado con
services/_templateque sugiere DDB-first. - RLS olvidado = leak ⇒ middleware + tests obligatorios.
Alternatives considered¶
- Aurora Serverless v2 desde dev: descartado por costo mínimo (cuesta más que t4g.micro hasta scale > 0.5 ACU sostenido). Re-evaluar en qa.
- DynamoDB para todo: descartado, reportería compleja se vuelve dolorosa.
- Aurora Postgres en prod: mantener como opción evaluable cuando volumen lo justifique.
Tenant-isolation impact¶
- Tier lógico: RLS Postgres + IAM LeadingKeys DDB + KMS CMK por tenant en S3/DDB confidencial.
- Tier físico: aislamiento por cuenta AWS.
Blast radius¶
- RDS dev caída ⇒ greenfield no productivo, sin impacto. RDS prod caída ⇒ Multi-AZ failover automático en < 60 s.
- DDB caída regional ⇒ degradación severa (no hay multi-region V1; aceptable).
Cost note¶
- Dev (lógico): ~USD 13 RDS + ~USD 5 DDB + ~USD 1 S3 = ~USD 19/mes.
- Prod (lógico tier): ~USD 250 RDS + ~USD 50–500 DDB según tráfico + ~USD 10–50 S3.
- Prod tier físico (por tenant enterprise): + ~USD 60 RDS + ~USD 30 DDB baseline.
ISO 27001 controls touched¶
- A.8.2.3 (manejo de activos según clasificación): tag
DataClassification. - A.10.1.1 (criptografía): KMS CMK por tenant.
- A.12.3.1 (respaldo): PITR + Object Lock.
- A.13.1.3 (segregación): RLS + IAM LeadingKeys + cuenta-por-tenant.
Sources¶
docs/discovery/00-phase1-prerequisites.md3.1, 3.2, 3.3, 8.1, 8.2, 8.3 + Anexo A-03/A-04.docs/discovery/02-aws-inventory.md §4(baseline RDS legacy).- AWS Postgres 16 LTS docs.