Unidad 4.1: Integración de base de datos y caché
Introducción
Está construyendo la API de TaskBoard y la capa de datos debe ejecutarse en bases de datos administradas de IONOS CLOUD. Esto cambia algunas suposiciones que podría tener de otras nubes. Las bases de datos administradas aquí se acceden a través de una LAN privada, no de un punto de conexión público, todas las conexiones de cliente están cifradas con TLS, y los clústeres de bases de datos relacionales no exponen réplicas de lectura a las que pueda dirigir el tráfico de lectura (solo MongoDB Enterprise puede agregar secundarios legibles). Su palanca para escalar las lecturas no es un punto de conexión de réplica, sino la caché de In-Memory DB.
Esta unidad trata sobre el código de conexión. Conectará psycopg2 y SQLAlchemy con PostgreSQL, mysql-connector con MariaDB, pymongo con MongoDB, y redis-py con el clúster de In-Memory DB, todo con TLS y pooling. También abordará las dos realidades operativas que afectan a los desarrolladores: los límites de conexiones están fijados por la RAM del clúster y no pueden aumentarse mediante una bandera de cliente, y el Backup Service no realiza copias de seguridad de las bases de datos administradas, por lo que la recuperación depende del PITR propio de la base de datos y de sus propias volcados lógicos.
1. Conexión a Managed PostgreSQL
Los clústeres de PostgreSQL escuchan en el puerto 5432 y se encuentran en una LAN privada dentro de su centro de datos. El objeto del clúster contiene una datacenterId, una lanId y una primaryInstanceAddress, y su aplicación se conecta a la dirección de esa instancia principal. No existe un nombre de host público. El modo SSL predeterminado es prefer y TLS no puede desactivarse desde el cliente, por lo que debe planificar conexiones cifradas desde la primera línea de código.
Las versiones principales admitidas son 14, 15 y 16. El certificado del servidor se encadena con la raíz ISRG Root X1, por lo que un paquete de CA actualizado 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 de forma segura si no se puede negociar TLS. El host es la dirección de la instancia principal del clúster, accesible en su LAN privada.
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 SQL con f-strings; utilice marcadores de posición de parámetros, tal como se muestra arriba.
1.2 Los límites de conexión están fijados por la RAM
El valor de max_connections se calcula a partir de la RAM del clúster y no es configurable por el usuario. La siguiente tabla muestra la correspondencia exacta que aplica la plataforma.
| Tamaño de RAM | max_connections |
|---|---|
| 4 GB | 384 |
| 5 GB | 512 |
| 6 GB | 640 |
| 7 GB | 768 |
| 8 GB | 896 |
| >8 GB | 1000 |
De ese total, 11 conexiones están reservadas, por lo que el pool de su aplicación debe mantenerse por debajo del número publicado menos los slots reservados. Dado que este límite es fijo, no puede resolver el problema de "demasiadas conexiones" aumentando una configuración del servidor. Lo resuelve mediante pooling, como se explica a continuación.
2. Puesta en cola de conexiones
No hay réplicas de solo lectura. Cada lectura y cada escritura se dirige a la única instancia principal, por lo que una aplicación sin límites que abre una conexión por solicitud agotará rápidamente el límite fijo de max_connections. La puesta en cola de conexiones es obligatoria, no opcional, y dispone de dos capas que deben usarse juntas: una cola a nivel de aplicación y el gestor de colas PgBouncer administrado.
2.1 Puesta en cola a nivel de aplicación con SQLAlchemy
Establezca el límite de la cola de SQLAlchemy por debajo del límite del clúster y active pool_pre_ping para que las conexiones obsoletas se reciclen en lugar de entregarse a una solicitud durante una avería.
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 la aplicación, el clúster ve N * (pool_size + max_overflow) conexiones. Dimensione el pool en función del total del clúster dividido por el número de réplicas, dejando un margen.
2.2 Pooler PgBouncer administrado
Los clústeres de PostgreSQL ofrecen un pooler PgBouncer administrado. 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 las cargas de trabajo web típicas, de modo que una conexión de backend se mantenga solo durante la duración de una transacción, lo que multiplica la cantidad de clientes que el límite fijo de conexiones puede atender. Las funciones con ámbito de sesión, como las sentencias preparadas y SET, que deben persistir a lo largo de una sesión, requieren el modo session.
3. Integración de MariaDB y MongoDB
PostgreSQL es uno de varios motores administrados, y el modelo de conexión es coherente: LAN privada, TLS obligatorio, límites fijos. El controlador y el cálculo de los límites de conexión varían según el 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 conjunto de CAs estándar lo valida. Solo se ofrecen versiones con soporte a largo plazo, a partir de 10.6 (por ejemplo, 10.6 y 10.11). La replicación es solo asíncrona.
El max_connections del clúster es 500, y cada usuario tiene un límite de 250 conexiones. Ese límite por usuario es intencional: dado que un usuario puede ocupar como máximo 250 de los 500 espacios, es mucho menos probable que una sola aplicación descontrolada agote todo el clúster.
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 máximo de 2 TB por clúster, y una sola instancia no puede superar los 16 núcleos.
3.2 MongoDB
Los clústeres de MongoDB presentan una cadena de conexión en el formato mongodb+srv://m-<id>.mongodb.<region>.ionos.com, que normalmente se lee desde una salida de Terraform en lugar de codificarla de forma estática. Las versiones admitidas son 6.0 y 7.0. No se proporciona agrupación de conexiones a nivel de servidor, por lo que la agrupación a nivel de controlador en pymongo es su agrupación.
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}))
La cantidad máxima de conexiones escala en función de la RAM. La siguiente tabla muestra la asignación aplicada.
| RAM (GB) | max_connections |
|---|---|
| 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 limitado a la base de datos taskboard para el usuario de la aplicación, en lugar de un rol de administrador a nivel de clúster.
4. Caché de In-Memory DB (Redis)
Dado que los motores relacionales no exponen réplicas de lectura, el clúster de In-Memory DB es la forma nativa de IONOS CLOUD para escalar las lecturas relacionales. Es compatible con Redis OSS 7.2 y escucha en el puerto 6379. TLS utiliza una autoridad de certificación de Let's Encrypt una vez que configura la instancia para usarlo (TLS no está habilitado de forma predeterminada, por lo que debe habilitarlo de forma explícita, como lo hace el ejemplo de redis-py a continuación con ssl=True). Un clúster tiene hasta 5 nodos. El motor subyacente Valkey establece un máximo predeterminado de 10.000 conexiones de cliente (maxclients).
La política de expulsión predeterminada es allkeys-lru y la persistencia predeterminada es None, lo cual es la postura correcta para una caché pura: trátela como volátil y asegúrese siempre de poder 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 Cache-Aside
Cache-aside es la ruta de lectura predeterminada: se consulta la caché, se recurre a la base de datos si no hay coincidencia y, a continuación, se rellena la caché. Establezca un TTL para que las entradas obsoletas caduquen, incluso si ninguna escritura las invalida.
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 directa (Write-Through)
El patrón de escritura directa (write-through) actualiza la base de datos y la caché en la misma operación, de modo que las lecturas nunca devuelvan un valor obsoleto después de una escritura. Escriba primero en la base de datos y actualice la caché solo cuando el commit tenga éxito.
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 encaja en TaskBoard en dos áreas más: el almacenamiento de sesiones y la caché de los resultados de la consulta de la lista de tareas, que de otro modo sobrecargaría la única instancia 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 recuperación en un punto específico en el tiempo de la propia base de datos, no la de Backup Service. Backup Service no realiza copias de seguridad de DBaaS gestionadas, por lo que no debe suponer que los datos de sus tareas están capturados por una unidad de copia de seguridad. Para exportaciones lógicas que superen la ventana de PITR, programe pg_dump o la herramienta de volcado de MariaDB desde el código de la aplicación o una tarea cron, y almacene la salida en Object Storage.
PostgreSQL y MariaDB utilizan por defecto una ventana de recuperación en un punto específico en el tiempo de 7 días en los clústeres v1, pero en sus APIs v2 la retención es configurable de 1 a 365 días mediante backup.retentionDays, por lo que verifique la versión de la API y la configuración de retención de su clúster antes de suponer una ventana fija de 7 días. PITR se ejecuta a través de la API REST. La ruta base para PostgreSQL es https://api.ionos.com/databases/postgresql, y una restauración es una operación in situ, la base de datos no está disponible mientras se ejecuta.
5.1 Activación de PITR de PostgreSQL mediante 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. Restablezca las restricciones del código alrededor: solo se puede restaurar una copia de seguridad a la vez, el clúster debe estar AVAILABLE antes de que inicie una restauración, solo puede restaurar desde la misma versión principal o una 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, lo que puede reducir después.
Tarjeta rápida de referencia de la API
Puntos finales de API clave para la integración de bases de datos administradas:
| Método | Punto final | Descripción |
|---|---|---|
POST |
/databases/postgresql/clusters |
Crear un clúster de PostgreSQL |
GET |
/databases/postgresql/clusters/{clusterId} |
Obtener detalles del clúster, incluida la información de conexión |
PATCH |
/databases/postgresql/clusters/{clusterId} |
Modificar la configuración de conexión (habilitar PgBouncer, modo de agrupación) |
POST |
/databases/postgresql/clusters/{clusterId}/restore |
Activar una restauración PITR in situ |
POST |
/clusters (en in-memory-db.{region}.ionos.com) |
Crear un clúster de In-Memory DB (API v2, recomendada; la superficie de la v1 heredada /replicasets tiene un anuncio de obsolescencia) |
URL base: https://api.ionos.com/databases/postgresql (los 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 API TaskBoard con PostgreSQL y la caché de In-Memory DB, insertar una tarea, poner en caché la lectura y verificar TLS.
Requisitos previos:
- Cuenta de IONOS CLOUD con token de API
- Un clúster de PostgreSQL en ejecución y un conjunto de réplicas de In-Memory DB en una LAN privada compartida
- Python 3.10 o superior con
psycopg2-binary,sqlalchemyyredisinstalados - La
primaryInstanceAddressdel clúster y la dirección del nodo de In-Memory DB
Paso 1: Verificar TLS con 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 la 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: Caché de la lectura (cache-aside)
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 de validación:
- [ ]
psqlinformassl = t, confirmando que TLS está activo - [ ] Fila de tarea insertada y devolvió un id mediante SQLAlchemy
- [ ]
r.ping()devuelveTruea través de 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 desarrollo a evitar con la integración de bases de datos administradas:
-
Abrir una conexión por solicitud y agotar el límite
- Problema: Bajo carga, la aplicación lanza
FATAL: too many connections for roleosorry, too many clients already. - Causa:
max_connectionsestá fijado por la RAM del clúster (por ejemplo, 1000 para más de 8 GB), se reservan 11 ranuras y no hay réplicas de lectura para distribuir la carga. Una aplicación sin pool multiplica las conexiones por el número de réplicas. - Solución: Establezca el límite del pool por debajo del máximo y dirija el tráfico a través de PgBouncer en modo
transactionen el puerto6432:
create_engine(url, pool_size=20, max_overflow=10, pool_pre_ping=True) - Problema: Bajo carga, la aplicación lanza
-
Suponiendo que Backup Service protege los datos de su base de datos
- Problema: Una migración fallida elimina una tabla y no hay unidad de copia de seguridad desde la cual restaurar.
- Por qué ocurre: Backup Service no realiza copias de seguridad de DBaaS administrado. Los desarrolladores asumen que una sola herramienta de copia de seguridad cubre todo.
- Solución: Confiar en la recuperación en un punto en el tiempo (PITR) de la base de datos (ventana predeterminada de 7 días, configurable de 1 a 365 días en la API v2 mediante
backup.retentionDays) para la recuperación reciente y programar volcados lógicos para datos anteriores:
pg_dump "host=10.7.222.10 sslmode=require dbname=taskboard" | gzip > taskboard-$(date +%F).sql.gz -
Deshabilitar TLS para "hacer que funcione"
- Problema: La conexión falla localmente, por lo que un desarrollador recurre a
sslmode=disable. - Por qué ocurre: Un paquete de CA ausente o desactualizado hace que la validación del certificado falle, y la solución rápida parece ser desactivar TLS.
- Solución: TLS no puede deshabilitarse por parte del cliente en PostgreSQL (el modo predeterminado es
prefery no puede deshabilitarse), por lo que debe corregirse la cadena de CA. PostgreSQL se encadena aISRG Root X1, mientras que MariaDB e In-Memory DB utilizan Let's Encrypt. Actualice el paquete de CA del sistema y mantengasslmode=require.
- Problema: La conexión falla localmente, por lo que un desarrollador recurre a
Resumen
Ahora puede conectar el código de aplicaciones de producción a cada motor de base de datos administrado de IONOS CLOUD con TLS aplicado y conexiones agrupadas, y puede recuperar datos mediante el mecanismo adecuado. El modelo de conexión es coherente entre motores: LAN privada, transporte cifrado y un límite fijo de conexiones que debe respetar mediante agrupación, en lugar de luchar con un flag del servidor. La caché de In-Memory DB es su herramienta de escalado de lecturas, ya que los motores relacionales no exponen réplicas de lectura.
Específicamente para TaskBoard, PostgreSQL almacena las tareas, Redis cachea lecturas y sesiones, y la recuperación consiste en PITR de la base de datos más sus propias volcados programados. Construya esas utilidades de conexión una sola vez, con agrupación y TLS integrados, y reutilícelas en todos los servicios de la aplicación.
Puntos clave:
- Las bases de datos administradas se acceden a través de la LAN privada con TLS aplicado, nunca mediante un punto de acceso público
max_connectionsestá fijado por la RAM del clúster y no es configurable por el usuario, por lo que la agrupación es obligatoria- Los motores relacionales no tienen réplicas de lectura; la caché de In-Memory DB es la solución nativa de IONOS CLOUD para el escalado de lecturas
- PgBouncer en modo
transactionen el puerto6432multiplica los clientes que un límite fijo puede atender - Backup Service no realiza copias de seguridad de DBaaS; la recuperación es PITR de la base de datos (predeterminado de 7 días, configurable de 1 a 365 días en la API v2) más sus propios volcados
Terminología importante:
- PITR: Recuperación en un punto en el tiempo, que restaura un clúster a una marca de tiempo ISO-8601 dentro de la ventana de retención (predeterminado de 7 días; de 1 a 365 días configurable en la API v2) mediante la API REST
- PgBouncer: El agrupador de conexiones administrado de PostgreSQL en el puerto
6432, que admite los modos de agrupacióntransactionysession - Cache-aside: Un patrón de lectura que consulta la caché, recurre a la base de datos en caso de fallo y luego rellena 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 de LAN privada de la instancia principal de un clúster de base de datos a la que se conecta la aplicación
Próximos pasos
Continuar aprendiendo: Unidad 4.2: Integración de Object Storage
Temas relacionados: