Ampliando la capacidad de nuestro PostgreSQL con Pgpool-II

Antes de completar nuestro aprendizaje con Ansible vamos a configurar un elemento que nos dará más versatilidad en nuestras conexiones a PostgreSQL. En mi trabajo estoy teniendo unos problemas con el pool de conexiones a la base de datos y nos estamos planteando utilizar un proyecto llamado Pgpool-II que se coloca entre las aplicaciones y la base de datos dándonos mayor flexibilidad a la hora de manejar nuestro PostgreSQL.

Básicamente he seguido este completo tutorial. Para la configuración utilizaremos cuatro contenedores pero debéis tener en cuenta que si éstos corren sobre la misma máquina no mejoraremos el rendimiento ya que todas las operaciones en la base de datos utilizarán el mismo disco. En este primer post veremos cómo configurar la replicación entre dos nodos de PostgreSQL y en el siguiente configuraremos Pgpool-II.

Lo que pretendemos con este proceso es poder balancear carga entre varios nodos y tener alta disponibilidad. Con Pgpool-II conseguiremos que las peticiones que lleguen a la ip principal se repartan entre los diferentes nodos aunque las escrituras sólo irán al nodo maestro. Para la replicación entre los nodos utilizaremos el propio mecanismo implementado por los chicos de PostgreSQL aunque también podríamos confiar este trabajo a Pgpool-II.

Lo primero que vamos a hacer es configurarnos los contenedores con la última versión de nuestra base de datos favorita, pondremos las fuentes del propio proyecto PostgreSQL y lo clonaremos para después instalar los diferentes paquetes. Seguiremos las instrucciones de nuestro anterior post para el montaje de los contenedores. Una vez creado el primero le añadimos las fuentes:

(pgmaster) # echo 'deb http://apt.postgresql.org/pub/repos/apt/ xenial-pgdg main' > /etc/apt/sources.list.d/pgdg.list
(pgmaster) # wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | > sudo apt-key add -
OK  
(pgmaster) # apt update

Hemos llamado al primer contenedor pgmaster, a los otros les pondremos nombres que indicarán cada una de sus funciones. Para clonar a partir del primero ejecutaremos las siguientes órdenes:

$ lxc-stop -n ubuntu-pgmaster 
$ lxc-copy -n ubuntu-pgmaster -N ubuntu-pgslave
$ lxc-copy -n ubuntu-pgmaster -N ubuntu-pgpool-master
$ lxc-copy -n ubuntu-pgmaster -N ubuntu-pgpool-slave

Los cuatro contenedores tienen unos nombres muy descriptivos, un par de ellos serán los servidores de PostgreSQL, maestro y esclavo, pgmaster y pgslave respectivamente. Los otros dos serán los servidores de Pgpool-II, uno actuará de maestro y en el otro tendremos la configuración preparada para entrar en acción en caso de caída del principal, ya explicaremos esto en el siguiente post, ahora vamos a montar la replicación básica de PostgreSQL en la que se apoyará Pgpool-II. En nuestro taller hemos asignado cuatro ips consecutivas a los contenedores y tendremos una ip virtual para que Pgpool-II dé el servicio, pero vamos por partes.

La replicación entre nodos de PostgreSQL es muy fácil de configurar. Hay muchos tipos, como muy bien explica mi colega en el post referenciado al principio, y estoy con él, he elegido la replicación del tipo Transaction Log Shipping porque la he visto en más configuraciones y comparando brevemente con el resto es la que más me cuadra, pero creedme, la comparación ha sido muy breve. En la empresa siempre hemos utilizado ésta pero en una versión anterior. El procedimiento es muy sencillo, el servidor principal genera una serie de ficheros con las transacciones que se van realizando sobre él y los nodos los van consumiendo para reconstruir la base de datos tal cuál está en el nodo principal.

Con este método tenemos el problema de que si en un momento determinado perdemos la conexión desde el nodo esclavo y tardamos en recuperarlo puede que el servidor principal ya haya borrado parte de estos ficheros y no podamos restablecer el nodo secundario para continuar con la replicación. A partir de la versión 9.4 apareció un método diferente, mediante lo que ellos llaman slots. Ahora tenemos que notificar al maestro que tenemos un esclavo que se quiere sincronizar pero a cambio, cuando el esclavo consuma los archivos que le faltan para sincronizarse, el maestro los borrará. Con este método tenemos la ventaja de poder demorar en el tiempo la resincronización pero a cambio tendremos que tener controlado cuándo ha caído un nodo esclavo para que el maestro pueda borrar los archivos cuanto antes y no se llene su disco.

Sé que la explicación es muy densa pero merecía la pena contarlo para que tengamos claro qué estamos haciendo. Ahora vamos al lío. Instalaremos el servidor de PostgreSQL en ubuntu-pgmaster y en ubuntu-pgslave, el método es el siguiente:

# echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list
# wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
OK  
# apt update
# apt install postgresql-9.6

Para estas pruebas no tunearemos la base de datos aunque sabemos que la configuración por defecto no es la que más rendimiento nos da, si váis a utilizar un PostgreSQL en producción os aconsejo que busquéis recursos para este menester. Por supuesto tampoco vamos a cambiar el password de la base de datos ya que sólo lo vamos a utilizar, como hemos dicho, para testear la funcionalidad de Pgpool-II.

El siguiente paso es configurar la replicación, para ello necesitamos configurar el usuario para que las dos bases de datos se comuniquen, y crear un archivo, .pgpass en el directorio del usuario postgres:

# su - postgres
$ psql -U postgres
psql (9.6.3)  
Type "help" for help.

postgres=# CREATE ROLE replication WITH REPLICATION PASSWORD 'reppassword' LOGIN;  
CREATE ROLE  
postgres=# \q  
$ echo "*:*:*:replication:reppassword" > .pgpass
$ chmod 0600 .pgpass

Estos pasos tendremos que realizarlos en ambos servidores aunque el fichero sólo lo necesitemos en el master para realizar tareas de recuperación y sincronización. Tampoco debemos olvidar el hecho de que ambos servidores se tienen que comunicar y como la configuración por defecto del servidor PostgreSQL hace que sólo escuche en la interfaz de red local, tenemos que editar el fichero /etc/postgresql/9.6/main/postgresql.conf y cambiar la siguiente línea:

listen_address = '*'  

Con esto abrimos el servicio a todas las interfaces de red de la máquina pero el control de acceso tiene otro mecanismo de limitación, el archivo /etc/postgresql/9.6/main/pg_hba.conf, en el que debemos añadir la siguiente línea para que la replicación funcione:

host    replication     replication     192.168.1.0/24          md5  

Esta línea debemos añadirla en ambos servidores así como reiniciarlos para que los cambios surtan efecto:

systemctl restart postgresql.service  

Como os he comentado un poco más arriba necesitamos declarar un slot en el servidor principal, para ello editaremos el archivo de configuración del servidor maestro con los siguientes valores:

wal_level = replica  
max_replication_slots = 2  
max_wal_senders = 2  

Después de cambiar estos valores debemos reiniciar el servidor principal. La primera variable, que tiene tres posibles valores, minimal, replica y logical tenemos que fijarla al valor indicado porque es el mínimo necesario para la replicación elegida. Los otros dos valores indican cuántos slots podemos abrir simultáneamente, lo hemos establecido en dos por si luego nos apetece experimentar añadiendo un esclavo una vez montada toda la infraestructura, pero en realidad sólo necesitaríamos uno.

Despúes abrimos una consola psql en el servidor principal y ejecutamos la siguiente orden para declarar el slot que utilizará el nodo secundario:

(pgmaster) postgres=# SELECT * FROM pg_create_physical_replication_slot('libreadmin_test02');
     slot_name     | xlog_position 
-------------------+---------------
 libreadmin_test02 | 
(1 row)

Ahora viene una parte divertida, la construcción del esclavo, que en nomenclatura de PostgreSQL es el standby server. Digo divertida porque en principio nos tenemos que cargar por completo la estructura de directorios del servicio para luego replicar la del servidor principal. Los pasos, ejecutados en el esclavo, son los siguientes:

(pgslave) # systemctl stop postgresql.service 
(pgslave) # su - postgres
(pgslave) $ cd 9.6/
(pgslave) $ rm -Rf main/
(pgslave) $ pg_basebackup -v -D main -R -P -h 192.168.1.210 -p 5432 -U replication
pg_basebackup: initiating base backup, waiting for checkpoint to complete  
pg_basebackup: checkpoint completed  
22003/22003 kB (100%), 1/1 tablespace  
NOTICE:  WAL archiving is not enabled; you must ensure that all required WAL segments are copied through other means to complete the backup  
pg_basebackup: base backup completed  

Ahora editamos el fichero de configuración del servidor secundario y cambiamos estas dos líneas:

hot_standby = on  
hot_standby_feedback = on  

Ya podemos iniciar el servidor:

(pgslave) # systemctl start postgresql.service 

Vamos a comprobar si todo este trabajo ha dado sus frutos, lo primero que haremos será comprobar el estado de ambos servidores, para ello lo mejor es comprobar sus logs, ambos recogidos en /var/log/postgresql/postgresql-9.6-main.log:

Master:  
[...]
LOG:  autovacuum launcher started  
LOG:  database system is ready to accept connections  
Slave:  
[...]
LOG:  redo starts at 0/2000028  
LOG:  consistent recovery state reached at 0/2000130  
LOG:  database system is ready to accept read only connections  

Como véis, parece que todo está correcto, pasemos a la prueba de fuego, comprobar que realmente existe una replicación. Primero intentaremos crear una base de datos nueva en nuestro servidor secundario:

(pgslave) # su - postgres
(pgslave) $ createdb libreadmin
createdb: database creation failed: ERROR:  cannot execute CREATE DATABASE in a read-only transaction  
(pgslave) $ psql libreadmin
psql: FATAL:  database "pepe" does not exist  

Correcto, no hemos podido crear la base de datos y vemos que tampoco existe. Ahora hacemos lo mismo en el principal y crearemos una tabla de ejemplo:

(pgmaster) # su - postgres
(pgmaster) $ createdb libreadmin
(pgmaster) $ psql libreadmin
psql (9.6.3)  
Type "help" for help.

libreadmin=# CREATE TABLE films (  
libreadmin(#     code        char(5) CONSTRAINT firstkey PRIMARY KEY,  
libreadmin(#     title       varchar(40) NOT NULL,  
libreadmin(#     did         integer NOT NULL,  
libreadmin(#     date_prod   date,  
libreadmin(#     kind        varchar(10),  
libreadmin(#     len         interval hour to minute  
libreadmin(# );  
CREATE TABLE  

Y si nos vamos a nuestro servidor secundario comprobaremos cómo ha aparecido por arte de birlibirloque:

(pgslave) $ psql libreadmin
psql (9.6.3)  
Type "help" for help.

libreadmin=# \d  
films                firstkey             information_schema.  pg_catalog.          pg_temp_1.           pg_toast.            pg_toast_temp_1.     public.  
libreadmin=# \d films  
              Table "public.films"
  Column   |          Type           | Modifiers 
-----------+-------------------------+-----------
 code      | character(5)            | not null
 title     | character varying(40)   | not null
 did       | integer                 | not null
 date_prod | date                    | 
 kind      | character varying(10)   | 
 len       | interval hour to minute | 
Indexes:  
    "firstkey" PRIMARY KEY, btree (code)

Con esta sencilla configuración, unos buenos parámetros de rendimiento y un par de planes de contingencia bien documentados, podríais tener montado un sistema de base de datos bastante robusto. Pero mi consejo es que siempre monteís los servidores de bases de datos sobre el hierro, es decir, sin virtualización de por medio, ya que las propias cachés de los sistemas operativos intermedios y las de las bases de datos os podrían jugar malas pasadas.

Sobre los planes de contingencia os tendríais que plantear qué hacer cuando caiga un nodo esclavo y qué hacer cuando caiga el principal. En el primer caso es muy fácil volver a montar otro nodo standby, sólo tendréis que decidir si volvéis a usar el mismo servidor o lo desecháis utilizando hardware nuevo, todo dependiendo del problema aparecido. En el segundo caso debéis tener claro que el nodo principal no es necesario recuperarlo inmediatamente, lo mejor es tirar de uno de los nodos secundarios, con Pgpool-II veréis que esta acción es automática, y una vez decidido qué se va a hacer con el hardware que provocó el fallo, promocionar uno de los nodos secundarios a principal o establecer un hardware diferente como principal.

En el siguiente post montaremos Pgpool-II sobre la replicación establecida pero mientras os dejo con una de las sorpresas del primer SOS 4.8, los Kaiser Chiefs, de su disco de 2007 Yours Truly, Angry Mob rescatamos su melocotonazo Ruby con el que saltamos como locos allá por mayo de 2008, de lo mejor de ese año y por qué no decirlo, de varias ediciones más. Descanse en paz el SOS 4.8 y les deseamos toda la suerte del mundo al festival que lo ha sustituido.

Disfrutad!