Cloud SQL
Resumen Ejecutivo
Sección titulada «Resumen Ejecutivo»Cloud SQL es el servicio de bases de datos relacionales totalmente gestionado que utilizamos en HERA. Nos libera de la carga de administrar bases de datos, proporcionando alta disponibilidad (HA), backups automáticos, cifrado integrado y escalabilidad para nuestros motores PostgreSQL y MySQL.
Arquitectura de Alta Disponibilidad (HA)
Sección titulada «Arquitectura de Alta Disponibilidad (HA)»Para nuestras bases de datos de producción, la alta disponibilidad no es opcional. La configuración de HA de Cloud SQL garantiza la resiliencia ante fallos zonales.
Topología de HA
Sección titulada «Topología de HA»- Instancia Primaria: Atiende todo el tráfico de lectura y escritura.
- Instancia Standby: Es una copia exacta en una zona diferente. No atiende tráfico, pero está lista para tomar el control si la primaria falla. La replicación es síncrona.
- Réplica de Lectura: Instancia en otra región para recuperación ante desastres (DR) y para descargar consultas de solo lectura (ej. reportes). La replicación es asíncrona.
Naming de bases de datos y conexión desde servicios
Sección titulada «Naming de bases de datos y conexión desde servicios»Cada servicio desplegado en GKE o Cloud Functions consume una base de datos lógica dentro de una instancia Cloud SQL compartida. La convención de naming y el modelo de ownership son mandatorios para mantener aislamiento, trazabilidad y auditoría.
Convención de naming
Sección titulada «Convención de naming»Siguiendo los Estándares de Nombramiento en GCP, la base de datos lógica adopta el formato db_<producto>_<servicio> (snake_case) — incluye siempre producto y servicio para garantizar unicidad en instancias compartidas cross-producto (ver Instancias compartidas cross-producto) y evitar renames futuros.
| Aspecto | Valor | Ejemplo |
|---|---|---|
| Instancia Cloud SQL (dedicada por producto) | sql-<producto>-<motor> (project-scoped) | sql-tienda-herdez-postgres |
| Instancia Cloud SQL (compartida cross-producto) | sql-<capability>-<motor> o sql-shared-<motor> | sql-sites-externos-postgres |
| Base de datos lógica | db_<producto>_<servicio> (snake_case obligatorio por compatibilidad con motores SQL) | db_tienda_herdez_checkout, db_tienda_herdez_inventory, db_miel_carlota |
| Usuario SQL | Email del GSA: <gsa>@<gcp-project>.iam (tipo CLOUD_IAM_SERVICE_ACCOUNT) | gsa-tienda-herdez-backend-checkout-service@ghdz-grupo-ext-sites-prd.iam |
| Env var con el nombre | DB_<CONTEXT>_NAME (ver Estándares de Configuración R11) | DB_CHECKOUT_NAME=db_tienda_herdez_checkout |
El producto en el nombre de la DB debe coincidir con el nombre canónico declarado en Estructura de Repositorios. El servicio es la parte <detalle> del deployment (sin el prefijo <tipo>- ni el sufijo -service).
Modelo de ownership
Sección titulada «Modelo de ownership»| Regla | Detalle |
|---|---|
| Una base lógica por servicio | Cada servicio (deployment en GKE o Cloud Function) es dueño de su base de datos lógica. No se comparte la base entre servicios. |
| Sin acceso cross-database | Un servicio NO consulta la base de otro servicio directamente — se comunica vía el API del servicio dueño. Rompe acoplamiento y respeta bounded context del dominio. |
| Instancia compartida, schemas aislados | Varios db_<producto>_<servicio> pueden vivir en la misma instancia Cloud SQL si pertenecen al mismo producto/dominio; la separación lógica al nivel de base + usuario IAM es suficiente para el aislamiento en la práctica. |
Schema public | Cada db_<producto>_<servicio> usa el schema public por default. No se recomienda subdivisión en schemas adicionales salvo casos justificados (ej. multitenancy dentro del servicio). |
Modelo de instancia compartida — múltiples bases lógicas
Sección titulada «Modelo de instancia compartida — múltiples bases lógicas»Una sola instancia Cloud SQL puede hospedar múltiples bases de datos lógicas, una por servicio. Este es el modelo estándar en HERA por razones de costo (las instancias son caras), gobernanza (simplifica backups, HA, parches) y operación (menos superficie que administrar).
Topología canónica
Sección titulada «Topología canónica»Instancia Cloud SQL: sql-tienda-herdez-postgres Project: ghdz-grupo-ext-sites-prd Región: us-central1 (HA zonal + read-replica DR)
├─ db_tienda_herdez_checkout ← backend-checkout-service │ GSA: gsa-tienda-herdez-backend-checkout-service │ ├─ db_tienda_herdez_inventory ← backend-inventory-service │ GSA: gsa-tienda-herdez-backend-inventory-service │ ├─ db_tienda_herdez_catalog ← backend-catalog-service │ GSA: gsa-tienda-herdez-backend-catalog-service │ └─ db_tienda_herdez_notifications ← platform-notifications-service GSA: gsa-tienda-herdez-platform-notifications-service
── Recursos compartidos ── CPU, RAM, disco, max_connections, backups, HA standby, Audit Log
── Aislamiento ── GRANTs por GSA → cada servicio ve SÓLO su db_<producto>_<servicio>Reglas de compartición
Sección titulada «Reglas de compartición»| Regla | Detalle |
|---|---|
| Por defecto: una instancia por producto/dominio | Todos los servicios de un mismo producto comparten instancia. Ej. tienda-herdez/* comparten sql-tienda-herdez-postgres. |
| Una base lógica por servicio | Nunca se mezclan 2 servicios en la misma db_<producto>_<servicio>. |
| Acceso aislado por GSA + GRANTs | El GSA del servicio A no tiene permisos sobre db_b. Se enforce a nivel de GRANT, no sólo de convención. |
Sin SUPERUSER en GSAs de aplicación | Solo el rol DBA dispone de privilegios administrativos sobre la instancia. |
| Auditoría cuatrimestral | DBA revisa GRANTs e inventario instancia → DBs → servicios. |
Cuándo NO compartir instancia (criterios de split)
Sección titulada «Cuándo NO compartir instancia (criterios de split)»Aunque la compartición es el default, hay criterios claros para provisionar una instancia dedicada a uno o pocos servicios:
| Criterio | Justificación | Ejemplo |
|---|---|---|
| Criticidad distinta | Un Tier 1 no debe compartir recursos con un Tier 3 | checkout (Tier 1) vs. admin-reporting (Tier 3) → instancias separadas |
| Perfil de carga incompatible | OLTP mezclado con analytics/ETL satura CPU e I/O | data-* services en instancia propia |
| Clase de compliance | PCI-CDE debe estar aislado de datos internos | Checkout PCI en instancia dedicada con CMEK y Audit Log separado |
| SLA / HA diferentes | 99.99% no comparte con best-effort | |
| Límites del motor agotados | max_connections, shared_buffers, IOPS saturados por el conjunto actual | Split cuando la métrica agregada supera el umbral |
Recursos compartidos vs. aislados
Sección titulada «Recursos compartidos vs. aislados»| Recurso | ¿Compartido entre bases lógicas? | Impacto operacional |
|---|---|---|
| CPU / RAM | ✅ Compartido | Query lenta en db_A degrada rendimiento de db_B |
| Disco / IOPS | ✅ Compartido | Backup pesado ralentiza I/O de todas las DBs |
max_connections | ✅ Compartido | Pool excesivo en un servicio agota el pool global |
| Backup automático + PITR | ✅ Instance-level | Restore afecta todas las DBs a la vez |
| HA standby | ✅ Instance-level | Failover cubre la instancia completa |
| Cloud Audit Log | ✅ Instance-level | Logs mezclados; filtrar por resource.labels.database_id |
| Datos y GRANTs | ❌ Aislado por DB | Cada GSA ve sólo su db_<producto>_<servicio> — el aislamiento lógico se respeta |
Instancias compartidas cross-producto
Sección titulada «Instancias compartidas cross-producto»La convención sql-<producto>-<motor> asume una instancia por producto, pero HERA opera en la práctica con instancias corporativas compartidas por razones de costo — cada instancia Cloud SQL tiene un mínimo fijo (~US$50/mes en el tier más chico) multiplicado por los 3 ambientes (DEV/QA/PRD), y multiplicar eso por decenas de productos generaría un gasto no justificable para proyectos de bajo volumen.
Cuando una instancia aloja DBs de múltiples productos, el naming de la instancia y el modelo de gobierno cambian ligeramente:
Naming de la instancia compartida
Sección titulada «Naming de la instancia compartida»| Alcance | Formato | Ejemplo |
|---|---|---|
| Dedicada a un producto | sql-<producto>-<motor> | sql-tienda-herdez-postgres |
| Compartida por capability (varios productos del mismo área) | sql-<capability>-<motor> | sql-sitios-externos-postgres, sql-loyalty-postgres |
| Corporativa transversal (cualquier producto puede solicitar una DB aquí) | sql-shared-<motor> | sql-shared-postgres, sql-shared-mysql |
Por qué el naming db_<producto>_<servicio> es obligatorio aquí
Sección titulada «Por qué el naming db_<producto>_<servicio> es obligatorio aquí»En una instancia compartida donde conviven DBs de múltiples productos, omitir el producto en el nombre de la DB provoca colisión inevitable:
Instancia: sql-sitios-externos-postgres (compartida por capability)
├─ db_miel_carlota_cms ← producto: miel-carlota, servicio: cms ├─ db_flaveur_cms ← producto: flaveur, servicio: cms ├─ db_m22_cms ← producto: m22, servicio: cms ├─ db_cholula_cms ← producto: cholula, servicio: cms └─ db_thepossible_cms ← producto: the-possible, servicio: cms
── Sin producto en el nombre: los 5 productos colisionarían en "db_cms" ──Gobierno de la instancia compartida
Sección titulada «Gobierno de la instancia compartida»| Aspecto | Regla en instancia compartida |
|---|---|
| Ownership de la instancia | Platform Engineering + DBA. Los productos consumen DBs; no “poseen” la instancia. |
| Aprovisionamiento de DBs | Cada producto solicita su db_<producto>_<servicio> vía el módulo IaC grupo-herdez/platform/iac/cloudsql-instance (variable databases = [...]). |
| Aislamiento de datos | db_<producto>_<servicio> + GSA + GRANTs restringidos por DB. Un GSA de producto A no tiene acceso a db_<producto-b>_*. |
| Recursos compartidos | CPU/RAM/IOPS/max_connections se comparten entre productos — más crítico monitorear “noisy neighbor” cross-producto. |
| Criterios para forzar instancia dedicada | Criticidad Tier 1, compliance PCI-CDE, alta carga OLTP o límites del motor agotados (mismo set que ya documentado en la sección anterior, pero con más peso cuando hay múltiples productos). |
| FinOps | El costo de la instancia compartida se prorratea entre productos vía labels (product, criticality) o se asume como costo de plataforma. La decisión es de la mesa FinOps + Platform. |
Inventario y ownership — dónde se declara cada DB
Sección titulada «Inventario y ownership — dónde se declara cada DB»Para prevenir “DBs huérfanas” (creadas manualmente sin trazabilidad) y habilitar la auditoría automática del DBA, cada base lógica debe declararse en tres artefactos versionados:
| Artefacto | Dónde vive | Qué declara |
|---|---|---|
product-manifest.yml | grupo-herdez/products/<dominio>/<producto>/_shared/ | Lista de instancias Cloud SQL del producto y las DBs que hospedan |
service-manifest.yml | Repo del servicio consumidor | database.instance y database.name a los que se conecta |
Módulo IaC cloudsql-instance | Consumido desde _shared/iac-infra del producto | Recibe databases = [...] como variable; crea las DBs e IAM users |
El DBA audita cuatrimestralmente que:
- Toda DB en la instancia aparece en el módulo IaC (no se crean manualmente).
- Toda DB tiene un servicio owner declarado en algún
service-manifest.yml. - Los GRANTs activos coinciden con los declarados.
Conexión desde GKE y Cloud Functions
Sección titulada «Conexión desde GKE y Cloud Functions»Recap del mapeo pod → DB para servicios desplegados en la plataforma:
| Plataforma | Cómo se conecta el servicio |
|---|---|
| GKE Deployment | serviceAccountName (KSA) del pod → vinculado al GSA vía Workload Identity → Cloud SQL Auth Proxy sidecar en 127.0.0.1 → IAM DB Auth → db_<producto>_<servicio>. Ver Conexión Segura desde GKE. |
| Cloud Function | serviceAccount configurado al deploy (equivale al GSA de GKE) → conexión directa a la IP privada de la instancia con IAM DB Auth habilitada → db_<producto>_<servicio>. No requiere sidecar: el connector nativo de Cloud Functions negocia el token. |
En ambos casos:
- Env vars canónicas
DB_<CONTEXT>_HOST/PORT/NAME/USERNAMEinyectadas vía ConfigMap (GKE) o variables del deploy (Cloud Function). Ver Estándares de Configuración R11. - Nunca hardcoded en la imagen ni en el código.
- Un servicio = un GSA = un GRANT = una
db_<producto>_<servicio>— sin excepciones en PRD.
Conexión Segura desde GKE
Sección titulada «Conexión Segura desde GKE»La forma correcta de conectar una aplicación en GKE a Cloud SQL es utilizando el Cloud SQL Auth Proxy.
Autenticación passwordless con Workload Identity + IAM DB Auth
Sección titulada «Autenticación passwordless con Workload Identity + IAM DB Auth»En HERA, hemos estandarizado la eliminación de contraseñas estáticas. Para conectar una base de datos de forma segura sin gestionar secretos, implementamos un esquema de dos niveles:
- Nivel de Infraestructura (Workload Identity): El pod de la aplicación asume una identidad de Google Cloud (GSA) a través de su Service Account de Kubernetes (KSA). Consulta la Guía de Workload Identity para más detalles.
- Nivel de Base de Datos (IAM DB Auth): Cloud SQL se configura para aceptar la identidad del GSA como un usuario de base de datos válido, generando tokens de corta duración en lugar de contraseñas.
Configuración paso a paso
Sección titulada «Configuración paso a paso»1. Habilitar IAM Auth en la instancia
La instancia Cloud SQL debe tener el flag cloudsql.iam_authentication = on. Este flag se declara en el módulo de Terraform de la base de datos — habilita que Cloud SQL valide tokens OAuth 2.0 de identidades GCP en lugar de sólo usuarios SQL tradicionales con password.
| Aspecto | Valor | Justificación |
|---|---|---|
| Flag | cloudsql.iam_authentication | API oficial de Google Cloud SQL para IAM auth |
| Valor | on | Activa validación de tokens GCP en la conexión |
| Aplica a | Todas las instancias PRD y QA | Elimina credenciales estáticas de la infra |
2. Crear el usuario SQL mapeado al Service Account
Por cada servicio que consume la base, se crea un usuario SQL cuyo nombre es el correo del Google Service Account (GSA) y cuyo tipo es CLOUD_IAM_SERVICE_ACCOUNT. Con esto Cloud SQL reconoce los tokens de ese GSA como credenciales válidas.
| Aspecto | Valor |
|---|---|
| Nombre del usuario SQL | <nombre-gsa>@<gcp-project>.iam (formato fijo) |
| Tipo del usuario | CLOUD_IAM_SERVICE_ACCOUNT |
| Password | Ninguno — se autentica con tokens rotados automáticamente por GCP |
| Ejemplo | backend-checkout-service-prd@hera-prd.iam |
3. Otorgar permisos (GRANTs) dentro de la base de datos
El usuario recién creado no tiene permisos por defecto. Un DBA o un job de inicialización del producto debe ejecutar los GRANT específicos. Ejemplo mínimo para una app que lee y escribe en el schema public:
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO "<nombre-gsa>@<gcp-project>.iam";Política HERA de privilegios mínimos:
- Cada servicio recibe solo los permisos que ejercita su dominio (principio least privilege).
- Los
GRANT ALLestán prohibidos en PRD — se declara la lista explícita de operaciones. - Los privilegios se auditan cada cuatrimestre como parte de la revisión periódica de accesos (Security Engineer).
Ejemplos de Conexión en Código
Sección titulada «Ejemplos de Conexión en Código»Como la autenticación se delega al Auth Proxy y a las credenciales por defecto (ADC), el código no requiere contraseña. Para ambientes de producción, es obligatorio configurar un connection pool con manejo de desconexión.
Node.js (pg)
const { Pool } = require('pg');
// Al usar el Auth Proxy en 127.0.0.1, omitimos el password.// El proxy negocia el token IAM transparente para la app.const pool = new Pool({ user: process.env.DB_CHECKOUT_USERNAME, // email del GSA inyectado vía env var host: process.env.DB_CHECKOUT_HOST, // 127.0.0.1 (sidecar Auth Proxy) database: process.env.DB_CHECKOUT_NAME, // ej. db_tienda_herdez_checkout port: Number(process.env.DB_CHECKOUT_PORT), // 5432 // ¡Sin contraseña!
// Configuración de producción para connection pooling max: 20, // Máximo de conexiones persistentes idleTimeoutMillis: 30000, // Cierra clientes inactivos después de 30 segundos connectionTimeoutMillis: 2000, // Retorna error si no se conecta en 2 segundos});
// Manejo de errores de conexión a nivel de pool para evitar caídas silenciosaspool.on('error', (err, client) => { console.error('Error inesperado en cliente inactivo de la base de datos', err);});Python (SQLAlchemy)
import osfrom sqlalchemy import create_enginefrom sqlalchemy.pool import QueuePool
# Variables inyectadas al pod vía env vars (ver Estándares de Configuración R11)db_user = os.environ["DB_CHECKOUT_USERNAME"] # email del GSA, sin passworddb_name = os.environ["DB_CHECKOUT_NAME"] # ej. db_tienda_herdez_checkoutdb_host = os.environ["DB_CHECKOUT_HOST"] # 127.0.0.1 (sidecar Auth Proxy)db_port = os.environ["DB_CHECKOUT_PORT"] # 5432
# Configuración de producción con connection pooling y manejo de desconexiónengine = create_engine( f"postgresql://{db_user}@{db_host}:{db_port}/{db_name}", poolclass=QueuePool, pool_size=5, # Máximo de conexiones persistentes base max_overflow=10, # Conexiones extra permitidas en picos pool_timeout=30, # Segundos de espera si el pool está lleno antes de fallar pool_recycle=1800, # Reciclar conexiones después de 30 minutos pool_pre_ping=True # Verificar que la conexión sigue viva antes de usarla)Implementación como Sidecar
Sección titulada «Implementación como Sidecar»Desplegamos el proxy como un contenedor “sidecar” en el mismo pod que nuestra aplicación. Utilizamos el flag --auto-iam-authn para que el proxy utilice la identidad del pod (Workload Identity) para solicitar los tokens de base de datos automáticamente. La aplicación se conecta al proxy en 127.0.0.1.
apiVersion: apps/v1kind: Deploymentmetadata: name: mi-appspec: template: spec: serviceAccountName: mi-app-ksa # Imprescindible para Workload Identity containers: # --- Contenedor de la aplicación --- - name: mi-app image: gcr.io/mi-proyecto/mi-app:v1 env: - name: DB_HOST value: "127.0.0.1" # <-- La app apunta a localhost - name: DB_PORT value: "5432" - name: DB_USER value: "mi-app-gsa@mi-proyecto.iam" # DB_PASSWORD ha sido eliminado por completo en esquema passwordless
# --- Contenedor Sidecar: Cloud SQL Auth Proxy --- - name: cloud-sql-proxy image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.8.0 args: - "--structured-logs" # Habilita la autenticación con IAM DB Auth usando Workload Identity - "--auto-iam-authn" # Nombre de la instancia de conexión - "mi-proyecto:us-central1:hera-prd-db" securityContext: runAsNonRoot: true resources: requests: memory: "256Mi" cpu: "100m"Database as Code — gestión de schema
Sección titulada «Database as Code — gestión de schema»Todo cambio al schema de una base de datos (crear tabla, añadir columna, crear índice, eliminar constraint, etc.) debe aplicarse mediante un archivo de migración versionado en Git — igual que el código de aplicación. Este modelo se conoce como Database as Code (DBaC) y es obligatorio en HERA.
Por qué es obligatorio
Sección titulada «Por qué es obligatorio»| Sin DBaC | Con DBaC |
|---|---|
| SQL ad-hoc corrido por un dev contra PRD | Todo cambio es un archivo en db/migrations/ con su MR aprobado |
| Imposible saber quién/cuándo cambió qué | Audit trail completo vía git log + historial del tool |
| No se puede reproducir DEV desde cero | migrate up levanta la DB al estado actual desde el primer commit |
| Rollbacks manuales, frágiles | Migraciones con down.sql o versiones reversibles |
| Deploy de app + cambio de schema no coordinados | Pipeline aplica migrate up antes del deploy nuevo |
Herramientas aprobadas por stack
Sección titulada «Herramientas aprobadas por stack»HERA mantiene una lista cerrada de herramientas de migración por stack tecnológico. No se permite el uso de herramientas fuera de esta lista sin aprobación del DBA.
| Stack | Herramienta estándar | Alternativa aprobada |
|---|---|---|
| Java / Spring Boot | Flyway | Liquibase |
| Node.js | node-pg-migrate | Prisma Migrate · Knex |
| Python | Alembic (SQLAlchemy) | — |
| Go | golang-migrate | Atlas |
| .NET | EF Core Migrations | — |
| PHP | Doctrine Migrations | Phinx |
Convención de repo — db/migrations/
Sección titulada «Convención de repo — db/migrations/»Los archivos de migración viven en un directorio dedicado del repo del servicio:
backend-checkout-service/├── src/├── db/│ ├── migrations/│ │ ├── V001__create_orders_table.sql│ │ ├── V002__add_status_index.sql│ │ └── V003__add_customer_email.sql│ └── seeds/│ └── reference_data.sql├── Dockerfile└── service-manifest.ymlReglas:
- Prefijo de versión monótono (
V001,V002, …) o timestamp (20260421_120000) según la convención del tool elegido. - Un cambio por archivo — no agrupar
CREATE TABLE+CREATE INDEXsi son conceptualmente distintos. - Archivos nunca se modifican una vez mergeados a
main. Corrección de un error se hace con una migración nueva que revierte/ajusta. seeds/solo para datos de referencia no sensibles (catálogos, tipos enum). Datos de usuario NO van aquí.
Integración en el pipeline
Sección titulada «Integración en el pipeline»El Golden Pipeline incluye un stage pre-deploy que aplica las migraciones pendientes antes de desplegar la nueva versión de la aplicación.
| Job | Stage | Acción | Condición de fallo |
|---|---|---|---|
db-migrate | pre-deploy | <tool> migrate up sobre la instancia Cloud SQL del ambiente (DEV/QA/PRD) | Migration falla → el deploy de la app no corre → app sigue en la versión anterior |
Esto garantiza que la versión nueva de la app nunca corra sobre un schema desactualizado (ni al revés). Si la migration falla en QA/PRD, el deploy se cancela y el equipo remedia antes de reintentar.
Reglas duras — prohibido en HERA
Sección titulada «Reglas duras — prohibido en HERA»| Anti-pattern | Por qué | Qué hacer en su lugar |
|---|---|---|
❌ ALTER TABLE ejecutado manualmente desde psql/mysql contra PRD | Rompe audit trail y genera drift entre repo y DB real | Abrir MR con archivo de migración |
❌ DROP COLUMN/DROP TABLE sin aprobación explícita del DBA | Riesgo de pérdida de datos no reversible | MR con label schema-breaking-change + review obligatorio del DBA |
| ❌ Modificar un archivo de migration ya mergeado | Rompe la reproducibilidad: DEV y PRD quedan con historias distintas | Nueva migration que corrija o revierta |
❌ Usar ORM auto-migration en PRD (ej. hibernate.ddl-auto=update) | Cambios opacos sin revisión — el schema deriva sin trazabilidad | ORM en modo validate o none; schema evoluciona solo vía migrations explícitas |
| ❌ Migraciones que toman locks largos en tablas grandes en PRD | Riesgo de downtime/lock contention | Coordinar con DBA+SRE · usar estrategias online (CREATE INDEX CONCURRENTLY, pt-osc, gh-ost) |
RACI de schema migrations
Sección titulada «RACI de schema migrations»| Actividad | Responsable (R) | Aprobador (A) | Consultado (C) |
|---|---|---|---|
| Diseñar schema inicial de un servicio | Tech Lead + DBA | DBA | Arquitecto |
Redactar migration ADD COLUMN no-breaking | Developer | Tech Lead | DBA |
Redactar migration con breaking change (DROP, RENAME, type change) | Developer | DBA (obligatorio) | Tech Lead + SRE |
| Migration con riesgo de lock en tabla grande PRD | Developer | DBA + SRE | Tech Lead |
| Ejecutar migration en PRD | Pipeline (db-migrate job) | — | DBA (si breaking change) |
| Auditar historial de migrations | DBA (cuatrimestral) | — | Security Engineer |
Backups y Recuperación (PITR)
Sección titulada «Backups y Recuperación (PITR)»Nuestra estrategia de backups está diseñada para recuperarnos de cualquier tipo de desastre, desde un DELETE accidental hasta un fallo regional.
Política de Backups
Sección titulada «Política de Backups»| Tipo | Frecuencia | Retención (PRD) | Caso de Uso |
|---|---|---|---|
| Backup Automático | Diario (3 AM) | 30 días | Recuperación completa de la instancia. |
| PITR (Point-in-Time) | Continuo (Logs) | 7 días | Recuperación granular. Restaurar la base a un estado exacto, al segundo. Ideal para errores humanos. |
| Backup On-demand | Manual | Hasta eliminación | Realizado antes de cambios críticos como migraciones de esquema. |
| Export | Semanal | 90 días en GCS | Creación de una copia lógica para archivado o para cargar en otros sistemas (ej. BigQuery). |
Proceso de Recuperación
Sección titulada «Proceso de Recuperación»- Restaurar un backup: Crea una nueva instancia a partir de un backup específico.
Ventana de terminal gcloud sql backups restore BACKUP_ID --restore-instance=nueva-instancia-clone - Restaurar a un punto en el tiempo (PITR): Clona la instancia a un momento exacto. Es la herramienta más potente contra errores.
Ventana de terminal gcloud sql instances clone hera-prd-db hera-prd-db-recuperada \--point-in-time="2024-01-15T10:30:00Z"
Seguridad: Un Enfoque por Capas
Sección titulada «Seguridad: Un Enfoque por Capas»| Control | Configuración en HERA | Estado |
|---|---|---|
| Cifrado en Tránsito | SSL/TLS gestionado por el Auth Proxy. | ✅ |
| Cifrado en Reposo | Cifrado por defecto por Google. Usamos CMEK (Customer-Managed Keys) para datos sensibles. | ✅ |
| Acceso a la Red | IP Privada únicamente. No se exponen IPs públicas. | ✅ |
| Autenticación | Autenticación de base de datos de IAM. Los usuarios y SAs de IAM pueden autenticarse sin contraseña. | ✅ |
| Auditoría | Cloud Audit Logging habilitado para todas las operaciones administrativas y de acceso a datos. | ✅ |
Monitoreo con Query Insights
Sección titulada «Monitoreo con Query Insights»Query Insights es una herramienta indispensable para el diagnóstico de rendimiento de la base de datos. Nos ofrece una vista detallada de las consultas más lentas y que más recursos consumen.
| Métrica clave | Umbral de alerta (PRD) | Severidad |
|---|---|---|
| Utilización de CPU | > 80% por 15 min | Alta |
| Utilización de Memoria | > 90% por 10 min | Alta |
| Utilización de Disco | > 85% | Crítica |
| Retraso de Replicación | > 30 segundos | Media |
| Conexiones activas | > 80% del máximo | Media |
Con Query Insights, podemos identificar rápidamente consultas problemáticas y recibir recomendaciones, como la creación de un índice, para optimizarlas.
-- Query Insights detecta esta consulta como Top Consumer (Seq Scan completo)EXPLAIN ANALYZESELECT o.id, o.total, c.emailFROM orders oJOIN customers c ON o.customer_id = c.idWHERE o.status = 'pending'ORDER BY o.created_at DESC;
-- Resultado: Seq Scan on orders (cost=0.00..9240.00 rows=85000)-- Filter: (status = 'pending')-- Rows removed by filter: 164823
-- Recomendación de Query Insights: índice compuesto en (status, created_at)CREATE INDEX CONCURRENTLY idx_orders_status_date ON orders (status, created_at DESC);
-- Resultado post-optimización: Index Scan (cost=0.43..12.30 rows=3)-- Mejora: de 9 segundos a menos de 50 ms en tabla de 250K filas