17 min de lectura

Objetivos de aprendizaje

Al final de este módulo, podrás:

  • Conectar el código de aplicación a Managed PostgreSQL, MariaDB y MongoDB a través de la red privada LAN con TLS aplicado
  • Implementar la agrupación de conexiones a nivel de aplicación para proteger la instancia principal, ya que DBaaS no expone réplicas de lectura para absorber el tráfico de lectura
  • Integrar la caché In-Memory DB (Redis) utilizando patrones de cache-aside y write-through como la solución de escalado de lectura nativa de IONOS
  • Activar la recuperación punto-en-el-tiempo para PostgreSQL y MariaDB de forma programática a través de la REST API
  • Conectar la TaskBoard API a PostgreSQL para el almacenamiento de tareas y a Redis para la caché de sesión y lista de tareas

Unidad 4.1: Integración de base de datos y caché

Introducción

Está construyendo el TaskBoard API y el nivel de datos tiene que ejecutarse en bases de datos gestionadas de IONOS. Esto cambia algunas suposiciones que puede tener de otras nubes. Las bases de datos gestionadas aquí se acceden a través de una LAN privada, no a través de un punto de conexión público, cada conexión de cliente está cifrada con TLS, y los clústeres de bases de datos no exponen réplicas de lectura a las que pueda dirigir el tráfico de lectura. Su palanca de escalado de lectura no es un punto de conexión de réplica, es la caché de In-Memory DB.

Esta unidad es código de conexión. Conectará psycopg2 y SQLAlchemy a PostgreSQL, MySQL-conector a MariaDB, pymongo a MongoDB, y Redis-py a la caché de In-Memory DB Cluster, todo con TLS y agrupación. También manejará las dos realidades operativas que afectan a los desarrolladores: los techos de conexión están fijados por Cluster RAM y no pueden ser aumentados por una bandera de cliente, y el Backup Service no respalda las bases de datos gestionadas, por lo que la recuperación es la propia recuperación ante desastres en el punto en el tiempo (PITR) de la base de datos y sus propias copias de seguridad lógicas.

1. Conexión a Managed PostgreSQL

Los clústeres de PostgreSQL escuchan en el puerto 5432 y viven en una red privada LAN dentro de su centro de datos. El objeto Cluster contiene una datacenterId, una lanId y una primaryInstanceAddress, y su aplicación se conecta a esa dirección de instancia principal. No hay un nombre de host público. El modo de SSL predeterminado es prefer y TLS no se puede deshabilitar en el cliente, así que planee conexiones cifradas desde la primera línea de código.

Las versiones principales admitidas son 14, 15 y 16. La cadena de certificado del servidor se conecta a la raíz ISRG Root X1, por lo que un paquete de CA actual en el host de su aplicación valida la conexión sin configuración adicional.

1.1 Conexión con psycopg2

Conéctese con sslmode=require para que el controlador falle cerrado si TLS no se puede negociar. El host es la dirección de la instancia principal de Cluster accesible en su red privada LAN.

import psycopg2

conn = psycopg2.connect(
    host="10.7.222.10",        # primaryInstanceAddress from the cluster object
    port=5432,
    dbname="taskboard",
    user="taskboard_app",
    password="<from-secret-store>",
    sslmode="require",
    connect_timeout=10,
)
conn.autocommit = False

with conn.cursor() as cur:
    cur.execute(
        "INSERT INTO tasks (title, status) VALUES (%s, %s) RETURNING id",
        ("Ship unit 4.1", "open"),
    )
    task_id = cur.fetchone()[0]
    conn.commit()
print(f"Inserted task {task_id}")

Para asyncpg en un servicio asíncrono, pase ssl="require" y proporcione el mismo host, puerto y credenciales. Nunca construya cadenas de SQL con f-strings, use marcadores de parámetros como se muestra arriba.

1.2 Los límites de conexión están fijados por RAM

El valor de max_connections se calcula a partir de Cluster RAM y no es configurable por el usuario. La siguiente tabla es el mapeo exacto aplicado por la plataforma.

Tamaño de RAM max_conexiones
4 GB 384
5 GB 512
6 GB 640
7 GB 768
8 GB 896
>8 GB 1000

De estos, 11 conexiones están reservadas, por lo que su grupo de aplicaciones debe permanecer por debajo del número publicado menos las ranuras reservadas. Dado que este límite es fijo, no puede resolver "demasiadas conexiones" aumentando una configuración del servidor. Lo resuelve con agrupación, que se cubre a continuación.

2. Agrupación de Conexiones

No hay réplicas de lectura. Cada lectura y cada escritura golpea la instancia principal única, por lo que una aplicación ilimitada que abre una conexión por solicitud agotará rápidamente el límite fijo de max_connections . La agrupación es obligatoria, no opcional, y usted tiene dos capas para usar juntas: un pool a nivel de aplicación y el pooler gestionado de PgBouncer.

2.1 Pool a Nivel de Aplicación con SQLAlchemy

Limite el pool de SQLAlchemy bien por debajo del límite de Cluster y habilite pool_pre_ping para que las conexiones obsoletas se reciclen en lugar de ser asignadas a una solicitud en medio de un fallo.

from sqlalchemy import create_engine, text

engine = create_engine(
    "postgresql+psycopg2://taskboard_app:<pw>@10.7.222.10:5432/taskboard",
    connect_args={"sslmode": "require"},
    pool_size=20,          # steady-state connections
    max_overflow=10,       # burst headroom, keep total well under the ceiling
    pool_pre_ping=True,
    pool_recycle=1800,
)

with engine.connect() as conn:
    rows = conn.execute(text("SELECT id, title FROM tasks WHERE status = :s"),
                        {"s": "open"}).fetchall()

Si ejecuta N réplicas de aplicación, la Cluster ve N * (pool_size + max_overflow) conexiones. Dimensione el pool en función del total de Cluster dividido por su recuento de réplicas, dejando un margen.

2.2 Pooler Gestionado de PgBouncer

Los clústeres de PostgreSQL ofrecen un pooler gestionado de PgBouncer. Usted lo habilita y elige el modo de pool, los modos admitidos son transaction (predeterminado) y session. El pooler escucha en el puerto 6432 en lugar del puerto de la base de datos 5432.

# Route the app through PgBouncer: same host, pooler port 6432
engine = create_engine(
    "postgresql+psycopg2://taskboard_app:<pw>@10.7.222.10:6432/taskboard",
    connect_args={"sslmode": "require"},
    pool_size=10, max_overflow=5, pool_pre_ping=True,
)

Utilice el modo transaction para cargas de trabajo web típicas para que una conexión de backend se mantenga solo durante la duración de una transacción, lo que multiplica el número de clientes que el límite de conexión fijo puede servir. Las características de ámbito de sesión, como las declaraciones preparadas y SET que deben persistir en toda una sesión, necesitan el modo session.

3. MariaDB y MongoDB Integración

PostgreSQL es uno de los varios motores gestionados, y el modelo de conexión es coherente: LAN privado, TLS aplicado, límites fijos. El controlador y la matemática de límite de conexión difieren por motor.

3.1 MariaDB

MariaDB escucha en el puerto 3306. Todas las conexiones de cliente están cifradas con TLS y el certificado del servidor es emitido por Let's Encrypt, por lo que un paquete de CA estándar lo valida. Solo se ofrecen versiones de Soporte a Largo Plazo, a partir de la 10.6 (por ejemplo, 10.6 y 10.11). La replicación es asíncrona solo.

El Cluster max_connections es 500, y cada usuario tiene un límite de 250 conexiones. Ese límite por usuario es deliberado: porque un usuario puede tomar como máximo 250 de los 500 espacios, una sola aplicación de usuario desbordada es mucho menos probable que agote todo el Cluster.

import mysql.connector

cnx = mysql.connector.connect(
    host="10.7.222.20",
    port=3306,
    database="taskboard",
    user="taskboard_app",
    password="<pw>",
    ssl_disabled=False,        # TLS is enforced server-side regardless
    pool_name="taskboard_pool",
    pool_size=20,
)
cur = cnx.cursor()
cur.execute("SELECT id, title FROM tasks WHERE status = %s", ("open",))
for task_id, title in cur.fetchall():
    print(task_id, title)
cur.close(); cnx.close()

El almacenamiento tiene un límite de 2 TB por Cluster, y una sola instancia no puede exceder los 16 núcleos.

3.2 MongoDB

Los clústeres de MongoDB presentan una cadena de conexión en el formulario mongodb+srv://m-<id>.mongodb.<region>.ionos.com, que normalmente se lee desde una salida de Terraform en lugar de codificarla de forma rígida. Las versiones compatibles son 6.0 y 7.0. No se proporciona agrupación de conexiones en el lado del servidor, por lo que el grupo de controladores en pymongo es su grupo.

from pymongo import MongoClient

uri = "mongodb+srv://taskboard_app:<pw>@m-abc123.mongodb.de-txl.ionos.com"
client = MongoClient(
    uri,
    tls=True,
    maxPoolSize=50,
    serverSelectionTimeoutMS=5000,
    retryWrites=True,
)
db = client["taskboard"]
db.task_events.insert_one({"task_id": 42, "event": "created"})
print(db.task_events.count_documents({"task_id": 42}))

El recuento máximo de conexiones se escala con RAM. La siguiente tabla es el mapeo aplicado.

RAM (GB) max_conexiones
2 500 (Sandbox)
4 1000
6 2000
8 3000
12 5000
16 7000
24 11000
32 15000
48 23000
64 31000

La asignación de roles utiliza los roles integrados de MongoDB, como read, readWrite, dbAdmin y clusterMonitor. Otorgue readWrite con el alcance de la base de datos taskboard para el usuario de la aplicación en lugar de un rol de administrador Cluster.

4. In-Memory DB (Redis) Caching

Dado que no hay un motor gestionado que exponga réplicas de lectura, In-Memory DB Cluster es la forma nativa de IONOS para escalar lecturas. Es compatible con Redis OSS 7.2 y escucha en el puerto 6379, y TLS utiliza una autoridad de certificado Let's Encrypt una vez que configura la instancia para usarla (TLS no está habilitado de forma predeterminada, así que habilitarlo explícitamente, como el ejemplo Redis-py a continuación lo hace con SSL=True). Un Cluster tiene hasta 5 nodos. El motor Valkey subyacente tiene un máximo de 10,000 conexiones de cliente (maxclients) de forma predeterminada.

La política de expulsión predeterminada es allkeys-lru y la persistencia predeterminada es None, que es la postura correcta para una caché pura: trátela como volátil y siempre sea capaz de reconstruirla desde la base de datos de origen.

4.1 Conexión con Redis-py

import redis

r = redis.Redis(
    host="10.7.222.30",
    port=6379,
    password="<pw>",
    ssl=True,
    ssl_cert_reqs="required",
    socket_timeout=2,
    decode_responses=True,
)
r.set("health", "ok", ex=30)
print(r.get("health"))

4.2 Patrón de caché adyacente

El patrón de caché adyacente es el camino de lectura predeterminado: verificar la caché, volver a la base de datos en caso de fallo, y luego poblar la caché. Establezca un TTL para que las entradas obsoletas expiren incluso si no hay escritura que las invalide.

import json

def get_task(task_id, r, engine):
    key = f"task:{task_id}"
    cached = r.get(key)
    if cached:
        return json.loads(cached)              # cache hit
    with engine.connect() as conn:
        row = conn.execute(
            text("SELECT id, title, status FROM tasks WHERE id = :id"),
            {"id": task_id}).mappings().first()
    if row:
        r.set(key, json.dumps(dict(row)), ex=300)   # populate with 5 min TTL
    return dict(row) if row else None

4.3 Patrón de escritura

El patrón de escritura actualiza la base de datos y la caché en la misma operación, por lo que las lecturas nunca sirven un valor obsoleto después de una escritura. Escriba en la base de datos primero y solo actualice la caché una vez que se confirme el compromiso.

def update_task_status(task_id, status, r, engine):
    with engine.begin() as conn:                # commits on context exit
        conn.execute(text("UPDATE tasks SET status = :s WHERE id = :id"),
                     {"s": status, "id": task_id})
    r.set(f"task:{task_id}", json.dumps({"id": task_id, "status": status}),
          ex=300)

Redis se adapta a TaskBoard en dos lugares más: almacenamiento de sesión y almacenamiento en caché de los resultados de la consulta de la lista de tareas que de otro modo golpearían el único servidor principal en cada carga de página.

5. Recuperación: PITR y la brecha de Backup Service

La recuperación para bases de datos gestionadas es la propia recuperación en un momento dado de la base de datos, no la de Backup Service. La de Backup Service no crea copias de seguridad de las bases de datos gestionadas DBaaS, por lo que no debe asumir que sus datos de tareas están capturados por una unidad de copia de seguridad. Para las exportaciones lógicas más allá de la ventana de PITR, programe pg_dump o la herramienta de volcado de MariaDB desde el código de la aplicación o un trabajo cron y almacene la salida en Object Storage.

PostgreSQL y MariaDB ambos tienen como valor predeterminado una ventana de recuperación en un momento dado de 7 días en los clusters v1, pero en sus API v2 la retención es configurable desde 1 hasta 365 días a través de backup.retentionDays, así que verifique la versión y la configuración de retención de su Cluster antes de asumir una ventana fija de 7 días. PITR se ejecuta a través de REST API. La ruta base para PostgreSQL es https://api.ionos.com/databases/postgresql, y una restauración es una operación en el lugar, la base de datos no está disponible mientras se ejecuta.

5.1 Desencadenando la recuperación en un momento dado de PostgreSQL a través de API

curl -X POST \
  "https://api.ionos.com/databases/postgresql/clusters/${CLUSTER_ID}/restore" \
  -H "Authorization: Bearer ${IONOS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "recoveryTargetTime": "2026-06-04T09:30:00Z"
  }'

El recoveryTargetTime es una marca de tiempo ISO-8601 y no es inclusiva. Restricciones para restaurar el código: solo se puede restaurar una copia de seguridad a la vez, el Cluster debe estar AVAILABLE antes de que desencadenes una restauración, solo se puede restaurar desde la misma versión principal o una versión anterior, y una restauración puede mover la base de datos a otra región. La plataforma recomienda al menos 4 GB de RAM durante la restauración, que puede reducir de nuevo después.

API Tarjeta de referencia rápida

Puntos de conexión clave de API para la integración de bases de datos gestionadas:

Método Punto de conexión Descripción
POST /databases/postgresql/clusters Crear una PostgreSQL Cluster
GET /databases/postgresql/clusters/{clusterId} Obtener detalles de Cluster, incluyendo información de conexión
PATCH /databases/postgresql/clusters/{clusterId} Modificar la configuración de conexión (habilitar PgBouncer, modo de grupo)
POST /databases/postgresql/clusters/{clusterId}/restore Activar la restauración PITR en su lugar
POST /clusters (en in-memory-db.{region}.ionos.com) Crear una In-Memory DB Cluster (v2 API, recomendado; la superficie heredada v1 /replicasets tiene un anuncio de deprecación)

URL base: https://api.ionos.com/databases/postgresql (hosts específicos de la región se aplican a In-Memory DB y MariaDB) Autenticación: Authorization: Bearer <token>

Laboratorio de código

Objetivo: Conectar la TaskBoard API a PostgreSQL y el caché In-Memory DB, insertar una tarea, cachear la lectura y verificar TLS.

Requisitos previos:

  • Cuenta de IONOS Cloud con token API
  • Un PostgreSQL Cluster en ejecución y un conjunto de réplicas In-Memory DB en una red privada compartida LAN
  • Python 3.10+ con psycopg2-binary, sqlalchemy, y redis instalados
  • La dirección primaryInstanceAddress de Cluster y la dirección Node de In-Memory DB

Paso 1: Verificar TLS en PostgreSQL

PGPASSWORD=$PW psql "host=10.7.222.10 port=5432 dbname=taskboard user=taskboard_app sslmode=require" -c "SELECT ssl FROM pg_stat_ssl WHERE pid = pg_backend_pid();"

Salida esperada:

 ssl
-----
 t

Paso 2: Crear la tabla de tareas

PGPASSWORD=$PW psql "host=10.7.222.10 sslmode=require dbname=taskboard user=taskboard_app" \
  -c "CREATE TABLE IF NOT EXISTS tasks (id SERIAL PRIMARY KEY, title TEXT, status TEXT);"

Salida esperada:

CREATE TABLE

Paso 3: Insertar una tarea desde Python

from sqlalchemy import create_engine, text
engine = create_engine("postgresql+psycopg2://taskboard_app:<pw>@10.7.222.10:5432/taskboard",
                       connect_args={"sslmode": "require"}, pool_size=5, pool_pre_ping=True)
with engine.begin() as c:
    tid = c.execute(text("INSERT INTO tasks (title, status) VALUES (:t, 'open') RETURNING id"),
                    {"t": "Lab task"}).scalar()
print("task id:", tid)

Salida esperada:

task id: 1

Paso 4: Conectar con el caché

import redis
r = redis.Redis(host="10.7.222.30", port=6379, password="<pw>", ssl=True,
                ssl_cert_reqs="required", decode_responses=True)
print(r.ping())

Salida esperada:

True

Paso 5: Cachear la lectura (al lado del caché)

import json
key = f"task:{tid}"
if not r.get(key):
    with engine.connect() as c:
        row = c.execute(text("SELECT id, title, status FROM tasks WHERE id = :id"),
                        {"id": tid}).mappings().first()
    r.set(key, json.dumps(dict(row)), ex=300)
print(r.get(key))

Salida esperada:

{"id": 1, "title": "Lab task", "status": "open"}

Paso 6: Confirmar que la segunda lectura es un acierto de caché

print("cached:", r.ttl(key), "seconds remaining")

Salida esperada:

cached: 300 seconds remaining

Lista de verificación:

  • [ ] psql informa ssl = t, confirmando que TLS está activo
  • [ ] La fila de la tarea se insertó y devolvió un id a través de SQLAlchemy
  • [ ] r.ping() devuelve True sobre TLS
  • [ ] La segunda lectura devuelve el valor desde Redis, no desde PostgreSQL

Limpieza:

PGPASSWORD=$PW psql "host=10.7.222.10 sslmode=require dbname=taskboard user=taskboard_app" -c "DROP TABLE tasks;"
# Flush the lab cache key
redis-cli -h 10.7.222.30 -p 6379 -a "$PW" --tls DEL task:1

Errores comunes

Errores de los desarrolladores que deben evitarse con la integración de bases de datos gestionadas:

  1. Abrir una conexión por solicitud y agotar el límite

    • Problema: Bajo carga, la aplicación lanza FATAL: too many connections for role o sorry, too many clients already.
    • Por qué sucede: max_connections es fijo en Cluster RAM (por ejemplo, 1000 por encima de 8 GB), 11 ranuras están reservadas y no hay réplicas de lectura para distribuir la carga. Una aplicación no agrupada multiplica las conexiones por el recuento de réplicas.
    • Solución: Limitar el grupo bien por debajo del límite y enrutrar a través de PgBouncer en transaction modo en el puerto 6432:
    create_engine(url, pool_size=20, max_overflow=10, pool_pre_ping=True)
    
  2. Asumiendo que el Backup Service protege sus datos de base de datos

    • Problema: Una mala migración elimina una tabla y no hay unidad de copia de seguridad para restaurar.
    • Por qué sucede: El Backup Service no respalda la base de datos gestionada DBaaS. Los desarrolladores asumen que una herramienta de copia de seguridad cubre todo.
    • Solución: Confíe en la recuperación PITR de la base de datos (ventana predeterminada de 7 días, configurable de 1 a 365 días en el v2 API a través de backup.retentionDays) para la recuperación reciente y programe volcados lógicos para cualquier cosa más antigua:
    pg_dump "host=10.7.222.10 sslmode=require dbname=taskboard" | gzip > taskboard-$(date +%F).sql.gz
    
  3. Deshabilitar TLS para "hacer que funcione"

    • Problema: La conexión falla localmente, por lo que un desarrollador recurre a sslmode=disable.
    • Por qué sucede: Un paquete de certificación de autoridad (CA) faltante o desactualizado falla en la validación de certificados, y la solución rápida parece ser deshabilitar TLS.
    • Solución: TLS no se puede deshabilitar por el cliente en PostgreSQL (el modo predeterminado es prefer y no se puede deshabilitar), así que corrige la cadena de certificación CA en su lugar. PostgreSQL se encadena a ISRG Root X1, MariaDB y In-Memory DB utilizan Let's Encrypt. Actualice el paquete de certificación de autoridad del sistema y mantenga sslmode=require.

Resumen

Ahora puede conectar el código de aplicación de producción a cada motor de base de datos gestionado por IONOS con TLS aplicado y conexiones agrupadas, y puede recuperar datos a través del mecanismo adecuado. El modelo de conexión es coherente en todos los motores: LAN privado, transporte cifrado y un límite de conexión fijo que respeta con agrupación en lugar de luchar con una bandera de servidor. La caché In-Memory DB es su herramienta de escalado de lectura porque ninguno de los motores de base de datos expone réplicas de lectura.

Para TaskBoard específicamente, PostgreSQL mantiene las tareas, Redis almacena en caché las lecturas y sesiones, y la recuperación es PITR de base de datos más sus propias copias de seguridad programadas. Construya esos ayudantes de conexión una vez, con agrupación y TLS integrados, y reutilícelos en cada servicio de la aplicación.

Puntos clave:

  • Las bases de datos gestionadas se acceden a través del LAN privado con TLS aplicado, nunca a través de un punto de conexión público
  • max_connections está fijado por Cluster RAM y no es configurable por el usuario, por lo que la agrupación es obligatoria
  • No hay réplicas de lectura, la caché In-Memory DB es la solución de escalado de lectura nativa de IONOS
  • PgBouncer en modo transaction en el puerto 6432 multiplica los clientes que puede atender un límite fijo
  • El Backup Service no respalda DBaaS, la recuperación es PITR de base de datos (7 días por defecto, configurable de 1 a 365 días en la v2 API) más sus propias copias de seguridad

Terminología importante:

  • PITR: Recuperación en un momento específico, restaurando una Cluster a una marca de tiempo ISO-8601 dentro de la ventana de retención (7 días por defecto; 1-365 días configurable en la v2 API) a través de la REST API
  • PgBouncer: El agrupador de conexiones Managed PostgreSQL en el puerto 6432, que admite los modos de agrupación transaction y session
  • Cache-aside: Un patrón de lectura que comprueba la caché, vuelve a la base de datos en caso de error y luego pobló la caché con un TTL
  • Write-through: Un patrón de escritura que actualiza la base de datos y la caché en la misma operación para evitar lecturas obsoletas
  • primaryInstanceAddress: La dirección LAN privada de la instancia principal de una base de datos Cluster a la que se conecta la aplicación

Próximos pasos

Continuar aprendiendo: Unidad 4.2: Object Storage Integración

Temas relacionados: