Le carnet de bord contre le registre des manifestes

Sur le port, deux écritures cohabitent. Le registre des manifestes, tenu ligne à ligne, colonnes fixes, où chaque conteneur est déclaré exactement comme le précédent. Et le carnet de bord, une fiche par navire, où chaque capitaine consigne ce qui le concerne : le tirant d'eau pour l'un, la chaîne du froid pour l'autre, l'escorte réglementaire pour un troisième.

Personne n'a jamais essayé de faire entrer le carnet de bord dans le registre. Les deux servent, séparément, et pour des questions différentes.

Azure Cosmos DB est le carnet de bord. Une fiche par entité, une forme qui varie d'une fiche à l'autre, un accès très rapide dès qu'on sait quelle fiche on cherche, et une capacité qui monte en ajoutant des rangées plutôt qu'en agrandissant l'armoire.

Ce n'est pas « la base NoSQL d'Azure », formule qui ne dit rien d'utile. C'est un service dont trois décisions prises la première semaine déterminent le comportement, la latence et la facture des trois années suivantes.

L'interface se fige à la création du compte. La clé de partition se fige à la création du conteneur. Le débit et la cohérence, eux, se changent en une commande. Toute la difficulté est dans cet écart.

Trois décisions, deux qu'on ne rattrape pas

Un exploitant qui loue une travée d'entrepôt fixe trois choses avant d'y poser la première palette : le type de bordereau qu'il utilisera, la façon dont il range les allées, et la cadence de manutention qu'il paie. La dernière se renégocie en un appel. Les deux premières engagent l'outillage et le plan de rangement, et ne se défont qu'en déménageant.

Chez Cosmos DB, ces trois choix sont l'interface, la clé de partition et le débit, auxquels s'ajoute un quatrième réglage propre aux magasins répartis : le niveau de cohérence.

Avant de les dérouler, une question préalable, qui n'est pas rhétorique : ce magasin est-il le bon ?

Situation Verdict
Lecture d'un élément par sa clé, à fort débit, avec une latence de quelques millisecondes pour
Documents dont la forme varie d'un enregistrement à l'autre, ou évolue souvent pour
Écriture depuis plusieurs régions, avec la réplication prise en charge par le service pour
Volume qui croît sans plafond prévisible, avec un partitionnement géré par le service pour
Jointures entre plusieurs collections, agrégats sur tout le jeu de données contre
Transaction atomique touchant des entités qui ne partagent pas la même clé de partition contre
Requêtes analytiques libres sur l'historique complet contre, sauf à passer par le magasin analytique

Les transactions méritent une phrase de plus, parce que c'est le point qui surprend le plus les équipes venues du relationnel. Un lot transactionnel ou une procédure stockée s'exécutent à l'intérieur d'une seule partition logique. Dès que deux entités n'ont pas la même valeur de clé de partition, l'atomicité redevient un problème d'application.

La suite du billet prend les décisions dans l'ordre où elles se posent : l'interface, puis la clé de partition, puis le débit en unités de requête, puis la cohérence, puis la distribution multi-région, et enfin les réglages qu'on ajuste en cours de route.

Un magasin non relationnel ne remplace pas une base relationnelle, il répond à d'autres questions. Le billet sur les bases relationnelles Azure traite l'autre moitié du sujet, et les deux cohabitent dans presque tous les systèmes sérieux.

Choisir son interface

Le port accepte plusieurs formats de bordereau, parce que les armateurs arrivent avec leurs habitudes. Le format maison est le plus complet ; les autres existent pour ne pas obliger un client à réécrire ses procédures.

Cosmos DB fonctionne pareil. Il expose plusieurs interfaces qui parlent aux pilotes existants d'autres moteurs, et une interface native qui est celle du service lui-même.

Interface Ce que c'est Ce que le choix engage
NoSQL l'interface native, documents JSON, langage de requête proche de SQL l'outillage le plus complet, les nouveautés y arrivent en premier
MongoDB compatibilité avec le protocole de MongoDB on garde ses pilotes, on hérite d'un niveau de compatibilité à vérifier fonction par fonction
Cassandra compatibilité avec le langage de requête de Cassandra modèle en colonnes larges, clé de partition déclarée dans le schéma
Gremlin graphe de sommets et d'arêtes traversées natives, écosystème plus étroit
Table clé-valeur, compatible avec le stockage table Azure reprise directe d'un code existant, modèle de données pauvre

La règle de décision tient en deux lignes. Nouveau développement : l'interface native, sans hésiter. Migration d'un applicatif existant qui parle déjà l'un des protocoles compatibles : l'interface correspondante, en validant d'abord les opérations réellement utilisées.

Le compte porte son interface pour de bon : elle se choisit à la création et ne se change pas ensuite. Changer d'avis revient à créer un compte et à recopier les données.

Un cas à part mérite d'être connu : l'offre MongoDB en mode vCore, facturée par nœud de cluster et non en unités de requête. Le modèle économique et le modèle d'exploitation y sont différents, et la page de choix d'interface en donne les critères à jour.

Le choix d'interface engage l'outillage disponible et la portabilité du code. Il se fait une fois, et il survit à l'équipe qui l'a fait.

La clé de partition

Un entrepôt bien tenu range ses palettes par armateur, parce que c'est comme ça qu'on vient les chercher. Un entrepôt rangé par date d'arrivée oblige à traverser toutes les allées pour rassembler la cargaison d'un seul client, et concentre toute l'activité du jour dans la même travée.

Partitions logiques et partitions physiques

Une partition logique regroupe tous les éléments qui partagent la même valeur de clé de partition. Elle a un plafond de taille documenté, et surtout elle vit entièrement sur une seule machine.

Une partition physique héberge une ou plusieurs partitions logiques. C'est elle qui porte réellement le stockage et le débit, et le service en ajoute automatiquement quand le conteneur grossit.

Le point structurant est la répartition du débit : les unités de requête provisionnées sur un conteneur sont partagées équitablement entre ses partitions physiques. Trente mille unités réparties sur trois partitions physiques donnent dix mille par partition, et pas une de plus pour celle qui travaille.

flowchart TB C["Conteneur
30 000 RU/s provisionnées"] --> P1["Partition physique 1
budget 10 000 RU/s"] C --> P2["Partition physique 2
budget 10 000 RU/s"] C --> P3["Partition physique 3
budget 10 000 RU/s"] P1 --> L1["clé = date du jour
demande 25 000 RU/s"] P2 --> L2["clé = date J-1
demande proche de zéro"] P2 --> L3["clé = date J-2"] P3 --> L4["clé = date J-3"] P3 --> L5["clé = date J-4"] classDef conteneur fill:#1f2428,stroke:#8a4d16,color:#f5f2ec; classDef froide fill:#ede9e1,stroke:#8a4d16,color:#2c3338; classDef chaude fill:#a0413e,stroke:#8a4d16,color:#f5f2ec; class C conteneur; class P2,P3,L2,L3,L4,L5 froide; class P1,L1 chaude;

La partition brûlante, chiffrée

Le schéma décrit une panne banale et coûteuse. La clé de partition est la date, toutes les écritures du jour visent donc la même partition logique, donc la même partition physique.

Demande d'écriture en pointe                     25 000 RU/s
Débit provisionné sur le conteneur               30 000 RU/s
Partitions physiques                                       3
Budget par partition physique                    10 000 RU/s

Servi par la partition du jour                   10 000 RU/s
Rejeté en limitation (code 429)                  15 000 RU/s   soit 60 %
Inutilisé sur les deux autres partitions         20 000 RU/s

Le réflexe classique consiste à provisionner davantage. Il ne sert à rien : ajouter du débit finit par déclencher de nouvelles partitions physiques, entre lesquelles le total est de nouveau réparti. La partition brûlante reste enfermée dans le budget d'une seule d'entre elles, et la facture, elle, monte pour de bon.

La règle de choix

Une bonne clé satisfait trois conditions simultanément : beaucoup de valeurs distinctes, une répartition régulière des requêtes entre ces valeurs, et une présence dans le filtre de la requête la plus fréquente.

Le troisième critère est celui qu'on oublie. Une requête qui ne porte pas la clé de partition part en éventail sur toutes les partitions, paie en unités de requête proportionnellement au nombre de partitions interrogées, et attend la plus lente.

-- Requête à une seule partition : le filtre porte la clé
SELECT * FROM c WHERE c.tenantId = @tenantId AND c.statut = 'ouvert'

-- Requête en éventail : le moteur interroge toutes les partitions
SELECT * FROM c WHERE c.statut = 'ouvert'

Quand aucun champ existant ne convient, on en fabrique un. La clé synthétique concatène deux propriétés, ou ajoute un suffixe calculé pour éclater une valeur trop chargée.

{
  "id": "cmd-88213",
  "tenantId": "acme",
  "cle": "acme-07",
  "date": "2026-08-26",
  "statut": "ouvert",
  "montant": 1290.5
}

Ici, cle vaut l'identifiant du client suivi d'un compartiment tiré du numéro de commande. Douze compartiments transforment un client unique et brûlant en douze partitions logiques réparties sur toutes les partitions physiques, au prix d'une lecture qui interroge douze clés au lieu d'une.

Clé retenue Symptôme observé Correction
Une date ou un horodatage toutes les écritures sur la partition du jour, les autres inertes identifiant d'entité, ou date composée avec un compartiment
Un statut ou un booléen deux ou trois partitions logiques au total, quel que soit le volume en faire un champ indexé, jamais une clé
L'identifiant de client en multi-locataire un gros client sature sa partition et atteint le plafond de taille clé hiérarchique, ou clé composée avec un compartiment
Un identifiant unique par élément écritures parfaitement réparties, mais chaque lecture métier part en éventail la clé du regroupement réellement lu
La région ou le pays quelques valeurs, très inégales identifiant d'entité, la région restant un champ filtrable

Pour le cas multi-locataire, les clés de partition hiérarchiques sont la réponse propre : jusqu'à trois niveaux imbriqués, ce qui permet à un locataire volumineux de s'étaler sur plusieurs partitions physiques tout en gardant ses lectures ciblées.

Changer de clé après coup impose de créer un conteneur et d'y recopier les données, avec la bascule applicative que cela suppose. Des travaux de copie de conteneur existent pour outiller l'opération, mais l'opération reste une migration.

Une clé de partition se déduit de la requête qu'on exécutera dix mille fois par minute, pas de la structure du document. C'est la décision dont l'erreur se paie en semaines de migration.

Les unités de requête

Le quai ne facture pas les conteneurs, il facture la cadence de manutention réservée. Que les grues tournent ou non, la réservation court ; et le jour où le trafic dépasse la cadence réservée, les camions attendent devant la barrière.

L'unité de requête est cette cadence. C'est une monnaie unique qui agrège processeur, entrées-sorties et mémoire, avec un point de repère documenté : la lecture d'un élément d'un kilo-octet par son identifiant et sa clé de partition coûte une unité. Une écriture du même élément en coûte plusieurs fois plus, parce qu'il faut aussi mettre les index à jour.

Chaque réponse renvoie sa consommation exacte, dans un en-tête que les kits de développement exposent directement. C'est l'outil de mesure le plus utile du service, et le plus sous-employé : trois requêtes profilées en recette évitent des mois d'approximation.

Trois modes de facturation

Mode Ce qu'on paie Le profil qui le rend évident Ce qu'il coûte quand on se trompe
Débit manuel le débit provisionné, en continu charge stable et connue, 24 heures sur 24 on paie la pointe toute la journée
Mise à l'échelle automatique le maximum consommé dans l'heure, à un tarif par unité majoré charge irrégulière ou saisonnière on paie la majoration sans jamais l'amortir
Sans serveur les unités réellement consommées développement, recette, charge sporadique plafonds plus bas et périmètre régional restreint

Le débit se provisionne au niveau de la base, partagé entre ses conteneurs, ou au niveau d'un conteneur, qui le garde pour lui. Le partage à l'échelle de la base économise sur un grand nombre de petits conteneurs peu sollicités, et se retourne contre soi dès qu'un conteneur devient dominant.

La mise à l'échelle automatique fait varier le débit entre le dixième du plafond fixé et ce plafond. Le tarif par unité y est majoré, ce qui donne un seuil de rentabilité qui se calcule.

Profil mesuré sur 24 h, un conteneur
  pointe                 10 000 RU/s pendant  4 h
  plateau                 1 000 RU/s pendant 20 h

Débit manuel : la pointe se provisionne en permanence
  10 000 x 24                          = 240 000 unités-heures

Mise à l'échelle automatique, plafond 10 000
  plancher = 10 % du plafond           =   1 000 RU/s
  facturation = maximum de l'heure, majoré d'un facteur 1,5
  4 h  x 10 000 x 1,5                  =  60 000
  20 h x  1 000 x 1,5                  =  30 000
  total                                =  90 000 unités-heures

Rapport : 2,7 fois moins cher pour la même pointe absorbée.

Le seuil se lit dans la majoration elle-même : tant que la consommation moyenne reste sous les deux tiers de la pointe, la mise à l'échelle automatique gagne. Au-dessus, le débit manuel redevient le bon choix, et la documentation du service donne les seuils exacts en vigueur.

Limitation et nouvelle tentative

Quand la demande dépasse le budget d'une partition physique sur un intervalle donné, le service refuse l'excédent avec un code de limitation et un en-tête indiquant le délai à respecter avant de réessayer.

Les kits de développement officiels réessaient tout seuls, un nombre de fois et pendant une durée configurables. C'est confortable et c'est un piège : les nouvelles tentatives se voient dans la latence perçue bien avant de se voir dans les erreurs applicatives.

D'où la règle d'exploitation : surveiller le taux de requêtes limitées et la consommation en unités par partition physique, pas seulement le taux d'erreur global. Une partition brûlante se lit dans ces deux courbes des semaines avant de provoquer un incident.

Le débit se dimensionne sur le coût mesuré des requêtes réelles multiplié par leur fréquence, jamais sur le volume de données stocké. Tout le reste est du chiffre au doigt mouillé.

La cohérence

Le terminal jumeau, de l'autre côté du bassin, tient une copie du carnet de bord. La question n'est pas de savoir si les deux copies finissent par correspondre, c'est de savoir ce qu'on accepte de lire dans l'intervalle, et combien on paie pour réduire cet intervalle.

Cosmos DB expose cinq niveaux, réglés par défaut au niveau du compte, qu'une requête peut assouplir individuellement sans jamais pouvoir les renforcer.

Niveau Ce qu'il garantit Coût en lecture Ce qu'il coûte ailleurs
Forte toute lecture voit la dernière écriture validée environ le double des niveaux souples latence d'écriture entre régions, et incompatibilité avec les écritures multi-région
Obsolescence limitée le retard est borné par un nombre de versions ou une durée environ le double des niveaux souples les écritures peuvent être ralenties quand le retard approche la borne
Session dans une même session, on relit ses propres écritures, dans l'ordre tarif de base les garanties tombent dès qu'on sort de la session
Préfixe cohérent on ne voit jamais des écritures dans le désordre tarif de base le retard n'est pas borné
Éventuelle les copies convergent, sans ordre garanti tarif de base tout, sauf le prix

Le niveau Session est le défaut, et c'est un bon défaut. Il couvre le cas qui compte vraiment dans une application classique : l'utilisateur qui valide un formulaire et recharge la page immédiatement après doit voir sa propre modification.

Deux erreurs symétriques se rencontrent en production. Choisir la cohérence forte par principe, et découvrir la facture en unités et la latence des écritures inter-région. Ou choisir la cohérence éventuelle pour la performance, et passer six mois à écrire du code défensif pour compenser des lectures en retard.

Le réglage par requête est la sortie élégante. On garde Session au niveau du compte, et on assouplit explicitement les lectures qui tolèrent du retard : un tableau de bord, un compteur, une liste de suggestions. La documentation des niveaux de cohérence détaille les garanties formelles de chacun.

La cohérence se choisit par cas d'usage, pas par conviction. Le bon réglage est le plus souple qui laisse encore l'application correcte, et il s'écrit dans le code de la requête.

Distribuer sur plusieurs régions

Le second terminal ne sert à rien s'il ne fait que regarder le premier travailler. La vraie décision est de savoir s'il reçoit lui aussi des navires, ou s'il attend son tour comme port de repli.

Ajouter une région de lecture est une case à cocher, et l'effet est immédiat : les lectures partent vers la région la plus proche du client, la latence tombe, et les écritures continuent de remonter vers la région d'écriture.

Activer les écritures multi-région change de nature. Chaque région accepte des écritures locales, ce qui supprime le trajet vers la région maîtresse, et introduit un problème que le mode à région d'écriture unique n'a pas : deux écritures concurrentes sur le même élément dans deux régions.

sequenceDiagram participant C as Client participant N as Région Nord participant S as Région Sud C->>N: "écriture de l'élément 42" N-->>C: "accusé et jeton de session" N->>S: "réplication asynchrone" C->>S: "lecture de l'élément 42 avec le jeton" S-->>C: "réponse servie une fois le jeton rattrapé" Note over C,S: "sans jeton de session, la lecture peut renvoyer la version précédente"

Le service propose deux politiques de résolution de conflit : la dernière écriture l'emporte, arbitrée sur l'horodatage interne ou sur une propriété numérique qu'on désigne, ou une résolution personnalisée avec une procédure de fusion et un flux de conflits à traiter. Les politiques de résolution méritent d'être lues avant d'activer la fonction, pas après le premier écrasement silencieux.

Deux conséquences à retenir. La cohérence forte n'est pas disponible avec des écritures multi-région, par construction. Et chaque région ajoutée facture son propre débit, donc le coût suit le nombre de régions.

Le basculement géré par le service, avec un ordre de priorité déclaré, relève de la continuité et sera traité au palier 3. La configuration se fait ici, ses conséquences se vivent là-bas.

Les lectures multi-région coûtent une case à cocher. Les écritures multi-région coûtent un modèle de conflits, et ce modèle est une décision applicative, pas une décision d'infrastructure.

Indexation, durée de vie et flux de modification

Trois réglages restent, moins spectaculaires, mais qui font la différence entre un conteneur sain et un conteneur qui coûte trois fois son prix.

L'indexation. Par défaut, toutes les propriétés sont indexées. C'est excellent pour un usage de lecture varié et médiocre pour une charge d'écriture intensive, puisque chaque écriture paie la mise à jour de tous les index.

{
  "indexingMode": "consistent",
  "includedPaths": [ { "path": "/tenantId/?" }, { "path": "/statut/?" } ],
  "excludedPaths": [ { "path": "/*" } ]
}

Cette politique inverse le réglage : rien n'est indexé sauf les deux chemins réellement filtrés. Sur un conteneur d'événements écrit en masse et lu par clé, l'économie en unités d'écriture est immédiate. Les index composites, eux, servent aux tris portant sur plusieurs propriétés, et la page des politiques d'indexation donne la syntaxe complète.

La durée de vie. Un conteneur peut expirer ses éléments automatiquement, avec une valeur par défaut et une valeur par élément. La suppression est une tâche de fond qui consomme les unités disponibles, ce qui signifie qu'un conteneur saturé nettoie plus lentement.

Le flux de modification. Chaque conteneur expose le journal ordonné de ses écritures, consommable par un déclencheur de fonction ou par une bibliothèque de traitement. C'est la brique d'intégration standard du service : matérialiser une vue, alimenter un moteur de recherche, déclencher une notification.

Une limite à connaître avant de bâtir dessus : le mode courant du flux de modification rapporte les créations et les mises à jour, pas les suppressions. Le contournement usuel est la suppression logique, un champ marqué puis une expiration par durée de vie, ce qui produit une mise à jour visible dans le flux.

Pour l'analytique, le magasin analytique en colonnes se synchronise depuis le magasin transactionnel et s'interroge sans consommer ses unités de requête. C'est le pont naturel vers la chaîne d'intégration de données, sujet du billet suivant.

Ces trois réglages se posent en une heure et se paient tous les mois. L'indexation par défaut, en particulier, est le premier poste d'économie d'un conteneur à forte écriture.

Les magasins voisins, quand ils suffisent

Toutes les marchandises n'ont pas besoin d'un entrepôt sous température dirigée. Une consigne à casiers fait très bien l'affaire, pour beaucoup moins cher.

Le compte de stockage Azure embarque deux magasins non relationnels souvent oubliés, traités plus largement dans le billet sur les comptes de stockage.

Besoin Le service qui suffit Le moment de passer à Cosmos DB
Table clé-valeur, gros volume, lectures par clé stockage table latence garantie, distribution multi-région, index secondaires
File de travail simple entre deux composants stockage file ordre strict, publication vers plusieurs abonnés, transactions
Cache de session ou de calcul cache managé en mémoire persistance durable exigée

Le stockage table répond à une part surprenante des besoins réels, avec sa clé de partition et sa clé de ligne, pour un coût sans commune mesure. La bascule vers Cosmos DB se justifie quand la latence devient contractuelle, quand la donnée doit vivre dans plusieurs régions, ou quand les requêtes cessent de porter uniquement sur la clé.

Le bon réflexe consiste à retenir le magasin le moins cher qui répond encore à la question posée, et à garder le plus capable pour le jour où la question change. L'interface Table de Cosmos DB rend d'ailleurs la migration ultérieure peu douloureuse.

Scénario B : DynamoDB, 20 septembre 2015

Le meilleur cas documenté sur le couple partitionnement et débit ne vient pas d'Azure. AWS a publié un rapport détaillé sur la panne de DynamoDB dans sa région US-East, survenue au petit matin du 20 septembre 2015, et ce rapport décrit exactement les mécanismes traités plus haut.

Le service s'appuie sur un composant de métadonnées qui détient la répartition des partitions. Chaque serveur de stockage lui demande la liste des partitions dont il a la charge, et se retire du service s'il n'obtient pas cette liste dans le délai imparti.

Deux évolutions ont préparé le terrain. Les index secondaires globaux avaient fait grossir la quantité de métadonnées associée à certaines tables, donc allongé le temps de réponse des demandes correspondantes. Le délai d'attente, lui, n'avait pas été révisé en proportion.

Un incident réseau bref a suffi à déclencher la suite. Une partie des serveurs de stockage n'a pas reçu ses métadonnées à temps, s'est retirée du service, puis a redemandé la totalité de sa répartition. Le composant de métadonnées s'est retrouvé submergé par ces demandes simultanées, ce qui a fait échouer de nouvelles demandes, ce qui a retiré d'autres serveurs, en boucle.

La sortie de crise est la partie la plus instructive. Tant que le composant de métadonnées restait saturé, aucun serveur ne pouvait revenir en ligne, et les tentatives de reprise entretenaient la saturation. Les équipes ont dû suspendre les demandes adressées au composant pour faire retomber les nouvelles tentatives, puis lui ajouter de la capacité avant que le service ne se remette à répondre, ce qui a pris plusieurs heures. Des services de la même région qui dépendaient de DynamoDB ont été affectés en cascade.

Les remédiations annoncées visent le même point : ajouter de la capacité au composant de métadonnées, surveiller la taille des listes de partitions, espacer les demandes des serveurs de stockage et allonger le délai qui leur est accordé, et surtout découper ce composant en plusieurs instances servant chacune une partie du parc.

Rien, dans cette histoire, ne met en cause le modèle de données. Ce qui a lâché, c'est la mécanique qui associe les partitions à des machines et le budget qui la fait tenir. La transposition tient en une phrase.

Un magasin non relationnel ne tombe presque jamais à cause de son moteur. Il tombe à cause d'une répartition de partitions déséquilibrée, d'un budget de débit atteint, et d'un mécanisme de reprise qui redemande tout en même temps.

Points clés

  • L'interface se fige à la création du compte, la clé de partition à la création du conteneur. Le reste se règle et se corrige ; ces deux-là se recopient.
  • Une clé de partition se choisit sur trois critères simultanés : beaucoup de valeurs distinctes, une répartition régulière des requêtes, et une présence dans le filtre de la requête dominante. Faute de champ adéquat, on fabrique une clé synthétique ou hiérarchique.
  • Le débit provisionné est partagé équitablement entre les partitions physiques. Une partition brûlante reste donc enfermée dans le budget de la sienne, et provisionner davantage ne la sauve pas.
  • Le mode de facturation se déduit du profil de charge : manuel pour une charge stable, mise à l'échelle automatique tant que la moyenne reste sous les deux tiers de la pointe, sans serveur pour le sporadique et les environnements hors production.
  • La cohérence se règle par requête plutôt que par principe. Session en défaut, assouplissement explicite là où le retard est tolérable, cohérence forte réservée aux cas qui la justifient et incompatible avec les écritures multi-région.

Dans la série

Palier 2 : Construire les terminaux. Domaine : Stockage & données.


Pour aller plus loin