Engineering Blog - Prexy: supernopea haku ja korvaussäännöt

Tervetuloa ensimmäiseen suunnittelublogiimme. Se saattaa olla hieman teknisempi kuin mihin olet tottunut muista blogeista, mutta olemme tehneet parhaamme, jotta se olisi kaikille ymmärrettävää. Tässä artikkelissa kerromme Prexystä, Clonable uudesta ohjelmistosta, jota käytetään korvaussääntöjen soveltamiseen.

Tausta

Clonable käyttäjänä saatat jo tuntea korvaussääntö-toiminnon. Voit käyttää näitä haku- ja korvaussääntöjä vaihtaaksesi tekstin tai koodin kappaleita itse valitsemallasi vaihtoehdolla. Voit käyttää tätä esimerkiksi API-avaimen tai analytiikkatunnuksen korvaamiseen, jotta voit luoda erilaiset analytiikat alkuperäiselle sivustollesi ja käännetyille klooneillesi. Sisäisesti näitä samoja sääntöjä käytetään myös alkuperäisen verkkotunnuksen korvaamiseen kloonin verkkotunnuksella ja moniin muihin asioihin.

Clonable ensimmäisestä versiosta lähtien tätä toimintoa ei ole koskaan päivitetty, mikä ei sinänsä ole yllättävää, sillä se hoiti tehtävänsä hienosti. Silti parannuksia oli mahdollista tehdä sekä suorituskyvyn että käyttäjäystävällisyyden osalta. Silloin tällöin me Clonable otamme tuotteestamme osan, jota ei ole käytetty pitkään aikaan, ja aloitamme sen parantamisen. Esimerkiksi viime vuonna uudistimme koko datainfrastruktuurimme nopeuttaaksemme, skaalautuvammaksi ja vikasietoisemmaksi, ja sitä ennen rakensimme myös URL-käännöstoiminnot alusta alkaen. Tällä vuosineljänneksellä oli vuorossa korvaussäännöt.

Vanha tilanne ja rajoitukset

Clonable korvaussääntöjä sovelletaan vasta aivan viimeisessä vaiheessa, juuri ennen vastauksen lähettämistä takaisin asiakkaalle. Clonable ensimmäisessä versiossa tämä päätettiin siksi toteuttaa NGINX-verkkopalvelimessa käyttäen OpenRestyn luomaa replace-filter-nginx-moduulia. Moduuli käyttää streaming-regex-moottorina sregexiä, joka on myös OpenRestyn luoma. Virtausnäkökulma on tässä tapauksessa tärkeä, koska emme halua ladata jokaista vastausta kokonaan muistiin. Jos näin tehtäisiin, useat suuret vastaukset voisivat aiheuttaa NGINX:n kaatumisen tai kaatumisen, koska muistia ei ole riittävästi. Toinen sregexin tärkeä ominaisuus on, että se voi käsitellä useita rivejä rinnakkain. Tämä johtuu siitä, että jokaisella kloonilla on jo valmiiksi joitakin oletussääntöjä ja lisäksi joskus joitakin sääntöjä, jotka on lisätty nimenomaan kloonia varten. Jos näitä ei voitaisi käsitellä rinnakkain, jouduttaisiin silti puskuroimaan koko vastaus, koska se pitäisi pystyä sovittamaan ensimmäisen säännön jälkeen uudelleen toiseen sääntöön.

Viime vuonna kohtasimme kuitenkin yhä useammin outoja suorituskykyongelmia. Nämä ongelmat olivat usein lyhytaikaisia, mutta saattoivat ääritapauksissa pidentää sivun vasteaikaa sekunneilla. Tällaisen piikin jälkeen sivu latautui usein taas normaalisti, joten ongelmaa oli vaikea jäljentää. Vianmääritystä varten lisäsimme kaikkiin vastauksiin Clonable-Timings-otsakkeen, jonka avulla pystyimme karkeasti määrittämään, mikä käännösprosessin vaihe vei niin paljon aikaa. Tämä paljasti, että lähes kaikissa tapauksissa ylävirta vastasi nopeasti, ja käännös oli myös melko sujuvaa. Käännöksen valmistumisen ja pyynnön täydellisen täyttämisen välillä oli kuitenkin aukko, ja tämä antoi meille vihjeen siitä, että ongelma saattoi liittyä korvaussääntöihin.

NGINX:n samanaikaisuusmalli

Tämä ei kuitenkaan vielä antanut meille vastausta siihen, miksi näitä viiveitä esiintyi vain satunnaisesti ja miksi näin tapahtui, kun palvelimia ei kuormitettu edes puoleen niiden kapasiteetista. Ymmärtääksemme tätä ilmiötä paremmin meidän on tarkasteltava hieman syvällisemmin sitä, miten NGINX käsittelee työkuormia.

NGINX:ssä on työntekijäarkkitehtuuri, jossa on yksi pääprosessi ja useita työprosesseja. Yhteydet jaetaan työläisten kesken, jotta useita pyyntöjä voidaan käsitellä samanaikaisesti. Tässä kokoonpanossa on kuitenkin myös haittapuoli: kun työntekijä on kiireinen käsittelemään pyyntöä, muut pyynnöt, jotka on myös osoitettu samalle työntekijälle, joutuvat odottamaan. Tämä voi aiheuttaa sen, että yksi raskas pyyntö aiheuttaa viivästyksiä useille pyynnöille, vaikka muilla työntekijöillä ei olisi mitään tekemistä. Tämä vaikutus on hyvä olla alla olevassa kuvassa. Tämän kuvan luomisen aikana stressitesti on käynnissä 10 eri yhteydellä. Kuvasta näkyy tällöin selvästi, että kolme työntekijää on kiireinen ja neljäs ei tee juuri mitään.

Prexyn aika

Kun pullonkaula oli selvästi näkyvissä, laadimme suunnitelman sen parantamiseksi. Projektin nimeksi tuli Prexy, joka on RegExin ja Proxyn yhdistelmä. Lopputulokselle oli kolme päävaatimusta:

  1. Sen pitäisi korvata nykyinen kokoonpano. Korvaussääntöjä olisi sovellettava samalla tavalla, jotta vältetään asioiden rikkoutuminen nykyisissä asetuksissa.

  2. Korvaussäännöt on suoratoistettava, jotta ratkaisu ei käyttäisi liikaa muistia.

  3. Uuden ratkaisun pitäisi olla nopeampi kuin nykyisen ratkaisun.

Ensin meidän oli etsittävä tehokas monisäikeinen suoritusaika, joka pystyisi käsittelemään pyyntöjä tehokkaasti. Useiden vaihtoehtojen harkinnan jälkeen valinta osui Rust-ohjelmointikielen Tokyo-runtimeen. Rust on tunnettu siitä, että sillä voi kirjoittaa nopeita ohjelmia ilman muiden matalan tason kielten, kuten C++:n, turvallisuusriskejä. Tokyo-ajoaika on joustava asynkroninen ajoaika, joka on tehty verkottuneita sovelluksia varten. Yksi tärkeimmistä ominaisuuksista meille on se, että se on työn varastava. Tämä tarkoittaa, että toisin kuin NGINX:ssä, pyyntö ei ole sidottu yhteen työntekijään, vaan työntekijä, jolla ei ole mitään tekemistä, voi "varastaa" työtä toiselta työntekijältä. Näin palvelimiemme resursseja voidaan hyödyntää paremmin.

Ensimmäinen prototyyppi

Kun olimme valinneet taustalla olevat teknologiat, päätimme perustaa alustavan prototyypin, jotta voisimme arvioida karkeasti, kuinka paljon nopeuslisäystä tämä hanke antaisi meille ja kannattaisiko se ylipäätään. Alustavana toteutuksena regex-moottorille (osa, joka itse asiassa käsittelee ja soveltaa sääntöjä) käytimme vakiomuotoista regex-kirjastoa (tai cratea, kuten niitä kutsutaan Rust-terminologiassa). Tämä moottori, kuten sregex, ei ole backtracking-moottori, mikä tarkoittaa, että ReDoS-ongelmien riski on pienempi. ReDoS-ongelmassa säännöllinen lauseke kohtaa sellaisen syötteen, että sen arviointiin kuluva aika kasvaa eksponentiaalisesti. Tämä voi olla katastrofaalista suorituskyvylle, ja se aiheutti cloudflaressa suuren käyttökatkoksen. Meille on siis tärkeää, että käyttämämme moottori ei ole takautuva.

Rakensimme tämän regex-moottorin avulla alustavan prototyypin. Tässä prototyypissä käytettiin kovakoodattuja sääntöjä ja regex-moottori puskuroi koko vastauksen suoratoiston sijaan, mutta tämä riitti ensimmäiseen suorituskykytestaukseen.

Rakensimme tämän regex-moottorin avulla alustavan prototyypin. Tässä prototyypissä käytettiin kovakoodattuja sääntöjä ja regex-moottori puskuroi koko vastauksen suoratoiston sijaan, mutta tämä riitti ensimmäiseen suorituskykytestaukseen. Testiasetelmamme koostui kahdesta VM:stä, joista toisessa VM:ssä ajettiin asetusta, jossa sekä vanhaa että uutta ratkaisua voitiin käyttää. Näin pystyimme helposti tekemään vertailuja näiden kahden menetelmän välillä. Toisella palvelimella toimi NGINX, joka toimi alkuperäispalvelimena ja palveli testitiedostoa. Samalla palvelimella ajettiin myös wrk-työkalua, jota käytimme kuorman tuottamiseen. Tämä ei ole täysin optimaalista, sillä puhdasta dataa varten kuormitusgeneraattori kannattaa ajaa mieluummin erillisessä VM:ssä, mutta tässä VM:ssä oli riittävästi resursseja, jotta NGINX ja wrk eivät häirinneet toisiaan.

Ensimmäiset testitulokset olivat täysin selvät: Prexy oli noin 22 kertaa nopeampi yksittäisen pyynnön käsittelyssä (katso kuva alla). Tämä antoi projektille lopullisen vihreän valon, sillä näin suuren eron ansiosta oli riittävästi tilaa vaimentaa joitakin suorituskyvyn heikkenemisiä, joita saattaa esiintyä toiminnallisuuden loppuunsaattamisen yhteydessä.

Alustavan testauksen jälkeen toteutimme joitakin ilmeisiä optimointeja, kuten käännettyjen säännöllisten lausekkeiden välimuistiin tallentamisen. Toteutimme myös tehokkaamman tavan käyttää uudelleen yhteyksiä ylävirtaan. Näin saavutimme noin 20 prosentin nopeuslisäyksen. Tämän jälkeen testasimme molempia ratkaisuja myös huippukuormituksessa, jolloin palvelimelle lähetettiin mahdollisimman paljon pyyntöjä. Tässäkin ero näkyi selvästi, ja Prexy saavutti noin 25-kertaisen läpäisykyvyn vanhaan ratkaisuun verrattuna.

Suoratoisto regex-moottori

Yksi tämän projektin vaatimuksista oli, että käytettävän regex-moottorin tulisi olla streaming. Prototyypin regex-luomus ei ole, joten tällä alalla tarvittiin lisätyötä. Regex crate osoittautui kuitenkin erittäin optimoiduksi, joten päätimme kuitenkin ottaa tämän toteutuksen streaming-moottorimme perustaksi. Kun dataa suoratoistetaan regex-moottorin kautta, on tärkeää pitää mielessä muutama asia. Ensinnäkin et tiedä, mitä dataa on vielä tulossa. Siksi sinun kannattaa aloittaa työskentely osittaisten osumien kanssa: osumien, jotka eivät ole vielä täydellisiä, mutta jotka ovat jo osuneet yhteen yhden tai useamman merkin kanssa. Kun löydät täydellisen osuman, sinun on tarkistettava, ettei päällekkäisiä osittaisia osumia ole, koska nämäkin voivat päätyä osumiksi (ja pidempi osuma voittaa lyhyemmän osuman päällekkäisyystapauksessa). Voit siis aloittaa ottelun käsittelyn vain, jos kaikki osittaiset ottelut eivät ole täydellisiä otteluita.

Muita optimointeja

Odotetusti regex-moottorin uudelleenrakentaminen vaikutti negatiivisesti Prexyn suorituskykyyn. Vanhaan ratkaisuun verrattuna oli kuitenkin vielä runsaasti liikkumavaraa, ja lisäämällä lisää optimointeja päädyimme jälleen jopa korkeampaan suorituskykyyn kuin ennen regex-moottorin uudelleenrakentamista. Yksi käyttämistämme optimoinneista oli sen muistaminen, oliko sääntöä sovellettava tiettyyn tiedostoon. Staattiset aineistot, kuten CSS- ja JS-tiedostot, eivät juuri koskaan muutu, ja siksi on hyödytöntä suorittaa sääntöä joka kerta, kun se ei koskaan vastaa kyseistä tiettyä tiedostoa. Vaihtoehtona tälle oli korvattujen tiedostojen tulosten välimuistiin tallentaminen, mutta tämän strategian haittapuolena on, että kaikkien tiedostojen tallentaminen vie paljon muistia. Kun muistamme, mitä sääntöjä on sovellettava, tarvitsemme vain muutaman tavun tiedostoa kohti tallentamalla tiedot bittikarttana.

Lisäksi hyödynnämme täysimääräisesti regex-moottorin optimointeja esimerkiksi siten, että emme säilytä kaappausryhmiä, kun niitä ei käytetä korvauksessa. Tämän seurauksena regex-moottorin tarvitsee muistaa vain koko vastaavuuden alku ja loppu, mikä säästää työtä. Poistimme käytöstä myös nimetyt kaappausryhmät, koska tämä oli melko monimutkaista suoratoistomuunnoksessa, ja koska vanha ratkaisu ei myöskään tukenut tätä, se ei välttämättä ollut tarpeen. Kaikki nämä optimoinnit tuottivat lopulta seuraavan suorituskyvyn:

Lopputulos

Kun olimme testanneet Prexyä laajasti varmistaaksemme, että sen käyttäytyminen oli todellakin sama kuin vanhan ratkaisun, aloimme ottaa sitä käyttöön asteittain. Noin viikon aikana aktivoimme Prexyn jokaiseen kloonaukseen. Seurannassamme aktivoinnin ajoitus oli usein helppo havaita. Noin 50 prosentin lasku vasteajoissa ei ollut harvinaista. Vähäisiä, noin 10 prosentin eroja havaittiin myös muilla asiakkailla, joilla oli hyvin vähän korvaussääntöjä. Oma verkkosivustomme nopeutui noin 30 % (~50 ms).

Koko infrastruktuurissamme havaitsimme seuraavat erot Prexyn käyttöönoton jälkeen.

  • 33 % vähemmän RAM-muistia

  • 20 % vähemmän suorittimen kulutusta

  • 10-50 % nopeammat vastaukset

Kaiken kaikkiaan voimme siis sanoa, että Prexy oli onnistunut projekti. Korvaamalla vanhentuneen NGINX-moduulin voimme nyt käyttää uudempia ja tehokkaampia tekniikoita, joilla on selkeä mitattavissa oleva ero kaikille asiakkaillemme. Jatkamme tulevaisuudessa Clonable parantamista sekä uusien ominaisuuksien että nykyisten toimintojen parantamisen osalta.


Kiitos, että luit tämän suunnittelublogin. Kerro meille, jos haluat saada tällaisia teknisiä näkemyksiä tuotteestamme useammin. Innostuitko tästä projektista? Tutustu sitten avoimet työpaikat -sivuumme :)

Aloita Clonable käyttö ilmaiseksi jo tänään

Tutustu siihen, miten voit hallita kansainvälisiä verkkosivustoja keskitetysti yhdeltä alustalta.

Käytämme evästeitä käyttökokemuksesi parantamiseksi, verkkosivuston liikenteen analysoimiseksi ja palveluidemme kehittämiseksi.

Lue evästekäytäntömme