Upozornění, která se spustí jednou, ne jednou na každý server
Jedno z našich provozních upozornění – „nízký stav kreditů“ pro publikovanou aplikaci tvůrce – mělo dorazit nejvýš jednou denně. Jednoho odpoledne dorazilo pětkrát. Příčina: každá kopie našeho API si v paměti pamatovala, která upozornění už odeslala, takže dva servery odeslaly jedno upozornění dvakrát, a každé nasazení tuto paměť smazalo a nechalo je odeslat znovu. Stejný vzor způsoboval, že background úloha dělala svou práci dvakrát. Obě teď koordinují přes sdílené úložiště a obě při jeho nedostupnosti selhávají na bezpečné straně.
Příznak
Mythex posílá týmu upozornění, když něco potřebuje pozornost: služba je nedostupná, úloha se zasekla, nebo kreditový zůstatek tvůrce, jehož aplikace je publikovaná, klesá. Každé upozornění má okno deduplikace – období, během něhož se stejné upozornění odešle jen jednou. U upozornění na nízké kredity je to 24 hodin.
Pět kopií stejného upozornění během jednoho odpoledne znamenalo, že okno nedrželo. Horší bylo, že přicházela po dvojicích, zhruba 11 minut od sebe.
Proč se to stalo
Balíček pro upozornění držel deduplikaci v paměti: tabulku „klíč upozornění → kdy naposledy odesláno“ uvnitř běžícího procesu. To funguje, když je jeden proces, který se nikdy nerestartuje. Produkce nemá ani jedno:
- Dvě repliky API. Produkce běží na dvou kopiích API. Každá měla vlastní tabulku, takže každá upozornění odeslala jednou. To jsou ty dvojice.
- Nasazení. Každé nasazení nahradí obě repliky čerstvými procesy s prázdnými tabulkami. Po nasazení se upozornění odeslalo znovu, jako by nikdy předtím odesláno nebylo.
Mezera asi 11 minut mezi oběma upozorněními v každé dvojici pocházela z úlohy, která upozornění vyvolává. Běží na obou replikách a bere zámek, takže repliky ho střídavě vyhrávají, a každá upozornění vyvolala ve chvíli, kdy na ni přišla řada.
V témž kódu byla druhá, tišší chyba. Aby tabulka nerostla donekonečna, mazala se sama, jakmile měla 500 klíčů – smazala každou položku starší 15 minut bez ohledu na její okno. Při zátěži tak 24hodinová deduplikace vydržela jen čtvrt hodiny.
Oprava
Jeden sdílený záznam na upozornění
Než API upozornění odešle, teď si klíč deduplikace upozornění nárokuje v Redisu – sdíleném úložišti v paměti, se kterým mluví obě repliky a které se nerestartuje, když se restartuje API. Nárok je jedna operace „nastav tento klíč, jen pokud neexistuje, a vypršej po dobu okna“. Replika, která nárok získá jako první, upozornění odešle; druhá najde klíč obsazený a mlčí. Nový proces po nasazení klíč stále najde.
Klíč a okno pocházejí ze stejného pravidla, které nyní sdílí balíček pro upozornění, takže se obě vrstvy nemohou rozejít v tom, co se počítá jako „stejné upozornění“.
Selhat otevřeně, ne zavřeně
Nejdůležitější rozhodnutí bylo, co dělat, když se Redis nedá dosáhnout. Zvolili jsme odeslat upozornění přesto. Pokud je Redis nedostupný nebo neodpoví do 1,5 sekundy, upozornění odejde. Duplicitní upozornění je šum. Zahozené upozornění znamená, že se někdo nedozví.
Nejzávažnější upozornění, P0, deduplikace vůbec nezdržuje.
Každá položka si drží vlastní okno
V deduplikaci v paměti balíčku pro upozornění si teď každá položka ukládá, kdy její okno končí, a mazání odstraní jen položky, jejichž okno už uplynulo. Klíč s 24hodinovým oknem zůstane 24 hodin, bez ohledu na to, kolik dalších upozornění přijde.
Stejná chyba, jinde
Stejný vzor zdvojnásobil i kus práce na pozadí: reconciliation úlohu, která udržuje náš záznam o stavu každého pracovního prostoru v souladu s poskytovatelem sandboxů. Každý průchod vypíše každý sandbox v účtu – tisíce v produkci, asi pět sekund – plus každý běžící nebo pozastavený projekt. Obě repliky ji spouštěly každé dva minuty.
Její zápisy jsou idempotentní – jejich spuštění dvakrát dá stejný výsledek jako jednou – takže se nic nerozbilo. Jen šlo o dvojnásobek práce. Nyní se každé dvouminutové okno nárokuje v Redisu stejně, jak si naše měřiče využití už nárokovaly svá okna, a průchod spustí jen replika, která okno získá. Pokud se Redis nedá dosáhnout, průchod proběhne přesto: práce navíc dvakrát je neškodná, nedělat ji není.
Jak jsme to testovali
Testy modelují skutečné nastavení. Načtou modul upozornění dvakrát, jako dvě oddělené „repliky“, nasměrují je na jedno sdílené úložiště, z každé vyvolají stejné upozornění, pak načtou třetí čerstvou kopii, která zastupuje „po nasazení“, a vyvolají ho znovu. Odešle se přesně jedno upozornění. Další testy ověřují, že upozornění jiného tvůrce stále odejde, že P0 se odešle z obou replik a že se upozornění odešle i tehdy, když je úložiště nedostupné nebo se nedostaví do časového limitu. Oprava mazání má vlastní test: klíč s 24hodinovým oknem přežije 600 dalších upozornění, která přijdou o 20 minut později.
U reconciliation úlohy dvě repliky tikající současně plus třetí tik ve stejném okně vytvoří jeden výpis.
Co si z toho bereme
- Stav v paměti patří jednomu procesu. Cokoli, co musí platit napříč servery – „odesláno jednou“, „proběhlo jednou“ – musí žít tam, kde ho sdílejí.
- Nasazení jsou restarty. Stav, který přežije jen do dalšího restartu, nepřežije běžný pracovní den.
- Rozhodněte, jak selhat. U upozornění by nedostupné sdílené úložiště mělo znamenat víc upozornění, ne méně.
- Hledejte vzor, ne jen výskyt. Upozornění bylo hlasitý případ; zdvojená reconciliation úloha byla stejná chyba, tiše stojící práci.
Nic z toho není vidět zevnitř Mythexu. Více o tom, jak udržujeme platformu stabilní, najdete v článcích jak jsme dostali nasazení na nula chyb 502 a chybějící swap soubor. Pokud jste s myšlenkou běhu více kopií služby začátečník, náš průvodce co je server pokrývá základy.