Aller au contenu principal

Deux clusters, un seul change feed : répliquer à travers un WAN sans étirer Zaris

· 11 minutes de lecture
Clustron Team
Ingénierie des systèmes distribués

Deux clusters indépendants reliés par un change feed reprenable à travers un WAN

Tôt ou tard, quelqu'un pose la question : nous avons un cluster dans une région et des utilisateurs dans une autre — ne peut-on pas simplement ajouter quelques nœuds là-bas et laisser un seul cluster couvrir les deux ? C'est un réflexe raisonnable. Un cluster, un espace de noms, une chaîne de connexion, des données partout. Ce réflexe est aussi un piège, et il vaut la peine d'être précis sur le pourquoi.

Un cluster Zaris est conçu pour un réseau de datacenter. L'appartenance est suivie avec des heartbeats sur des minuteries courtes. La possession d'une partition se déplace entre nœuds via une poignée de main qui suppose que l'autre côté répond vite. Le basculement clôture un propriétaire suspecté mort et promeut un réplica à l'échelle de quelques secondes. Chacun de ces mécanismes est calibré pour une liaison où l'aller-retour se mesure en microsecondes et où un nœud « lent » est véritablement malade — et non simplement à quatre-vingts millisecondes et un océan de distance. Mettez la moitié des nœuds de l'autre côté d'un WAN et vous n'avez pas construit un cluster géo-distribué ; vous avez construit un seul cluster qui prend désormais la latence WAN normale pour une défaillance, fait flapper la possession, et risque le split-brain au premier hoquet de la liaison entre régions.

La bonne forme n'est donc pas un seul cluster étiré. Ce sont deux clusters indépendants — chacun autonome, chacun gardant sa possession et son basculement entièrement sur son propre LAN — reliés par un change feed asynchrone et reprenable. Cet article porte sur ce motif : pourquoi l'indépendance est la décision porteuse, et comment le log d'opérations de Zaris vous remet déjà la seule chose dont un réplicateur WAN a réellement besoin — un curseur depuis lequel reprendre.

Pourquoi un cluster étiré est le mauvais outil​

Il est utile de nommer les hypothèses précises qu'un cluster unique formule, car chacune se brise différemment sur un WAN.

  • L'appartenance fondée sur les heartbeats décide qu'un nœud a disparu quand il manque quelques battements. Une liaison inter-régions qui ajoute de la latence et la partition occasionnelle déclenchera ce détecteur en fonctionnement sain. Le cluster déclarera morts des nœuds vivants.
  • Le protocole de possession déplace les partitions entre nœuds avec une poignée de main qui attend une réponse rapide. À travers un WAN, ces transitions calent, réessaient et se concurrencent — exactement le churn que la machinerie de possession et de cut-over est conçue pour résoudre rapidement sur un LAN.
  • Les minuteries de basculement promeuvent un réplica quand le propriétaire semble mort. Sur un WAN, « semble mort » et « est juste très loin » deviennent indiscernables, d'où des promotions parasites — et si la liaison se rétablit avec un réplica promu de chaque côté, vous avez deux propriétaires pour une même partition.

Rien de tout cela n'est un défaut. C'est un store qui fait exactement ce qu'un store intra-datacenter à faible latence devrait faire, appliqué à un réseau qu'il n'a jamais été destiné à couvrir. La solution n'est pas de relâcher chaque minuterie jusqu'à ce que le cluster tolère un WAN — cela le rendrait aussi beaucoup trop lent à tolérer de vraies défaillances. La solution est de cesser de demander à un seul cluster de le faire.

La brique que vous possédez déjà : un log rejouable​

Zaris conserve, par segment, un log d'opérations ordonné par révision. Chaque écriture acceptée fait avancer une révision monotone et ajoute une entrée. Ce log est le substrat que les réplicas et les nouveaux propriétaires de partition utilisent pour se remettre à niveau sans geler les écritures : un consommateur qui connaît la dernière révision qu'il a vue peut rejouer en avant depuis ce point et converger vers la vérité courante.

Cette propriété — rejouer en avant depuis une révision connue — est tout le fondement d'un réplicateur WAN. Un change feed n'est rien de plus qu'un lecteur de longue durée de ce log qui diffuse les entrées dans l'ordre des révisions et se souvient de l'endroit où il s'est arrêté. Ce dont il se souvient est un curseur : la révision la plus haute qu'il a durablement appliquée, par segment.

Parce que le log est ordonné par révision et que la relecture démarre d'un point que vous choisissez, le feed possède les deux traits qu'exige une liaison WAN :

  1. Reprenable. La liaison va tomber — c'est le fait qui définit un WAN. Quand elle revient, le suiveur se reconnecte et demande tout ce qui suit son curseur stocké. Pas de re-copie complète, pas de coordination, juste « reprendre à la révision N ».
  2. Idempotent à la relecture. N'appliquez une entrée que lorsque sa révision est supérieure à la dernière appliquée pour ce segment. Si une reconnexion redélivre la queue, le suiveur saute ce qu'il possède déjà. La livraison en double devient un no-op, ce qui permet au feed d'être au-moins-une-fois au lieu de devoir être exactement-une-fois.

Le motif, de bout en bout​

Deux clusters. Appelons-les A (la source) et B (le suiveur). Chacun est un cluster Zaris ordinaire et indépendant qui ne sait rien des rouages internes de l'autre.

Dans le cluster B, vous exécutez un suiveur de réplication — un client qui consomme le change feed de A et écrit les opérations dans B localement. Son état est minuscule et durable : un curseur par segment. La boucle est sans gloire, ce qui est tout l'intérêt :

  1. Se connecter à A, demander le change feed pour les segments (ou préfixes de clé) que vous voulez répliquer, en commençant après le curseur stocké de chaque segment.
  2. Recevoir les entrées dans l'ordre des révisions. Pour chacune, appliquer l'opération à B — SET, DELETE, expiration — gardée par la vérification de révision afin que la redélivraison soit inoffensive.
  3. Faire avancer et persister le curseur au fur et à mesure que les entrées sont durablement appliquées.
  4. À toute déconnexion, revenir à l'étape 1. Le curseur est là où vous vous êtes arrêté.

Tout ce dont le suiveur a besoin pour survivre à une interruption vit dans ce curseur. Il n'y a pas de transaction distribuée à travers le WAN, pas d'appartenance partagée, pas de quorum inter-régions. Un hoquet sur la liaison ne vous coûte qu'un peu de fraîcheur.

Ce que vous échangez : le RPO n'est pas nul, et c'est le marché​

Cette réplication est asynchrone. Le suiveur est toujours à quelque distance derrière la source — autant de révisions produites dans A mais pas encore appliquées dans B. Cette distance est votre Objectif de point de récupération (Recovery Point Objective) : si la région A disparaît à l'instant, vous perdez tout ce qui n'avait pas encore atteint B.

Ce n'est pas un défaut dont il faut s'excuser ; c'est le compromis que vous choisissez explicitement. L'alternative — faire attendre aux écritures de A un accusé de réception de B avant de valider — mettrait un aller-retour WAN sur votre chemin d'écriture et abandonnerait la faible latence qui est toute la raison pour laquelle vous exécutez Zaris. La réplication inter-régions synchrone transforme chaque écriture en une requête transcontinentale. Presque personne n'en veut réellement une fois la facture de latence vue.

Le cadrage honnête est donc : vous choisissez le RPO. Un suiveur bien alimenté sur une liaison saine reste à quelques révisions près — une obsolescence inférieure à la seconde en pratique. Une liaison saturée ou qui flappe le laisse dériver davantage. Le curseur rend la dérive bornée et visible — vous pouvez mesurer exactement à quelle distance B est derrière en comparant son curseur à la révision courante de A — mais il ne la rend pas nulle. Dimensionnez votre tolérance en conséquence, et alertez sur le retard de curseur comme vous alerteriez sur le retard d'un réplica à l'intérieur d'un cluster.

Quand la source bascule, le feed s'en moque​

Voici où le fait de bâtir sur le log d'opérations paie une seconde fois. À l'intérieur du cluster A, la partition que lit votre feed changera occasionnellement de propriétaire — un nœud meurt, un réplica est promu, une partition migre. Le log survit à cela : il est indexé par révision, et un nouveau propriétaire poursuit la même séquence de révisions. Pour le suiveur, un basculement côté source n'est qu'une reconnexion de plus. Il rétablit le feed contre la topologie courante de A et reprend depuis son curseur. Les révisions qu'il a déjà appliquées sont toujours les révisions qu'il a déjà appliquées ; celles qui lui manquent sont toujours après le curseur. Le suiveur n'a jamais à comprendre qui, dans A, possède la partition — seulement quelle révision il a vue en dernier.

C'est la raison discrète pour laquelle la conception curseur-sur-log vaut plus qu'un relais naïf « lire la queue des écritures et les expédier ». Un relais qui diffuse les écritures en direct sans position durable indexée par révision n'a rien depuis quoi reprendre quand la topologie source change. Le log donne au suiveur un système de coordonnées stable qui survit à n'importe quel propriétaire individuel.

La portée est un choix, et les conflits sont une frontière​

Deux autres choses à décider délibérément.

Portée des données. Vous n'avez pas à tout répliquer. Le feed est par segment, et vous pouvez sélectionner les segments ou préfixes de clé qui comptent — un locataire spécifique, un jeu de données spécifique, la tranche dont une région de reprise après sinistre a réellement besoin. Répliquer moins signifie moins de trafic WAN et un suiveur plus petit ; répliquer tout vous donne un secours chaud complet. Décidez à dessein plutôt que par défaut.

Direction et conflits. Le motif ci-dessus est unidirectionnel : A est la source de vérité, B est un suiveur / secours majoritairement en lecture. C'est le cas propre, et il n'a pas de problème de conflit — il y a exactement un rédacteur par clé. Au moment où vous le rendez bidirectionnel — les deux régions acceptant des écritures pour les mêmes clés — vous avez introduit des rédacteurs concurrents séparés par un WAN, et aucune plomberie de change feed ne résout cela pour vous. Vous devez désormais une politique de résolution de conflits : last-writer-wins sur une horloge en laquelle vous avez confiance, possession par clé pour que les deux régions n'écrivent jamais la même clé, ou fusion au niveau applicatif. L'actif-actif est une conception réelle et parfois nécessaire, mais c'est une décision de modélisation des données, pas une fonctionnalité de transport. Optez d'abord pour l'unidirectionnel ; n'adoptez le bidirectionnel que lorsque vous avez décidé, explicitement, comment deux rédacteurs se réconcilient.

La forme à retenir​

Un WAN n'est pas un LAN lent ; c'est un médium différent où les partitions sont un événement normal plutôt qu'une urgence. Un cluster unique, à juste titre, refuse de prétendre le contraire — ses minuteries et son protocole de possession sont réglés pour traiter un long silence comme une défaillance, car à l'intérieur d'un datacenter c'en est presque toujours une. Donc vous n'étirez pas le cluster. Vous en exécutez deux, chacun souverain sur son propre réseau, et vous les reliez avec la seule primitive bâtie pour tolérer une liaison qui va et vient : un log ordonné par révision que vous pouvez rejouer, et un curseur qui se souvient d'où vous en étiez.

L'indépendance est la décision qui rend le reste sûr. Le change feed n'est que le tuyau. Le log d'opérations vous a déjà donné le curseur.