Nospiežot Publish divreiz, tagad publicē vienreiz
Trīs publicēšanas pieprasījumi vienai un tai pašai lietotnei sasniedza mūsu API ar 84 milisekunžu starpību, un katrs no tiem sāka pilnu tās lietotnes izvietošanu. Tie sacentās par to pašu hostingu, vairāki iegūtie izlaidumi neizdevās, jo kāds cits bija pa priekšu, un vienīgā redzamā pazīme bija e-pasts „release failed” — pati lietotne galu galā bija kārtībā. Tagad publicēšana, kas ierodas, kamēr citai publicēšanai tai pašai lietotnei jau notiek, nepalaiž otru būvēšanu. Datubāze izlemj, kurš pieprasījums uzvar, un pārējiem tiek pateikta patiesība: publicēšana jau notiek.
Kas notika
Kad Mythex publicē lietotni, API ieraksta izvietojumu tai un lūdz mūsu orķestratoru — pakalpojumu, kas veic būvēšanu — to uzbūvēt un izlaist. Publicēšana var sākties no pogas Publish, no citas pārlūka cilnes vai no aģenta, kad to lūdz publicēt. Dubultklikšķis, divas cilnes vai aģenta pieprasījums, kas ierodas divreiz, var visi radīt vairāk nekā vienu publicēšanu tai pašai lietotnei gandrīz vienlaikus.
Šajā gadījumā trīs ieradās 84 ms laikā. Katrs palaida pilnu izvietošanu. Hostēšanas platformā izlaidumi tai pašai lietotnei tika izveidoti tajā pašā sekundē, un trīs no tiem neizdevās, jo citi jau bija procesā. Vēlāks izlaidums iestrēga, bet pēc tam lietotne darbojās.
Tas padarīja problēmu viegli nepamanāmu. Lietotājs redzēja vienu darbojošos lietotni. Kļūmes parādījās tikai e-pastos par neizdevušos izlaidumu.
Kāpēc nekas to neapturēja
Publicēšanas kodam tiešām bija solis, kas skatījās uz projekta procesā esošajām izvietošanām. Bet tas darīja to, ka pirms sākšanas atzīmēja katru procesā esošo izvietošanu kā neizdevušos — „aizstāts ar jaunu publicēšanu”. Tas bija paredzēts, lai attīrītu būvēšanas, kas palikušas pēc avarējuša procesa.
Praksē tas nozīmēja, ka otra publicēšana, kas ieradās vidējā būvēšanā, pasludināja pirmo par mirušu un sāka savu. Abas tika pabeigtas pret to pašu lietotni. Nebija bloķēšanas; bija solis, kas aktīvi atbrīvoja ceļu sacensībai.
Bija arī otrs, dīvaināks simptoms lietotnes pirmajai publicēšanai. Kad vēl nav izvietojuma ieraksta, ko izmantot, divas vienlaicīgas pirmās publicēšanas abas mēģināja ievietot vienu. Datubāzei ir unikāls indekss lietotnes saites nosaukumam (tās slug, piemēram, your-app in your-app.mythex.ai), tāpēc otrā ievietošana neizdevās. API to pārtulkoja kā „šis saites nosaukums ir aizņemts” — ieteikdams lietotājam pārdēvēt lietotni, kas tajā brīdī publicējās veiksmīgi zem šī nosaukuma.
Risinājums: prasība ir bloķēšana
Izvietojuma ieraksta pieprasīšana tagad ir viens nosacīts datubāzes atjauninājums: paņem šo rindu un atzīmē to kā būvējamu tikai tad, ja to nepublicē neviens cits. Tā kā tas ir viens paziņojums, datubāze izlemj par pieprasījumiem, kas ierodas tajā pašā milisekundē. Tieši viens no tiem maina rindu. Pārējie redz, ka nav mainīta neviena rinda, apstājas un nekad nesasniedz orķestratoru.
Bloķēšanai ir arī jābūt izejai, kad tās turētājs nomirst. Ja API restartējās vidējā publicēšanā, rinda paliktu atzīmēta kā „building” mūžīgi, un lietotni nevarētu publicēt. Tāpēc rindu, kuras īpašnieks vairs nesūta sirdsdarbības signālus — darbojošā publicēšana rindu atjauno ik pēc 30 sekundēm, un pieci minūšu klusums skaitās kā miris —, var pārņemt atkal. Šis slieksnis nāk no sirdsdarbības signāla, nevis no minējuma par to, cik ilgi var ilgt būvēšana. Lietotāja pašu Dockerfile būvēšana var likumīgi ilgt ilgi, un lēna būvēšana, kas joprojām sūta sirdsdarbības signālu, saglabā savu prasību.
Solis, kas visus procesā esošos izvietojumus atzīmēja kā neizdevušos pirms publicēšanas, vairs netiek palaists. Tiek notīrītas tikai rindas, ko atstājis miris process.
Pateikt zaudētājam patiesību
Pieprasījums, kas zaudē, tagad saņem skaidru atbildi: publish_in_progress, kopā ar darbojošā izvietojuma ID. Katra vieta, kas var sākt publicēšanu, to apstrādā:
- Darba vieta seko jau notiekošajai būvēšanai, nevis rāda kļūdu pāri tai.
- Aģentam pasaka, ka šīs lietotnes publicēšana jau notiek, un ka jāgaida, nevis ka kaut kas neizdevās — agrāk tas varēja likt tam „labot” Dockerfile un mēģināt vēlreiz.
- Pirmās publicēšanas saites nosaukuma sadursme tiek pārbaudīta: ja rinda, kas tur nosaukumu, ir šī projekta pašu darbojošā publicēšana, atbilde ir „jau tiek publicēts”, nevis „šis nosaukums ir aizņemts”. Īsta sadursme ar citu projektu joprojām saņem pārdēvēšanas ziņojumu.
Kā to pārbaudījām
Uzrakstījām reproducēšanas skriptu, kas vienlaikus palaiž trīs publicēšanas vienai lietotnei ar īstu datubāzi un izveidotu orķestratoru, un skaita, cik izvietojumus orķestrators pieprasa. Tas aptver pirmo publicēšanu un dzīvas lietotnes pārpublicēšanu. Pirms labojuma tas skaitīja divus izvietojumus. Pēc — vienu.
Skripts aptver arī izeju: atzīmē lietotnes rindu kā būvējamu bez sirdsdarbības signāla desmit minūtes, publicē vēlreiz un pārbauda, ka jaunā publicēšana pārņem rindu un darbojas.
Vienību testi notur bloķēšanu pie divām lietām, ko bloķēšanai jādara: no trim vienlaicīgām pārpublicēšanām tieši viena tiek cauri, un pārējām divām pasaka, ka publicēšana darbojas; un būvēšanu, kuras īpašnieks nomira, var pārņemt. Cits tests pārbauda, ka jauns mēģinājums nepārnes iepriekšējā mēģinājuma kļūdas ziņojumu.
Ko no tā guvām
- Tīrīšanas kods var būt sacensība maskā. „Atzīmē visu procesā esošo kā neizdevušos, pēc tam sāc” izskatās pēc higiēnas. Ar diviem izsaucējiem tā ir katrs atceļ otru.
- Ļauj datubāzei izlemt. Lasīšana, kam seko rakstīšana, atstāj spraugu citam pieprasījumam. Viens nosacīts atjauninājums tādu neatstāj.
- Katrai bloķēšanai vajag derīguma termiņu, kas saistīts ar dzīvību. Bez tā avārija pārvēršas iestrēgušā lietotnē.
- Kļūdu ziņojumi ir instrukcijas. „Šis nosaukums ir aizņemts” lika cilvēkiem darīt nepareizo. Pareizais ziņojums bija vienkāršāks un patiess.
Vairāk par publicēšanu ir dokumentācijā: kā publicēt. Un saistīts labojums: tava publicētā lietotne paliek augšā, kamēr to pārpublicē.