Unidad 4.1: Integración de base de datos y almacenamiento en caché
Introducción
Está construyendo la TaskBoard API y la capa de datos debe ejecutarse en las bases de datos gestionadas de IONOS CLOUD. Eso cambia algunas suposiciones que puede traer de otras nubes. Aquí, las bases de datos gestionadas se alcanzan a través de una LAN privada, no de un endpoint público, cada conexión de cliente está cifrada con TLS, y los clústeres de bases de datos relacionales no exponen read replicas a las que dirigir el tráfico de lectura (solo MongoDB Enterprise puede añadir secundarios legibles). Su palanca de escalado de lecturas no es un endpoint de réplica, es la caché de In-Memory DB.
Esta unit es código de conexión. Conectará psycopg2 y SQLAlchemy a PostgreSQL, mysql-connector a MariaDB, pymongo a MongoDB y redis-py al clúster de In-Memory DB, todo con TLS y pooling. También manejará 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 con un flag del cliente, y el Backup Service no respalda las bases de datos gestionadas, por lo que la recuperación es el propio PITR de la base de datos más sus propios volcados lógicos.
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: una piscina a nivel de aplicación y la piscina gestionada de PgBouncer.
2.1 Piscina a Nivel de Aplicación con SQLAlchemy
Limite la piscina 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 la piscina en función del total de Cluster dividido por su recuento de réplicas, dejando un margen.
2.2 Piscina Gestionada de PgBouncer
Los clústeres de PostgreSQL ofrecen una piscina gestionada de PgBouncer. Usted la habilita y elige el modo de piscina, los modos admitidos son transaction (predeterminado) y session. La piscina 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. 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, y TLS utiliza una autoridad de certificación Let's Encrypt una vez que configure la instancia para utilizarla (TLS no está habilitado de forma predeterminada, así que habilitarlo explícitamente, como lo hace el ejemplo de redis-py a continuación con ssl=True). Un clúster tiene hasta 5 nodos. El motor Valkey subyacente tiene un máximo de 10,000 conexiones de cliente (maxclients) por defecto.
La política de expulsión predeterminada es allkeys-lru y la persistencia se establece en 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 Cache-Aside
Cache-aside 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 sesiones 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 CLOUD 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 privada, transporte cifrado y un límite de conexión fijo que respeta con agrupación en lugar de luchar con una bandera del servidor. La caché In-Memory DB es su herramienta de escalado de lectura porque los motores relacionales no exponen réplicas de lectura.
Para TaskBoard específicamente, PostgreSQL almacena las tareas, Redis almacena en caché lecturas y sesiones, y la recuperación es PITR de base de datos más sus propias copias 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 de la LAN privada con TLS aplicado, nunca a través de un punto final 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é In-Memory DB es la solución de escalado de lectura nativa de IONOS CLOUD
- PgBouncer en
transactionmodo en el puerto6432multiplica los clientes que puede servir un límite fijo - El servicio Backup Service no respalda DBaaS, la recuperación es PITR de base de datos (7 días predeterminados, configurable 1-365 días en la API v2) más sus propias copias
Terminología importante:
- PITR: Recuperación en un momento específico, restaurando un clúster a una marca de tiempo ISO-8601 dentro de la ventana de retención (7 días predeterminados; 1-365 días configurable en la API v2) a través de la API REST
- PgBouncer: El pooler de conexiones gestionado PostgreSQL en el puerto
6432, que admitetransactionysessionmodos de agrupación - Cache-aside: Un patrón de lectura que verifica la caché, vuelve a la base de datos en caso de fallo 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 del clúster principal que se conecta a la aplicación
Próximos pasos
Continuar aprendiendo: Unidad 4.2: Object Storage Integración
Temas relacionados: