Compilación de Postgresql con la extensión de Spock para cluster Multi-master con un sólo nodo

· 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 una máquina con Ubuntu 24.04 Server por ejemplo.

Repositorio de Scripts de instalación

Repositorio de Scripts

Primeros pasos

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:

  1. Instalación de dependencias: Se instalan las dependencias necesarias para compilar Postgresql y Spock.
  2. Preparación del directorio de código fuente: Se crea el directorio donde se va a descargar y compilar Postgresql.
  3. Descarga de Postgresql: Se descarga la versión especificada de Postgresql.
  4. Extracción de fuentes: Se extraen los archivos de Postgresql.
  5. Clonación de Spock: Se clona el repositorio de Spock desde GitHub.
  6. Aplicación de parches: Se aplican los parches necesarios para integrar Spock con Postgresql.
  7. Configuración de Postgresql: Se configura la compilación de Postgresql con las opciones necesarias.
  8. Compilación e instalación de Postgresql: Se compila e instala Postgresql en el directorio especificado.
  9. Configuración del PATH: Se añade el directorio de Postgresql al PATH del sistema para que sea accesible.
  10. Creación del usuario Postgres: Se crea el usuario postgres si no existe.
  11. Configuración del directorio de datos: Se crea el directorio de datos para Postgresql y se le asigna el propietario correcto.
  12. Inicialización del clúster de base de datos: Se inicializa el clúster de base de datos en el directorio de datos.
  13. Instalación de Spock: Se compila e instala Spock en el directorio de Postgresql.
  14. Configuración de Spock en postgresql.conf: Se configura el archivo postgresql.conf para habilitar Spock y sus parámetros necesarios.
  15. 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
./install_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:

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.

Prueba de funcionamiento

Para probar que Spock está funcionando correctamente, podemos ejecutar los siguientes comandos en la consola de Postgresql:

## Paso 1: Crear las Bases de Datos

-- Crear base de datos origen (proveedor)
CREATE DATABASE proveedor_db;

-- Crear base de datos destino (suscriptor)
CREATE DATABASE suscriptor_db;

Paso 2: Configurar la Base de Datos Proveedor

Conectar la base de datos proveedor y configurarla:

-- Conectar a proveedor_db
\c proveedor_db

-- Crear la extensión spock
CREATE EXTENSION spock;

-- 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');

-- Crear nodo spock proveedor
SELECT spock.node_create(
    node_name := 'proveedor1',
    dsn := 'host=localhost port=5432 dbname=proveedor_db'
);

-- Añadir tabla al conjunto de replicación
SELECT spock.repset_add_table(
    set_name := 'default',
    relation := 'public.clientes',
    synchronize_data := true
);

-- Alternativa: replicar todas las tablas del esquema public
-- SELECT spock.repset_add_all_tables('default', ARRAY['public']);

Paso 3: Configurar la Base de Datos Suscriptor

Conectar a la base de datos suscriptor y configurarla:

-- Conectar a suscriptor_db
\c suscriptor_db

-- Crear extensión spock
CREATE EXTENSION spock;

-- Crear nodo del suscriptor
SELECT spock.node_create(
    node_name := 'suscriptor1',
    dsn := 'host=localhost port=5432 dbname=suscriptor_db'
);

-- Crear suscripción con sincronización completa
SELECT spock.sub_create(
    subscription_name := 'sub1',
    provider_dsn := 'host=localhost port=5432 dbname=proveedor_db',
    replication_sets := ARRAY['default', 'ddl_sql'],
    synchronize_structure := true,  -- Por defecto viene a false
    synchronize_data := true
);

Paso 4: Verificar la Replicación

-- Esperar la sincronización (aunque es inmediata en este ejemplo)
SELECT spock.sub_wait_for_sync('sub1');

-- Verificar que los datos se replicaron
SELECT * FROM public.clientes;

-- Verificar el estado de la suscripción
SELECT * FROM spock.sub_show_status();

Paso 5: Añadir más datos

-- En proveedor_db, insertar un nuevo registro
\c proveedor_db
INSERT INTO public.clientes (nombre, email)
VALUES ('Lucia', 'lucia@example.com');

-- verificar
\c suscriptor_db
SELECT * FROM public.clientes ORDER BY id;

Otros comandos probados

-- Resincronizar una tabla específica si es necesario
SELECT spock.sub_resync_table(
    subscription_name := 'sub1',
    relation := 'public.clientes',
    truncate := true -- Borra los datos antes de volver a sincronizar
);

-- Ver el estado de todos los nodos
SELECT * FROM spock.node;

-- Ver el estado de las suscripciones
SELECT * FROM spock.subscription;

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 explicado los pasos necesarios para instalar y configurar Postgresql, así como la configuración de Spock para habilitar la replicación entre bases de datos. También hemos realizado una prueba de funcionamiento para verificar que la replicación está funcionando correctamente.