Unité 4.1 : Intégration de la base de données et de la mise en cache
Introduction
Vous développez l'API TaskBoard et la couche de données doit s'exécuter sur des bases de données gérées IONOS CLOUD. Cela modifie certaines hypothèses que vous pourriez avoir en tête en provenance d'autres clouds. Les bases de données gérées ici sont accessibles via un LAN privé, et non via un point d'accès public, chaque connexion client est chiffrée en TLS, et les clusters de bases de données relationnelles n'exposent aucune réplique de lecture vers laquelle diriger le trafic en lecture (seul MongoDB Enterprise peut ajouter des secondaires en lecture). Votre levier de mise à l'échelle en lecture n'est pas un point d'accès de réplique, mais le cache In-Memory DB.
Cette unité porte sur le code de connexion. Vous allez configurer psycopg2 et SQLAlchemy pour PostgreSQL, mysql-connector pour MariaDB, pymongo pour MongoDB, et redis-py pour le cluster In-Memory DB, le tout avec TLS et mise en pool. Vous traiterez également les deux réalités opérationnelles qui posent problème aux développeurs : les plafonds de connexion sont fixés par la RAM du cluster et ne peuvent pas être augmentés par un paramètre client, et le Backup Service ne sauvegarde pas les bases de données gérées, de sorte que la restauration repose sur le PITR propre à la base de données et sur vos propres déchargements logiques.
1. Connexion à Managed PostgreSQL
Les clusters PostgreSQL écoutent sur le port 5432 et sont situés sur un LAN privé à l'intérieur de votre centre de données. L'objet du 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 le TLS ne peut pas être désactivé par le client, il est donc recommandé de prévoir des connexions chiffrées dès la première ligne de code.
Les versions principales prises en charge sont les 14, 15 et 16. La chaîne de certificats du serveur remonte jusqu'à la racine ISRG Root X1, de sorte qu'un bundle CA à jour sur votre hôte d'application valide la connexion sans configuration supplémentaire.
1.1 Connexion avec psycopg2
Connectez-vous avec sslmode=require afin que le pilote échoue de manière sécurisée si le TLS ne peut pas être négocié. L'hôte est l'adresse de l'instance principale du cluster, accessible sur votre LAN privé.
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, le même port et les mêmes identifiants. Ne construisez jamais de chaînes SQL avec des f-strings, utilisez des espaces réservés pour les paramètres comme indiqué ci-dessus.
1.2 Les limites de connexion sont fixées par la RAM
La valeur de max_connections est calculée à partir de la RAM du cluster et n'est pas configurable par l'utilisateur. Le tableau suivant présente le mappage exact appliqué 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 |
Parmi celles-ci, 11 connexions sont réservées, de sorte que votre pool d'application doit rester inférieur au nombre publié moins les emplacements réservés. Étant donné que cette limite est fixe, vous ne pouvez pas résoudre le problème de « trop de connexions » en augmentant un paramètre du serveur. Vous le résolvez avec le pooling, abordé ensuite.
2. Mise en pool des connexions
Il n'y a pas de répliques de lecture. Chaque lecture et chaque écriture est dirigée vers l'instance principale unique, de sorte qu'une application non limitée qui ouvre une connexion par requête épuiser rapidement la limite fixe max_connections. La mise en pool est obligatoire, et non facultative, et vous disposez de deux niveaux à utiliser ensemble : un pool côté application et le pooler PgBouncer géré.
2.1 Pool au niveau de l'application avec SQLAlchemy
Limitez le pool SQLAlchemy bien en dessous de la limite du cluster et activez pool_pre_ping afin que les connexions obsolètes soient recyclées plutôt que d'être attribuées à une requête en cours 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éplicas d'application, le cluster détecte N * (pool_size + max_overflow) connexions. Dimensionnez le pool en fonction du total du cluster divisé par votre nombre de réplicas, en laissant une marge.
2.2 Pooler PgBouncer managé
Les clusters PostgreSQL offrent un pooler PgBouncer managé. Vous l'activez et choisissez le mode de pool, les modes pris en charge sont transaction (par défaut) et session. Le pooler é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 d'arrière-plan ne soit maintenue que pendant la durée d'une transaction, ce qui multiplie le nombre de clients que le plafond de connexions fixes peut servir. Les fonctionnalités liées à la session, telles que les instructions préparées et SET, qui doivent persister au-delà d'une session, nécessitent le mode session.
3. Intégration MariaDB et MongoDB
PostgreSQL est l'un des plusieurs moteurs gérés, et le modèle de connexion est cohérent : LAN privée, TLS imposé, limites fixes. Le pilote et le calcul des limites de connexion varient selon le moteur.
3.1 MariaDB
MariaDB écoute sur le port 3306. Toutes les connexions clients sont chiffrées en TLS et le certificat du serveur est émis par Let's Encrypt, de sorte qu'un bundle CA standard le valide. Seules les versions Long-Term Support sont proposées, à partir de 10.6 (par exemple 10.6 et 10.11). La réplication est uniquement asynchrone.
Le max_connections du cluster est de 500, et chaque utilisateur est limité à 250 connexions. Cette limite par utilisateur est volontaire : comme un utilisateur peut occuper au maximum 250 des 500 emplacements, un utilisateur d'application défaillant a beaucoup moins de chances d'asphyxier l'ensemble du 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 est limité à 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 de 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, de sorte que 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 nombre maximal de connexions évolue en fonction de la mémoire RAM. Le tableau suivant présente la correspondance appliquée.
| RAM (Go) | max_connections |
|---|---|
| 2 | 500 (Sandbox) |
| 4 | 1000 |
| 6 | 2000 |
| 8 | 3000 |
| 12 | 5000 |
| 16 | 7000 |
| 24 | 11000 |
| 32 | 15000 |
| 48 | 23000 |
| 64 | 31000 |
L'attribution des rôles utilise les rôles intégrés de MongoDB tels que read, readWrite, dbAdmin et clusterMonitor. Accordez readWrite limité à la base de données taskboard pour l'utilisateur de l'application, plutôt qu'un rôle d'administrateur à l'échelle du cluster.
4. Mise en cache avec In-Memory DB (Redis)
Étant donné que les moteurs relationnels n'exposent aucune réplique de lecture, le cluster In-Memory DB est la méthode native d'IONOS CLOUD pour mettre à l'échelle les lectures relationnelles. Il est compatible avec Redis OSS 7.2 et écoute sur le port 6379. TLS utilise une autorité de certification Let's Encrypt une fois que vous avez configuré l'instance pour l'utiliser (TLS n'est pas activé par défaut, il faut donc l'activer explicitement, comme le fait l'exemple redis-py ci-dessous avec ssl=True). Un cluster comporte au maximum 5 nœuds. Le moteur Valkey sous-jacent est configuré par défaut pour un maximum de 10 000 connexions clients (maxclients).
La politique d'éviction par défaut est allkeys-lru et la persistance est configurée par défaut sur None, ce qui est la posture correcte pour une pure mise en cache : considérez-la comme volatile et assurez-vous toujours de pouvoir la 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 Motif Cache-Aside
Cache-aside est le chemin de lecture par défaut : vérifier le cache, revenir à la base de données en cas d'échec, puis alimenter le cache. Définir une durée de vie (TTL) afin que les 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 Write-Through
Le modèle write-through 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, puis mettez à jour le cache uniquement une fois que l'engagement a 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 s'intègre à TaskBoard dans deux autres domaines : le stockage des sessions et la mise en cache des résultats des requêtes de la liste des tâches, qui autrement solliciteraient intensivement l'instance principale unique à chaque chargement de page.
5. Récupération : PITR et l'écart avec le Backup Service
La récupération pour les bases de données gérées est la récupération à un instant donné (point-in-time recovery) propre à la base de données, et non celle du Backup Service. Le Backup Service ne sauvegarde pas les instances DBaaS gérées, il ne faut donc pas supposer que les données de vos tâches sont capturées par une unité de sauvegarde. Pour les exports logiques au-delà de la fenêtre PITR, planifiez pg_dump ou l'outil de dump MariaDB depuis le code applicatif ou une tâche cron, et stockez la sortie dans Object Storage.
PostgreSQL et MariaDB utilisent par défaut une fenêtre de récupération à un instant donné de 7 jours sur les clusters v1, mais sur leurs API v2, la rétention est configurable de 1 à 365 jours via backup.retentionDays. Vérifiez donc la version de l'API et le paramètre de rétention de votre cluster avant de supposer une fenêtre fixe de 7 jours. Le PITR s'exécute via l'API REST. Le chemin de base pour PostgreSQL est https://api.ionos.com/databases/postgresql, et une restauration est une opération sur place : la base de données est indisponible pendant son exécution.
5.1 Déclenchement du PITR PostgreSQL via l'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. Contraintes de restauration à prendre en compte : une seule sauvegarde peut être restaurée à la fois, le cluster doit être AVAILABLE avant de déclencher une restauration, vous ne pouvez restaurer que depuis la même version majeure ou une version antérieure, et une restauration peut déplacer la base de données vers une autre région. La plateforme recommande au moins 4 Go de RAM pendant la restauration, ce que vous pouvez réduire par la suite.
Fiche de référence rapide de l'API
Points de terminaison API clés pour l'intégration de bases de données gérées :
| Méthode | Point de terminaison | Description |
|---|---|---|
POST |
/databases/postgresql/clusters |
Créer un cluster PostgreSQL |
GET |
/databases/postgresql/clusters/{clusterId} |
Récupérer les détails du 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 une restauration PITR sur place |
POST |
/clusters (sur in-memory-db.{region}.ionos.com) |
Créer un cluster In-Memory DB (API v2, recommandée ; la surface v1 /replicasets obsolète 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>
Atelier de code
Objectif : Connecter l'API TaskBoard à PostgreSQL et au cache In-Memory DB, insérer une tâche, mettre en cache la lecture et vérifier le TLS.
Prérequis :
- Un compte IONOS CLOUD avec un jeton API
- Un cluster PostgreSQL en cours d'exécution et un ensemble de répliques In-Memory DB sur un LAN privé partagé
- Python 3.10 ou supérieur avec
psycopg2-binary,sqlalchemyetredisinstallés - L'adresse
primaryInstanceAddressdu cluster et l'adresse du nœud In-Memory DB
Étape 1 : Vérifier le TLS vers 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 : Connexion 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 la lecture en cache (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 accès au cache
print("cached:", r.ttl(key), "seconds remaining")
Sortie attendue :
cached: 300 seconds remaining
Liste de contrôle de validation :
- [ ]
psqlsignalessl = t, confirmant que TLS est actif - [ ] La ligne de tâche a été insérée et a renvoyé un id via SQLAlchemy
- [ ]
r.ping()renvoieTruevia TLS - [ ] La seconde lecture renvoie la valeur depuis Redis, et non depuis 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
Erreurs courantes
Erreurs de développement à éviter lors de l'intégration d'une base de données gérée :
-
Ouvrir une connexion par requête et épuiser la limite
- Problème : Sous charge, l'application lève
FATAL: too many connections for roleousorry, too many clients already. - Cause :
max_connectionsest fixé par la RAM du cluster (par exemple 1000 au-delà de 8 Go), 11 slots sont réservés, et il n'y a pas de répliques de lecture pour répartir la charge. Une application sans pool multiplie les connexions par le nombre de répliques. - Correction : Limiter le pool bien en dessous de la limite et router 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 lève
-
En supposant que le Backup Service protège les données de votre base de données
- Problème : Une mauvaise migration supprime une table et il n'y a aucune unité de sauvegarde à partir de laquelle effectuer une restauration.
- Pourquoi cela se produit : Le Backup Service ne sauvegarde pas les instances DBaaS gérées. Les développeurs supposent qu'un seul outil de sauvegarde couvre tout.
- Solution : Faites confiance à la PITR de la base de données (fenêtre par défaut de 7 jours, configurable de 1 à 365 jours sur l'API v2 via
backup.retentionDays) pour les récupérations récentes et planifiez des dévidements 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 « faire fonctionner »
- Problème : La connexion échoue localement, si bien qu'un développeur se tourne vers
sslmode=disable. - Pourquoi cela se produit : Un bundle CA manquant ou obsolète entraîne l'échec de la validation du certificat, et le correctif rapide consiste à désactiver 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é), il faut donc corriger la chaîne CA à la place. PostgreSQL utilise des chaînes versISRG Root X1, tandis que MariaDB et In-Memory DB utilisent Let's Encrypt. Mettez à jour le bundle CA du système et conservezsslmode=require.
- Problème : La connexion échoue localement, si bien qu'un développeur se tourne vers
Résumé
Vous pouvez désormais connecter le code applicatif de production à chaque moteur de base de données managé IONOS CLOUD avec TLS appliqué et connexions mutualisées, et vous pouvez récupérer les données par le mécanisme approprié. Le modèle de connexion est cohérent entre les moteurs : LAN privée, transport chiffré et un plafond de connexions fixe que vous respectez grâce à la mutualisation plutôt qu'en luttant contre un drapeau serveur. Le cache In-Memory DB est votre outil de mise à l'échelle en lecture, car les moteurs relationnels n'exposent pas de répliques de lecture.
Pour TaskBoard spécifiquement, PostgreSQL stocke les tâches, Redis met en cache les lectures et les sessions, et la récupération repose sur la PITR de la base de données ainsi que sur vos propres sauvegardes planifiées. Construisez ces aides de connexion une seule fois, avec mutualisation et TLS intégrés, puis réutilisez-les pour chaque service de l'application.
Points clés :
- Les bases de données managées sont accessibles via la LAN privée avec TLS appliqué, jamais par un point d'accès public
max_connectionsest fixé par la RAM du cluster et n'est pas configurable par l'utilisateur, donc la mutualisation est obligatoire- Les moteurs relationnels n'ont pas de répliques de lecture ; le cache In-Memory DB est la solution native IONOS CLOUD pour la mise à l'échelle en lecture
- PgBouncer en mode
transactionsur le port6432multiplie le nombre de clients qu'un plafond fixe peut servir - Le Backup Service ne sauvegarde pas DBaaS, la récupération repose sur la PITR de la base de données (7 jours par défaut, configurable de 1 à 365 jours sur l'API v2) ainsi que sur vos propres sauvegardes
Terminologie importante :
- PITR : Récupération à un instant donné, restauration d'un cluster à un horodatage ISO-8601 dans la fenêtre de rétention (7 jours par défaut ; 1 à 365 jours configurables sur l'API v2) via l'API REST
- PgBouncer : Le mutualisateur de connexions PostgreSQL managé sur le port
6432, prenant en charge les modes de mutualisationtransactionetsession - Cache-aside : Un motif de lecture qui vérifie le cache, bascule sur la base de données en cas d'échec, puis alimente le cache avec une durée de vie
- Write-through : Un motif 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 de l'instance primaire d'un cluster de base de données à laquelle l'application se connecte
Prochaines étapes
Continuer l'apprentissage : Unité 4.2 : Intégration Object Storage
Sujets connexes :