Realiza una PITR de una base de datos de PostgreSQL en Compute Engine


En este instructivo, se muestra cómo configurar el proceso de archivado y, luego, realizar una recuperación de un momento determinado (PITR) de una base de datos de PostgreSQL que se ejecuta en Compute Engine.

En este instructivo, crearás una base de datos de demostración y ejecutarás la carga de trabajo de una aplicación. Luego, configurarás los procesos de archivado y de copia de seguridad. A continuación, aprenderás a verificar los procesos de copia de seguridad, archivado y recuperación. Por último, aprenderás a recuperar la base de datos en un momento específico.

Este instructivo está dirigido a administradores de bases de datos, operadores de sistemas, profesionales de DevOps y arquitectura de nube interesados en la configuración de una estrategia de copia de seguridad y recuperación para bases de datos de PostgreSQL.

En este instructivo, se supone que estás familiarizado con los contenedores de Docker y que te sientes cómodo con Compute Engine, los comandos de Linux y los motores de las bases de datos de PostgreSQL.

Objetivos

  • Configurar un proceso de copia de seguridad y archivado
  • Realizar una PITR
  • Supervisar la copia de seguridad
  • Verificar una recuperación

Costos

En este documento, usarás los siguientes componentes facturables de Google Cloud:

Para generar una estimación de costos en función del uso previsto, usa la calculadora de precios. Es posible que los usuarios nuevos de Google Cloud califiquen para obtener una prueba gratuita.

Cuando finalices las tareas que se describen en este documento, puedes borrar los recursos que creaste para evitar que continúe la facturación. Para obtener más información, consulta Cómo realizar una limpieza.

Antes de comenzar

  1. Accede a tu cuenta de Google Cloud. Si eres nuevo en Google Cloud, crea una cuenta para evaluar el rendimiento de nuestros productos en situaciones reales. Los clientes nuevos también obtienen $300 en créditos gratuitos para ejecutar, probar y, además, implementar cargas de trabajo.
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  3. Asegúrate de que la facturación esté habilitada para tu proyecto de Google Cloud.

  4. Habilita las API de Compute Engine and Cloud Storage.

    Habilita las API

  5. Instala Google Cloud CLI.
  6. Para inicializar la CLI de gcloud, ejecuta el siguiente comando:

    gcloud init
  7. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Go to project selector

  8. Asegúrate de que la facturación esté habilitada para tu proyecto de Google Cloud.

  9. Habilita las API de Compute Engine and Cloud Storage.

    Habilita las API

  10. Instala Google Cloud CLI.
  11. Para inicializar la CLI de gcloud, ejecuta el siguiente comando:

    gcloud init
  12. En la consola de Google Cloud, activa Cloud Shell.

    Activar Cloud Shell

    En la parte inferior de la consola de Google Cloud, se inicia una sesión de Cloud Shell en la que se muestra una ventana de línea de comandos. Cloud Shell es un entorno de shell con Google Cloud CLI ya instalada y con valores ya establecidos para el proyecto actual. La sesión puede tardar unos segundos en inicializarse.

Conceptos

Antes de comenzar con el instructivo, revisa los siguientes conceptos de PostgreSQL:

  • Archivado continuo. Es el proceso en el que la base de datos guarda transacciones secuenciales en un archivo de forma continua.
  • Registro de escritura por adelantado (WAL). Los cambios en los archivos de datos se registran en el WAL antes de realizarse en el archivo.
  • Registro WAL. Cada transacción que se aplica a la base de datos recibe el formato de registro WAL y se almacena como tal.
  • Archivos de segmentos. Los archivos de segmentos tienen nombres que aumentan de forma monótona y contienen tantos registros WAL como sea posible. El tamaño del archivo se puede configurar y tiene un valor predeterminado de 16 MiB. Si esperas transacciones de un recuento o un tamaño voluminosos, puedes elegir un tamaño mayor para reducir la cantidad total de archivos de segmentos generados y disminuir la carga de la administración de archivos.

Para obtener más información, consulta La confiabilidad y el registro de escritura por adelantado.

En el siguiente diagrama, se muestra cómo los WAL se conservan en dos etapas.

2 etapas de WAL persistentes.

En el diagrama anterior, la primera etapa de los WAL persistentes consiste en que el motor de la base de datos registre transacciones de escritura en el búfer de WAL de forma simultánea a la escritura en una tabla. Cuando se confirma la transacción, el búfer de WAL se escribe en el disco (se borran sus datos) durante la segunda etapa y se agrega al archivo de segmentos WAL.

Elige una PITR

Una PITR es apropiada para las siguientes situaciones:

  • Minimizar el objetivo de punto de recuperación (RPO). El RPO es el tiempo máximo de pérdida de datos que se tolera sin que tenga un impacto significativo en los procesos empresariales. Si se guardan todas las transacciones en los WAL entre las instantáneas de copia de seguridad, se disminuye de forma drástica la cantidad de datos perdidos, ya que se cuenta con las transacciones desde la última copia de seguridad completa para su aplicación a la base de datos.
  • Minimizar el objetivo de tiempo de recuperación (RTO). El RTO es la cantidad de tiempo necesaria para recuperar una base de datos si se produce un evento destructivo. Después de configurar las copias de seguridad de objetos binarios y el archivado de registros, el tiempo necesario para recuperar la base de datos puede ser mínimo.
  • Solucionar un error de corrupción de datos o una falla administrativa. Si la versión de un código causa daños catastróficos en los datos o si se comete un error irrecuperable durante el mantenimiento de rutina, puedes realizar una recuperación a un momento anterior a ese.

En algunas arquitecturas de aplicaciones, como una arquitectura de microservicios, puede que haya bases de datos paralelas que requieran recuperaciones independientes. Por ejemplo, puede que una aplicación de venta minorista tenga datos de clientes en una base de datos y detalles de pedidos de venta minorista junto con información del inventario en otras bases de datos. Según el estado general de los datos, es posible que debas recuperar una, dos o todas las bases de datos en paralelo.

Una PITR no es apropiada para las siguientes situaciones:

  • El RPO es amplio. Si tu política de recuperación ante desastres puede tolerar la pérdida de cualquier transacción recibida después de la instantánea más reciente, puedes evitar los pasos adicionales y enfocarte en reducir el tiempo de recuperación de los datos.
  • Se necesita una recuperación completa de la base de datos. Si tu meta es recuperar hasta la transacción más reciente, el objetivo de recuperación es la marca de tiempo de la última transacción conservada. Esta situación es un caso específico de PITR, pero la meta se conoce de forma semántica como una recuperación completa.

Consideraciones de rendimiento

El proceso de archivado agrega una carga adicional de E/S en el servidor de la base de datos. La carga adicional depende de las características de tu carga de trabajo, ya que es proporcional al volumen de transacciones de escritura, actualización y eliminación.

Si deseas reducir el impacto de E/S que podría generar la actividad de archivado de WAL en tu base de datos principal, puedes realizar los archivados de WAL periódicos mediante el uso de una réplica de solo lectura.

Esta configuración aísla la base de datos principal de las actividades de E/S por lotes relacionadas con la transferencia de los archivos WAL. Las transacciones que se dirigen a la réplica de solo lectura se transmiten en un flujo constante desde la base de datos principal, lo que exige un impacto mucho menor en la capacidad de procesamiento de estado estable.

Además, si la topología de la base de datos de producción ya incluye una réplica de solo lectura, esta configuración no agrega ninguna carga adicional de administración, precios, ni de ningún otro tipo.

Arquitectura de referencia

En el siguiente diagrama, se ilustra la arquitectura que implementas en este instructivo.

Infraestructura de nube de una PITR mediante Compute Engine y Cloud Storage.

En este instructivo, crearás una infraestructura de nube para observar una PITR que usa los siguientes componentes:

  • Un servidor de bases de datos de PostgreSQL que se ejecuta en Compute Engine
  • Cloud Storage para el almacenamiento de instantáneas y registros de transacciones

En el siguiente diagrama, se muestran los dos contenedores de Docker que se inician en la máquina virtual (VM) de la base de datos de PostgreSQL. Como en una separación de intereses, el servidor de la base de datos se ejecuta en uno de los contenedores y el archivador de WAL se ejecuta en el otro.

Contenedores de Docker para el servidor de la base de datos y el archivador de WAL.

En este diagrama, se muestra cómo se asignan los volúmenes de Docker en cada contenedor a los puntos de activación del Persistent Disk en la VM host.

Configura variables de entorno

Los comandos y las secuencias de comandos que se usan en este instructivo se basan en variables de entorno de shell.

  1. En Cloud Shell, configura las variables de entorno de tu proyecto, el nombre de la instancia y la base de datos de PostgreSQL de demostración.

    export PROJECT_ID=your-gcp-project
    export PG_INSTANCE_NAME=instance-pg-pitr
    export POSTGRES_PASSWORD=PasswordIsThis
    export POSTGRES_PITR_DEMO_DBNAME=pitr_demo
    

    Reemplaza los siguientes elementos:

    • your-gcp-project: Es el nombre del proyecto que creaste para este instructivo.
    • PasswordIsThis: Es una contraseña segura para la base de datos de PostgreSQL.
  2. Configura la variable de entorno de la zona de Google Cloud. Reemplaza choose-an-appropriate-zone por una zona de Google Cloud.

    export ZONE=choose-an-appropriate-zone
    export REGION=${ZONE%-[a-z]}
    
  3. Configura la variable de entorno de la subred de la nube privada virtual (VPC) predeterminada de la región de tu zona:

    export SUBNETWORK_URI=$(gcloud compute networks subnets \
        describe default --format=json --region=$REGION | \
        jq --raw-output '.ipCidrRange')
    
  4. Configura la variable de entorno del bucket de Cloud Storage. Reemplaza archive-bucket por un nombre único para el bucket de Cloud Storage en el que se guardan los WAL.

    export ARCHIVE_BUCKET=archive-bucket
    

Crea un bucket de Cloud Storage

  • Crea un bucket de Cloud Storage para guardar los archivos WAL desde la base de datos de PostgreSQL:

    gsutil mb gs://${ARCHIVE_BUCKET}
    

Permite el acceso a instancias de direcciones IP privadas

En cuanto a las instancias que se usan en este instructivo, como en muchos casos prácticos de producción, no es necesario que las instancias de VM obtengan direcciones IP públicas. Sin embargo, necesitas acceso para conectarte mediante una shell segura y las instancias requieren acceso a la Internet pública a fin de extraer las imágenes de contenedor de ejemplo. Debes configurar una puerta de enlace de traducción de direcciones de red (NAT) y un Identity-Aware Proxy (IAP) para la redirección de TCP.

Crear una puerta de enlace NAT

Debido a que las instancias de VM que creas no tienen direcciones IP públicas, debes crear una puerta de enlace NAT para que las instancias puedan extraer imágenes de contenedores de Docker Hub.

  1. En Cloud Shell, crea un Cloud Router:

    export CLOUD_ROUTER_NAME=${PROJECT_ID}-nat-router
    gloud compute routers create $CLOUD_ROUTER_NAME \
        --network=default --region=$REGION
    
  2. Crea la puerta de enlace NAT:

    gcloud compute routers nats create ${PROJECT_ID}-nat-gateway \
        --region=$REGION \
        --router=$CLOUD_ROUTER_NAME \
        --auto-allocate-nat-external-ips \
        --nat-all-subnet-ip-ranges
    

Configura IAP para la redirección de TCP

IAP controla el acceso a las aplicaciones y las VM en la nube que se ejecutan en Google Cloud. IAP verifica la identidad del usuario y el contexto de la solicitud para determinar si el usuario puede acceder a una VM.

  1. En Cloud Shell, permite el tráfico desde el bloque de red de redirección de TCP hacia las instancias de tu proyecto:

    export IAP_FORWARDING_CIDR=35.235.240.0/20
    gcloud compute --project=$PROJECT_ID firewall-rules create \
        cloud-iap-tcp-forwarding --direction=INGRESS  \
        --priority=1000 --network=default \
        --action=ALLOW --rules=all  \
        --source-ranges=$IAP_FORWARDING_CIDR
    
  2. Para conectarte mediante un túnel de reenvío de TCP, agrega una vinculación de política de Identity and Access Management (IAM). Reemplaza your-email-address por la dirección de correo electrónico que usas para acceder a la consola de Google Cloud.

    export GRANT_EMAIL_ADDRESS=your-email-address
    gcloud projects add-iam-policy-binding $PROJECT_ID \
       --member=user:$GRANT_EMAIL_ADDRESS \
       --role=roles/iap.tunnelResourceAccessor
    

Crea la infraestructura de la base de datos de PostgreSQL

  1. En Cloud Shell, clona el repositorio de origen que contiene las secuencias de comandos de configuración y cambia el contexto de la shell al repositorio local:

    git clone https://github.com/GoogleCloudPlatform/gcs-postgresql-recovery-tutorial
    cd gcs-postgresql-recovery-tutorial
    
  2. Para crear y configurar la instancia de VM de la base de datos, ejecuta la siguiente secuencia de comandos:

    cd bin
    ./create_postgres_instance.sh
    

    En el caso de este instructivo, la secuencia de comandos inicia una instancia de VM en la zona elegida con el sistema operativo optimizado para contenedores y dos discos persistentes nuevos conectados. En este caso, puedes ignorar el mensaje de advertencia que muestra la API acerca del rendimiento bajo de E/S debido a que las secuencias de comandos crean discos persistentes pequeños.

Revisa la configuración de cloud-init

Cloud-init es un paquete de distribución múltiple que inicializa una instancia en la nube.

Revisa la siguiente muestra de código de cloud-init:

write_files:
- path: /var/tmp/docker-entrypoint-initdb.d/init-pitr-demo-db.sql
  permissions: 0644
  owner: root
  content: |
    CREATE DATABASE ${POSTGRES_PITR_DEMO_DBNAME};

    \c ${POSTGRES_PITR_DEMO_DBNAME}

    CREATE SCHEMA pitr_db_schema;

    CREATE TABLE pitr_db_schema.customer
       (id SERIAL NOT NULL,
        name VARCHAR(255),
        create_timestamp TIMESTAMP DEFAULT current_timestamp,
        PRIMARY KEY (id));

    CREATE TABLE pitr_db_schema.invoice
       (id SERIAL NOT NULL,
        customer_id INTEGER
          REFERENCES pitr_db_schema.customer(id),
        description VARCHAR(1000),
        create_timestamp TIMESTAMP DEFAULT current_timestamp,
        PRIMARY KEY (customer_id, id));

- path: /etc/systemd/system/postgres.service
  permissions: 0644
  owner: root
  content: |
    [Unit]
    Requires=docker.service
    After=docker.service
    Description=postgres docker container

    [Service]
    TimeoutStartSec=0
    KillMode=none
    Restart=always
    RestartSec=5s
    ExecStartPre=-/usr/bin/docker kill postgres-db
    ExecStartPre=-/usr/bin/docker rm -v postgres-db
    ExecStart=/usr/bin/docker run -u postgres --name postgres-db \
                                  -v /var/tmp/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d \
                                  -v /mnt/disks/data:/var/lib/postgresql/data \
                                  -v /mnt/disks/wal:/var/lib/postgresql/wal \
                                  -e PGDATA=/var/lib/postgresql/data/pgdata \
                                  -e POSTGRES_PASSWORD=${POSTGRES_PASSWORD} \
                                  -e POSTGRES_INITDB_WALDIR=/var/lib/postgresql/wal/pg_wal \
                                  -p ${POSTGRES_PORT}:${POSTGRES_PORT} \
                               postgres:11-alpine
    ExecStop=-/usr/bin/docker stop postgres-db
    ExecStopPost=-/usr/bin/docker rm postgres-db

- path: /etc/systemd/system/wal_archive.service
  permissions: 0644
  owner: root
  content: |
    [Unit]
    Requires=docker.service postgres.service
    After=docker.service postgres.service
    Description=WAL archive docker container

    [Service]
    TimeoutStartSec=10min
    Type=oneshot
    ExecStart=/usr/bin/docker run --name wal-archive \
                                  -v /mnt/disks/wal/pg_wal_archive:/mnt/wal_archive \
                               google/cloud-sdk:slim gsutil mv /mnt/wal_archive/[0-9A-F]*[0-9A-F] gs://${ARCHIVE_BUCKET}
    ExecStopPost=-/usr/bin/docker rm wal-archive

- path: /etc/systemd/system/wal_archive.timer
  permissions: 0644
  owner: root
  content: |
    [Unit]
    Description=Archive WAL to GCS (every 5 minutes)

    [Timer]
    OnBootSec=5min
    OnUnitInactiveSec=5min
    OnUnitActiveSec=5min

    [Install]
    WantedBy=timers.target

En este instructivo, se usa cloud-init para realizar las siguientes acciones:

  1. Crear dos dispositivos de almacenamiento en bloque de Persistent Disk
  2. Crear los sistemas de archivos en ambos dispositivos: uno para los datos y otro para los registros de archivos
  3. Activar los dispositivos en puntos de activación lógicos en la instancia de VM, que se comparten con los contenedores de Docker
  4. Crear y, luego, iniciar un servicio systemd (postgres.service), que inicia un contenedor de Docker de PostgreSQL que contiene los siguientes elementos:
    • Los discos persistentes activados como volúmenes
    • El puerto de PostgreSQL (5432) publicado en el host de VM
  5. Crear un archivo /var/tmp/docker-entrypoint-initdb.d/init-pitr-demo-db.sql para crear un conjunto simple de tablas en un esquema y una base de datos de demostración
  6. Crear e iniciar un segundo servicio systemd (wal_archive.service) que ejecute un contenedor de Docker de Google Cloud CLI con los discos WAL activados como un volumen. Este servicio crea una copia de seguridad de los archivos WAL almacenados en Cloud Storage
  7. Crear, habilitar y, luego, iniciar un temporizador systemd (wal_archive.timer) que ejecute el wal_archive.service de forma periódica
  8. Garantizar que el puerto de PostgreSQL (5432) esté abierto para la subred de VPC, de forma que el generador de transacciones pueda alcanzar el puerto de la base de datos

Modifica la configuración de la instancia de la base de datos

El servidor de la base de datos se encuentra en ejecución, pero debes configurar el acceso a la red y comenzar el proceso de archivado de WAL.

Conéctate a la instancia de VM de la base de datos

  1. En la consola de Google Cloud, ve a la página Instancias de VM.

    Ir a Instancias de VM

  2. Para abrir la shell de una terminal, junto a la instancia instance-pg-pitr que creaste, haz clic en SSH.

  3. En la shell de la terminal, verifica que se haya iniciado el contenedor de Docker: docker ps

    El resultado es similar a este:

    CONTAINER ID   IMAGE                COMMAND                  CREATED              STATUS              PORTS   NAMES
    8bb65d8c1197   postgres:11-alpine   "docker-entrypoint.s…"   About a minute ago   Up About a minute           postgres-db
    

    Si el contenedor aún no se encuentra en ejecución, espera un momento y, luego, usa el mismo comando para volver a realizar la verificación.

Permite conexiones de red entrantes en la base de datos

  1. En la shell de la terminal de la instancia instance-pg-pitr, abre el archivo de configuración de autenticación basado en el host de PostgreSQL para editarlo:

    sudoedit /mnt/disks/data/pgdata/pg_hba.conf
    
  2. Para quitar el acceso predeterminado de todas las direcciones IP a la base de datos, comenta la siguiente línea desde el final del archivo mediante el agregado de # al comienzo de la línea. La línea del archivo es similar a la siguiente:

    #host all all all md5
    
  3. Para permitir conexiones protegidas por contraseñas desde hosts en el bloque CIDR 10.0.0.0/8, agrega la siguiente línea al final del archivo:

    host    all             all             10.0.0.0/8               md5
    

    Esta entrada habilita la conectividad desde la subred de VPC en la que se creará el generador de transacciones más adelante.

  4. Guarda y, luego, cierra el archivo.

Configura el archivado de WAL

  1. En la shell de la terminal de la instancia instance-pg-pitr, edita el archivo postgresql.conf:

    sudoedit /mnt/disks/data/pgdata/postgresql.conf
    
  2. Reemplaza las líneas comentadas existentes archive_mode, archive_command y archive_timeout por las siguientes:

    archive_mode=on
    archive_command = '( ARCHIVE_PATH=/var/lib/postgresql/wal/pg_wal_archive;
    test ! -f $ARCHIVE_PATH/%f && cp %p $ARCHIVE_PATH/%f.cp && mv
    $ARCHIVE_PATH/%f.cp $ARCHIVE_PATH/%f ) '
    archive_timeout = 120
    

    Cuando reemplaces las líneas en el archivo modificado, será similar al siguiente fragmento de código:

    
    .... illustrative snippet start ....
    
    # - Archiving -
    archive_mode=on
    archive_command = '( ARCHIVE_PATH=/var/lib/postgresql/wal/pg_wal_archive;  test ! -f $ARCHIVE_PATH/%f && cp %p $ARCHIVE_PATH/%f.cp && mv $ARCHIVE_PATH/%f.cp $ARCHIVE_PATH/%f ) '
    archive_timeout = 120
    #------------------------------------------------------------------------------
    # REPLICATION
    #------------------------------------------------------------------------------
    
    .... illustrative snippet end ....
    
    
  3. Guarda y, luego, cierra el archivo.

Aplica y verifica los cambios de configuración

  1. En la shell de la terminal de la instancia instance-pg-pitr, reinicia el contenedor para aplicar los cambios:

    sudo systemctl restart postgres
    
  2. Verifica los archivos de segmentos WAL:

    sudo ls -l /mnt/disks/wal/pg_wal
    

    El resultado es similar a este:

    total 16388
    -rw------- 1 postgres 70 16777216 Sep  5 23:07 000000010000000000000001
    drwx------ 2 postgres 70     4096 Sep  5 23:05 archive_status
    
  3. Comprueba la conectividad de red a la base de datos:

    export LOCAL_IP=127.0.0.1
    docker exec postgres-db psql -w --host=$LOCAL_IP \
          --command='SELECT 1'
    

    El resultado es similar a este:

    ?column?
    ----------
           1
    (1 row)
    
  4. Cierra la conexión SSH a la instancia.

Inicia el generador de transacciones para propagar la base de datos

Mediante los siguientes pasos, se inicia un programa de Go que genera transacciones para este instructivo. El programa se ejecuta dentro de un contenedor en una instancia de VM.

La imagen para el contenedor ya se encuentra compilada y alojada en un proyecto con un Container Registry público.

  1. En Cloud Shell, cambia al directorio del generador de transacciones:

    cd ~/gcs-postgresql-recovery-tutorial/bin
    
  2. Configura las variables de entorno:

    export TRANS_GEN_INSTANCE_NAME=instance-trans-gen
    export POSTGRES_HOST_IP=$(gcloud compute instances describe  \
        --format=json --zone=${ZONE} ${PG_INSTANCE_NAME} | \
        jq --raw-output '.networkInterfaces[0].networkIP')
    
  3. Para ejecutar el generador de transacciones, inicia la instancia:

    ./run_trans_gen_instance.sh
    

    Ignora el mensaje de advertencia acerca del rendimiento bajo de E/S.

  4. Espera unos minutos y comprueba que las transacciones lleguen a la base de datos de PostgreSQL:

    gcloud compute ssh $PG_INSTANCE_NAME \
       --tunnel-through-iap \
       --zone=$ZONE \
       --command="docker exec postgres-db psql \
       --dbname=$POSTGRES_PITR_DEMO_DBNAME \
       --command='SELECT COUNT(*) FROM pitr_db_schema.customer;'"
    

    El resultado contiene un recuento mayor que 0 cuando el generador de transacciones agrega registros a la base de datos:

     count
    -------
       413
    (1 row)
    

Configura el programa de copia de seguridad de instantáneas de objetos binarios

Puedes crear una copia de seguridad de los discos persistentes en función de un programa y conservarlos durante un tiempo definido en la política de recursos.

Crea el programa de instantáneas

  1. En Cloud Shell, configura las variables de entorno:

    export ZONE=zone-of-your-instance
    export SNAPSHOT_SCHEDULE_NAME=pg-disk-schedule
    export REGION=${ZONE%-[a-z]}
    export SNAPSHOT_WINDOW_START=$(TZ=":GMT" date "+%H:00")
    export SNAPSHOT_RETENTION_DAYS=2
    export SNAPSHOT_FREQUENCY_HOURS=1
    

    Reemplaza zone-of-your-instance por la zona de Google Cloud en la que antes iniciaste la VM de la base de datos.

  2. Crea el programa de instantáneas:

    gcloud compute resource-policies create snapshot-schedule \
        $SNAPSHOT_SCHEDULE_NAME \
        --region=$REGION \
        --max-retention-days=$SNAPSHOT_RETENTION_DAYS \
        --on-source-disk-delete=apply-retention-policy \
        --hourly-schedule=$SNAPSHOT_FREQUENCY_HOURS \
        --start-time=$SNAPSHOT_WINDOW_START \
        --storage-location=$REGION
    

Conecta el programa de instantáneas a los discos

Cuando ejecutaste la secuencia de comandos para crear una instancia, se crearon los datos y los volúmenes de WAL como dos discos persistentes independientes. Para crear instantáneas de discos persistentes en función de un programa definido, debes asociar una política de recursos a cada disco persistente. En este caso, deseas que las instantáneas de los discos se produzcan de forma simultánea, por lo que debes usar la misma política para ambos discos persistentes conectados a la VM de Compute Engine.

  1. En Cloud Shell, configura las variables de entorno:

    export SNAPSHOT_SCHEDULE_NAME=pgdata-disk-schedule
    export PG_INSTANCE_NAME=instance-pg-pitr
    export ZONE=zone-of-your-instance
    
  2. Conecta la política de programación al disco de datos persistentes:

    gcloud beta compute disks add-resource-policies ${PG_INSTANCE_NAME}-data \
        --resource-policies $SNAPSHOT_SCHEDULE_NAME \
        --zone $ZONE
    
  3. Conecta la política de programación al disco de WAL persistente:

    gcloud beta compute disks add-resource-policies ${PG_INSTANCE_NAME}-wal \
        --resource-policies $SNAPSHOT_SCHEDULE_NAME \
        --zone $ZONE
    

Ejecuta una instantánea de forma manual

Las instantáneas programadas se producen dentro de la ventana de programación, por lo que es poco probable que se haya tomado una de forma inmediata cuando creaste el programa (opcional). Si no quieres esperar a que se produzca la instantánea programada, puedes ejecutar la instantánea inicial de forma manual.

  1. En Cloud Shell, configura las variables de entorno:

    export ZONE=zone-of-your-instance
    export PG_INSTANCE_NAME=instance-pg-pitr
    export REGION=${ZONE%-[a-z]}
    
  2. Crea una instantánea de los dos discos persistentes de instancias de PostgreSQL:

    gcloud compute disks snapshot \
        ${PG_INSTANCE_NAME}-data ${PG_INSTANCE_NAME}-wal \
        --snapshot-names=${PG_INSTANCE_NAME}-data-`date+%s`,${PG_INSTANCE_NAME}-wal-`date +%s` \
        --zone=$ZONE --storage-location=$REGION
    
  3. Visualiza las instantáneas que creaste:

    gcloud compute snapshots list
    

    El resultado es similar a este:

    NAME                              DISK_SIZE_GB  SRC_DISK                                   STATUS
    instance-pg-pitr-data-1578339767  200           us-central1-f/disks/instance-pg-pitr-data  READY
    instance-pg-pitr-wal-1578339767   100           us-central1-f/disks/instance-pg-pitr-wal   READY
    

Realiza una PITR

A menudo, se realiza una PITR para recuperar los datos que se perdieron debido a un error operativo o programático.

En esta sección del instructivo, realizarás una actualización de la base de datos para simular una pérdida catastrófica de datos. Luego, simularás una respuesta en estado de pánico antes de iniciar una recuperación hasta el momento antes de que se emitiera el comando erróneo.

Asegúrate de que se pueda realizar una PITR

Antes de realizar una PITR, debes esperar el tiempo suficiente para que se ejecuten los siguientes elementos:

  • Las copias de seguridad de objetos binarios (instantáneas de discos)
  • El archivado de WAL

Para este instructivo, archive_timeout se estableció de forma atípica en 120 segundos con el fin de forzar la rotación frecuente de los archivos WAL. Además, debes esperar hasta que se realice al menos una instantánea programada del disco o debes tomar una instantánea del disco de forma manual.

  1. Comprueba que se haya tomado al menos una instantánea:

    1. En la consola de Google Cloud, ve a la página Instantáneas.

      Ir a la página de instantáneas

    2. Verifica que haya al menos dos instantáneas: una para el volumen de datos y otra para el volumen de WAL, por ejemplo, instance-pg-pitr--us-central1-a-20190805023535-i3hpw7kn.

  2. Verifica que los archivos de segmentos estén archivados en Cloud Storage:

    1. En la consola de Google Cloud, ve a la página Navegador de Cloud Storage.

      Ir a la página Navegador de Cloud Storage

    2. Haz clic en archive-bucket.

      Bucket de Cloud Storage que contiene objetos.

Daña los datos

Para simular una pérdida catastrófica de datos, abre una shell de línea de comandos en la base de datos de PostgreSQL y daña los datos en la tabla que propaga el generador de transacciones.

  1. En la consola de Google Cloud, ve a la página Instancias de VM.

    Ir a la página Instancias de VM

  2. En la instancia instance-pg-pitr, haz clic en SSH.

  3. En la terminal de SSH, ejecuta el frontend basado en la terminal de PostgreSQL en el contenedor de Docker:

    docker exec -it postgres-db psql --dbname=pitr_demo
    
  4. Para modificar una fila en la tabla de clientes, envía una declaración DML de SQL con un error de ortografía intencional en la shell de PostgreSQL:

    UPDATE pitr_db_schema.customer
    SET name = 'New Name for customer id=1';
    WHERE id = 1;
    

    El resultado es similar a este:

    UPDATE 999
    pitr_demo=#  WHERE id = 1;
    ERROR:  syntax error at or near "WHERE"
    LINE 1: WHERE id = 1;
            ^
     

    El error se generó debido a que se insertó un punto y coma adicional antes de la cláusula WHERE. Se actualizaron todas las filas de la base de datos. Ahora puedes realizar una PITR para recuperar las filas que se modificaron mediante la declaración incorrecta.

Determina el tiempo deseado para la recuperación

El primer paso en una PITR es determinar el tiempo deseado para la recuperación. Este tiempo se determina mediante el examen de los datos para identificar un punto que se encuentre un poco antes del evento que dañó los datos.

  1. En el shell de la terminal de la instancia instance-pg-pitr, obtén la marca de tiempo máxima de las filas dañadas:

    SELECT MAX(create_timestamp)::timestamptz
      FROM pitr_db_schema.customer
    WHERE name = 'New Name for customer id=1';
    

    El resultado es similar a este:

                 max              .
    -------------------------------
    2019-08-05 18:14:58.226511+00
    (1 row)
    

    En una base de datos de producción, la consulta para determinar el tiempo deseado de recuperación es más compleja, en especial en los casos en que la tabla afectada es grande y la columna indicativa no se encuentra indexada.

  2. Copia el resultado. Debes usar el valor que muestra esta consulta en el siguiente paso.

Recupera la base de datos

En el caso de este instructivo, una secuencia de comandos de recuperación automatiza la PITR. Te recomendamos que tengas un proceso automatizado para recuperar la base de datos y que lo pruebes de forma periódica.

  1. En Cloud Shell, cambia el directorio de trabajo actual a la ubicación de la secuencia de comandos de recuperación:

    cd ~/gcs-postgresql-recovery-tutorial/bin
    
  2. Configura las variables de entorno que se requieren para la secuencia de comandos. Reemplaza YYYY-MM-DD HH:MM:SS.999999+00 por el resultado de la consulta que copiaste antes.

    export PROJECT_ID=$(gcloud config get-value project)
    export PG_INSTANCE_NAME=instance-pg-pitr
    export POSTGRES_PASSWORD=PasswordIsThis
    export PG_INSTANCE_NAME=instance-pg-pitr
    export RECOVER_BUCKET=archive-bucket
    export PIT_RECOVERY_TARGET="YYYY-MM-DD HH:MM:SS.999999+00"
    export ZONE=zone-of-your-instance
    
  3. Ejecuta la secuencia de comandos de recuperación:

    ./recover_to_point_in_time.sh
    

Comprende la secuencia de comandos de recuperación

En esta sección, se proporcionan algunos detalles sobre los parámetros de entrada y los pasos que realiza la secuencia de comandos.

La secuencia de comandos requiere que se configuren las siguientes variables de entorno:

  • PIT_RECOVERY_TARGET: Es el tiempo deseado de recuperación.
  • PROJECT_ID: Es el proyecto en el que se encuentra la instancia PG_INSTANCE_NAME.
  • ZONE: Es la zona en la que se encuentra la instancia PG_INSTANCE_NAME.
  • PG_INSTANCE_NAME: Es la instancia en la que se ejecuta la instancia de PostgreSQL de producción.
  • RECOVER_BUCKET: Es el bucket de Cloud Storage en el que se archivan los archivos de segmentos WAL.
  • POSTGRES_PASSWORD: Es la contraseña que se usa para el usuario de la base de datos de PostgreSQL.

La secuencia de comandos realiza los siguientes pasos:

  1. Determina las instantáneas más recientes del disco en función de la fecha y hora deseadas para la recuperación.
  2. Crea un archivo cloud-init.yaml que se proporciona a una VM de almacenamiento optimizada para contenedores que ejecuta la base de datos de PITR. El archivo cloud-init.yaml crea archivos de configuración y ejecuta varios comandos del sistema para establecer el siguiente entorno:

    • Un contenedor gcsfuse que activa el bucket de almacenamiento del archivo de segmentos WAL como un volumen que, luego, se expone al host con una activación de vinculación de Docker
    • Un contenedor postgres-db en el que el motor de la base de datos ejecuta los siguientes elementos:

      • El sistema de archivos host en el que los discos persistentes se conectan como volúmenes
      • El sistema de archivos host en el que el bucket de Cloud Storage se conecta como un volumen
    • Un archivo de recuperación recovery.conf en el directorio de datos de PostgreSQL que contiene la siguiente información:

      • La fecha deseada
      • El comando restore: es un comando de copia con parámetros que la base de datos usa para copiar archivos de segmentos WAL desde el sistema de archivos de archivado, según sea necesario. %f es el archivo de segmentos y %p es la ruta de acceso que usa la base de datos para procesar archivos durante la recuperación
    • La configuración de archive_ se comenta desde el archivo de configuración postgresql.conf para evitar que se dañe el directorio de archivos WAL

  3. Inicia la instancia de PITR que contiene la siguiente información:

    • Un nombre creado mediante la combinación de la variable de entorno $PG_INSTANCE_NAME y los valores alfanuméricos de la variable de entorno $PIT_RECOVERY_TARGET
    • Los discos persistentes creados a partir de las instantáneas del disco identificadas de forma previa

El siguiente es un archivo recovery.conf de ejemplo:

restore_command = '(test -d /var/lib/postgresql/wal/pg_wal_recover && cp /var/lib/postgresql/wal/pg_wal_recover/%f %p ) '
recovery_target_time='YYYY-MM-DD HH:MM:SS UTC'
recovery_target_inclusive=true

Valida la recuperación

  1. En la consola de Google Cloud, ve a la página Instancias de VM.

    Ir a la página Instancias de VM

  2. En la instancia instance-pg-pitr-YYYYMMDDHHMMSS, haz clic en SSH.

  3. En las terminales SSH, ejecuta el frontend basado en la terminal de PostgreSQL en el contenedor de Docker:

    docker exec -it postgres-db psql --dbname=pitr_demo
    

    Si recibes el siguiente error, espera un momento para que se inicie el contenedor de PostgreSQL y vuelve a ejecutar el comando:

    Error: No such container: postgres-db
    
  4. Verifica los datos en la tabla de clientes:

    SELECT * FROM pitr_db_schema.customer
    WHERE id > (SELECT MAX(id)-10 FROM pitr_db_schema.customer);
    

    El resultado es similar a este:

       id  |           name            |      create_timestamp
    ------+---------------------------+----------------------------
      711 | customer_name_from_golang | 2019-12-06 18:03:51.229444
      712 | customer_name_from_golang | 2019-12-06 18:03:52.531755
      713 | customer_name_from_golang | 2019-12-06 18:03:53.555441
      714 | customer_name_from_golang | 2019-12-06 18:03:54.581872
      715 | customer_name_from_golang | 2019-12-06 18:03:55.607459
      716 | customer_name_from_golang | 2019-12-06 18:03:56.633362
      717 | customer_name_from_golang | 2019-12-06 18:03:57.658523
      718 | customer_name_from_golang | 2019-12-06 18:03:58.685469
      719 | customer_name_from_golang | 2019-12-06 18:03:59.706939
    

    En el nombre, se muestra el valor que creó el generador de transacciones. La última fila tiene una marca de tiempo anterior al tiempo deseado de recuperación (el que proporcionaste en la secuencia de comandos de recuperación en una variable de entorno). En función de la cantidad de registros que necesites recuperar, es posible que tengas que esperar unos minutos para que se actualicen todas las filas.

Realiza una limpieza

La manera más fácil de eliminar la facturación es borrar el proyecto de Google Cloud que creaste para el instructivo. Como alternativa, puedes borrar los recursos individuales.

Borra el proyecto

  1. En la consola de Google Cloud, ve a la página Administrar recursos.

    Ir a Administrar recursos

  2. En la lista de proyectos, elige el proyecto que quieres borrar y haz clic en Borrar.
  3. En el diálogo, escribe el ID del proyecto y, luego, haz clic en Cerrar para borrar el proyecto.

¿Qué sigue?