Publish'i kaks korda vajutamine avaldab nüüd ühe korra
Sama rakenduse kolm avaldamispäringut jõudsid meie API-sse 84 millisekundi vahega ja iga käivitas selle rakenduse täieliku juurutuse. Need võistlesid üksteisega samale hostingule, mitmed tulemuslikud väljalasked ebaõnnestusid, sest keegi oli juba jõudnud ette, ja ainus nähtav märk oli e-kiri „release failed“ — rakendus ise lõppes korras. Nüüd ei käivita avaldamine, mis saabub, kui sama rakenduse jaoks juba üks töötab, teist ehitust. Andmebaas otsustab, milline päring võidab, ja teistele öeldakse tõtt: avaldamine on juba käimas.
Mis juhtus
Kui avaldad Mythexis rakenduse, salvestab API juurutuse ja palub meie orkestraatorilt — teenuselt, mis käitab ehitusi — ehitada ja väljastada selle. Avaldamine võib alata nupust Publish, teisest brauseri vahelehest või agendilt, kui palud tal avaldada. Topeltklõps, kaks vahelehte või agendipäring, mis saabub kaks korda, võivad kõik tekitada rohkem kui ühe avaldamise sama rakenduse jaoks peaaegu samal hetkel.
Sel juhul saabusid kolm 84 ms jooksul. Igaüks käivitas täieliku juurutuse. Hostingplatvormil loodi sama rakenduse väljalasked samas sekundis ja kolm neist ebaõnnestus, sest teised olid juba töös. Hilisem väljalase jäi kinni, kuid rakendus töötas hiljem.
Just see muutis selle märkamise raskeks. Kasutaja nägi ühte töötavat rakendust. Tõrked ilmusid ainult e-kirjadena „release failed“.
Miks miski ei peatanud seda
Avaldamise kood sisaldas küll sammu, mis vaatas projekti töös olevaid juurutusi. Aga see, mida ta tegi, oli märkida enne alustamist kõik töös olevad juurutused ebaõnnestunuks — „asendatud uue avaldamisega“. See oli mõeldud selleks, et koristada ehitusi, mille katkenud protsess maha jättis.
Praktikas tähendas see, et teine avaldamine, mis saabus ehituse ajal, kuulutas esimese surnuks ja alustas oma. Mõlemad jooksid lõpuni sama rakenduse vastu. Lukku ei olnud; oli samm, mis aktiivselt tegi võistluse teed vabaks.
Esimese avaldamise juures rakenduse jaoks oli veel teinegi, veidram sümptom. Kui juurutuskirjet, mida uuesti kasutada, ei olnud veel olemas, proovisid kaks samaaegset esimest avaldamist mõlemad sisestada ühe. Andmebaasil on ainulaadne indeks rakenduse lingi nimel (slug, nagu your-app lehel your-app.mythex.ai), nii et teine sisestus ebaõnnestus. API tõlkis selle sõnumiks „see lingi nimi on võetud“ — soovitades kasutajal ümber nimetada rakendus, mis just sel hetkel avaldati edukalt selle nime all.
Parandus: lukk on nõue
Juurutuskirje nõudmine on nüüd üks tingimuslik andmebaasi uuendus: võta see rida ja märgi ta ehitavaks ainult siis, kui keegi seda ei avalda. Kuna see on üks lause, lahendab andmebaas samal millisekundil saabuvad päringud. Täpselt üks neist muudab rida. Teised näevad, et nulli rida muutus, peatuvad ega jõua kunagi orkestraatorini.
Lukul peab olema ka väljapääs, kui selle omanik sureb. Kui API taaskäivitus avaldamise keskel, jääks rida märgituna „ehitamisel“ igavesti ja rakendust ei saaks avaldada. Seega saab rea, mille omanik on lõpetanud südamelöögisõnumite saatmise — käimasolev avaldamine uuendab rida iga 30 sekundi järel, ja viis minutit vaikust loetakse surnuks —, uuesti nõuda. See lävi tuleb südamelöögist, mitte oletusest, kui kaua ehitus võiks kesta. Kasutaja enda Dockerfile'i ehitamine võib seaduspäraselt kesta kaua, ja aeglane ehitus, mis endiselt südamelööke saadab, säilitab oma nõude.
Samm, mis rakenduse igas töös olevas juurutuses tõrke tekitas, enam ei jookse enne avaldamist. Puhastatakse ainult read, mille surnud protsess maha jättis.
Kaotajale tõe ütlemine
Päring, mis kaotab, saab nüüd selge vastuse: publish_in_progress koos käimasoleva juurutuse ID-ga. Iga koht, kus avaldamist alustada saab, käsitleb seda:
- Töötoa järgib juba käimasolevat ehitust, selle asemel et näidata tõrke selle peal.
- Agenti teavatakse, et selle rakenduse avaldamine on juba käimas ja ta peab ootama, mitte ei öelda, et midagi ebaõnnestus — mis varem võis saata ta „parandama“ Dockerfile'i ja proovima uuesti.
- Esimese avaldamise kokkupõrge lingi nimel kontrollitakse: kui rida, mis nime hoiab, on selle projekti enda käimasolev avaldamine, on vastus „juba avaldamisel“, mitte „see nimi on võetud“. Päris kokkupõrge teise projektiga saab siiski ümbernimetamise sõnumi.
Kuidas me kontrollisime
Kirjutasime reprodutseerimisskripti, mis käivitab ühe rakenduse kolm avaldamist samal ajal päris andmebaasi ja kõnealuse orkestraatori vastu, ning loendab, mitu juurutust orkestraatorilt küsitakse. See katab esmase avaldamise ja elava rakenduse uuesti avaldamise. Enne parandust loendas see kaks juurutust, pärast seda ühe.
Skript katab ka väljapääsu: märgib rakenduse rea ehitavaks ilma südamelöökideta kümme minutit, avaldab uuesti ja kontrollib, et uus avaldamine nõuab rea ja käivitub.
Ühiktestid hoiavad lukku nende kahe asja juures, mida lukk peab tegema: kolme samaaegse uuesti avaldamise korral pääseb läbi täpselt üks ja kaks teist saavad teate, et avaldamine on käimas; ning ehitus, mille omanik suri, saab nõuda. Üks teine test kontrollib, et uus katse ei kanna üle eelmise katse veateadet.
Mida sellest õppisime
- Puhastuskood võib olla varjatud võistlus. „Tühista kõik käimasolev, siis alusta“ näeb välja nagu hügieen. Kahe kutsujaga on see iga ühe tühistamine teise poolt.
- Lase andmebaasil otsustada. Lugemine, millele järgneb kirjutamine, jätab vahe teisele päringule. Üks tingimuslik uuendus seda ei jäta.
- Iga lukk vajab aegumist, mis on seotud elusoleku kontrolliga. Ilma selleta muutub kokkujooks kinni jäänud rakenduseks.
- Veateated on juhised. „See nimi on võetud“ ütles inimestele teha vale asi. Õige sõnum oli lihtsam ja tõene.
Rohkem avaldamisest dokumentatsioonis: kuidas avaldada. Ja seotud parandus: sinu avaldatud rakendus jääb üles ka uuesti avaldamise ajal.