Aller au contenu

Persistance distribuée

Choisir les bonnes bases, comprendre les trade-offs de consistency et partitionner les données à l'échelle.


Polyglot persistence

Chaque sous-domaine utilise la base la mieux adaptée a ses besoins. Un catalogue produits avec des attributs variables n'a pas les mêmes contraintes qu'un système de paiement ou qu'un moteur de recommandation.

Besoin Type Exemples Quand choisir
Transactions ACID, schéma structure Relationnel PostgreSQL, MySQL Données critiques, relations complexes, reporting
Schéma flexible, hiérarchies Document MongoDB, CouchDB Catalogue, profils utilisateurs, contenu
Cache, sessions, configuration Key-value Redis, etcd Accès sub-milliseconde, données éphémères
Time-series, analytics, gros volume Colonnes Cassandra, ClickHouse Métriques, logs, IoT, requêtes analytiques
Relations complexes, graphes Graphe Neo4j Recommandations, fraude, réseaux sociaux
Recherche full-text Recherche Elasticsearch, Meilisearch Autocompletion, facettes, similarite

Relationnel (PostgreSQL, MySQL)

ACID complet, jointures, contraintes referentielles. La base par défaut quand les besoins ne sont pas encore clairs. PostgreSQL supporte aussi du JSON natif, des tableaux, et des extensions pour time-series (TimescaleDB) et recherche (pg_trgm).

Document (MongoDB, CouchDB)

Schémas flexibles sans migration de colonnes. Bon pour les entités avec des attributs variables ou imbriques. Attention aux jointures : elles n'existent pas nativement — il faut denormaliser ou accepter plusieurs requêtes.

Key-value (Redis, etcd)

Lectures et écritures en O(1). Redis sert de cache, de store de sessions, de queue légère, de pub/sub. etcd est specialise dans la configuration distribuée et la coordination de cluster.

Colonnes (Cassandra, ClickHouse)

Optimises pour les écritures massives et les lectures analytiques sur des colonnes spécifiques. Cassandra excelle sur les time-series et les données réparties geographiquement. ClickHouse sur les requêtes analytiques OLAP.

Graphe (Neo4j)

Les relations sont des citoyens de première classe. Quand votre domaine est intrinsequement un graphe (réseau social, arbre de dépendances, détection de fraude), un graphe bat un schéma relationnel sur les requêtes traversant plusieurs niveaux de relations.

Gouvernance du polyglot

Chaque base ajoutee a un coût opérationnel : backups, monitoring, migrations, mises à jour de sécurité, expertise interne. Questions a se poser :

  1. PostgreSQL avec les bonnes extensions ne suffit-il pas ?
  2. Le gain de performance est-il mesure où suppose ?
  3. L'équipe a-t-elle l'expertise pour opérer cette base en production ?
  4. Le gain justifie-t-il la complexité supplémentaire ?

Commencer simple

PostgreSQL couvre 80% des cas d'usage. Ne diversifiez que quand un besoin spécifique l'exige — chaque base supplémentaire est un système a opérer, a monitorer, a sauvegarder et a maintenir.


CAP et PACELC

Les théorèmes qui gouvernent les trade-offs dans les systèmes distribués.

Théorème CAP

En présence d'une partition réseau, un système distribué ne peut garantir que deux des trois propriétés :

  • Consistency (C) : chaque lecture reçoit la donnée la plus récente ou une erreur
  • Availability (A) : chaque requête reçoit une réponse (pas necessairement la plus récente)
  • Partition tolérance (P) : le système continue malgre des pertes de messages réseau

La partition réseau est incontournable en distribué — le choix réel est entre CP et AP.

CP (Consistency + Partition tolérance) : le système refuse de répondre plutôt que de retourner une donnée incorrecte. PostgreSQL en mode synchrone, etcd, ZooKeeper. Pour les données financieres et les verrous distribués.

AP (Availability + Partition tolérance) : le système répond toujours, même avec une donnée potentiellement stale. Cassandra, DynamoDB. Pour les feeds, compteurs, préférences utilisateurs.

Extension PACELC

CAP ne parle que du comportement en cas de partition. PACELC couvre aussi le comportement normal :

  • En cas de Partition (P) : trade-off entre Availability (A) et Consistency (C)
  • Else, en opération normale (E) : trade-off entre Latency (L) et Consistency (C)
Base CAP Opération normale Comportement en partition
PostgreSQL CP Privilegier C Refuse si replica pas synchronisé
Cassandra AP Privilegier L Répond avec potentielle stale data
DynamoDB AP Configurable Eventual consistency par défaut
etcd CP Privilegier C Leader election, pas de split-brain
CouchDB AP Privilegier L Merge des conflits à la reconnexion
MongoDB CP Privilegier C (configurable) Bascule sur replica si primaire down

Implications pratiques

Ne pas sur-optimiser pour le théorème : CAP décrit le comportement en cas de partition, événement rare dans un datacenter bien configuré. La majorité du temps, PACELC apporte plus de valeur.

Choisir en fonction du domaine : si votre domaine requiert ACID, re-implémenter ces garanties dans l'application est plus complexe et moins fiable qu'une base CP.

Configurer par opération : DynamoDB (strongly consistent reads) et Cassandra (CONSISTENCY_LEVEL par requête) permettent de choisir par opération. Mutations critiques en CP, lectures de cache en AP.


Partitioning

Distribuer les données sur plusieurs nœuds quand une seule machine ne suffit plus.

Pourquoi partitionner

Une seule machine atteint ses limites en volume de stockage, débit d'écriture, ou latence géographique. Le sharding distribué les données horizontalement, chaque nœud responsable d'un sous-ensemble.

Le sharding est différent de la réplication : la réplication duplique les mêmes données (haute disponibilité), le sharding divise en sous-ensembles distincts (scalabilité). Les deux sont utilisés ensemble : N shards, chacun répliqué M fois.

Stratégies de partitionnement

Hash-based : la partition key est hashee, le résultat déterminé le nœud. Distribution uniforme mais pas de range queries.

shard = hash(customer_id) % nb_shards

Range-based : répartition par plages de valeurs. Permet les range queries mais expose aux hot spots si les données récentes sont concentrees.

Geographic : répartition par region (EU, US, APAC). Réduit la latence locale et répond aux contraintes de souveraineté (RGPD).

Critères de choix de la partition key

Une bonne partition key a :

  • Cardinalite élevée : suffisamment de valeurs distinctes pour distribuer sur N shards
  • Distribution uniforme : éviter qu'un sous-ensemble concentre la majorité du trafic
  • Alignement avec les accès patterns : les requêtes fréquentes ciblent un seul shard
Partition key Stratégie Avantage Risque
customer_id Hash Distribution uniforme, isolation client Cross-customer reports = scatter-gather
created_at Range Range queries, archivage facile Hot spot sur le shard "aujourd'hui"
region Geographic Data locality, RGPD Déséquilibre si une region domine
order_id Hash Distribution maximale Lister commandes d'un client = scatter

Hot spots

Un mauvais choix de partition key crée des hot spots. Un status avec 3 valeurs possibles est une très mauvaise partition key — 90% des écritures iront sur pending.


Consistent hashing

Le hashing simple (hash(key) % N) pose un problème : quand on ajoute ou retire un nœud, presque toutes les clés changent de nœud. Le consistent hashing résout ca.

Principe de l'anneau

Les nœuds et les clés sont positionnes sur un anneau virtuel (espace de hash circulaire). Chaque clé est assignee au premier nœud rencontre dans le sens horaire.

graph TD
    subgraph "Anneau de hash"
        N1["Noeud A<br/>position 0"]
        N2["Noeud B<br/>position 90"]
        N3["Noeud C<br/>position 180"]
        N4["Noeud D<br/>position 270"]
    end

    K1["Cle X<br/>hash=45"] --"→ Noeud B"--> N2
    K2["Cle Y<br/>hash=200"] --"→ Noeud D"--> N4
    K3["Cle Z<br/>hash=350"] --"→ Noeud A"--> N1

Quand un nœud est ajoute, seules les clés entre le nouveau nœud et son predecesseur migrent. Quand un nœud est retire, ses clés vont au nœud suivant. Le reste du système n'est pas impacte.

Virtual nodes (vnodes)

Un seul nœud physique est représenté par plusieurs points (vnodes) sur l'anneau. Ca amélioré l'équilibrage : un nœud avec plus de capacité reçoit plus de vnodes. Cassandra utilisé par défaut 256 vnodes par nœud.

Impact sur le resharding

Sans consistent hashing, ajouter un nœud nécessité de migrer une fraction importante des données. Avec consistent hashing, seule 1/N-ieme des données migré (N = nombre de nœuds). Avec des vnodes, la migration est encore plus fine et distribuée.


Cross-shard queries

Une requête qui touche plusieurs shards (scatter-gather) est plus coûteuse qu'une requête sur un seul shard — elle est envoyee en parallèle a tous les shards, les résultats sont fusionnes.

Stratégies pour réduire le scatter-gather

Denormalisation : dupliquer les données sur plusieurs shards pour éviter les jointures inter-shards. Le coût est la cohérence : les copies doivent être mises à jour de manière asynchrone.

Fan-out à l'écriture : écrire sur plusieurs shards lors de la mutation plutôt que de scatter à la lecture. Twitter utilise ce pattern pour les timelines : quand un utilisateur publie un tweet, il est écrit dans la timeline de chaque follower (fan-out on write). La lecture est O(1) par utilisateur.

Index global : maintenir un index secondaire qui mappe les attributs non-partition-key aux shards contenant les données. L'index est lui-même distribué et éventuellement consistant.

Pipeline analytique dédié : les requêtes aggregees (top-N, sommes globales) sont intrinsequement du scatter-gather. Les isoler dans un datawarehouse (BigQuery, Snowflake, ClickHouse) alimentee par CDC, plutôt que de les exécuter sur la base transactionnelle.

graph LR
    subgraph "Transactionnel"
        S1[(Shard 1)]
        S2[(Shard 2)]
        S3[(Shard 3)]
    end

    CDC[CDC / ETL] --"replique"--> DW[(Data Warehouse<br/>ClickHouse / BigQuery)]
    S1 --> CDC
    S2 --> CDC
    S3 --> CDC

    API[API Service] --"requete par shard key"--> S1
    API --"requete par shard key"--> S2
    Analytics[Dashboard] --"requete analytique"--> DW

Données à grande échelle

Au-delà du partitionnement, gérer les données à grande échelle implique des stratégies supplémentaires.

Tiering de stockage

Toutes les données n'ont pas la même fréquence d'accès. Un tiering automatique déplacé les données vers le stockage le moins coûteux en fonction de leur âge :

Tier Stockage Latence Coût Données
Hot SSD, mémoire ms Élevé Derniers jours/semaines
Warm HDD, S3 Standard Secondes Moyen Derniers mois
Cold S3 Glacier, archivage Minutes-h Faible Historique, conformité

Compaction et TTL

Les bases distribuées accumulent des versions et des tombstones. La compaction fusionne les anciennes versions et purge les données expirees. Cassandra et Kafka utilisent la compaction de manière intensive.

Définir un TTL (Time To Live) sur les données éphémères : sessions (heures), métriques détaillées (jours), logs (semaines). Sans TTL, le volume croit indéfiniment et le coût de stockage explose.

Multi-region

Déployer les données sur plusieurs regions pour la latence et la résilience :

  • Active-passive : une region principale, une region de secours. Basculement en cas de panne. Simple mais latence inter-region pour les écritures.
  • Active-active : les deux regions acceptent les écritures. Complexe : conflits d'écriture, résolution (LWW, CRDT). CockroachDB et Spanner gèrent ca nativement.

Coût du multi-region

Le multi-region ajoute de la complexité, du coût réseau inter-region, et des problèmes de consistency supplémentaires. Ne le déployer que pour des besoins réels de latence géographique ou de conformité reglementaire. Pour la plupart des applications, un seul datacenter avec réplication locale suffit.


Patterns de consistency

Les modèles disponibles et leurs implications.

Modèle Garantie Latence Cas d'usage
Strong consistency Toujours la donnée la plus récente Élevée Transactions bancaires, stocks
Eventual consistency Les replicas convergent dans le temps Faible Feeds sociaux, compteurs, préférences
Causal consistency Les opérations causalement liees sont ordonnées Moyenne Messaging, collaboration temps réel
Read-your-writes Un utilisateur voit ses propres écritures Variable Sessions, profils
Monotonic reads Pas de retour en arriere Variable Pagination, listings

Tunable consistency

La plupart des systèmes modernes offrent de la consistency configurable par requête. Avec Cassandra :

  • ONE : un seul replica répond. Latence minimale, risque de stale.
  • QUORUM : majorité des replicas. Bon équilibre latence/fraicheur.
  • ALL : tous les replicas. Consistency maximale, disponibilité dégradée.

La règle : QUORUM en écriture + QUORUM en lecture garantit la strong consistency (les quorums se chevauchent). ONE en lecture + QUORUM en écriture donne de l'eventual consistency avec écriture fiable.

Conflits et résolution

Quand deux nœuds acceptent des écritures concurrentes sur la même clé :

  • Last-write-wins (LWW) : la mutation la plus récente gagne. Simple mais les horloges distribuées ne sont jamais parfaitement synchronisées
  • CRDT : structures de données conçues pour fusionner sans conflit (compteurs, sets, listes)
  • Application-level merge : le code applicatif reçoit les deux versions et décidé. Le plus flexible mais le plus complexe

Eventual consistency est un choix délibéré

Eventual consistency n'est pas un défaut — c'est un choix délibéré pour gagner en latence et disponibilité. Le problème survient quand c'est un choix accidentel. Documenter le modèle de consistency de chaque opération dans votre API.


Chapitre suivant : CQRS et event sourcing — séparation lecture/écriture, projections et snapshots.