Alertes que s'envien una vegada, no una vegada per servidor
Una de les nostres alertes operatives —«crèdits baixos» per a l'app publicada d'un constructor— està pensada perquè ens arribi com a molt una vegada al dia. Una tarda, va arribar cinc vegades. La causa: cada còpia de la nostra API recordava a la seva pròpia memòria quines alertes ja havia enviat, així que dos servidors la van enviar dues vegades, i cada desplegament buidava aquella memòria i deixava que s'enviés de nou. El mateix patró feia que una feina de fons es fes dues vegades. Ara les dues coordinen a través d'un magatzem compartit, i les dues fallen pel costat segur si aquell magatzem no està disponible.
El símptoma
Mythex envia alertes a l'equip quan alguna cosa necessita atenció: un servei està caigut, una feina s'ha encallat, o el saldo de crèdits d'un constructor s'està acabant mentre la seva app està publicada. Cada alerta té una finestra de deduplicació, un període durant el qual la mateixa alerta només s'envia una vegada. Per a l'alerta de crèdits baixos, aquesta finestra és de 24 hores.
Cinc còpies de la mateixa alerta en una tarda volien dir que la finestra no aguantava. Pitjor encara, arribaven en parelles, uns 11 minuts separades.
Per què va passar
El paquet d'alertes guardava la deduplicació en memòria: una taula de «clau d'alerta → quan es va enviar per última vegada», dins del procés en execució. Això funciona quan hi ha un únic procés que mai no es reinicia. Producció no compleix cap de les dues condicions:
- Dues rèpliques de l'API. Producció executa dues còpies de l'API. Cadascuna tenia la seva pròpia taula, i cadascuna va enviar l'alerta una vegada. Aquestes són les parelles.
- Desplegaments. Cada desplegament substitueix les dues rèpliques per processos nous amb taules buides. Després d'un desplegament, l'alerta tornava a sortir com si mai no s'hagués enviat.
Els ~11 minuts entre les dues alertes de cada parella venien de la feina que genera aquesta alerta. S'executa a les dues rèpliques i agafa un bloqueig, de manera que les rèpliques el guanyen per torns, i cadascuna generava l'alerta quan li tocava.
Hi havia un segon error, més silenciós, en el mateix codi. Per evitar que la taula creixi per sempre, es netejava a si mateixa quan arribava a 500 claus, esborrant totes les entrades de més de 15 minuts, sigui quina sigui la seva finestra. Amb càrrega, una deduplicació de 24 hores podia durar un quart d'hora.
La correcció
Un registre compartit per alerta
Abans que l'API envïi una alerta, ara reclama la clau de deduplicació de l'alerta a Redis, un magatzem en memòria compartit amb què parlen les dues rèpliques i que no es reinicia quan ho fa l'API. La reclamació és una única operació de «defineix aquesta clau només si no existeix, i fes-la caducar després de la finestra». La primera rèplica que la reclama envia l'alerta; l'altra troba la clau ocupada i es queda callada. Un procés nou després d'un desplegament troba la clau encara allà.
La clau i la finestra surten de la mateixa regla que usa el paquet d'alertes, ara compartida entre ells, de manera que les dues capes no poden discrepar sobre què compta com «la mateixa alerta».
Fallar obert, no tancat
La decisió més important va ser què fer quan no es pot arribar a Redis. Vam triar enviar l'alerta igualment. Si Redis està caigut, o no respon en 1,5 segons, l'alerta surt. Una alerta duplicada és soroll. Una alerta que es perd és algú que no s'assabenta.
Les alertes més greus, P0, mai no s'aturen per la deduplicació.
Cada entrada conserva la seva pròpia finestra
En la deduplicació en memòria del paquet d'alertes, cada entrada ara guarda quan acaba la seva finestra, i la neteja només elimina les entrades cuja finestra ja ha acabat. Una clau de 24 hores es conserva 24 hores, per moltes altres alertes que arribin.
El mateix error, en un altre lloc
El mateix patró havia duplicat també una part de la feina de fons: una tasca de reconciliació que manté el nostre registre de l'estat de cada espai de treball alineat amb el del proveïdor de sandbox. Cada passada llista tots els sandbox del compte —milers en producció, uns cinc segons— més cada projecte en marxa o pausat. Les dues rèpliques l'executaven cada dos minuts.
Les seves escriptures són idempotents —executar-les dues vegades dona el mateix resultat que executar-les una—, així que res no es va trencar. Només era el doble de feina. Ara cada finestra de dos minuts es reclama a Redis de la mateixa manera que ja reclamaven els seus comptadors d'ús, i només la rèplica que la reclama executa la passada. Si no es pot arribar a Redis, la passada s'executa igualment: fer la feina dues vegades és inofensiu, no fer-la no ho és.
Com ho vam provar
Les proves reprodueixen la configuració real. Carreguen el mòdul d'alertes dues vegades, com si fossin dues «rèpliques» separades, les apunten a un mateix magatzem compartit, generen la mateixa alerta des de cadascuna i després carreguen una tercera còpia nova per simular «després d'un desplegament» i la generen de nou. S'envia exactament una alerta. Altres proves comproven que l'alerta d'un altre constructor encara surt, que un P0 s'envia des de les dues rèpliques, i que l'alerta s'envia també quan el magatzem està caigut o es queda penjat més enllà del temps límit. La correcció de la neteja té la seva pròpia prova: una clau de 24 hores sobreviu a 600 altres alertes que arriben 20 minuts després.
Per a la tasca de reconciliació, dues rèpliques que fan tic alhora, més un tercer tic dins de la mateixa finestra, produeixen un únic llistat.
Què n'hem après
- L'estat en memòria és per procés. Qualsevol cosa que ha de ser certa entre servidors —«enviat una vegada», «executat una vegada»— ha de viure en un lloc que comparteixin.
- Els desplegaments són reinicis. Un estat que només sobreviu fins al següent reinici no sobreviu a un dia de treball normal.
- Decideix com fallar. Per a les alertes, que un magatzem compartit estigui caigut ha de voler dir més alertes, no menys.
- Busca el patró, no només la instància. L'alerta era el cas més evident; la tasca de reconciliació duplicada era el mateix error, en silenci, desaprofitant feina.
Res d'això no es veu des de dins de Mythex. Per saber més sobre com mantenim la plataforma estable, consulta com vam reduir els desplegaments a zero errors 502 i el fitxer de swap que faltava. Si t'és nou el concepte d'executar més d'una còpia d'un servei, la nostra guia sobre què és un servidor en explica les bases.