Scale-To-Zero bases de datos postgresql

created: miércoles, oct. 1, 2025

Siempre buscamos construir servicios lo más eficientes posible en cuanto a recursos. Esto es cierto para los servicios que ofrecemos externamente y, igual de importante, para nuestra propia infraestructura interna. Muchas de nuestras herramientas internas, como sistemas de facturación y monitoreo, dependen de bases de datos PostgreSQL. Aunque son esenciales, estas bases de datos a menudo permanecen inactivas durante largos periodos, consumiendo RAM y ciclos de CPU sin razón.

Entonces, nos preguntamos: ¿podemos aplicar nuestra filosofía de scale-to-zero a nuestras propias bases de datos? La respuesta es sí. Hemos desarrollado un sistema para aprovisionar instancias PostgreSQL que solo se ejecutan cuando están siendo usadas activamente. Este diseño es increíblemente eficiente en cuanto a recursos, pero sí implica algunos compromisos, que exploraremos.

Aquí hay una vista esquemática de lo que construimos y cómo logramos esta escalabilidad dinámica.

flowchart LR
  subgraph Machine
    A[systemd.socket]
    B[systemd-socket-proxyd]
    D[(local disk)]
    A -- port 15432 --> B

    subgraph Docker-Compose
      C[postgres container]
    end
    B -- port 5432 --> C
    C -- data-dir --> D
  end

  X[Internet] -- port 25432 --> A

La Magia de la Activación por Socket

El núcleo de esta configuración es la activación por socket de systemd. En lugar de tener un contenedor PostgreSQL ejecutándose 24/7, dejamos que el sistema init systemd escuche en el puerto de la base de datos. Cuando una aplicación intenta conectarse, systemd intercepta la solicitud, inicia el contenedor de la base de datos bajo demanda y luego transfiere la conexión. Una vez que la base de datos ya no está en uso, se apaga automáticamente.

Este enfoque combina el poder de herramientas estándar y probadas de Linux: systemd para la gestión de servicios y activación por socket, y Docker Compose para definir nuestro entorno de base de datos en contenedores. Es simple, robusto y no requiere software personalizado.

Nuestras Decisiones Tecnológicas: ¿Por Qué Contenedores y Docker Compose?

Tomamos dos decisiones tecnológicas específicas para esta configuración: ejecutar PostgreSQL en un contenedor y gestionarlo con Docker Compose.

Desglosemos los archivos de configuración que hacen esto posible.

Los Componentes

Usamos una combinación de un archivo docker-compose.yml para definir la base de datos y tres unidades systemd para gestionar el ciclo de vida scale-to-zero.

1. La Definición de la Base de Datos: Docker Compose

Este es un archivo estándar docker-compose.yml. Define un contenedor PostgreSQL 18, mapea un puerto interno al host y monta un volumen para persistir los datos de la base de datos en el disco local. Esto asegura que aunque el contenedor se detenga, los datos permanezcan seguros. Todas las configuraciones documentadas en la imagen oficial de PostgreSQL en Docker Hub pueden usarse aquí, permitiendo una mayor personalización como crear usuarios o bases de datos específicos al iniciar.

/root/pg/pg1/docker-compose.yml

 1version: "3"
 2services:
 3  database:
 4    image: 'postgres:18'
 5    ports:
 6      - 127.0.0.1:14532:5432
 7    volumes:
 8      - /root/pg/pg1/data:/var/lib/postgresql
 9    environment:
10      POSTGRES_PASSWORD: SuperSecretAdminPassword

2. El Receptor: systemd Socket

Esta unidad .socket le indica a systemd que escuche en el puerto 24532 en todas las interfaces de red. Cuando llega una conexión TCP, systemd activará pg1-proxy.service. Este es el punto de entrada para todas las conexiones de base de datos.

/etc/systemd/system/pg1-proxy.socket

 1[Unit]
 2Description=Socket for pg1 pg proxy (24532->127.0.0.1:14532)
 3
 4[Socket]
 5ListenStream=0.0.0.0:24532
 6ReusePort=true
 7NoDelay=true
 8Backlog=128
 9
10[Install]
11WantedBy=sockets.target

3. El Proxy y Temporizador de Inactividad: Servicio systemd

Aquí vive la lógica bajo demanda. Cuando es activado por el socket, este servicio primero inicia el servicio real de base de datos (Requires=pg1-postgres.service). El comando ExecStartPre es un pequeño pero crítico bucle shell que revisa repetidamente si el puerto interno de PostgreSQL está abierto. Sin esta verificación, podría ocurrir una condición de carrera donde el proxy arranca y reenvía la conexión del cliente antes de que el contenedor PostgreSQL termine de inicializarse. Esto resultaría en un error inmediato de “Conexión Rechazada” para el cliente. Este script previo asegura que la transferencia sea fluida y que el cliente sólo se conecte una vez que la base de datos esté completamente lista.

El proceso principal es systemd-socket-proxyd, una herramienta integrada que reenvía la conexión entrante al puerto interno donde el contenedor PostgreSQL está escuchando (127.0.0.1:14532). La parte clave es --exit-idle-time=3min. Esto indica al proxy que salga automáticamente si ha estado inactivo durante tres minutos.

/etc/systemd/system/pg1-proxy.service

 1[Unit]
 2Description=Socket-activated TCP proxy to local Postgres on 14532
 3
 4Requires=pg1-postgres.service
 5After=pg1-postgres.service
 6
 7[Service]
 8Type=simple
 9Sockets=pg1-proxy.socket
10ExecStartPre=/bin/bash -c 'for i in {1..10}; do nc -z 127.0.0.1 14532 && exit 0; sleep 1; done; exit 0'
11ExecStart=/usr/lib/systemd/systemd-socket-proxyd --exit-idle-time=3min 127.0.0.1:14532

4. El Gestor del Contenedor: Servicio systemd

Este servicio gestiona el ciclo de vida de Docker Compose. Es iniciado por el servicio proxy. La directiva clave es StopWhenUnneeded=true. Esto vincula su ciclo de vida al servicio proxy. Cuando pg1-proxy.service se detiene (porque su temporizador de inactividad expiró), systemd ve que este servicio ya no es necesario y lo detiene automáticamente ejecutando docker-compose down. El contenedor se apaga, liberando todos sus recursos.

/etc/systemd/system/pg1-postgres.service

 1[Unit]
 2Description=postgres container
 3PartOf=pg1-proxy.service
 4StopWhenUnneeded=true
 5
 6[Service]
 7WorkingDirectory=/root/pg/pg1
 8
 9Type=simple
10ExecStart=/usr/bin/docker-compose up
11ExecStop=/usr/bin/docker-compose down
12
13Restart=on-failure
14RestartSec=2s
15TimeoutStopSec=30s

El Compromiso: Arranques en Frío

Esta configuración es increíblemente eficiente, pero trae una consideración importante: la latencia del “arranque en frío”. La primera conexión a la base de datos luego de un periodo de inactividad tendrá un retraso. El cliente debe esperar a que systemd ejecute docker-compose up y que el contenedor PostgreSQL se inicialice. En nuestra experiencia, esto lleva alrededor de un segundo para una base de datos pequeña, pero aumenta con el tamaño del almacenamiento.

Para muchos sistemas internos—CI/CD, trabajos batch o paneles administrativos de uso poco frecuente—este retraso es un compromiso perfectamente aceptable por el ahorro significativo de recursos. Para aplicaciones de alta carga y sensibles a la latencia en producción, una base de datos tradicional siempre activa sigue siendo la opción correcta.

Habilitando el Servicio

Para poner en línea una nueva base de datos, solo necesitamos habilitar las unidades systemd.

1systemctl daemon-reload
2systemctl enable pg1-proxy.service
3systemctl enable pg1-postgres.service
4systemctl enable --now pg1-proxy.socket

Una vez habilitada, la base de datos está lista para aceptar conexiones, pero no consumirá recursos hasta que llegue la primera. Este es otro pequeño paso en nuestra misión de eliminar el desperdicio, demostrando que incluso infraestructura esencial como una base de datos relacional puede ejecutarse de forma ágil y bajo demanda.