Cómo crear un nodo de réplica para PostgreSQL

En verano suelo estar mucho menos activo a la hora de trastear, y de hecho mi nube personal apenas ha sufrido cambios en los últimos meses. Sin embargo, había algo que llevaba tiempo queriendo hacer...

Pongámonos en contexto

Al principio de todo, cuando empecé a alojar mis propios servicios, cada uno de ellos tenía su propia base de datos. Esto me venía bien para tener todo aislado y bien compartimentado. Sin embargo, con el tiempo, que cada servicio tuviera su propia instancia de datos comenzó a impactar en los recursos... principalmente en el relacionado con la memoria de los servidores. Además, casi todos hacían uso del mismo tipo de motor de base de datos: PostgreSQL. Así pues, pensé que lo mismo había llegado el momento de remangarme y crear, al más puro estilo de El Señor de los Anillos, una base de datos que gobernara la información de todos mis servicios.

Dicho y hecho, creé una instancia en Proxmox de PostgreSQL mediante un contenedor LXC y, una a una, comencé a migrar las bases de datos de cada servicio y los reconfiguraba para que empezaran a hacer uso de ella. Cabe destacar que son todos los servicios que tengo en mi propia LAN, aunque hice lo propio en el VPS (motivo principal que me hizo empezar esta odisea... subir de RAM me disparaba el precio 😱).

Acto seguido creé un script que generaba una copia de seguridad de toda la base de datos, la encriptaba y la subía a mi servidor S3. En el VPS no lo vi necesario ya que cuento con las copias diarias que me ofrece OVH, y ya que las pago... ¡que trabajen!

Con esto, parecía que lo tenía todo: una base de datos que redujo el consumo de recursos drásticamente, un nodo dedicado para ella (aunque sea virtual... de momento), copias de seguridad para cuando el disco falle (y no "por si")... y, sin embargo, no me sentía satisfecho del todo. Sentía que me faltaba algo...

Con el tiempo, me di cuenta que, si bien no necesito alta disponibilidad, sí vendría bien tener una réplica para que, en caso de que el nodo principal cayera, ésta pudiera entrar a sustituirla. Que una cosa es tener un backup de los datos en una nube externa, y otra tener una instancia lista para operar si la principal cae. Y dicho y hecho... me puse con ello.

La réplica de streaming en PostgreSQL

Entre otras opciones, PostgreSQL nos permite hacer uso del sistema de réplica en streaming, el cual ni más ni menos, se trata de un servicio que mantiene una base de datos replicada en otra en tiempo real. Esto lo consigue de manera asíncrona, lo cual nos viene perfecto para olvidarnos de disparadores, sistemas tipo cron, etc. Además, nos permite mantener tantos nodos réplica como queramos, y no se ven afectados entre ellos: Si uno cae, o muere, el resto siguen replicando como si nada.

Para este tutorial, tan sólo configuraré una réplica, pero es tan fácil como repetirlo para tantas como queramos crear.

Configurar el servidor primario

Lo primero que necesitamos configurar es nuestro servidor primario para que admita conexiones de replicación y registrar suficientes transacciones como para permitir al servidor (o servidores) de respaldo que las siga.

El primero paso será editar el ficheor postgresql.conf que, en mi caso, está ubicado en /etc/postgresql/<version>/main, pero que en otras distros puede aparecer bajo /var/lib/pgsql/data. Lo abrimos, buscamos y modificamos (o añadimos si no existen) las siguientes líneas:

wal_level = replica
max_wal_senders = 4
max_replication_slots = 4
Configuración a añadir en el fichero postgresql.conf del servidor principal
  • wal_level determina cuánta información se registra en el Write-Ahead Log (WAL).
  • max_wal_senders define el número máximo de conexiones de replicación simultáneas que pueden hacerse a este servidor. Con 4 tendremos más que suficientes para un solo nodo de réplica.
  • max_replication_slots define el número máximo de espacios de réplica que se pueden crear en este servidor.

Además, necesitaremos configurar el fichero pg_hba.conf para permitir conexiones del servidor de respaldo. Podemos encontrar este archivo en la misma ubicación que postgresql.conf. Lo abrimos con nuestro editor favorito y le añadimos la siguiente línea al final (reemplaza replica_ip por la dirección IP del nodo donde esté la réplica):

host    replication     replicator     replica_ip/32    md5
Configuración a añadir en el fichero pg_hba.conf del servidor principal

Una vez hecho, necesitaremos crear un usuario (que según el fichero anterior llamaremos replicator) para que pueda conectarse a la base de datos principal y, valga la redundancia, replicar los datos. Para ello, nos conectaremos a la base de datos mediante el comando psql (recuerda hacerlo bajo un usuario autorizado, por defecto es el usuario postgres). Una vez en la consola, ejecutaremos:

CREATE ROLE replicator WITH REPLICATION LOGIN ENCRYPTED PASSWORD 'ContraseñaSuperSegura';
COMMIT;
Creación del usuario de réplica en la base de datos principal

Recordad que deberemos especificar una contraseña segura, y ésta la apuntaremos para más adelante. Saldremos de la consola mediante el comando \q y ya sólo nos quedará reiniciar el servicio para que los cambios tomen efecto:

sudo systemctl restart postgresql
Reinicio del servicio postgresql

Configurar el servidor de respaldo

Una vez nuestra base de datos está lista para recibir peticiones de réplica, nos vamos a nuestro servidor de respaldo. Lo primero que deberemos hacer es detener el servicio y purgar todo dato existente de la base de datos:

sudo systemctl stop postgresql
sudo rm -rf /var/lib/postgresql/<version>/main/*
Limpieza total de la base de datos en el nodo de réplica

Al igual que con el servidor original antes, la ruta donde se almacene la base de datos puede diferir, y ser algo como /var/lib/pgsql/data. Usa la que corresponda a tu distribución.

A continuación, deberemos copiar la base de datos del servidor primario al de respaldo, y para ello usaremos el comando pg_basebackup. En la siguiente orden, recuerda reemplazar master_ip por la IP del servidor primario (y hacerlo desde el usuario postgres o uno con permisos para usar el comando):

pg_basebackup -h master_ip -D /var/lib/postgresql/<version>/main/ -U replicator -v -P --wal-method=stream
Copia completa de la base de datos primaria a la de respaldo

Ahora, crearemos el fichero recovery.conf en la ruta anterior ( var/lib/postgresql/<version>/main/ o la que corresponda) con el siguiente contenido:

standby_mode = 'on'
primary_conninfo = 'host=master_ip port=5432 user=replicator password=your_password'
trigger_file = '/tmp/trigger_file' 
Configuración del fichero recovery.conf en el servidor de réplica
  • standby_mode indica que este servidor está en modo de respaldo.
  • primary_conninfo especifica cómo conectarse al servidor primario. Recuerda modificar master_ip con la IP del servidor primario y your_password con la contraseña con la que creamos el usuario replicator en el paso anterior.
  • trigger_file es un archivo que, si se crea, hará que este servidor salga del modo de respaldo y se convierta en un servidor primario.

Hecho esto, sólo nos resta volver a levantar el servicio, comprobar que no hemos cometido errores y ya tendríamos nuestra base de datos de respaldo manteniendo una copia en tiempo real del servidor principal lista para entrar en acción.

sudo systemctl start postgresql
sudo systemctl status postgresql
Inicio del servicio postgresql y comprobación de que se ha levantado

Uso de la réplica

Al proceso de pasar a usar la base de datos de respaldo como si fuera la principal se le denomina failover. Si bien por ahora sería un proceso manual que requiere de nuestra intervención, tengo la intención de automatizarlo en algún momento... Por ahora, en caso de que la base de datos principal cayera, tendría que hacer lo siguiente:

  • En el nodo de réplica, crear un fichero trigger_file tal y como lo definimos en la configuración: sudo touch /tmp/trigger_file. Esto hará que la base de datos deje de estar en modo de sólo lectura y pase a actuar como una base de datos completamente funcional.
  • En los servicios que hacen uso de la base de datos (si apuntan a ésta por IP) tendría que actualizar la IP con la del nodo de réplica o, si la tengo referenciada mediante el gestor de DNS local (con un Pi-Hole mismamente) tendría que actualizar en él la IP destino.

Hay herramientas que puede hacer este proceso automático, pero aún no las he investigado... Cuando lo haga, os haré un tutorial nuevo para ello 😃.

Consideraciones a tener en cuenta

La replicación de streaming en PostgreSQL replica toda la base de datos a nivel de bytes, lo que incluye no solo los datos en sí, sino también los usuarios, grupos, permisos, vistas, funciones almacenadas y cualquier otro objeto de la base de datos. Esto se debe a que todo eso está contenido en los archivos de datos que PostgreSQL está replicando.

Sin embargo, hay algunos puntos importantes que debemos tener en cuenta:

  1. La replicación en PostgreSQL es a nivel de clúster, no a nivel de base de datos. Un clúster de PostgreSQL puede contener muchas bases de datos, y todas se replicarán juntas. No puedes seleccionar una base de datos individual para replicar, tendrías que moverla a su propio clúster si quieres replicarla individualmente.
  2. Los cambios de configuración en el archivo postgresql.conf o en otros archivos de configuración del sistema no se replican. Si necesitas que esos cambios estén presentes en el servidor de respaldo, tendrás que hacerlos manualmente.
  3. La réplica está en un estado de sólo lectura. No puedes realizar cambios en la réplica directamente; los cambios deben hacerse en el servidor principal y luego se replicarán en el servidor de respaldo. Si necesitas un servidor de respaldo que también pueda aceptar escrituras, deberás considerar una solución de alta disponibilidad como la replicación lógica o el failover automático.

Y con esto terminamos el tutorial. Al final ha resultado un poco largo, pero creo que es bastante descriptivo y puede ser de utilidad en más de una ocasión. En cualquier caso, no está de más mantener copias encriptadas del clúster almacenadas en servicios externos a nuestra red local (yo uso, por ejemplo, un servidor MinIO y la capa gratuita de Backblaze). Crear la copia es tan sencillo como ejecutar el comando pg_dumpall -c -f ~/backup/dump_db.sql.

Si te ha gustado la entrada, o te ha sido útil y quieres ayudarme a pagar los gastos que conlleva el servidor y mantener así el blog libre completamente de anuncios, puedes hacer una donación en Bitcoin en la siguiente dirección: