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, yredisinstalados - La dirección
primaryInstanceAddressde 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:
- [ ]
psqlinformassl = t, confirmando que TLS está activo - [ ] La fila de la tarea se insertó y devolvió un id a través de SQLAlchemy
- [ ]
r.ping()devuelveTruesobre 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:
-
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. - Por qué sucede:
max_connectionses 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
transactionmodo en el puerto6432:
create_engine(url, pool_size=20, max_overflow=10, pool_pre_ping=True) - Problema: Bajo carga, la aplicación lanza
-
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 -
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
prefery no se puede deshabilitar), así que corrige la cadena de certificación CA en su lugar. PostgreSQL se encadena aISRG Root X1, MariaDB y In-Memory DB utilizan Let's Encrypt. Actualice el paquete de certificación de autoridad 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 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_connectionsestá 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
transactionen el puerto6432multiplica 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óntransactionysession - 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: