{"id":6853,"date":"2026-05-12T18:05:01","date_gmt":"2026-05-12T16:05:01","guid":{"rendered":"https:\/\/rootfan.com\/?p=6853"},"modified":"2026-05-12T18:28:18","modified_gmt":"2026-05-12T16:28:18","slug":"laboratorio-de-recuperacion-de-copias-de-seguridad-de-postgresql","status":"publish","type":"post","link":"https:\/\/rootfan.com\/es\/postgresql-backup-recovery-lab\/","title":{"rendered":"Laboratorio de Respaldo y Recuperaci\u00f3n de PostgreSQL 18 en Ubuntu 24.04 \u2014 pg_dump, pg_basebackup y pgBackRest"},"content":{"rendered":"<p>Si trabajas con PostgreSQL en producci\u00f3n, las copias de seguridad no son opcionales.<\/p>\n\n\n\n<p>En este laboratorio, constru\u00ed un entorno completo de respaldo y recuperaci\u00f3n de PostgreSQL 18 en Ubuntu 24.04 y prob\u00e9 los tres enfoques principales de respaldo:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>copias de seguridad l\u00f3gicas con <code>pg_dump<\/code><\/li>\n\n\n\n<li>copias de seguridad f\u00edsicas con <code>pg_basebackup<\/code><\/li>\n\n\n\n<li>copias de seguridad de nivel de producci\u00f3n con pgBackRest, incluida la archivaci\u00f3n de WAL y la recuperaci\u00f3n a un punto en el tiempo (PITR)<\/li>\n<\/ul>\n\n\n\n<p>Todo fue probado en una VM real con simulacros de recuperaci\u00f3n reales, no simulaciones.<\/p>\n\n\n\n<!--more-->\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<div class=\"wp-block-rank-math-toc-block\" id=\"rank-math-toc\"><h2>\u00cdndice<\/h2><nav><ul><li><a href=\"#the-environment\">El medioambiente<\/a><\/li><li><a href=\"#installing-postgresql-18\">Instalando PostgreSQL 18<\/a><\/li><li><a href=\"#creating-the-lab-database\">Creando la base de datos del laboratorio<\/a><\/li><li><a href=\"#creating-the-tables\">Creando las tablas<\/a><\/li><li><a href=\"#creating-backup-directories\">Creando directorios de copia de seguridad<\/a><\/li><li><a href=\"#part-1-pg-dump\">Parte 1 \u2014 pg_dump<\/a><ul><li><a href=\"#full-database-backup\">Copia de seguridad completa de la base de datos<\/a><\/li><li><a href=\"#schema-only-backup\">Copia de seguridad solo de esquema<\/a><\/li><li><a href=\"#single-table-backup\">Copia de seguridad de una sola tabla<\/a><\/li><li><a href=\"#full-restore-test\">Prueba de restauraci\u00f3n completa<\/a><\/li><li><a href=\"#disaster-recovery-drill\">Simulacro de recuperaci\u00f3n ante desastres: Restaurar una tabla<\/a><\/li><\/ul><\/li><li><a href=\"#part-2-pg-basebackup\">Parte 2 \u2014 pg_basebackup<\/a><ul><li><a href=\"#checking-wal-configuration\">Comprobando la configuraci\u00f3n de WAL<\/a><\/li><li><a href=\"#creating-the-physical-backup\">Creando la Copia de Seguridad F\u00edsica<\/a><\/li><li><a href=\"#verifying-the-backup\">Verificando la copia de seguridad<\/a><\/li><li><a href=\"#full-physical-restore-drill\">Simulacro de Restauraci\u00f3n F\u00edsica Completa<\/a><\/li><\/ul><\/li><li><a href=\"#part-3-pgbackrest\">Parte 3 \u2014 pgBackRest<\/a><ul><li><a href=\"#installing-pgbackrest\">Instalando pgBackRest<\/a><\/li><li><a href=\"#creating-the-repository-directory\">Creando el Directorio del Repositorio<\/a><\/li><li><a href=\"#configuring-pgbackrest\">Configuraci\u00f3n de pgBackRest<\/a><\/li><li><a href=\"#configuring-wal-archiving\">Configuraci\u00f3n del archivado de WAL<\/a><\/li><li><a href=\"#creating-the-stanza-and-running-check\">Creando la estrofa y ejecutando la comprobaci\u00f3n<\/a><\/li><li><a href=\"#full-backup\">Copia de seguridad completa<\/a><\/li><li><a href=\"#differential-backup\">Copia de seguridad diferencial<\/a><\/li><li><a href=\"#incremental-backup\">Copia de seguridad incremental<\/a><\/li><li><a href=\"#point-in-time-recovery\">Recuperaci\u00f3n en un momento dado (PITR)<\/a><\/li><\/ul><\/li><li><a href=\"#summary\">Resumen<\/a><\/li><li><a href=\"#final-thoughts\">Reflexiones finales<\/a><\/li><\/ul><\/nav><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"the-environment\">El medioambiente<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Componente<\/th><th>Valor<\/th><\/tr><\/thead><tbody><tr><td>SO<\/td><td>Ubuntu 24.04<\/td><\/tr><tr><td>PostgreSQL<\/td><td>18 (repositorio PGDG)<\/td><\/tr><tr><td>Herramientas de copia de seguridad<\/td><td>pg_dump, pg_basebackup, pgBackRest<\/td><\/tr><tr><td>Tipo de laboratorio<\/td><td>VM individual<\/td><\/tr><tr><td>Repositorio<\/td><td>Sistema de archivos local<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"installing-postgresql-18\">Instalando PostgreSQL 18<\/h2>\n\n\n\n<p>Us\u00e9 el repositorio oficial de PGDG en lugar del paquete predeterminado de Ubuntu, que incluye una versi\u00f3n anterior.<\/p>\n\n\n\n<p>Instalar el ayudante de repositorio:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo apt install -y postgresql-common\n<\/pre><\/div>\n\n\n<p>Ejecuta el script de configuraci\u00f3n de PGDG:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo \/usr\/share\/postgresql-common\/pgdg\/apt.postgresql.org.sh\n<\/pre><\/div>\n\n\n<p>Instalar PostgreSQL 18:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo apt install -y postgresql-18\n<\/pre><\/div>\n\n\n<p>Verificar que el cl\u00faster est\u00e9 en ejecuci\u00f3n:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\npg_lsclusters\n<\/pre><\/div>\n\n\n<p>Salida esperada:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Ver Puerto del Cl\u00faster Estado Propietario Directorio de Datos\n18  principal   5432 en l\u00ednea postgres \/var\/lib\/postgresql\/18\/main<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"creating-the-lab-database\">Creando la base de datos del laboratorio<\/h2>\n\n\n\n<p>Crear el usuario de la aplicaci\u00f3n:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo -u postgres psql -c &quot;CREATE USER banking WITH PASSWORD &#039;banking&#039;;&quot;\n<\/pre><\/div>\n\n\n<p>Crear la base de datos:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo -u postgres psql -c &quot;CREATE DATABASE bankingdb OWNER banking;&quot;\n<\/pre><\/div>\n\n\n<p>Conectar como usuario bancario y crear el esquema:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\npsql -h localhost -U banking -d bankingdb -c &quot;CREATE SCHEMA banking AUTHORIZATION banking;&quot;\n<\/pre><\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"creating-the-tables\">Creando las tablas<\/h2>\n\n\n\n<p>Cre\u00e9 cinco tablas que representan un peque\u00f1o esquema bancario: sucursales, empleados, clientes, cuentas y transacciones. La cadena de claves for\u00e1neas entre ellas ejercita las restricciones que importan en escenarios de restauraci\u00f3n reales.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: sql; title: ; notranslate\" title=\"\">\n-- Connected as banking@bankingdb\nCREATE TABLE banking.branches (\n  branch_id    INTEGER PRIMARY KEY,\n  branch_name  VARCHAR(100) NOT NULL,\n  city         VARCHAR(50),\n  country      VARCHAR(50),\n  opened_date  DATE\n);\n\nCREATE TABLE banking.employees (\n  employee_id  INTEGER PRIMARY KEY,\n  branch_id    INTEGER NOT NULL REFERENCES banking.branches(branch_id),\n  full_name    VARCHAR(100) NOT NULL,\n  role         VARCHAR(50),\n  hire_date    DATE\n);\n\nCREATE TABLE banking.customers (\n  customer_id  INTEGER PRIMARY KEY,\n  full_name    VARCHAR(100) NOT NULL,\n  email        VARCHAR(100),\n  country      VARCHAR(50),\n  created_at   TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE banking.accounts (\n  account_id    INTEGER PRIMARY KEY,\n  customer_id   INTEGER NOT NULL REFERENCES banking.customers(customer_id),\n  branch_id     INTEGER NOT NULL REFERENCES banking.branches(branch_id),\n  account_type  VARCHAR(20),\n  balance       NUMERIC(15,2) DEFAULT 0,\n  opened_at     TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n);\n\nCREATE TABLE banking.transactions (\n  txn_id       INTEGER PRIMARY KEY,\n  account_id   INTEGER NOT NULL REFERENCES banking.accounts(account_id),\n  employee_id  INTEGER REFERENCES banking.employees(employee_id),\n  txn_type     VARCHAR(20),\n  amount       NUMERIC(15,2) NOT NULL,\n  txn_date     TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n  description  VARCHAR(200)\n);\n<\/pre><\/div>\n\n\n<p>Insert\u00e9 18 filas en las cinco tablas \u2014 tres ramas, cuatro empleados, tres clientes, cuatro cuentas, cuatro transacciones. Este es el estado base que cada copia de seguridad en el laboratorio debe capturar y cada restauraci\u00f3n debe reproducir.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"creating-backup-directories\">Creando directorios de copia de seguridad<\/h2>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\n# pg_dump output \u2014 runs as the fernando OS user\nmkdir -p \/home\/fernando\/backups\/pg-dump\n\n# pg_basebackup output \u2014 runs as postgres\nsudo mkdir -p \/var\/lib\/postgresql\/backups\/pg-basebackup\nsudo chown -R postgres:postgres \/var\/lib\/postgresql\/backups\nsudo chmod -R 750 \/var\/lib\/postgresql\/backups\n<\/pre><\/div>\n\n\n<p>El directorio de repositorio de pgBackRest se crea por separado en la Parte 3.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"part-1-pg-dump\">Parte 1 \u2014 pg_dump<\/h2>\n\n\n\n<p><code>pg_dump<\/code> crea una exportaci\u00f3n l\u00f3gica de una base de datos \u2014 sentencias SQL o un formato binario comprimido que se puede reproducir en cualquier instancia compatible de PostgreSQL.<\/p>\n\n\n\n<p>Lo mejor para:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>migraciones entre versiones<\/li>\n\n\n\n<li>restauraciones de base de datos \u00fanica o tabla \u00fanica<\/li>\n\n\n\n<li>exportaciones solo de esquema para documentaci\u00f3n o control de versiones de DDL<\/li>\n<\/ul>\n\n\n\n<p>No apto para PITR o bases de datos grandes donde el tiempo de restauraci\u00f3n debe medirse en minutos en lugar de horas.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"full-database-backup\">Copia de seguridad completa de la base de datos<\/h3>\n\n\n\n<p>Us\u00e9 formato personalizado (<code>-F c<\/code>) \u2014 binario comprimido, restaurable \u00fanicamente con <code>pg_restore<\/code>, admite la restauraci\u00f3n paralela y opciones de selecci\u00f3n de tablas.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nPGPASSWORD=banking pg_dump \\\n  -h localhost \\\n  -U banking \\\n  -F c \\\n  -d bankingdb \\\n  -f \/home\/fernando\/backups\/pg-dump\/bankingdb.dump\n<\/pre><\/div>\n\n\n<p>Inspeccionar el contenido del volcado sin restaurar:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\npg_restore -l \/home\/fernando\/backups\/pg-dump\/bankingdb.dump | head -30\n<\/pre><\/div>\n\n\n<p>La tabla de contenido mostr\u00f3 esquemas, tablas, secuencias, restricciones y datos de tablas para las cinco tablas.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"schema-only-backup\">Copia de seguridad solo de esquema<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nPGPASSWORD=banking pg_dump \\\n  -h localhost \\\n  -U banking \\\n  -F c \\\n  --schema-only \\\n  -d bankingdb \\\n  -f \/home\/fernando\/backups\/pg-dump\/bankingdb-schema.dump\n<\/pre><\/div>\n\n\n<p>\u00datil para migraciones, control de versiones de DDL y para recrear entornos de destino vac\u00edos antes de una carga de datos.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"single-table-backup\">Copia de seguridad de una sola tabla<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nPGPASSWORD=banking pg_dump \\\n  -h localhost \\\n  -U banking \\\n  -F c \\\n  -t banking.transactions \\\n  -d bankingdb \\\n  -f \/home\/fernando\/backups\/pg-dump\/transactions.dump\n<\/pre><\/div>\n\n\n<p>El volcado solo contiene la tabla de transacciones y sus restricciones, no las tablas padre. Restaurarlo requiere que las tablas padre ya existan en el destino.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"full-restore-test\">Prueba de restauraci\u00f3n completa<\/h3>\n\n\n\n<p>Cre\u00e9 una base de datos nueva y vac\u00eda y restaur\u00e9 el volcado completo en ella:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo -u postgres psql -c &quot;CREATE DATABASE bankingdb_restore OWNER banking;&quot;\n\nPGPASSWORD=banking pg_restore \\\n  -h localhost \\\n  -U banking \\\n  -d bankingdb_restore \\\n  \/home\/fernando\/backups\/pg-dump\/bankingdb.dump\n<\/pre><\/div>\n\n\n<p>Verificar que los recuentos de filas coincidan con la fuente en las cinco tablas:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nPGPASSWORD=banking psql -h localhost -U banking -d bankingdb_restore -c &quot;\nSELECT &#039;branches&#039;     AS table_name, COUNT(*) FROM banking.branches\nUNION ALL SELECT &#039;employees&#039;,    COUNT(*) FROM banking.employees\nUNION ALL SELECT &#039;customers&#039;,    COUNT(*) FROM banking.customers\nUNION ALL SELECT &#039;accounts&#039;,     COUNT(*) FROM banking.accounts\nUNION ALL SELECT &#039;transactions&#039;, COUNT(*) FROM banking.transactions;&quot;\n<\/pre><\/div>\n\n\n<p>Entonces se elimin\u00f3 la base de datos de prueba:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo -u postgres psql -c &quot;DROP DATABASE bankingdb_restore;&quot;\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"disaster-recovery-drill\">Simulacro de recuperaci\u00f3n ante desastres: Restaurar una tabla<\/h3>\n\n\n\n<p>Simul\u00e9 una ca\u00edda accidental de tabla:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nPGPASSWORD=banking psql -h localhost -U banking -d bankingdb -c &quot;\nDROP TABLE banking.transactions CASCADE;&quot; \n<\/pre><\/div>\n\n\n<p>Restaurada solo esa tabla del volcado de transacciones dedicado:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nPGPASSWORD=banking pg_restore \\\n  -h localhost \\\n  -U banking \\\n  -d bankingdb \\\n  -t transactions \\\n  \/home\/fernando\/backups\/pg-dump\/transactions.dump\n<\/pre><\/div>\n\n\n<p>Se verific\u00f3 que las cuatro filas estuvieran de vuelta. Aqu\u00ed es donde <code>pg_dump<\/code> brilla genuinamente \u2014 recuperaci\u00f3n granular a nivel de objeto sin tocar el resto de la base de datos.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"part-2-pg-basebackup\">Parte 2 \u2014 pg_basebackup<\/h2>\n\n\n\n<p><code>pg_basebackup<\/code> realiza una copia binaria a nivel de sistema de archivos de todo el cl\u00faster de PostgreSQL. No entiende bases de datos ni tablas: copia archivos.<\/p>\n\n\n\n<p>Lo mejor para:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>arranque de r\u00e9plicas de streaming<\/li>\n\n\n\n<li>instant\u00e1neas de recuperaci\u00f3n ante desastres de cl\u00faster completo<\/li>\n\n\n\n<li>migraci\u00f3n de cl\u00faster a un nuevo servidor<\/li>\n<\/ul>\n\n\n\n<p>No apto para recuperaci\u00f3n a nivel de tabla o base de datos \u2014 la restauraci\u00f3n es todo o nada a nivel de cl\u00faster.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"checking-wal-configuration\">Comprobando la configuraci\u00f3n de WAL<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo -u postgres psql -c &quot;SHOW wal_level; SHOW max_wal_senders;&quot;\n<\/pre><\/div>\n\n\n<p>Salida esperada:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code> wal_level\n-----------\n r\u00e9plica\n\n max_wal_senders\n-----------------\n 10<\/code><\/pre>\n\n\n\n<p>Ambos est\u00e1n configurados correctamente por defecto en PostgreSQL 18.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"creating-the-physical-backup\">Creando la Copia de Seguridad F\u00edsica<\/h3>\n\n\n\n<p>Us\u00e9 el formato tar<code>-F t<\/code>) \u2014 produce comprimido <code>base.tar.gz<\/code> y <code>pg_wal.tar.gz<\/code> archivos en lugar de un \u00e1rbol de directorios descomprimido.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo -u postgres pg_basebackup \\\n  -D \/var\/lib\/postgresql\/backups\/pg-basebackup\/cluster \\\n  -F t \\\n  -z \\\n  -P \\\n  -c fast\n<\/pre><\/div>\n\n\n<p><code>-c r\u00e1pido<\/code> emite un punto de control al inicio de la copia de seguridad en lugar de esperar al pr\u00f3ximo programado \u2014 a\u00f1ade un breve pico de E\/S pero es la opci\u00f3n correcta para un laboratorio interactivo.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"verifying-the-backup\">Verificando la copia de seguridad<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nls -lh \/var\/lib\/postgresql\/backups\/pg-basebackup\/cluster\/\n<\/pre><\/div>\n\n\n<p>Archivos esperados:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Archivo<\/th><th>Prop\u00f3sito<\/th><\/tr><\/thead><tbody><tr><td><code>base.tar.gz<\/code><\/td><td>directorio de datos del cl\u00faster archivo<\/td><\/tr><tr><td><code>pg_wal.tar.gz<\/code><\/td><td>WAL capturado durante la copia de seguridad<\/td><\/tr><tr><td><code>manifiesto_de_copia_de_seguridad<\/code><\/td><td>lista de archivos con sumas de verificaci\u00f3n CRC32C (PostgreSQL 13+)<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Verificar la integridad antes de confiar en la copia de seguridad:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo -u postgres \/usr\/lib\/postgresql\/18\/bin\/pg_verifybackup --no-parse-wal \\\n  \/var\/lib\/postgresql\/backups\/pg-basebackup\/cluster\n<\/pre><\/div>\n\n\n<p>Una copia de seguridad que nunca has verificado es una copia de seguridad en la que no puedes confiar.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"full-physical-restore-drill\">Simulacro de Restauraci\u00f3n F\u00edsica Completa<\/h3>\n\n\n\n<p>Detener PostgreSQL:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo systemctl stop postgresql\n<\/pre><\/div>\n\n\n<p>Mueva el directorio de datos existente a un lado:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo mv \/var\/lib\/postgresql\/18\/main \/var\/lib\/postgresql\/18\/main.before-restore\nsudo mkdir -p \/var\/lib\/postgresql\/18\/main\nsudo chown postgres:postgres \/var\/lib\/postgresql\/18\/main\nsudo chmod 700 \/var\/lib\/postgresql\/18\/main\n<\/pre><\/div>\n\n\n<p>Extraer el archivo base:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo -u postgres tar -xzf \\\n  \/var\/lib\/postgresql\/backups\/pg-basebackup\/cluster\/base.tar.gz \\\n  -C \/var\/lib\/postgresql\/18\/main\n<\/pre><\/div>\n\n\n<p>Extraer el archivo WAL:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo -u postgres mkdir -p \/var\/lib\/postgresql\/18\/main\/pg_wal\nsudo -u postgres tar -xzf \\\n  \/var\/lib\/postgresql\/backups\/pg-basebackup\/cluster\/pg_wal.tar.gz \\\n  -C \/var\/lib\/postgresql\/18\/main\/pg_wal\n<\/pre><\/div>\n\n\n<p>Iniciar PostgreSQL:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo systemctl start postgresql\nsudo systemctl status postgresql@18-main\n<\/pre><\/div>\n\n\n<p>Verifique que los recuentos de filas coincidan con la l\u00ednea de base en las cinco tablas:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nPGPASSWORD=banking psql -h localhost -U banking -d bankingdb -c &quot;\nSELECT &#039;branches&#039;     AS table_name, COUNT(*) FROM banking.branches\nUNION ALL SELECT &#039;employees&#039;,    COUNT(*) FROM banking.employees\nUNION ALL SELECT &#039;customers&#039;,    COUNT(*) FROM banking.customers\nUNION ALL SELECT &#039;accounts&#039;,     COUNT(*) FROM banking.accounts\nUNION ALL SELECT &#039;transactions&#039;, COUNT(*) FROM banking.transactions;&quot;\n<\/pre><\/div>\n\n\n<p>Eliminar el directorio movido aparte:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo rm -rf \/var\/lib\/postgresql\/18\/main.before-restore\n<\/pre><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"part-3-pgbackrest\">Parte 3 \u2014 pgBackRest<\/h2>\n\n\n\n<p>pgBackRest es la soluci\u00f3n de copias de seguridad de nivel de producci\u00f3n para PostgreSQL. Combina copias de seguridad f\u00edsicas con archivado de WAL integrado, tipos de copia de seguridad incremental y diferencial, gesti\u00f3n de retenci\u00f3n, E\/S paralela y recuperaci\u00f3n a un punto en el tiempo, todo en una sola herramienta.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"installing-pgbackrest\">Instalando pgBackRest<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo apt install -y pgbackrest\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"creating-the-repository-directory\">Creando el Directorio del Repositorio<\/h3>\n\n\n\n<p>pgBackRest no crea el directorio del repositorio autom\u00e1ticamente; este debe existir y ser propiedad de <code>postgres<\/code> antes de que se cree la estrofa.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo mkdir -p \/var\/lib\/pgbackrest\nsudo chown postgres:postgres \/var\/lib\/pgbackrest\nsudo chmod 750 \/var\/lib\/pgbackrest\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"configuring-pgbackrest\">Configuraci\u00f3n de pgBackRest<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo mkdir -p \/etc\/pgbackrest\nsudo tee \/etc\/pgbackrest\/pgbackrest.conf &gt; \/dev\/null &lt;&lt; &#039;EOF&#039;\n&#x5B;global]\nrepo1-path=\/var\/lib\/pgbackrest\nrepo1-retention-full=2\nlog-level-console=info\nlog-level-file=detail\n\n&#x5B;main]\npg1-path=\/var\/lib\/postgresql\/18\/main\nEOF\n<\/pre><\/div>\n\n\n<p>El nombre de la estrofa <code>principal<\/code> coincide con el nombre del cl\u00faster de PostgreSQL de Ubuntu \u2014 el predeterminado en cualquier instalaci\u00f3n de PostgreSQL de Ubuntu.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"configuring-wal-archiving\">Configuraci\u00f3n del archivado de WAL<\/h3>\n\n\n\n<p>Se necesitan tres par\u00e1metros en <code>\/etc\/postgresql\/18\/main\/postgresql.conf<\/code>:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\">\nwal_level = replica\narchive_mode = on\narchive_command = &#8216;pgbackrest &#8211;stanza=main archive-push %p'\n<\/div>\n\n\n<p><code>modo_archivo<\/code> requiere un reinicio completo de PostgreSQL \u2014 no es un par\u00e1metro recargable.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo systemctl restart postgresql\n<\/pre><\/div>\n\n\n<p>Verifica que los par\u00e1metros surtieron efecto:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo -u postgres psql -c &quot;SHOW archive_mode; SHOW archive_command; SHOW wal_level;&quot;\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"creating-the-stanza-and-running-check\">Creando la estrofa y ejecutando la comprobaci\u00f3n<\/h3>\n\n\n\n<p>Una stanza es la configuraci\u00f3n con nombre de pgBackRest para un cl\u00faster de PostgreSQL. <code>stanza-crear<\/code> inicializa la estructura de directorios del repositorio \u2014 ejec\u00fatelo exactamente una vez.<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo -u postgres pgbackrest --stanza=main stanza-create\n<\/pre><\/div>\n\n\n<p>Luego ejecuta <code>checar<\/code> \u2014 pgBackRest fuerza a un cambio de WAL, archiva el segmento y lo lee de vuelta:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo -u postgres pgbackrest --stanza=main check\n<\/pre><\/div>\n\n\n<p>Este es el paso de verificaci\u00f3n previo a la copia de seguridad m\u00e1s importante. Si <code>checar<\/code> si falla, las copias de seguridad no se podr\u00e1n recuperar.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"full-backup\">Copia de seguridad completa<\/h3>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo -u postgres pgbackrest --stanza=main --type=full backup\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"differential-backup\">Copia de seguridad diferencial<\/h3>\n\n\n\n<p>Despu\u00e9s de insertar una nueva fila de transacci\u00f3n, realic\u00e9 una copia de seguridad diferencial:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo -u postgres pgbackrest --stanza=main --type=diff backup\n<\/pre><\/div>\n\n\n<p>El tama\u00f1o de la copia de seguridad fue una peque\u00f1a fracci\u00f3n del total; solo se almacenaron los bloques modificados.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"incremental-backup\">Copia de seguridad incremental<\/h3>\n\n\n\n<p>Despu\u00e9s de insertar otra fila:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo -u postgres pgbackrest --stanza=main --type=incr backup\n<\/pre><\/div>\n\n\n<p>La cadena de copias de seguridad se convirti\u00f3 en: Completa \u2192 Diferencial \u2192 Incremental.<\/p>\n\n\n\n<p>Inspeccionar el cat\u00e1logo completo:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo -u postgres pgbackrest info\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"point-in-time-recovery\">Recuperaci\u00f3n en un momento dado (PITR)<\/h3>\n\n\n\n<p>Esta fue la parte m\u00e1s importante del laboratorio.<\/p>\n\n\n\n<p>Capturar la marca de tiempo actual antes del desastre:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nRECOVERY_TARGET=$(sudo -u postgres psql -d bankingdb -Atc &quot;SELECT now()::text;&quot;)\necho &quot;Recovery target: $RECOVERY_TARGET&quot;\nsleep 2\n<\/pre><\/div>\n\n\n<p><code>RECUPERACI\u00d3N_OBJETIVO<\/code> es una variable de shell de bash. Almacena la marca de tiempo exacta de PostgreSQL capturada antes del desastre. <\/p>\n\n\n\n<p>Todos los pasos de PITR se ejecutan en la misma sesi\u00f3n de terminal, por lo que la variable permanece activa entre comandos. <\/p>\n\n\n\n<p>Se pasa directamente a pgBackRest <code>--objetivo<\/code> par\u00e1metro, que le indica que reproduzca el WAL hasta ese momento exacto y se detenga.<\/p>\n\n\n\n<p>Simula el desastre \u2014 elimina la tabla de transacciones:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nPGPASSWORD=banking psql -h localhost -U banking -d bankingdb -c &quot;\nDROP TABLE banking.transactions CASCADE;&quot; \n<\/pre><\/div>\n\n\n<p>Detener PostgreSQL:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo systemctl stop postgresql\n<\/pre><\/div>\n\n\n<p>Restaurar a la marca de tiempo anterior a la ca\u00edda:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo -u postgres pgbackrest --stanza=main \\\n  --type=time \\\n  &quot;--target=$RECOVERY_TARGET&quot; \\\n  --target-action=promote \\\n  --delta \\\n  restore\n<\/pre><\/div>\n\n\n<p><code>--delta<\/code> compara el directorio de datos existente con la copia de seguridad y reemplaza solo los archivos modificados \u2014 mucho m\u00e1s r\u00e1pido que una reextracci\u00f3n completa cuando la mayor\u00eda de los archivos no han cambiado.<\/p>\n\n\n\n<p>Inicia PostgreSQL \u2014 entra en modo de recuperaci\u00f3n, reproduce WAL hasta la marca de tiempo objetivo, luego se promueve a lectura\/escritura:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nsudo systemctl start postgresql\nsudo tail -20 \/var\/log\/postgresql\/postgresql-18-main.log\n<\/pre><\/div>\n\n\n<p>Verificar que la tabla est\u00e1 de vuelta:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code\" data-no-translation=\"\"><pre class=\"brush: bash; title: ; notranslate\" title=\"\">\nPGPASSWORD=banking psql -h localhost -U banking -d bankingdb -c &quot;\nSELECT COUNT(*) FROM banking.transactions;&quot; \n<\/pre><\/div>\n\n\n<p>Esperado: 6 filas \u2014 4 iniciales m\u00e1s las 2 insertadas durante los pasos diferencial e incremental. PITR funcion\u00f3.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"summary\">Resumen<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Propiedad<\/th><th>pg_dump<\/th><th>pg_basebackup<\/th><th>pgBackRest<\/th><\/tr><\/thead><tbody><tr><td>Nivel de copia de seguridad<\/td><td>L\u00f3gico (SQL)<\/td><td>F\u00edsico (sistema de archivos)<\/td><td>F\u00edsico (sistema de archivos)<\/td><\/tr><tr><td>Granularidad<\/td><td>Base de datos \/ esquema \/ tabla<\/td><td>Cluster completo<\/td><td>Cluster completo<\/td><\/tr><tr><td>Restauraci\u00f3n entre versiones<\/td><td>S\u00ed<\/td><td>No<\/td><td>No<\/td><\/tr><tr><td>Copias de seguridad incrementales<\/td><td>No<\/td><td>No (PG17+ limitado)<\/td><td>S\u00ed<\/td><\/tr><tr><td>PITR<\/td><td>No<\/td><td>Solo con archivado WAL separado<\/td><td>S\u00ed \u2014 integrado<\/td><\/tr><tr><td>Adecuado para producci\u00f3n diaria<\/td><td>No<\/td><td>No<\/td><td>S\u00ed<\/td><\/tr><tr><td>Adecuado para migraci\u00f3n entre versiones<\/td><td>S\u00ed<\/td><td>No<\/td><td>No<\/td><\/tr><tr><td>Adecuado para recuperaci\u00f3n a nivel de tabla<\/td><td>S\u00ed<\/td><td>No<\/td><td>No<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Reglas de decisi\u00f3n pr\u00e1cticas:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Utilice <code>pg_dump<\/code> para migraciones entre versiones, copias de seguridad selectivas de tablas y capturas puntuales ad hoc.<\/li>\n\n\n\n<li>Utilice <code>pg_basebackup<\/code> principalmente para arrancar r\u00e9plicas de streaming.<\/li>\n\n\n\n<li>Utilice pgBackRest para todo en producci\u00f3n \u2014 incremental diario, PITR, retenci\u00f3n, archivado de WAL, todo en una sola herramienta.<\/li>\n<\/ul>\n\n\n\n<p>En entornos reales los combinas: pgBackRest para protecci\u00f3n continua, <code>pg_dump<\/code> para migraciones y recuperaci\u00f3n a nivel de objeto.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"final-thoughts\">Reflexiones finales<\/h2>\n\n\n\n<p>Una copia de seguridad solo es \u00fatil si la recuperaci\u00f3n funciona realmente.<\/p>\n\n\n\n<p>La disciplina m\u00e1s importante en este laboratorio no fue ejecutar los comandos de respaldo, sino ejecutar los simulacros de restauraci\u00f3n y verificar los recuentos de filas despu\u00e9s.<\/p>\n\n\n\n<p>Probar las restauraciones no es opcional.<\/p>\n\n\n\n<p>Si est\u00e1s planeando una migraci\u00f3n de Oracle a PostgreSQL y necesitas ayuda para dise\u00f1ar la estrategia de copia de seguridad y recuperaci\u00f3n para el entorno de destino, <a href=\"https:\/\/rootfan.com\/es\/servicios\/\">ver mis servicios \u2192<\/a><\/p>","protected":false},"excerpt":{"rendered":"<p>If you work with PostgreSQL in production, backups are not optional. In this lab, I built a complete PostgreSQL 18 backup and recovery environment on Ubuntu 24.04 and tested all three major backup approaches: Everything was tested on a real VM with actual recovery drills \u2014 not simulations.<\/p>","protected":false},"author":1,"featured_media":6858,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"rank_math_focus_keyword":"PostgreSQL backup recovery pgBackRest","rank_math_title":"PostgreSQL 18 Backup & Recovery Lab \u2014 pg_dump, pg_basebackup, pgBackRest","rank_math_description":"A complete PostgreSQL 18 backup and recovery lab on Ubuntu 24.04 covering pg_dump, pg_basebackup and pgBackRest \u2014 including PITR and restore drills.","rank_math_robots":"","rank_math_og_title":"","rank_math_og_description":"","_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[126],"tags":[77,78,160,159,158,161,163,110,81,4,29,162],"class_list":["post-6853","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-postgresql","tag-archive","tag-backup","tag-pg_basebackup","tag-pg_dump","tag-pgbackrest","tag-pitr","tag-point-in-time-recovery","tag-restore","tag-step-by-step","tag-tutorial","tag-ubuntu","tag-wal-archiving"],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/rootfan.com\/wp-content\/uploads\/pexels-photo-1753392.jpeg?fit=1716%2C1300&ssl=1","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/posts\/6853","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/comments?post=6853"}],"version-history":[{"count":7,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/posts\/6853\/revisions"}],"predecessor-version":[{"id":6862,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/posts\/6853\/revisions\/6862"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/media\/6858"}],"wp:attachment":[{"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/media?parent=6853"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/categories?post=6853"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rootfan.com\/es\/wp-json\/wp\/v2\/tags?post=6853"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}