Aller au contenu principal
DevOpsMar 28, 2026

Deep EVM #26 : Sharding vs partitionnement — Architecture pour tables massives

OS
Open Soft Team

Engineering Team

La différence fondamentale

Le partitionnement et le sharding divisent tous les deux les données en morceaux plus petits, mais opèrent à des niveaux différents. Le partitionnement divise une table sur le même serveur de base de données. Le sharding distribue les données sur plusieurs serveurs. Comprendre quand utiliser chaque approche est essentiel.

Partitionnement (même serveur)

┌─────────────────────────────┐
│       Serveur PostgreSQL     │
│  ┌─────────┐ ┌─────────┐   │
│  │ Part. 1 │ │ Part. 2 │   │
│  └─────────┘ └─────────┘   │
│  ┌─────────┐ ┌─────────┐   │
│  │ Part. 3 │ │ Part. 4 │   │
│  └─────────┘ └─────────┘   │
└─────────────────────────────┘

Avantages : transactions ACID, requêtes cross-partition transparentes, pas de changement d’application.

Sharding (serveurs multiples)

┌──────────┐  ┌──────────┐  ┌──────────┐
│ Shard 1  │  │ Shard 2  │  │ Shard 3  │
│ (PG)     │  │ (PG)     │  │ (PG)     │
└──────────┘  └──────────┘  └──────────┘
      ↑              ↑              ↑
      └──────────────┼──────────────┘
                     │
            ┌────────────────┐
            │ Router/Proxy   │
            └────────────────┘

Avantages : mise à l’échelle horizontale du débit et du stockage, isolation des défaillances.

Hachage cohérent

Pour distribuer les données uniformément entre les shards :

use std::collections::BTreeMap;

struct ConsistentHash {
    ring: BTreeMap<u64, ShardId>,
    virtual_nodes: u32,
}

impl ConsistentHash {
    fn get_shard(&self, key: &[u8]) -> ShardId {
        let hash = xxhash(key);
        // Trouver le premier nœud >= hash sur l'anneau
        self.ring.range(hash..)
            .next()
            .or_else(|| self.ring.iter().next()) // Boucler
            .map(|(_, shard)| *shard)
            .unwrap()
    }
}

Le hachage cohérent minimise la redistribution des données lors de l’ajout/suppression de shards.

Requêtes cross-shard

Le défi principal du sharding : les requêtes qui touchent plusieurs shards.

async fn search_all_shards(
    shards: &[ShardConnection],
    query: &str,
) -> Vec<Result> {
    // Exécuter en parallèle sur tous les shards
    let futures = shards.iter()
        .map(|shard| shard.query(query));

    let results = futures::future::join_all(futures).await;

    // Fusionner et trier les résultats
    results.into_iter()
        .flat_map(|r| r.unwrap_or_default())
        .sorted_by_key(|r| r.score)
        .collect()
}

Quand choisir quoi

CritèrePartitionnementSharding
Taille des données< 1 TB> 1 TB
Débit d’écriture< 50K/s> 50K/s
Transactions ACIDOuiLimité
Complexité opérationnelleFaibleÉlevée
Requêtes cross-donnéesTransparentesComplexes
CoûtUn serveurMultiple serveurs

Règle pratique : commencez par le partitionnement. Passez au sharding seulement quand un seul serveur ne suffit plus, malgré les optimisations.

Resharding

Ajouter des shards à un système existant est l’une des opérations les plus complexes :

  1. Ajouter les nouveaux shards
  2. Mettre à jour la fonction de hachage
  3. Migrer les données affectées en arrière-plan
  4. Basculer le routage progressivement
  5. Nettoyer les anciennes données

Le hachage cohérent minimise les données à déplacer — idéalement, seulement 1/N des données quand on passe de N à N+1 shards.

Conclusion

Le partitionnement est votre premier outil de mise à l’échelle — simple, transparent, pas de changement applicatif. Le sharding est le recours quand un seul serveur atteint ses limites. Planifiez votre clé de sharding soigneusement car en changer est extrêmement coûteux.