Lab práctico · Semana 5: Almacenamiento y bases de datos

Cloud SQL con alta disponibilidad, réplica, copia de seguridad y conmutación por error

⏱ 90-120 minDificultad: mediaApartados: 2.21.2

Qué vas a construir

Una instancia pequeña de Cloud SQL for PostgreSQL con alta disponibilidad regional (primario y standby en dos zonas de Madrid), una réplica de lectura, una copia de seguridad bajo demanda que usarás para recuperarte de un «desastre» (un DROP TABLE) y una conmutación por error manual para ver qué pasa con las zonas y la IP.

flowchart LR
  CS["Cloud Shell (psql)"] --> IP["IP de la instancia"]
  IP --> P["Primario lab11-pg (zona A)"]
  P -- "síncrona" --> S["Standby (zona B)"]
  P -- "asíncrona" --> R["Réplica lab11-pg-replica (solo lectura)"]
  P -.->|"copia bajo demanda"| B["Backup antes-del-desastre"]

Antes de empezar

  • Proyecto dedicado con facturación y Cloud Shell abierto.
  • Las máquinas de núcleo compartido (db-g1-small) no tienen SLA: sirven para aprender, nunca para producción.
export PROJECT_ID=$(gcloud config get-value project)
export REGION=europe-southwest1
export INSTANCE=lab11-pg
export REPLICA=lab11-pg-replica
export DB_PASS='CambiaEstaClave-2026'
gcloud services enable sqladmin.googleapis.com

Paso 1: crear la instancia con HA, copias automáticas y PITR

Qué haces y por qué:

  • --availability-type=REGIONAL crea primario y standby en dos zonas con replicación síncrona: sobrevive a la caída de una zona.
  • --edition=ENTERPRISE es imprescindible: con PostgreSQL 16 o superior, gcloud usa Enterprise Plus por defecto, que es mucho más cara y no admite núcleo compartido.
  • --backup-start-time activa las copias automáticas diarias y --enable-point-in-time-recovery guarda los logs de transacciones para poder restaurar a un momento concreto.

Por consola: SQL → Crear instancia → PostgreSQL, edición Enterprise, versión 16, región europe-southwest1, disponibilidad zonal Varias zonas (alta disponibilidad), tipo de máquina Núcleo compartido → 1 vCPU, 1,7 GB, almacenamiento SSD de 10 GB.

Con gcloud (tarda unos 10 minutos):

gcloud sql instances create $INSTANCE \
  --database-version=POSTGRES_16 \
  --edition=ENTERPRISE \
  --tier=db-g1-small \
  --region=$REGION \
  --availability-type=REGIONAL \
  --storage-type=SSD \
  --storage-size=10GB \
  --backup-start-time=03:00 \
  --enable-point-in-time-recovery \
  --root-password="$DB_PASS"

Comprueba la configuración y, sobre todo, las dos zonas:

gcloud sql instances describe $INSTANCE \
  --format="yaml(settings.edition,settings.tier,settings.availabilityType,gceZone,secondaryGceZone,ipAddresses)"

Anota gceZone (primario), secondaryGceZone (standby) y la IP pública.

Paso 2: crear datos de prueba

gcloud sql connect autoriza temporalmente la IP de Cloud Shell y abre psql (te pedirá la contraseña de postgres, la de DB_PASS):

gcloud sql connect $INSTANCE --user=postgres

Dentro de psql:

CREATE DATABASE tienda;
\c tienda
CREATE TABLE pedidos (id SERIAL PRIMARY KEY, cliente TEXT, importe NUMERIC(10,2), creado TIMESTAMPTZ DEFAULT now());
INSERT INTO pedidos (cliente, importe) VALUES ('Ana', 120.50), ('Luis', 89.90), ('Marta', 300.00);
SELECT * FROM pedidos;
\q

Paso 3: conmutación por error manual

Provoca un failover para ver que el standby toma el control. En producción ocurre solo cuando el primario deja de responder; hacerlo a mano es una buena práctica para probar que la aplicación reconecta bien.

Consola: instancia → Conmutación por error (Failover). Con gcloud:

gcloud sql instances failover $INSTANCE
gcloud sql operations list --instance=$INSTANCE --limit=3
gcloud sql instances describe $INSTANCE --format="yaml(gceZone,secondaryGceZone,ipAddresses)"

Fíjate en que gceZone y secondaryGceZone se han intercambiado, pero la IP es la misma. Vuelve a conectarte y comprueba que los tres pedidos siguen ahí (ninguna transacción confirmada se pierde con la replicación síncrona):

gcloud sql connect $INSTANCE --user=postgres --database=tienda
SELECT count(*) FROM pedidos;
\q

Paso 4: réplica de lectura

Crea una réplica en la misma región (tarda unos minutos). En un plan de DR real la pondrías en otra región cambiando --region (por ejemplo europe-west1).

gcloud sql instances create $REPLICA \
  --master-instance-name=$INSTANCE \
  --region=$REGION \
  --tier=db-g1-small

Si tu proyecto rechaza el núcleo compartido para réplicas, usa --tier=db-custom-1-3840 (1 vCPU y 3,75 GB), que cuesta algo más por hora.

Conéctate a la réplica e intenta escribir:

gcloud sql connect $REPLICA --user=postgres --database=tienda
SELECT * FROM pedidos;
INSERT INTO pedidos (cliente, importe) VALUES ('Pepe', 10);
\q

La lectura funciona; la escritura falla con un error de transacción de solo lectura. La réplica escala lecturas, pero no acepta escrituras mientras no la promociones.

Paso 5: copia de seguridad, «desastre» y restauración

Haz una copia bajo demanda antes de la operación arriesgada:

gcloud sql backups create --instance=$INSTANCE --description="antes-del-desastre"
gcloud sql backups list --instance=$INSTANCE

Anota el ID de la copia antes-del-desastre. Ahora simula el error humano:

gcloud sql connect $INSTANCE --user=postgres --database=tienda
DROP TABLE pedidos;
\q

Comprueba en la réplica que la tabla también ha desaparecido: la réplica no es una copia de seguridad, replica el error al instante.

Para restaurar una copia sobre una instancia, la instancia no puede tener réplicas. Borra la réplica y restaura (la instancia se reinicia y sus datos actuales se sobrescriben):

gcloud sql instances delete $REPLICA --quiet
gcloud sql backups restore ID_DE_LA_COPIA --restore-instance=$INSTANCE

Conéctate y verifica que los tres pedidos han vuelto:

gcloud sql connect $INSTANCE --user=postgres --database=tienda
SELECT * FROM pedidos;
\q
Opcional: restauración a un momento concreto (PITR)

Con PITR podrías haber recuperado el estado de «un segundo antes del DROP TABLE» sin haber hecho una copia manual. La restauración PITR crea una instancia nueva (un clon), así que duplica el coste mientras exista. Solo si quieres probarlo (y bórralo después):

gcloud sql instances clone $INSTANCE ${INSTANCE}-pitr \
  --point-in-time='2026-10-01T10:42:00Z'

Sustituye la fecha por un momento anterior al borrado en formato UTC (RFC 3339).

Comprueba que funciona

  • describe muestra availabilityType: REGIONAL, edición ENTERPRISE y dos zonas distintas en gceZone y secondaryGceZone.
  • Tras el failover, las zonas se intercambian y la IP no cambia.
  • La réplica permite SELECT y rechaza INSERT.
  • Tras restaurar la copia, la tabla pedidos vuelve a tener sus tres filas.

Limpieza

Borra todo lo creado (la réplica ya la borraste en el paso 5; el comando la ignora si no existe). Por si la protección contra borrado estuviera activada, quítala antes:

gcloud sql instances delete $REPLICA --quiet 2>/dev/null
gcloud sql instances delete ${INSTANCE}-pitr --quiet 2>/dev/null
gcloud sql instances patch $INSTANCE --no-deletion-protection
gcloud sql instances delete $INSTANCE --quiet
gcloud sql instances list

gcloud sql instances list debe salir vacío. Al borrar la instancia, las copias automáticas se eliminan y, como no pediste copia final (--enable-final-backup), no queda nada facturable. Revisa al día siguiente el informe de facturación para confirmar que el cargo de Cloud SQL se ha detenido.

Preguntas para pensar como arquitecto

Tu instancia con HA sufre la caída completa de la región europe-southwest1. ¿Qué te salva y qué no?

La HA no, porque primario y standby están en la misma región. Te salva una réplica entre regiones que promocionas a instancia independiente (o, en Enterprise Plus, el switchover de DR avanzado), más las copias de seguridad, que por defecto se guardan en una ubicación multirregional. El RPO sería el retraso de la replicación asíncrona (segundos) y el RTO, lo que tardes en promocionar y reconfigurar la aplicación.

El equipo propone usar el standby de HA para lanzar los informes pesados y no cargar el primario. ¿Qué respondes?

Que no es posible: el standby de Cloud SQL no sirve lecturas. Para informes, crea una réplica de lectura (o usa read pools en Enterprise Plus). Si los informes son analíticos y grandes, valora exportar o replicar los datos a BigQuery con Datastream, o usar AlloyDB con su motor columnar.

¿Por qué en producción no usarías db-g1-small aunque la carga sea pequeña?

Porque las máquinas de núcleo compartido no están cubiertas por el SLA de Cloud SQL y comparten CPU con otros clientes, con rendimiento impredecible. En producción usa una máquina dedicada (al menos 1-2 vCPU) de la edición que requiera tu SLA: 99,95 % con Enterprise o 99,99 % con mantenimiento incluido en Enterprise Plus.

Un desarrollador borró una tabla a las 10:42 y la última copia es de las 03:00. ¿Cómo minimizas la pérdida?

Con PITR: clonas la instancia a las 10:41:59 usando las copias automáticas más los logs de transacciones. Obtienes una instancia nueva con los datos hasta ese momento y copias la tabla perdida a la instancia original (o rediriges la aplicación al clon). Restaurar la copia de las 03:00 perdería 7 horas y 42 minutos de datos de todas las tablas.


Volver al módulo