在 Compute Engine 上执行 PostgreSQL 数据库的 PITR


本教程介绍如何设置归档过程,然后对 Compute Engine 上运行的 PostgreSQL 数据库执行时间点恢复 (PITR)

在本教程中,您将创建一个演示数据库并运行应用工作负载。然后,您可以配置归档和备份过程。接下来,您将学习如何验证备份、归档和恢复过程。最后,您会学习如何将数据库恢复到特定时间点。

本教程适用于有兴趣为 PostgreSQL 数据库配置备份和恢复策略的数据库管理员、系统运维人员、DevOps 专业人员和云架构师。

本教程假定您熟悉 Docker 容器,并且熟悉 Linux 命令、PostgreSQL 数据库引擎和 Compute Engine。

目标

  • 设置备份和归档过程。
  • 执行 PITR。
  • 监控备份。
  • 验证恢复过程。

费用

在本文档中,您将使用 Google Cloud 的以下收费组件:

您可使用价格计算器根据您的预计使用情况来估算费用。 Google Cloud 新用户可能有资格申请免费试用

完成本文档中描述的任务后,您可以通过删除所创建的资源来避免继续计费。如需了解详情,请参阅清理

准备工作

  1. 登录您的 Google Cloud 账号。如果您是 Google Cloud 新手,请创建一个账号来评估我们的产品在实际场景中的表现。新客户还可获享 $300 赠金,用于运行、测试和部署工作负载。
  2. 在 Google Cloud Console 中的项目选择器页面上,选择或创建一个 Google Cloud 项目

    转到“项目选择器”

  3. 确保您的 Google Cloud 项目已启用结算功能

  4. 启用 Compute Engine and Cloud Storage API。

    启用 API

  5. 安装 Google Cloud CLI。
  6. 如需初始化 gcloud CLI,请运行以下命令:

    gcloud init
  7. 在 Google Cloud Console 中的项目选择器页面上,选择或创建一个 Google Cloud 项目

    转到“项目选择器”

  8. 确保您的 Google Cloud 项目已启用结算功能

  9. 启用 Compute Engine and Cloud Storage API。

    启用 API

  10. 安装 Google Cloud CLI。
  11. 如需初始化 gcloud CLI,请运行以下命令:

    gcloud init
  12. 在 Google Cloud 控制台中,激活 Cloud Shell。

    激活 Cloud Shell

    Cloud Shell 会话随即会在 Google Cloud 控制台的底部启动,并显示命令行提示符。Cloud Shell 是一个已安装 Google Cloud CLI 且已为当前项目设置值的 Shell 环境。该会话可能需要几秒钟时间来完成初始化。

概念

在开始学习本教程之前,请查看以下 PostgreSQL 概念:

  • 持续归档。如果数据库持续将顺序事务保存到文件。
  • 预写式日志 (WAL)。对数据文件的更改会先记录在 WAL 中,然后再对数据文件进行更改。
  • WAL 记录。应用到数据库的每个事务都会格式化并存储为 WAL 记录。
  • 分段文件。分段文件的文件名单调递增,并且包含尽可能多的 WAL 记录。文件大小是可配置的,默认为 16 MiB。如果您希望批量事务的数量(或计数)能够减少生成的分段文件的总数并减少文件管理负担,则可以选择较大的大小。

如需了解详情,请参阅可靠性和预写式日志

下图显示了如何分两个阶段持久保留 WAL。

两个阶段的永久性 WAL。

在上图中,持久保留 WAL 的第一阶段包含数据库引擎在写入表的同时将写入事务记录在 WAL 缓冲区中。提交事务后,系统会在第二阶段将 WAL 缓冲区内容写入(刷新)到磁盘,并将这些内容附加到 WAL 分段文件。

选择 PITR

PITR 适合以下场景:

  • 最大限度地减少恢复点目标 (RPO)。RPO 是数据丢失在严重影响业务流程之前所允许的最长时间。在两次备份快照之间将所有事务都保存到 WAL 中会大幅减少丢失的数据量,因为您可以将上次完整备份以来的事务应用于数据库。
  • 最大限度地减少恢复时间目标 (RTO)。RTO 是发生破坏性事件时恢复数据库所需的时间量。设置二进制备份和日志归档后,恢复数据库所需的时间可能最短。
  • 数据损坏 Bug 或管理不当的补救措施。如果代码发布会导致灾难性数据损坏,或在例行维护期间出现不可恢复的错误,您可以恢复到这个时刻之前。

在某些应用架构(例如微服务架构)中,可能存在需要独立恢复的并行数据库。例如,零售应用可能将客户数据存储在一个数据库中,而将零售订单详情和库存信息存储在其他数据库中。根据数据的整体状态,可能需要恢复一个数据库,也可能需要并行恢复两个或所有数据库。

PITR 不适合以下场景:

  • RPO 很大。如果您的灾难恢复政策可以容忍在最近快照之后丢失接收到的任何事务这一情况,您可以避免额外的步骤,专注于缩短数据恢复时间。
  • 需要完整的数据库恢复。如果您的目标是恢复到最近的事务,则恢复目标是上次持久保留事务的时间戳。此场景是一个具体的 PITR 实例,但在语义上,此目标称为完整恢复

性能考虑因素

归档过程会对数据库服务器产生额外的 I/O 负载。额外负载取决于工作负载的特性,因为它与写入、更新和删除事务量成比例。

如果要减少 WAL 归档活动可能对主数据库产生的 I/O 影响,您可以使用只读副本执行定期 WAL 归档。

此配置会将主数据库与面向批处理的 I/O 活动(与 WAL 文件的传输相关)隔离开来。目的地为只读副本的事务会以恒定数据流的形式从主数据库中传输,因此对稳定状态吞吐量的影响要小得多。

此外,如果您的生产数据库拓扑已包含只读副本,则此配置不会增加任何额外的负担:管理、价格或其他。

参考架构

下图说明了您在本教程中实现的架构。

使用 Compute Engine 和 Cloud Storage 的 PTR 云基础架构。

在本教程中,您将创建云基础架构以观察使用以下组件的 PITR:

  • 在 Compute Engine 上运行的 PostgreSQL 数据库服务器。
  • 用于存储快照和事务日志的 Cloud Storage。

下图显示了在 PostgreSQL 数据库虚拟机 (VM) 上启动的两个 Docker 容器。关注点分离是指数据库服务器在其中一个容器中运行,而 WAL 归档程序在另一个容器中运行。

用于数据库服务器和 WAL 归档程序的 Docker 容器。

此图显示了每个容器中的 Docker 卷如何映射到主机虚拟机上的 Persistent Disk 装载点。

设置环境变量

本教程中使用的脚本和命令依赖于 shell 环境变量。

  1. 在 Cloud Shell 中,为您的项目、实例名称和 PostgreSQL 数据库演示设置环境变量。

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

    替换以下内容:

    • your-gcp-project:您为本教程创建的项目的名称。
    • PasswordIsThis:PostgreSQL 数据库的安全密码。
  2. 为 Google Cloud 区域设置环境变量。将 choose-an-appropriate-zone 替换为 Google Cloud 区域

    export ZONE=choose-an-appropriate-zone
    export REGION=${ZONE%-[a-z]}
    
  3. 为区域所在地区的默认 Virtual Private Cloud (VPC) 子网设置环境变量:

    export SUBNETWORK_URI=$(gcloud compute networks subnets \
        describe default --format=json --region=$REGION | \
        jq --raw-output '.ipCidrRange')
    
  4. 为 Cloud Storage 存储分区设置环境变量。将 archive-bucket 替换为保存 WAL 所在的 Cloud Storage 存储分区的唯一名称。

    export ARCHIVE_BUCKET=archive-bucket
    

创建 Cloud Storage 存储分区

  • 创建一个 Cloud Storage 存储分区以归档 PostgreSQL 数据库中的 WAL 文件:

    gsutil mb gs://${ARCHIVE_BUCKET}
    

允许访问专用 IP 地址实例

对于本教程中使用的实例,与许多生产使用场景一样,不需要虚拟机实例获取公共 IP 地址。但是,这些实例需要访问公共互联网才能拉取示例容器映像,并且您需要访问权限才能使用 Secure Shell 进行连接。您可以配置网络地址转换 (NAT) 网关并为 TCP 转发配置 Identity-Aware Proxy (IAP)

创建 NAT 网关

因为您创建的虚拟机实例没有公共 IP 地址,所以您可以创建 NAT 网关,以便这些实例可以从 Docker Hub 中拉取容器映像。

  1. 在 Cloud Shell 中,创建一个 Cloud Router 路由器:

    export CLOUD_ROUTER_NAME=${PROJECT_ID}-nat-router
    gloud compute routers create $CLOUD_ROUTER_NAME \
        --network=default --region=$REGION
    
  2. 创建 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
    

为 TCP 转发配置 IAP

IAP 可控制对 Google Cloud 上运行的云应用和虚拟机的访问。IAP 会验证请求的用户身份和上下文,以确定用户是否可以访问虚拟机。

  1. 在 Cloud Shell 中,允许来自 TCP 转发网络块的流量进入项目中的实例:

    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. 如需使用 TCP 转发隧道进行连接,请添加 Identity and Access Management (IAM) 政策绑定。将 your-email-address 替换为您用于登录 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
    

创建 PostgreSQL 数据库基础架构

  1. 在 Cloud Shell 中,克隆包含配置脚本的源代码库,并将 shell 上下文更改为本地代码库:

    git clone https://github.com/GoogleCloudPlatform/gcs-postgresql-recovery-tutorial
    cd gcs-postgresql-recovery-tutorial
    
  2. 如需创建和配置数据库虚拟机实例,请运行以下脚本:

    cd bin
    ./create_postgres_instance.sh
    

    对于本教程,此脚本将在您选择的区域中启动一个虚拟机实例,其中包含容器优化操作系统和两个挂接的新永久性磁盘。在这种情况下,您可以忽略 API 返回的有关 I/O 性能不佳的警告消息,因为脚本会创建小型永久性磁盘。

查看 cloud-init 配置

Cloud-init 是一个用于初始化云实例的多分布软件包。

查看以下 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

在本教程中,cloud-init 用于执行以下操作:

  1. 创建两个 Persistent Disk 块存储设备。
  2. 在两个设备上创建文件系统:一个用于数据,一个用于归档日志。
  3. 在虚拟机实例上与 Docker 容器共享的逻辑装载点装载设备。
  4. 创建然后启动 systemd 服务 (postgres.service),该服务将启动 PostgreSQL Docker 容器并执行以下操作:
    • 永久性磁盘会装载为卷。
    • 将 PostgreSQL 端口 (5432) 发布到虚拟机主机。
  5. 创建一个 /var/tmp/docker-entrypoint-initdb.d/init-pitr-demo-db.sql 文件以在演示数据库和架构中创建一组简单的表。
  6. 创建并启动第二个 systemd 服务 (wal_archive.service),该服务将运行 Google Cloud CLI Docker 容器并将 WAL 磁盘装载为卷。此服务会将已归档的 WAL 文件备份到 Cloud Storage。
  7. 创建、启用然后启动可定期运行 wal_archive.servicesystemd 计时器 (wal_archive.timer)。
  8. 确保 PostgreSQL 端口 (5432) 已为 VPC 子网打开,以便事务生成器可以访问数据库端口。

修改数据库实例配置

数据库服务器正在运行,但您需要配置网络访问权限并启动 WAL 归档过程。

连接到数据库虚拟机实例

  1. 在 Google Cloud Console 中,前往虚拟机实例页面。

    前往“虚拟机实例”

  2. 如需打开终端 shell,请点击您创建的 instance-pg-pitr 实例旁边的 SSH

  3. 在终端 shell 中,检查 Docker 容器是否已启动:docker ps

    输出内容类似如下:

    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
    

    如果容器尚未运行,请稍等片刻,然后使用同一命令再次检查。

允许连到数据库的入站网络连接

  1. instance-pg-pitr 实例的终端 shell 中,打开基于 PostgreSQL 主机的身份验证配置文件以进行修改:

    sudoedit /mnt/disks/data/pgdata/pg_hba.conf
    
  2. 如需移除对数据库的默认所有 IP 地址访问权限,请通过在文件末尾添加 #,从该文件的末尾注释掉该行。文件中的行类似于以下内容:

    #host all all all md5
    
  3. 如需允许从 10.0.0.0/8 CIDR 块中的主机进行受密码保护的连接,请在文件末尾添加以下行:

    host    all             all             10.0.0.0/8               md5
    

    此条目支持从稍后会创建事务生成器的 VPC 子网进行连接。

  4. 保存然后关闭文件。

配置 WAL 归档

  1. instance-pg-pitr 实例的终端 shell 中,修改 postgresql.conf 文件:

    sudoedit /mnt/disks/data/pgdata/postgresql.conf
    
  2. 将现有被注释掉的 archive_modearchive_commandarchive_timeout 行替换为以下内容:

    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
    

    如果您替换修改后的文件中的行,则结果类似于以下代码段:

    
    .... 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. 保存然后关闭文件。

应用并验证配置更改

  1. instance-pg-pitr 实例的终端 shell 中,重启容器以应用更改:

    sudo systemctl restart postgres
    
  2. 检查是否有 WAL 分段文件:

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

    输出内容类似如下:

    total 16388
    -rw------- 1 postgres 70 16777216 Sep  5 23:07 000000010000000000000001
    drwx------ 2 postgres 70     4096 Sep  5 23:05 archive_status
    
  3. 检查是否有数据库的网络连接:

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

    输出内容类似如下:

    ?column?
    ----------
           1
    (1 row)
    
  4. 关闭连到实例的 SSH 连接。

启动事务生成器以填充数据库

以下步骤将启动一个 Go 程序,为本教程生成事务。该程序会在虚拟机实例上的容器内运行。

该容器的映像已完成构建并托管在具有公共 Container Registry 的项目中。

  1. 在 Cloud Shell 中,切换到事务生成器目录:

    cd ~/gcs-postgresql-recovery-tutorial/bin
    
  2. 设置环境变量:

    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. 如需运行事务生成器,请启动实例:

    ./run_trans_gen_instance.sh
    

    请忽略有关 I/O 性能不佳的警告消息。

  4. 稍等片刻,然后检查事务是否到达 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;'"
    

    事务生成器将记录添加到数据库时,输出会包含一个大于 0 的计数:

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

配置二进制快照备份时间表

您可以根据时间表备份永久性磁盘,并将永久性磁盘保留一段时间(在资源政策中定义这段时间)。

创建快照时间表

  1. 在 Cloud Shell 中,设置环境变量:

    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
    

    zone-of-your-instance 替换为您之前启动数据库虚拟机的 Google Cloud 区域。

  2. 创建快照时间表:

    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
    

将快照时间表附加到磁盘

运行脚本以创建实例时,数据卷和 WAL 卷就已创建为两个独立的永久性磁盘。如需根据所定义的时间表创建永久性磁盘快照,请将资源政策与每个永久性磁盘关联。在这种情况下,您希望磁盘快照并发生成,因此对挂接到 Compute Engine 虚拟机的两个永久性磁盘使用相同的政策。

  1. 在 Cloud Shell 中,设置环境变量:

    export SNAPSHOT_SCHEDULE_NAME=pgdata-disk-schedule
    export PG_INSTANCE_NAME=instance-pg-pitr
    export ZONE=zone-of-your-instance
    
  2. 将时间表政策附加到永久性数据磁盘:

    gcloud beta compute disks add-resource-policies ${PG_INSTANCE_NAME}-data \
        --resource-policies $SNAPSHOT_SCHEDULE_NAME \
        --zone $ZONE
    
  3. 将时间表政策附加到永久性 WAL 磁盘:

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

手动运行快照

(可选)计划快照会在时间表时段内发生,因此在创建时间表后不太可能立即截取快照。如果您不想等待计划快照,则可以手动运行初始快照。

  1. 在 Cloud Shell 中,设置环境变量:

    export ZONE=zone-of-your-instance
    export PG_INSTANCE_NAME=instance-pg-pitr
    export REGION=${ZONE%-[a-z]}
    
  2. 创建两个 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. 查看您创建的快照:

    gcloud compute snapshots list
    

    输出内容类似如下:

    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
    

执行 PITR

通常情况下,执行 PITR 是为了恢复因操作或程序化失误而丢失的数据。

在本教程的这一部分中,您将执行数据库更新以模拟灾难性数据损失。然后,您可以先模拟恐慌响应,再开始恢复到发出错误命令之前的那一刻。

确保可以执行 PITR

在执行 PITR 之前,您必须留出足够的时间来运行以下各项:

  • 二进制备份(磁盘快照)
  • WAL 归档

在本教程中,archive_timeout 已设置为非常低的值(120 秒),以强制频繁轮替 WAL 文件。您还必须等到至少有一个计划的磁盘快照执行完毕,否则您需要手动截取磁盘的快照

  1. 检查是否至少截取了一个快照:

    1. 在 Google Cloud Console 中,转到快照页面。

      转到“快照”页面

    2. 验证是否至少有两个快照 - 一个用于数据卷,一个用于 WAL 卷,例如 instance-pg-pitr--us-central1-a-20190805023535-i3hpw7kn

  2. 检查分段文件是否已归档到 Cloud Storage:

    1. 在 Google Cloud 控制台中,转到 Cloud Storage 浏览器页面。

      转到“Cloud Storage 浏览器”页面

    2. 点击 archive-bucket

      包含对象的 Cloud Storage 存储分区。

破坏数据

如需模拟灾难性数据损失,请打开 PostgreSQL 数据库的命令行 shell,并破坏由事务生成器填充的表数据。

  1. 在 Google Cloud Console 中,前往虚拟机实例页面。

    前往“虚拟机实例”页面

  2. 对于 instance-pg-pitr 实例,点击 SSH

  3. 在 SSH 终端中,在 Docker 容器中运行基于 PostgreSQL 终端的前端:

    docker exec -it postgres-db psql --dbname=pitr_demo
    
  4. 如需修改客户表中的行,请在 PostgreSQL shell 中提交有意拼错的 SQL DML 语句:

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

    输出内容类似如下:

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

    生成此错误是因为在 WHERE 子句之前插入了一个额外的分号。数据库中的所有行均已更新。您现在可以执行 PITR 以恢复该错误语句所修改的行。

确定恢复的目标时间

PITR 中的第一步是确定恢复的目标时间。如需确定此时间,您可以检查您的数据以识别稍微早于数据破坏性事件的某一时间点。

  1. instance-pg-pitr 实例的终端 shell 中,获取已损坏行的最大时间戳:

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

    输出内容类似如下:

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

    在生产数据库中,用于确定恢复目标的查询更加复杂,尤其是在受影响的表较大且指示列未编入索引的情况下。

  2. 复制结果;您需要在下一步中使用此查询返回的值。

恢复数据库

在本教程中,恢复脚本会自动执行 PITR。我们建议您采用自动过程来恢复数据库,并定期测试此过程。

  1. 在 Cloud Shell 中,将当前工作目录更改为恢复脚本的位置:

    cd ~/gcs-postgresql-recovery-tutorial/bin
    
  2. 设置脚本所需的环境变量。将 YYYY-MM-DD HH:MM:SS.999999+00 替换为您之前复制的查询输出。

    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. 运行恢复脚本:

    ./recover_to_point_in_time.sh
    

了解恢复脚本

本部分详细介绍了输入参数以及脚本执行的步骤。

该脚本要求设置以下环境变量:

  • PIT_RECOVERY_TARGET:恢复目标时间。
  • PROJECT_IDPG_INSTANCE_NAME 实例所在的项目。
  • ZONEPG_INSTANCE_NAME 实例所在的区域。
  • PG_INSTANCE_NAME:生产 PostgreSQL 实例正在其中运行的实例。
  • RECOVER_BUCKET:WAL 分段文件在其中进行归档的 Cloud Storage 存储分区。
  • POSTGRES_PASSWORD:PostgreSQL 数据库用户使用的密码。

该脚本会执行以下步骤:

  1. 根据恢复目标日期和时间来确定最近的磁盘快照。
  2. 创建一个 cloud-init.yaml 文件,该文件将提供给运行 PTR 数据库的容器优化存储虚拟机。cloud-init.yaml 文件会创建配置文件并运行多个系统命令以建立以下环境:

    • 一个 gcsfuse 容器,此容器会将 WAL 分段文件归档存储分区装载为卷,然后通过 Docker 绑定装载向主机公开该卷。
    • 一个 postgres-db 容器,数据库引擎在其中运行以下命令:

      • 永久性磁盘在其中挂接为卷的主机文件系统。
      • Cloud Storage 存储分区在其中挂接为卷的主机文件系统。
    • PostgreSQL 数据目录中包含以下信息的 recovery.conf 恢复文件:

      • 目标日期。
      • restore 命令:一个参数化复制命令,数据库使用此命令从归档文件系统中按需复制 WAL 分段文件。%f 是分段文件,而 %p 是数据库在恢复期间用于处理文件的路径。
    • archive_ 设置已从 postgresql.conf 设置文件中注释掉,以避免破坏 WAL 归档目录。

  3. 使用以下信息启动 PITR 实例:

    • 通过将 $PIT_RECOVERY_TARGET 环境变量中的字母数字值与 $PG_INSTANCE_NAME 环境变量相组合来创建的名称。
    • 从之前识别的磁盘快照创建的永久性磁盘。

下面是一个 recovery.conf 文件示例:

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

验证恢复过程

  1. 在 Google Cloud Console 中,前往虚拟机实例页面。

    前往“虚拟机实例”页面

  2. 对于 instance-pg-pitr-YYYYMMDDHHMMSS 实例,点击 SSH

  3. 在 SSH 终端中,在 Docker 容器中运行基于 PostgreSQL 终端的前端:

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

    如果您收到以下错误,请等待 PostgreSQL 容器启动,然后重新运行该命令:

    Error: No such container: postgres-db
    
  4. 检查客户表中的数据:

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

    输出内容类似如下:

       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
    

    该名称显示了由事务生成器创建的值。最后一行的时间戳早于恢复目标(您在环境变量中向恢复脚本提供的时间戳)。根据您需要恢复的记录数,您可能需要等待几分钟才能更新所有行。

清理

避免产生费用的最简单方法是删除您为本教程创建的 Google Cloud 项目。或者,您也可以删除各个资源。

删除项目

  1. 在 Google Cloud 控制台中,进入管理资源页面。

    转到“管理资源”

  2. 在项目列表中,选择要删除的项目,然后点击删除
  3. 在对话框中输入项目 ID,然后点击关闭以删除项目。

后续步骤