Unité 4.1 : Intégration de la base de données et du cache
Introduction
Vous construisez le TaskBoard API et le niveau de données doit fonctionner sur les bases de données gérées IONOS. Cela modifie quelques hypothèses que vous pourriez avoir par rapport à d'autres clouds. Les bases de données gérées ici sont accessibles via un réseau LAN privé, et non via un point de terminaison public, chaque connexion client est chiffrée avec TLS, et les clusters de bases de données n'exposent pas de réplicas de lecture que vous pouvez pointer vers le trafic de lecture. Votre levier de mise à l'échelle de lecture n'est pas un point de terminaison de réplica, c'est le cache In-Memory DB.
Cette unité concerne le code de connexion. Vous allez connecter psycopg2 et SQLAlchemy à PostgreSQL, le connecteur MySQL à MariaDB, pymongo à MongoDB, et Redis-py au In-Memory DB Cluster, le tout avec TLS et mise en pool. Vous allez également gérer les deux réalités opérationnelles qui posent problème aux développeurs : les plafonds de connexion sont fixés par Cluster RAM et ne peuvent pas être augmentés par un indicateur client, et le Backup Service ne sauvegarde pas les bases de données gérées, donc la récupération est la propre récupération PITR de la base de données et vos propres dumps logiques.
1. Connexion au Managed PostgreSQL
Les clusters PostgreSQL écoutent sur le port 5432 et sont hébergés sur un réseau privé LAN à l'intérieur de votre centre de données. L'objet Cluster contient un datacenterId, un lanId et un primaryInstanceAddress, et votre application se connecte à cette adresse d'instance principale. Il n'y a pas de nom d'hôte public. Le mode SSL par défaut est prefer et TLS ne peut pas être désactivé par le client, vous devez donc planifier des connexions chiffrées dès la première ligne de code.
Les versions majeures prises en charge sont 14, 15 et 16. La chaîne de certificats du serveur se rattache au ISRG Root X1 Root, un bundle de certificats d'autorité de certification actuel sur votre hôte d'application valide donc la connexion sans configuration supplémentaire.
1.1 Connexion avec psycopg2
Connectez-vous avec sslmode=require pour que le pilote échoue en mode sécurisé si TLS ne peut pas être négocié. L'hôte est l'adresse de l'instance principale Cluster accessible sur votre réseau privé 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}")
Pour asyncpg dans un service asynchrone, passez ssl="require" et fournissez le même hôte, port et informations d'identification. Ne construisez jamais des chaînes SQL avec des f-strings, utilisez des espaces réservés de paramètres comme indiqué ci-dessus.
1.2 Les limites de connexion sont fixes par RAM
La valeur max_connections est calculée à partir de Cluster RAM et n'est pas configurable par l'utilisateur. Le tableau suivant est la mise en correspondance exacte imposée par la plateforme.
| Taille de RAM | max_connections |
|---|---|
| 4 Go | 384 |
| 5 Go | 512 |
| 6 Go | 640 |
| 7 Go | 768 |
| 8 Go | 896 |
| >8 Go | 1000 |
Sur ces derniers, 11 connexions sont réservées, votre pool d'applications doit donc rester en dessous du nombre publié moins les emplacements réservés. Puisque ce plafond est fixe, vous ne pouvez pas résoudre le problème des "trop de connexions" en augmentant un paramètre de serveur. Vous le résolvez avec le regroupement, abordé dans la section suivante.
2. Regroupement de connexions
Il n'y a pas de répliques de lecture. Chaque lecture et chaque écriture touche l'instance principale unique, donc une application non limitée qui ouvre une connexion par requête épuisera rapidement le plafond fixe max_connections . Le regroupement est obligatoire, et non facultatif, et vous avez deux couches à utiliser ensemble : un regroupement au niveau de l'application et le regroupement géré PgBouncer.
2.1 Regroupement au niveau de l'application avec SQLAlchemy
Limitez le regroupement SQLAlchemy bien en dessous du plafond Cluster et activez pool_pre_ping pour que les connexions obsolètes soient recyclées plutôt que transmises à une requête en cas d'échec.
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 vous exécutez N répliques d'application, le Cluster voit N * (pool_size + max_overflow) connexions. Dimensionnez le regroupement par rapport au total Cluster divisé par votre nombre de répliques, en laissant une marge.
2.2 Regroupement géré PgBouncer
Les clusters PostgreSQL offrent un regroupement géré PgBouncer. Vous l'activez et choisissez le mode de regroupement, les modes pris en charge sont transaction (par défaut) et session. Le regroupement écoute sur le port 6432 plutôt que sur le port de la base de données 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,
)
Utilisez le mode transaction pour les charges de travail Web typiques afin qu'une connexion back-end ne soit maintenue que pendant la durée d'une transaction, ce qui multiplie le nombre de clients que le plafond de connexion fixe peut servir. Les fonctionnalités à portée de session, telles que les instructions préparées et SET qui doivent persister tout au long d'une session, nécessitent le mode session.
3. MariaDB et MongoDB Intégration
PostgreSQL est l'un des plusieurs moteurs gérés, et le modèle de connexion est cohérent : LAN privé, TLS appliqué, limites fixes. Le pilote et les mathématiques de limitation de connexion diffèrent par moteur.
3.1 MariaDB
MariaDB écoute sur le port 3306. Toutes les connexions clientes sont chiffrées avec TLS et le certificat serveur est émis par Let's Encrypt, donc un bundle CA standard le valide. Seules les versions à support à long terme sont proposées, à partir de 10.6 (par exemple 10.6 et 10.11). La réplication est asynchrone uniquement.
La Cluster max_connections est de 500, et chaque utilisateur est limité à 250 connexions. Cette limite par utilisateur est délibérée : car un utilisateur peut prendre au maximum 250 des 500 emplacements, une application utilisateur défectueuse est beaucoup moins susceptible de priver l'ensemble de la 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()
Le stockage atteint un maximum de 2 To par Cluster, et une instance unique ne peut pas dépasser 16 cœurs.
3.2 MongoDB
Les clusters MongoDB présentent une chaîne de connexion sous la forme mongodb+srv://m-<id>.mongodb.<region>.ionos.com, que vous lisez généralement à partir d'une sortie Terraform plutôt que de la coder en dur. Les versions prises en charge sont 6.0 et 7.0. Le regroupement de connexions côté serveur n'est pas fourni, donc le pool côté pilote dans pymongo est votre pool.
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}))
Le décompte de connexions maximum évolue avec RAM. Le tableau suivant est le mappage appliqué.
| RAM (Go) | max_connections |
|---|---|
| 2 | 500 (Bac à sable) |
| 4 | 1000 |
| 6 | 2000 |
| 8 | 3000 |
| 12 | 5000 |
| 16 | 7000 |
| 24 | 11000 |
| 32 | 15000 |
| 48 | 23000 |
| 64 | 31000 |
L'attribution de rôles utilise les rôles intégrés MongoDB tels que read, readWrite, dbAdmin, et clusterMonitor. Accordez readWrite avec une portée taskboard pour la base de données de l'utilisateur de l'application, plutôt qu'un rôle d'administrateur Cluster-large.
4. In-Memory DB (Redis) Mise en cache
Puisque aucun moteur géré n'expose de réplicas de lecture, le In-Memory DB Cluster est la méthode native IONOS pour mettre à l'échelle les lectures. Il est compatible avec Redis OSS 7.2 et écoute sur le port 6379, et TLS utilise une autorité de certification Let's Encrypt une fois que vous configurez l'instance pour l'utiliser (TLS n'est pas activé par défaut, vous devez donc l'activer explicitement, comme le fait l'exemple Redis-py ci-dessous avec SSL=True). Un Cluster peut avoir jusqu'à 5 nœuds. Le moteur Valkey sous-jacent définit par défaut un maximum de 10 000 connexions clientes (maxclients).
La politique d'éviction par défaut est allkeys-lru et la persistance par défaut est None, ce qui est la bonne posture pour un cache pur : le traiter comme volatile et toujours être en mesure de le reconstruire à partir de la base de données source.
4.1 Connexion avec 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 Modèle Cache-Aside
Le modèle cache-aside est le chemin de lecture par défaut : vérifiez le cache, revenez à la base de données en cas de défaut, puis remplissez le cache. Définissez un TTL pour que les entrées obsolètes expirent même si aucune écriture ne les 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 Modèle d'écriture en lecture-écriture
L'écriture en lecture-écriture met à jour la base de données et le cache dans la même opération, de sorte que les lectures ne servent jamais une valeur obsolète après une écriture. Écrivez d'abord dans la base de données, et mettez à jour le cache uniquement une fois que le commit est réussi.
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 convient à TaskBoard dans deux endroits supplémentaires : le stockage de session et la mise en cache des résultats de requête de liste de tâches qui sinon surchargeraient l'instance principale à chaque chargement de page.
5. Récupération : PITR et l'écart Backup Service
La récupération pour les bases de données gérées est la récupération à un moment donné de la base de données elle-même, et non le Backup Service. Le Backup Service ne sauvegarde pas les DBaaS gérés, vous ne devez donc pas supposer que vos données de tâches sont capturées par une unité de sauvegarde. Pour les exportations logiques au-delà de la fenêtre PITR, planifiez pg_dump ou l'outil de dump MariaDB à partir du code d'application ou d'un travail cron et stockez la sortie dans Object Storage.
PostgreSQL et MariaDB ont tous deux une fenêtre de récupération à un moment donné de 7 jours par défaut sur les grappes v1, mais sur leurs API v2, la rétention est configurable de 1 à 365 jours via backup.retentionDays, vous devez donc vérifier la version et le paramètre de rétention de votre Cluster avant de supposer une fenêtre de 7 jours fixe. Le PITR s'exécute via REST API. Le chemin de base pour PostgreSQL est https://api.ionos.com/databases/postgresql, et une restauration est une opération en place, la base de données est donc indisponible pendant son exécution.
5.1 Déclenchement de la récupération à un moment donné de PostgreSQL via 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"
}'
Le recoveryTargetTime est un horodatage ISO-8601 et n'est pas inclusif. Restaurez les contraintes de code autour : seule une sauvegarde peut être restaurée à la fois, le Cluster doit être AVAILABLE avant de déclencher une restauration, vous ne pouvez restaurer qu'à partir de la même version majeure ou d'une version plus ancienne, et une restauration peut déplacer la base de données vers une autre région. La plateforme recommande au moins 4 Go RAM pendant la restauration, que vous pouvez réduire à nouveau par la suite.
API Référence Quick Card
Points de terminaison clés API pour l'intégration de la base de données gérée :
| Méthode | Point de terminaison | Description |
|---|---|---|
POST |
/databases/postgresql/clusters |
Créer un PostgreSQL Cluster |
GET |
/databases/postgresql/clusters/{clusterId} |
Obtenir les détails Cluster, y compris les informations de connexion |
PATCH |
/databases/postgresql/clusters/{clusterId} |
Modifier les paramètres de connexion (activer PgBouncer, mode de pool) |
POST |
/databases/postgresql/clusters/{clusterId}/restore |
Déclencher la restauration PITR en place |
POST |
/clusters (sur in-memory-db.{region}.ionos.com) |
Créer un In-Memory DB Cluster (v2 API, recommandé ; la surface v1 /replicasets héritée a une dépréciation annoncée) |
URL de base : https://api.ionos.com/databases/postgresql (les hôtes spécifiques à la région s'appliquent à In-Memory DB et MariaDB)
Authentification : Authorization: Bearer <token>
Laboratoire de code
Objectif : Connecter le TaskBoard API à PostgreSQL et au cache In-Memory DB, insérer une tâche, mettre en cache la lecture et vérifier TLS.
Prérequis :
- Compte IONOS Cloud avec jeton API
- Un PostgreSQL Cluster et un ensemble de répliques In-Memory DB en cours d'exécution sur un réseau privé partagé LAN
- Python 3.10+ avec
psycopg2-binary,sqlalchemyetredisinstallés - L'adresse
primaryInstanceAddressde Cluster et l'adresse Node de In-Memory DB
Étape 1 : Vérifier TLS sur 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();"
Sortie attendue :
ssl
-----
t
Étape 2 : Créer la table des tâches
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);"
Sortie attendue :
CREATE TABLE
Étape 3 : Insérer une tâche à partir de 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)
Sortie attendue :
task id: 1
Étape 4 : Se connecter au cache
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())
Sortie attendue :
True
Étape 5 : Mettre en cache la lecture (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))
Sortie attendue :
{"id": 1, "title": "Lab task", "status": "open"}
Étape 6 : Confirmer que la deuxième lecture est un coup de cache
print("cached:", r.ttl(key), "seconds remaining")
Sortie attendue :
cached: 300 seconds remaining
Liste de validation :
- [ ]
psqlsignalessl = t, confirmant que TLS est actif - [ ] La ligne de tâche est insérée et a renvoyé un id via SQLAlchemy
- [ ]
r.ping()renvoieTruesur TLS - [ ] La deuxième lecture renvoie la valeur de Redis, et non de PostgreSQL
Nettoyage :
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
Pièges courants
Erreurs de développement à éviter avec l'intégration de base de données gérée :
-
Ouverture d'une connexion par requête et épuisement du plafond
- Problème : Sous charge, l'application lance
FATAL: too many connections for roleousorry, too many clients already. - Pourquoi cela se produit :
max_connectionsest fixé par Cluster RAM (par exemple 1000 au-dessus de 8 Go), 11 emplacements sont réservés, et il n'y a pas de réplicas de lecture pour répartir la charge. Une application non regroupée multiplie les connexions par le nombre de réplicas. - Correction : Limitez le regroupement bien en dessous du plafond et acheminez-le via PgBouncer en mode
transactionsur le port6432:
create_engine(url, pool_size=20, max_overflow=10, pool_pre_ping=True) - Problème : Sous charge, l'application lance
-
En supposant que le Backup Service protège vos données de base de données
- Problème : Une mauvaise migration supprime une table et il n'y a pas d'unité de sauvegarde pour restaurer.
- Pourquoi cela se produit-il : Le Backup Service ne sauvegarde pas les DBaaS gérés. Les développeurs supposent qu'un outil de sauvegarde couvre tout.
- Correction : Fiez-vous à la récupération PITR de la base de données (fenêtre par défaut de 7 jours, configurable de 1 à 365 jours sur le v2 API via
backup.retentionDays) pour une récupération récente et planifiez des dumps logiques pour tout ce qui est plus ancien :
pg_dump "host=10.7.222.10 sslmode=require dbname=taskboard" | gzip > taskboard-$(date +%F).sql.gz -
Désactiver TLS pour "le faire fonctionner"
- Problème : La connexion échoue localement, donc un développeur utilise
sslmode=disable. - Pourquoi cela se produit : Un bundle CA manquant ou obsolète entraîne une échec de validation de certificat, et la solution rapide semble être la désactivation de TLS.
- Correction : TLS ne peut pas être désactivé par le client sur PostgreSQL (le mode par défaut est
preferet ne peut pas être désactivé), donc corrigez la chaîne CA à la place. PostgreSQL est lié àISRG Root X1, MariaDB et In-Memory DB utilisent Let's Encrypt. Mettez à jour le bundle CA système et conservezsslmode=require.
- Problème : La connexion échoue localement, donc un développeur utilise
Résumé
Vous pouvez maintenant connecter le code d'application de production à chaque moteur de base de données géré par IONOS avec TLS appliqué et des connexions regroupées, et vous pouvez récupérer des données via le mécanisme approprié. Le modèle de connexion est cohérent sur tous les moteurs : LAN privé, transport chiffré, et un plafond de connexion fixe que vous respectez avec le regroupement plutôt que de lutter avec un indicateur de serveur. Le cache In-Memory DB est votre outil de mise à l'échelle des lectures, car aucun des moteurs de base de données n'expose de réplicas de lecture.
Pour TaskBoard en particulier, PostgreSQL contient les tâches, Redis met en cache les lectures et les sessions, et la récupération est la récupération PITR de la base de données plus vos propres dumps planifiés. Construisez ces aides de connexion une fois, avec le regroupement et TLS intégrés, et réutilisez-les dans tous les services de l'application.
Points clés :
- Les bases de données gérées sont accessibles via le LAN privé avec TLS appliqué, jamais un point de terminaison public
max_connectionsest fixé par Cluster RAM et n'est pas configurable par l'utilisateur, donc le regroupement est obligatoire- Il n'y a pas de réplicas de lecture, le cache In-Memory DB est la solution de mise à l'échelle des lectures native IONOS
- PgBouncer en mode
transactionsur le port6432multiplie les clients qu'un plafond fixe peut servir - Le Backup Service ne sauvegarde pas DBaaS, la récupération est la récupération PITR de la base de données (7 jours par défaut, configurable 1-365 jours sur la v2 API) plus vos propres dumps
Terminologie importante :
- PITR : Récupération à un moment donné, restaurant une Cluster à un horodatage ISO-8601 dans la fenêtre de rétention (7 jours par défaut ; 1-365 jours configurable sur la v2 API) via la REST API
- PgBouncer : Le pooler de connexions Managed PostgreSQL sur le port
6432, prenant en charge les modes de regroupementtransactionetsession - Cache-aside : Un modèle de lecture qui vérifie le cache, revient à la base de données en cas de défaillance, puis remplit le cache avec une durée de vie
- Write-through : Un modèle d'écriture qui met à jour la base de données et le cache dans la même opération pour éviter les lectures obsolètes
- primaryInstanceAddress : L'adresse LAN privée d'une instance principale d'une base de données Cluster à laquelle l'application se connecte, avec LAN et TLS pour les autres bases de données, comme LAN et Cluster, et enfin LAN
Prochaines étapes
Continuer l'apprentissage : Unité 4.2 : Object Storage Intégration
Sujets connexes :