Persistance des données
Progression
#Persistance des données
Choisissez un SGBD adapté. Un SQL relationnel convient aux invariants forts et aux requêtes expressives; des bases clé‑valeur ou documents simplifient certains accès à grande échelle mais déplacent des contraintes vers l’application. Écrivez des requêtes lisibles, indexez avec mesure et entourez vos écritures de transactions.
Les index accélèrent les lectures au prix d’écritures plus coûteuses. Un index utile correspond à un prédicat fréquent et sélectif; un index redondant alourdit sans bénéfice. Sur plusieurs colonnes, l’ordre compte. Mesurez avec des plans d’exécution et supprimez les index inactifs.
Les transactions protègent les invariants. Les niveaux d’isolation influencent les anomalies visibles: lecture sale, lecture non répétable, phantom reads. Par défaut, READ COMMITTED
suffit souvent; REPEATABLE READ
ou SERIALIZABLE
se réservent aux zones critiques avec parcimonie et des patterns compatibles.
Mini‑exercice: modélisez users
, sessions
, posts
(SQL) et écrivez les requêtes de base (création, lookup, invalidation de session). Ajoutez un index composite pour accélérer SELECT * FROM posts WHERE author_id=? ORDER BY created_at DESC LIMIT ?
et expliquez le choix.
#Animation: de la modélisation au backup
#Diagramme: requête dans une transaction
#Diagramme: écriture idempotente (Idempotency-Key)
#Conseils de persistance
- Entourez les écritures d’une transaction; choisissez l’isolation la plus faible acceptable.
- Rendez les handlers idempotents (uniques, upsert, Idempotency-Key) pour supporter les retries.
- Détectez les deadlocks et rejouez la transaction avec backoff borné.
- Mesurez avec EXPLAIN et supprimez les index peu utilisés.
- Automatisez les backups et testez la restauration régulièrement.
#Modélisation SQL: users, sessions, posts
1-- Utilisateurs2CREATE TABLE users (3 id BIGSERIAL PRIMARY KEY,4 email TEXT NOT NULL UNIQUE,5 password_hash TEXT NOT NULL,6 created_at TIMESTAMPTZ NOT NULL DEFAULT now()7);8 9-- Sessions côté serveur10CREATE TABLE sessions (11 id UUID PRIMARY KEY, -- identifiant de session12 user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,13 created_at TIMESTAMPTZ NOT NULL DEFAULT now(),14 last_seen_at TIMESTAMPTZ,
Règle pratique: égalité → plage → tri. Placez d’abord les colonnes filtrées par égalité, puis celles filtrées par plage, puis l’ordre de tri. Les B‑trees supportent le tri ASC/DESC
si spécifié dans l’index.
#Transactions et anomalies d’isolation
1-- Niveau par défaut souvent suffisant2SET TRANSACTION ISOLATION LEVEL READ COMMITTED;3 4-- Zone critique : passer temporairement en REPEATABLE READ/SERIALIZABLE5BEGIN;6SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;7-- ... opérations critiques ...8COMMIT;
- Lecture non répétable: T1 lit A; T2 modifie A et commit; T1 relit A et observe une valeur différente (READ COMMITTED).
- Phantom read: T1 lit COUNT(*) avec un prédicat; T2 insère une ligne correspondant au prédicat; T1 relit et observe un nouvel élément (REPEATABLE READ évite non‑répétable mais pas toujours les phantoms selon SGBD).
#Concurrence: éviter la perte de mise à jour
1-- Contrôle d’accès optimiste (OCC) par version2ALTER TABLE posts ADD COLUMN version INT NOT NULL DEFAULT 0;3 4-- Mise à jour sûre5-- application: passe la version lue; si 0 ligne affectée → recharger/relire6UPDATE posts7SET title = $1, body = $2, version = version + 18WHERE id = $id AND version = $current_version;
Combinez UNIQUE
/ON CONFLICT DO NOTHING/UPDATE
et des Idempotency‑Keys pour rendre vos handlers rejouables sans effets secondaires.
#Idempotency‑Key: schéma minimal
1CREATE TABLE idem_keys (2 key TEXT PRIMARY KEY,3 result_hash TEXT NOT NULL,4 created_at TIMESTAMPTZ NOT NULL DEFAULT now()5);6 7-- Garde-fou: purge accélérée par âge8CREATE INDEX idx_idem_gc ON idem_keys (created_at);
#Gestion des deadlocks (retry borné)
1// Pseudo‑code Node + pg2async function withTxRetry(client, fn, { maxRetries = 3, baseMs = 50 } = {}) {3 for (let attempt = 0; attempt <= maxRetries; attempt++) {4 try {5 await client.query('BEGIN');6 const out = await fn(client);7 await client.query('COMMIT');8 return out;9 } catch (err:any) {10 await client.query('ROLLBACK');11 // Postgres: 40P01 = deadlock detected, 40001 = serialization_failure12 if (['40P01', '40001'].includes(err.code) && attempt < maxRetries) {13 const backoff = baseMs * 2 ** attempt + Math.floor(Math.random() * baseMs);14 await new Promise(r => setTimeout(r, backoff));
#Mesure: lire un plan d’exécution
1EXPLAIN (ANALYZE, BUFFERS)2SELECT id, title3FROM posts4WHERE author_id = $15ORDER BY created_at DESC6LIMIT 20;7/*8 Index Scan using idx_posts_author_created_desc on posts ...9 Filter: (author_id = $1)10 Rows Removed by Filter: 011*/
#Sauvegarde et restauration (PostgreSQL)
1# Sauvegarde2pg_dump --format=custom --file=backup.dump "$DATABASE_URL"3# Restauration4pg_restore --clean --if-exists --dbname="$DATABASE_URL" backup.dump