Compilación de Postgresql con la extensión de Spock para cluster Multi-master con dos nodos
· 20 min · linux
Introducción
En este artículo, vamos a explicar cómo compilar Postgresql con la extensión de Spock para crear un cluster Multi-master. Esta configuración es útil para aplicaciones que requieren alta disponibilidad y escalabilidad.
Requisitos previos
Para empezar deberías contar dos máquinas con Ubuntu 24.04 Server por ejemplo. nodo1: 192.168.1.91 nodo2: 192.168.1.92
Repositorio de Scripts de instalación
Primeros pasos en ambos nodos
Fichero config.sh
#!/bin/bash
PG_VERSION="17.5"
PG_VERSION_BASE="17"
SRC_DIR="/usr/local/src/postgres17-spock"
INSTALL_PREFIX="/usr/local/pgsql"
DATA_DIR="${INSTALL_PREFIX}/data"
NUM_CORES=$(nproc)
PG_CTL="${INSTALL_PREFIX}/bin/pg_ctl"
PGDATA="${DATA_DIR}"
SERVICE_FILE="/etc/systemd/system/postgresql.service"
Este fichero nos permite definir las variables que vamos a utilizar en el script de compilación e instalación.
Fichero install_postgres_spock.sh
source ./config.sh
set -euo pipefail
install_dependencies() {
echo "Instalando dependencias..."
sudo apt update
sudo apt install -y make build-essential libssl-dev zlib1g-dev libreadline-dev \
libxml2-dev libxslt-dev libkrb5-dev flex bison libxml2-utils xsltproc \
ccache pkg-config git libjansson-dev
}
# /usr/local/src/
prepare_source_dir() {
echo "Preparando directorio de código fuente..."
sudo mkdir -p "$SRC_DIR"
sudo chown "$(whoami)" "$SRC_DIR"
cd "$SRC_DIR"
}
download_postgres() {
if [ ! -f "postgresql-${PG_VERSION}.tar.gz" ]; then
echo "Descargando PostgreSQL ${PG_VERSION}..."
wget "https://ftp.postgresql.org/pub/source/v${PG_VERSION}/postgresql-${PG_VERSION}.tar.gz"
fi
}
extract_sources() {
echo "Extrayendo PostgreSQL..."
tar -xvzf "postgresql-${PG_VERSION}.tar.gz"
}
clone_spock() {
if [ ! -d "spock" ]; then
echo "Clonando Spock..."
git clone https://github.com/pgEdge/spock
fi
}
apply_patches() {
echo "Aplicando parches de Spock..."
cd "${SRC_DIR}/postgresql-${PG_VERSION}"
for patch in ../spock/patches/pg${PG_VERSION_BASE}-*.diff; do
echo "Aplicando $(basename "$patch")"
patch -p1 < "$patch"
done
}
configure_postgres() {
echo "Configurando compilación..."
./configure --prefix="$INSTALL_PREFIX" \
--with-readline --with-zlib --with-icu --with-openssl --with-libxml
}
build_and_install_postgres() {
echo "Compilando PostgreSQL..."
make -j"$NUM_CORES"
sudo make install
}
configure_path() {
echo "Añadiendo ${INSTALL_PREFIX}/bin al PATH del sistema..."
sudo tee /etc/profile.d/pgsql.sh >/dev/null <<EOF
#!/bin/sh
export PATH=${INSTALL_PREFIX}/bin:\$PATH
EOF
sudo chmod +x /etc/profile.d/pgsql.sh
source /etc/profile.d/pgsql.sh
}
create_postgres_user() {
if ! id postgres >/dev/null 2>&1; then
echo "Creando usuario postgres..."
sudo adduser --system --home /var/lib/postgresql --group --shell /bin/bash postgres
fi
}
# configurar directorio de datos de postgres
setup_data_dir() {
echo "Creando directorio de datos..."
sudo mkdir -p "$DATA_DIR"
sudo chown postgres:postgres "$DATA_DIR"
}
# inicializar el clúster
init_db_cluster() {
echo "Inicializando base de datos..."
sudo -u postgres "${INSTALL_PREFIX}/bin/initdb" -D "$DATA_DIR"
}
install_spock() {
echo "Compilando e instalando Spock..."
cd "${SRC_DIR}/spock"
cp compat${PG_VERSION_BASE}/* .
env PATH="${INSTALL_PREFIX}/bin:$PATH" make -j"$NUM_CORES"
sudo env PATH="${INSTALL_PREFIX}/bin:$PATH" make install
}
# configurar postgresql.conf para Spock
# borra y añade al final
configure_spock() {
echo "🛠️ Configurando postgresql.conf para Spock..."
CONF_FILE="${DATA_DIR}/postgresql.conf"
sudo sed -i "/^#*shared_preload_libraries *=/d" "$CONF_FILE"
sudo sed -i "/^#*track_commit_timestamp *=/d" "$CONF_FILE"
sudo sed -i "/^#*wal_level *=/d" "$CONF_FILE"
sudo sed -i "/^#*max_worker_processes *=/d" "$CONF_FILE"
sudo sed -i "/^#*max_replication_slots *=/d" "$CONF_FILE"
sudo sed -i "/^#*max_wal_senders *=/d" "$CONF_FILE"
{
echo ""
echo "# Configuración necesaria para Spock"
echo "shared_preload_libraries = 'spock'"
echo "track_commit_timestamp = on"
echo "wal_level = 'logical'"
echo "max_worker_processes = 10 # one per database needed on provider node"
echo ""
echo "max_replication_slots = 10 # one per node needed on provider node"
echo "max_wal_senders = 10 # one per node needed on provider node"
} | sudo tee -a "$CONF_FILE" >/dev/null
}
# resumen
show_summary() {
echo "✅ PostgreSQL ${PG_VERSION} y Spock compilados e instalados correctamente."
echo "🔧 PostgreSQL instalado en: $INSTALL_PREFIX"
echo "📁 Datos en: $DATA_DIR"
}
main() {
install_dependencies
prepare_source_dir
download_postgres
extract_sources
clone_spock
apply_patches
configure_postgres
build_and_install_postgres
configure_path
create_postgres_user
setup_data_dir
init_db_cluster
install_spock
configure_spock
show_summary
}
main
Como vemos aquí definimos una serie de funciones que nos permiten realizar la instalación de Postgresql y Spock. Veamos un poco el paso a paso de lo que hace el script:
- Instalación de dependencias: Se instalan las dependencias necesarias para compilar Postgresql y Spock.
- Preparación del directorio de código fuente: Se crea el directorio donde se va a descargar y compilar Postgresql.
- Descarga de Postgresql: Se descarga la versión especificada de Postgresql.
- Extracción de fuentes: Se extraen los archivos de Postgresql.
- Clonación de Spock: Se clona el repositorio de Spock desde GitHub.
- Aplicación de parches: Se aplican los parches necesarios para integrar Spock con Postgresql.
- Configuración de Postgresql: Se configura la compilación de Postgresql con las opciones necesarias.
- Compilación e instalación de Postgresql: Se compila e instala Postgresql en el directorio especificado.
- Configuración del PATH: Se añade el directorio de Postgresql al PATH del sistema para que sea accesible.
- Creación del usuario Postgres: Se crea el usuario
postgres
si no existe. - Configuración del directorio de datos: Se crea el directorio de datos para Postgresql y se le asigna el propietario correcto.
- Inicialización del clúster de base de datos: Se inicializa el clúster de base de datos en el directorio de datos.
- Instalación de Spock: Se compila e instala Spock en el directorio de Postgresql.
- Configuración de Spock en postgresql.conf: Se configura el archivo
postgresql.conf
para habilitar Spock y sus parámetros necesarios. - Resumen: Se muestra un resumen de la instalación y configuración realizada.
Lanzamiento del script de compilación e instalación
Para lanzar el script, primero debemos darle permisos de ejecución:
chmod +x config.sh
chmod +x install_postgres_spock.sh
./instal_postgres_spock.sh
fichero setup_postgres_service.sh
Para configurar Postgresql podemos ejecutar el siguiente script llamado setup_postgres_service.sh:
#!/bin/bash
# variables de configuración
source ./config.sh
set -euo pipefail
# servicio systemd
create_service_file() {
echo "Creando archivo de servicio systemd..."
sudo tee "$SERVICE_FILE" > /dev/null <<EOF
[Unit]
Description=PostgreSQL database server
After=network.target
[Service]
Type=forking
User=postgres
Group=postgres
ExecStart=${PG_CTL} start -D ${PGDATA} -l ${PGDATA}/logfile
ExecStop=${PG_CTL} stop -D ${PGDATA}
ExecReload=${PG_CTL} reload -D ${PGDATA}
Environment=PATH=${INSTALL_PREFIX}/bin:/usr/bin:/bin
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
}
enable_service() {
echo "Recargando systemd y habilitando el servicio..."
sudo systemctl daemon-reexec
sudo systemctl daemon-reload
sudo systemctl enable postgresql
sudo systemctl start postgresql
}
main() {
create_service_file
enable_service
echo "✅ Servicio PostgreSQL configurado y iniciado."
}
main
Este script crea un archivo de servicio para Postgresql en systemd, lo habilita y lo inicia.
Lanzamiento del script de configuración del servicio
Para lanzar el script, primero debemos darle permisos de ejecución:
chmod +x setup_postgres_service.sh
./setup_postgres_service.sh
Verificación del lanzamiento del servicio
Para verificar que el servicio se ha iniciado correctamente, podemos ejecutar el siguiente comando:
systemctl status postgresql
Acceso a la consola de Postgresql
Para acceder a la consola de Postgresql, nos cambiamos al usuario postgres
:
sudo -iu postgres
Y luego ejecutamos el comando:
psql -h localhost -p 5432 -U postgres
Si ha ido todo bien, deberíamos ver el prompt de Postgresql y poder ejecutar comandos SQL. Como por ejemplo, podemos crear una base de datos de prueba:
CREATE DATABASE testdb;
o listar las bases de datos existentes:
\l
Ficheros de configuración
Los ficheros de configuración de Postgresql se encuentran en el directorio de datos que hemos especificado en el script de instalación. Por defecto, este directorio es /usr/local/pgsql/data
. Los ficheros más importantes son:
postgresql.conf
: Configuración principal del servidor.pg_hba.conf
: Configuración de autenticación de clientes.
Configuración de Spock
Como vemos en el fichero install_postgres_spock.sh
, hemos configurado Spock en el archivo postgresql.conf
añadiendo las siguientes líneas:
# Add settings for extensions here
# Configuración necesaria para Spock
shared_preload_libraries = 'spock'
track_commit_timestamp = on
wal_level = 'logical'
max_worker_processes = 10 # one per database needed on provider node
max_replication_slots = 10 # one per node needed on provider node
max_wal_senders = 10 # one per node needed on provider node
Esto habilita Spock y configura los parámetros necesarios para su funcionamiento.
Configuración del cluster Multi-master
Para configurar un cluster Multi-master con Spock, debemos realizar los siguientes pasos en ambos nodos:
- Configurar el archivo
postgresql.conf
: Asegurarnos de que el puerto y la dirección IP están configurados correctamente. Por ejemplo:
listen_addresses = '*'
O bien expecificar la IP del nodo 1:
listen_addresses = '192.168.1.91'
O bien expecificar la IP del nodo 2:
listen_addresses = '192.168.1.92'
- Configurar el archivo
pg_hba.conf
: Permitir conexiones entre los nodos en ambos nodos. Por ejemplo:
# Permitir conexiones desde la ip del nodo 1 y la ip del nodo 2
host all all 192.168.1.91/32 trust
host all all 192.168.1.92/32 trust
- Reiniciar el servidor::
sudo systemctl restart postgresql
- Comprobar que podemos conectarnos a la IP del nodo 1 del servidor del nodo 1 o el nodo 2:
psql -h 192.168.1.91 -p 5432 -U postgres
- Comprobar que podemos conectarnos a la IP del nodo 2 del servidor del nodo 1 o el nodo 2:
psql -h 192.168.1.92 -p 5432 -U postgres
- Crear un usuario de replicación: En ambos nodos, creamos un usuario de replicación que se utilizará para la replicación entre los nodos:
-- Crea el usuario con SUPERUSER y REPLICATION
CREATE ROLE replicator
WITH LOGIN
SUPERUSER
ENCRYPTED PASSWORD 'TuPasswordSegura';
- Crear una misma base de datos en ambos nodos:
CREATE DATABASE testdb;
- Comprobar que se ha creado correctamente el usuario en ambos nodos: En ambos nodos, debemos verificar que se ha creado correctamente el usuario. Esto se hace ejecutando el siguiente comando en la consola de Postgresql:
\du
- Comprobar que tenemos conectividad con el usuario replicator a ambos nodos:
psql -h 192.168.1.91 -p 5432 -U replicator -W testdb
Debería pedirnos la contraseña y conectarnos correctamente a la base de datos testdb
en el nodo 1.
Recuerda que la contraseña que hemos definido es TuPasswordSegura
.
Ahora comprobamos el nodo 2:
psql -h 192.168.1.92 -p 5432 -U replicator -W testdb
Debería pedirnos la contraseña y conectarnos correctamente a la base de datos testdb
en el nodo 2.
8. Crea la extensión de Spock en la bbdd creada en ambos nodos:
Primero conextamos a la base de datos testdb
:
\c testdb
Para luego crear la extensión de Spock en esa base de datos:
CREATE EXTENSION spock;
- Registramos los nodos en Spock: Desde el nodo 1, ejecutamos el siguiente comando:
SELECT spock.node_create(
'proveedor1',
'host=192.168.1.91 user=replicator dbname=testdb'
);
Desde el nodo 2, ejecutamos el siguiente comando:
SELECT spock.node_create(
'proveedor2',
'host=192.168.1.92 user=replicator dbname=testdb'
);
- Comprobamos que los nodos se han registrado correctamente: Desde el nodo 1, ejecutamos el siguiente comando:
SELECT * FROM spock.node;
Desde el nodo 2, ejecutamos el siguiente comando:
SELECT * FROM spock.node;
Deberíamos ver ambos nodos registrados correctamente en la tabla spock.node
.
11. Creamos una tabla de ejemplo:
Desde el nodo 1, ejecutamos el siguiente comando:
-- Crear tabla de ejemplo
CREATE TABLE public.clientes (
id SERIAL PRIMARY KEY,
nombre TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
creado_en TIMESTAMP DEFAULT now()
);
-- Insertar datos de prueba
INSERT INTO public.clientes (nombre, email)
VALUES
('Ana', 'ana@example.com'),
('Juan', 'juan@example.com');
- Comprobamos que la tabla se ha creado correctamente: Desde el nodo 1, ejecutamos el siguiente comando:
SELECT * FROM public.clientes;
- Configurar el conjunto de replicación: Desde el nodo 1, ejecutamos el siguiente comando:
-- Añadir tabla al conjunto de replicación
SELECT spock.repset_add_table(
set_name := 'default',
relation := 'public.clientes',
synchronize_data := true
);
O bien incluir toda la base de datos:
-- Alternativa: replicar todas las tablas del esquema public
SELECT spock.repset_add_all_tables('default', ARRAY['public']);
- Crear la subscripción desde el nodo 2: Desde el nodo 2, ejecutamos el siguiente comando:
-- Crear suscripción con sincronización completa
SELECT spock.sub_create(
subscription_name := 'sub_2_to_1',
provider_dsn := 'host=192.168.1.91 user=replicator dbname=testdb',
replication_sets := ARRAY['default', 'ddl_sql'],
synchronize_structure := true, -- Por defecto viene a false
synchronize_data := true
);
- Comprobar el estado de la suscripción: Desde el nodo 2, ejecutamos el siguiente comando:
SELECT spock.sub_wait_for_sync('sub_2_to_1');
Ahora comprobamos que la subscripción existe en su tabla:
SELECT * FROM spock.sub_show_status();
Si todo ha ido bien, deberíamos ver que la suscripción se ha sincronizado correctamente y que los datos de la tabla clientes
se han replicado al nodo 2.
16. Comprobar que los datos se han replicado correctamente:
Desde el nodo 2, ejecutamos el siguiente comando:
SELECT * FROM public.clientes;
Y deberían aparecer los mismos datos que insertamos en el nodo 1:
testdb=# SELECT * FROM public.clientes;
id | nombre | email | creado_en
----+--------+------------------+---------------------------
1 | Ana | ana@example.com | 2025-06-22 19:04:52.09408
2 | Juan | juan@example.com | 2025-06-22 19:04:52.09408
(2 rows)
- Configurar la subscripción desde el nodo 1 al nodo 2: Desde el nodo 1, ejecutamos el siguiente comando:
-- Crear suscripción con sincronización completa
SELECT spock.sub_create(
subscription_name := 'sub_1_to_2',
provider_dsn := 'host=192.168.1.92 user=replicator dbname=testdb',
replication_sets := ARRAY['default', 'ddl_sql'],
synchronize_structure := true, -- Por defecto viene a false
synchronize_data := true
);
- Comprobar el estado de la suscripción: Desde el nodo 1, ejecutamos el siguiente comando:
SELECT spock.sub_wait_for_sync('sub_1_to_2');
Ahora comprobamos que la subscripción existe en su tabla:
SELECT * FROM spock.sub_show_status();
- Comprobar que replica desde el nodo 2 al nodo 1: Desde el nodo 2, ejecutamos el siguiente comando:
\c testdb
INSERT INTO public.clientes (nombre, email)
VALUES ('Marta', 'marta@example.com');
SELECT * FROM public.clientes;
- Comprobar que los datos se han replicado correctamente al nodo 1: Desde el nodo 1, ejecutamos el siguiente comando:
\c testdb
select * from public.clientes;
Y deberían aparecer los mismos datos que insertamos en el nodo 2.
Limitaciones
- Requiere privilegios de superusuario: la administración y replicación con Spock solo funciona si el rol es superusuario.
- No replica tablas UNLOGGED ni TEMPORARY: estas tablas quedan fuera del flujo de replicación lógica.
- Una sola base de datos a la vez: para replicar varias BBDD hay que crear relaciones proveedor/suscriptor por cada una; no existe un ajuste global.
- Clave primaria o identidad de réplica obligatoria: sin PRIMARY KEY o REPLICA IDENTITY válida, no se pueden replicar UPDATE ni DELETE.
- REPLICA IDENTITY FULL no soportado.
- Solo un índice único/PK permitido: en escenarios multi-upstream o con escrituras locales, la resolución de conflictos usa un único índice, de lo contrario puede fallar la replicación.
- Índices únicos no pueden ser diferibles: Spock no admite restricciones DEFERRABLE basadas en índices.
- Sin “flush” de cola de replicación: no hay forma de congelar transacciones en el maestro y esperar a que el suscriptor las procese antes de cambios de esquema.
- Claves foráneas no aplicadas en el suscriptor: si un FOREIGN KEY fallaría en el destino, la fila se inserta igual.
- TRUNCATE … CASCADE solo en origen y sin RESTART IDENTITY: el reinicio de identidad no se propaga.
- Secuencias con latencia: el estado de una secuencia se envía periódicamente, no en tiempo real (se recomienda usar Snowflake Sequences).
- Triggers de réplica siempre disparados: el proceso de aplicación y el COPY inicial corren con session_replication_role = replica.
- Compatibilidad entre versiones de PostgreSQL limitada: funciona mejor de versiones antiguas a nuevas; la sincronización inicial solo admite misma versión o de menor a mayor.
- Codificaciones homogéneas: no es posible replicar entre bases con distinta encoding (recomendada UTF-8).
- No replica objetos grandes (large objects): la decodificación lógica de PostgreSQL no cubre los large objects.
- Modo solo lectura en memoria: la bandera de read-only se guarda en RAM y afecta a nivel de SQL, pero no impide escrituras de fondo en los archivos.
Conclusión
En este artículo hemos visto cómo compilar Postgresql con la extensión de Spock para crear un cluster Multi-master. Hemos configurado dos nodos y hemos verificado que la replicación funciona correctamente entre ellos. Esta configuración es útil para aplicaciones que requieren alta disponibilidad y escalabilidad, permitiendo que ambos nodos puedan recibir escrituras y lecturas de forma simultánea.