Inženierijas blogs – Prexy: ārkārtīgi ātri meklēšanas un aizstāšanas noteikumi

Laipni lūdzam mūsu pirmajā inženierijas blogā. Tas, iespējams, ir nedaudz tehniskāks, nekā jūs esat pieraduši no citiem blogiem, taču mēs esam centušies to padarīt saprotamu ikvienam. Šajā rakstā mēs runāsim par Prexy — jaunu programmatūru „Clonable” ietvaros, ko izmanto aizstāšanas noteikumu piemērošanai.

Fons

Kā Clonable lietotājs jūs, iespējams, jau pazīstat aizstāšanas noteikumu funkcionalitāti. Šos meklēšanas un aizstāšanas noteikumus varat izmantot, lai tekstu vai kodu fragmentus aizstātu ar paša izvēlētu variantu. To varat izmantot, piemēram, lai aizstātu API atslēgu vai analītikas ID, tādējādi veicot atšķirīgas analīzes gan avota vietnei, gan tās tulkotajiem kloniem. Iekšēji šie paši noteikumi tiek izmantoti arī, lai aizstātu jūsu sākotnējo domēna vārdu ar klona domēna vārdu, kā arī vairākām citām darbībām.

Kopš pašas pirmās Clonable versijas šī funkcionalitāte nekad nav tikusi mainīta, un tas pats par sevi nav nekas neparasts, jo tā lieliski pildīja savu uzdevumu. Tomēr bija iespējami uzlabojumi gan veiktspējas, gan lietošanas ērtuma jomā. Reizēm mēs izvēlamies kādu Clonable produkta daļu, kurai jau ilgu laiku nav veltīts laiks, lai to uzlabotu. Tādējādi pagājušajā gadā mēs pilnībā pārskatījām visu mūsu datu infrastruktūru, lai padarītu to ātrāku, mērogojamāku un kļūdu izturīgāku, un šim nolūkam mēs jau iepriekš no jauna izveidojām URL tulkošanas funkcionalitāti. Šajā ceturksnī kārta bija pienākusi aizstāšanas noteikumiem.

Iepriekšējā situācija un ierobežojumi

Clonable aizstāšanas noteikumi tiek piemēroti tikai pašā pēdējā posmā, tieši pirms atbildes nosūtīšanas klientam. Tāpēc Clonable pirmajā versijā tika nolemts to īstenot NGINX tīmekļa serverī, izmantojot replace-filter-nginx-module — moduli, ko izstrādājusi OpenResty. Modulis izmanto „sregex” kā straumēšanas regulāro izteiksmju dzinēju, ko arī izstrādājusi OpenResty. Šajā gadījumā straumēšanas aspekts ir svarīgs, jo mēs nevēlamies katru atbildi pilnībā ielādēt atmiņā. Ja mēs to darītu, daži liela apjoma atbildes varētu izraisīt NGINX kļūdu vai iesaldēšanos, jo pieejamā atmiņa būtu nepietiekama. Otra svarīga „sregex” īpašība ir tā, ka tas spēj vienlaikus apstrādāt vairākus noteikumus. Katrs klons jau ir saņēmis vairākus standarta noteikumus, kā arī dažkārt ir pievienoti vairāki noteikumi, kas ir specifiski šim klonam. Ja tos nevarētu apstrādāt vienlaikus, mums joprojām būtu jābuferizē visa atbilde, jo pēc pirmā noteikuma mums vēlreiz būtu jāpārskata atbilde, lai apstrādātu otro noteikumu.

Tomēr pērn mēs arvien biežāk saskārāmies ar dīvainām veiktspējas problēmām. Šīs problēmas bieži bija īslaicīgas, taču ekstremālos gadījumos varēja pagarināt lapas atbildes laiku par dažām sekundēm. Pēc viena šāda pīķa lapa bieži vien atkal ielādējās normāli, tāpēc problēmu bija grūti atkārtot. Lai to turpinātu atkļūdot, mēs visām atbildēm pievienojām Clonable-Timings galvenes rindu, kas ļāva mums aptuveni noteikt, kurš tulkošanas procesa posms aizņēma tik daudz laika. No tā izrietēja, ka gandrīz visos gadījumos augšupstraumes serveris reaģēja ātri un arī tulkošana noritēja diezgan raiti. Tomēr starp tulkošanas pabeigšanu un pieprasījuma pilnīgu pabeigšanu bija neliels laika intervāls, un tas mums deva mājienu, ka tas varētu būt saistīts ar aizstāšanas noteikumiem.

NGINX vienlaicīguma modelis

Tomēr tas vēl nedeva atbildi uz jautājumu, kāpēc šie kavējumi radās tikai sporādiski un kāpēc tas notika, lai gan serveri nebija noslogoti pat uz pusi no to jaudas. Lai labāk izprastu šo parādību, mums ir nedaudz dziļāk jāizpēta, kā NGINX apstrādā darba slodzi.

NGINX izmanto darba procesu arhitektūru ar vienu galveno procesu un vairākiem darba procesiem. Savienojumi tiek sadalīti starp darba procesiem, lai vienlaikus varētu apstrādāt vairākus pieprasījumus. Tomēr šādai konfigurācijai ir arī trūkums: ja kāds darba process ir aizņemts ar viena pieprasījuma apstrādi, pārējie pieprasījumi, kas arī ir piešķirti tam pašam darba procesam, ir spiesti gaidīt. Tas var izraisīt situāciju, ka viens smags pieprasījums var radīt kavēšanos vairāku pieprasījumu apstrādē, pat ja pārējie darba procesi nav noslogoti. Šo efektu labi var redzēt zemāk attēlā. Šī attēla izveides laikā tiek veikts slodzes tests 10 dažādiem savienojumiem. Attēlā ir skaidri redzams, ka trīs darba procesi ir aizņemti, bet ceturtais gandrīz neko nedara.

Iepazīstieties ar Prexy

Kad šis šķērslis bija skaidri redzams, mēs izstrādājām plānu, kā to uzlabot. Šim projektam tika dots nosaukums „Prexy” – vārdu „RegEx” un „Proxy” apvienojums. Galarezultātam tika izvirzītas trīs galvenās prasības:

  1. Tam jābūt tiešam aizvietotājam pašreizējai konfigurācijai. Aizvietošanas noteikumi jāpiemēro tādā pašā veidā, lai novērstu problēmas esošajās konfigurācijās.

  2. Aizstāšanas noteikumus jāpiemēro pakāpeniski, lai novērstu pārmērīgu atmiņas patēriņu.

  3. Jaunajam risinājumam jābūt ātrākam nekā pašreizējam risinājumam.

Vispirms mums bija jāmeklē jaudīga daudzpavedienu izpildes vide, kas spētu efektīvi apstrādāt pieprasījumus. Pēc vairāku variantu izvērtēšanas izvēle krita uz „Tokio“ izpildes vidi programmēšanas valodai „Rust“. „Rust“ ir pazīstama ar to, ka ar tās palīdzību var rakstīt ātrus programmas, izvairoties no drošības riskiem, kas raksturīgi citām zemā līmeņa valodām, piemēram, C++. „Tokio” izpildes vide ir elastīga asinhronā izpildes vide, kas izstrādāta tīkla lietojumprogrammām. Viena no mums vissvarīgākajām funkcijām ir fakts, ka tā atbalsta „work-stealing”. Tas nozīmē, ka, atšķirībā no NGINX, pieprasījums nav piesaistīts vienam darba procesam, bet darba process, kam nav ko darīt, var „nozagt” darbu no cita darba procesa. Tādējādi mūsu serveru resursus var izmantot efektīvāk.

Pirmais prototips

Pēc pamatā esošo tehnoloģiju izvēles mēs nolēmām izveidot pirmo prototipu, lai novērtētu, cik lielu ātruma pieaugumu šis projekts mums aptuveni sniegtu un vai tas vispār ir tā vērts. Kā pirmo regex dzinēja (daļa, kas faktiski apstrādā un piemēro noteikumus) īstenojumu mēs izmantojām standarta regex bibliotēku (vai „crate”, kā to sauc Rust terminoloģijā). Šis dzinējs, tāpat kā sregex, ir bez atgriešanās (non-backtracking), kas nozīmē, ka ir mazāks risks saskarties ar ReDoS problēmu. ReDoS gadījumā regulārais izteiksmes dzinējs saskaras ar tādu ievadi, ka laiks, kas nepieciešams ievades izvērtēšanai, pieaug eksponenciāli. Tas var būt katastrofāli ietekmēt veiktspēju, un Cloudflare gadījumā tas izraisīja nopietnu sistēmas traucējumu. Tāpēc mums ir svarīgi, lai dzinējs, ko mēs izmantojam, būtu bez atgriezeniskās izsekošanas.

Izmantojot šo regulāro izteiksmju dzinēju, mēs izveidojām pirmo prototipu. Šis prototips izmantoja cietkodētas regulas, un regulāro izteiksmju dzinējs buferizēja visu atbildi, nevis to straumēja, taču tas bija pietiekami, lai veiktu pirmo veiktspējas testu. 

Izmantojot šo regulāro izteiksmju dzinēju, mēs izveidojām pirmo prototipu. Šis prototips izmantoja cietkodētas regulas, un regulāro izteiksmju dzinējs buferizēja visu atbildi, nevis to straumēja, taču tas bija pietiekami, lai veiktu pirmo veiktspējas testu. Mūsu testa konfigurācija sastāvēja no divām virtuālajām mašīnām (VM). Vienā VM darbojās konfigurācija, kurā varēja darboties gan vecais, gan jaunais risinājums. Tādējādi mēs varējām viegli salīdzināt abas metodes. Uz otra servera darbojās NGINX, kas kalpoja kā izcelsmes serveris un nodrošināja testa failu. Uz tā paša servera darbojās arī rīks wrk, ar kura palīdzību mēs ģenerējām slodzi. Tas nav pilnīgi optimāls risinājums, jo, lai iegūtu precīzus datus, vislabāk būtu slodzes ģeneratoru darbināt atsevišķā VM, taču šai VM bija pietiekami daudz resursu, lai NGINX un wrk netraucētu viens otram.

Pirmie testa rezultāti bija nepārprotami: Prexy vienu pieprasījumu apstrādāja aptuveni 22 reizes ātrāk (sk. attēlu zemāk). Tas deva projektam galīgo zaļo gaismu, jo, ņemot vērā tik lielu atšķirību, bija pietiekami daudz rezerves, lai kompensētu iespējamus veiktspējas kritumus, kas varētu rasties, pilnveidojot funkcionalitāti.

Pēc pirmā testa mēs veicām vairākus acīmredzamus uzlabojumus, piemēram, kompilēto regulāro izteiksmju kešēšanu. Turklāt mēs ieviesām efektīvāku veidu, kā atkārtoti izmantot savienojumus ar augšupstraumes serveri. Tādējādi mēs panācām ātruma pieaugumu aptuveni par 20 %. Tad mēs pārbaudījām abus risinājumus arī maksimālās slodzes apstākļos, nosūtot uz serveri pēc iespējas vairāk pieprasījumu. Arī šeit atšķirība bija labi redzama, un „Prexy” sasniedza caurlaidspēju, kas bija aptuveni 25 reizes lielāka nekā vecajam risinājumam.

Regex dzinēja straumēšana

Viena no šī projekta prasībām bija, lai izmantotais regulāro izteiksmju dzinējs būtu straumējošs. Prototipa regulāro izteiksmju dzinējs tāds nav, tāpēc šajā jomā bija nepieciešams papildu darbs. Tomēr regulāro izteiksmju dzinējs izrādījās ļoti labi optimizēts, tāpēc mēs nolēmām šo implementāciju izmantot kā pamatu mūsu straumējošajam dzinējam. Kad dati tiek straumēti caur regulāro izteiksmju dzinēju, ir vairākas lietas, kas ir svarīgi ņemt vērā. Pirmkārt, jūs nezināt, kādi dati vēl sekos. Tāpēc jums jāstrādā ar daļējām atbilstībām: atbilstībām, kas vēl nav pilnīgas, bet jau ir saskaņojušas vienu vai vairākus simbolus. Atrodot pilnīgu atbilstību, ir jāpārbauda, vai nav pārklājošos daļēju atbilstību, jo tās galu galā arī var būt atbilstības (un pārklāšanās gadījumā garāka atbilstība uzvar īsāku atbilstību). Tātad saskaņojumu var sākt apstrādāt tikai tad, ja visi tajā brīdī esošie daļējie saskaņojumi nav izrādījušies pilnīgi saskaņojumi.

Turpmākas optimizācijas

Kā gaidīts, regulāro izteiksmju dzinēja pārveidošana negatīvi ietekmēja Prexy veiktspēju. Tomēr salīdzinājumā ar iepriekšējo risinājumu joprojām bija pietiekama rezerve, un, veicot papildu optimizācijas, mēs galu galā pat sasniedzām augstākus rādītājus nekā pirms regulāro izteiksmju dzinēja pārveidošanas. Viena no optimizācijām, ko mēs piemērojām, bija atmiņas saglabāšana par to, vai konkrētajai failai ir jāpiemēro kāda noteikuma. Statiskie resursi, piemēram, CSS un JS faili, gandrīz nekad nemainās, un tādēļ ir bezjēdzīgi katru reizi izpildīt noteikumu, ja tas nekad nesakrīt ar konkrēto failu. Alternatīvs risinājums bija aizstāšanas rezultātu saglabāšana kešatmiņā, taču šīs stratēģijas trūkums ir tas, ka visu failu saglabāšanai nepieciešama liela atmiņas kapacitāte. Atceroties, kuri noteikumi jāpiemēro, mums nepieciešami tikai daži baiti uz vienu failu, jo informāciju saglabājam kā bitmapu.

Turklāt mēs optimāli izmantojam optimizācijas regulāro izteiksmju dzinējā, piemēram, neuzglabājot uztveršanas grupas, ja tās netiek izmantotas aizstāšanā. Tādējādi regulāro izteiksmju dzinējam ir jāatceras tikai visa saskaņojuma sākums un beigas, un tas ietaupa darbu. Mēs esam arī atspējojuši nosauktās uztveršanas grupas, jo tas straumēšanas variantā bija diezgan sarežģīti un, tā kā vecais risinājums to arī neatbalstīja, tas nebija obligāti nepieciešams. Visas šīs optimizācijas galu galā nodrošināja šādu veiktspēju:

Gala rezultāts

Pēc Prexy rūpīgas testēšanas, lai pārliecinātos, ka tā darbība patiešām atbilst iepriekšējam risinājumam, mēs sākām pakāpenisku ieviešanu. Aptuveni nedēļas laikā mēs aktivizējām Prexy katram klonam. Mūsu uzraudzības sistēmā aktivizēšanas brīdis bieži bija labi redzams. Bija bieži novērojams, ka atbildes laiki samazinājās par aptuveni 50 %. Arī citiem klientiem, kuriem bija tikai nedaudz aizstāšanas noteikumu, tika novērotas mazākas atšķirības — aptuveni 10 %. Mūsu pašu tīmekļa vietne kļuva aptuveni par 30 % ātrāka (~50 ms).

Attiecībā uz mūsu vispārējo infrastruktūru pēc Prexy ieviešanas mēs novērojam šādas atšķirības.

  • Par 33 % mazāks RAM patēriņš

  • Par 20 % mazāks procesora resursu patēriņš

  • Par 10–50 % ātrākas atbildes

Kopumā varam secināt, ka Prexy bija veiksmīgs projekts. Aizstājot novecojušu NGINX moduli, tagad varam izmantot jaunākas un efektīvākas tehnoloģijas, kas visiem mūsu klientiem nodrošina skaidri izmērāmu atšķirību. Arī nākotnē turpināsim uzlabot „Clonable”, gan ieviešot jaunas funkcijas, gan pilnveidojot esošās.


Paldies, ka izlasījāt šo inženierijas blogu. Ļaujiet mums zināt, ja jums patīk biežāk saņemt šādus tehniskus ieskatus mūsu produktā. Vai šis projekts jūs ir pilnībā aizrāvis? Tad apskatiet arī mūsu vakanču lapu :)

Sāciet jau šodien bez maksas ar Clonable

Uzziniet, kā centralizēti pārvaldīt starptautiskas tīmekļa vietnes no vienas platformas.

Mēs izmantojam sīkdatnes, lai uzlabotu jūsu pieredzi, analizētu tīmekļa vietnes apmeklējumu statistiku un pilnveidotu mūsu pakalpojumus.

Iepazīstieties ar mūsu sīkdatņu politiku