k47.cz
výběr kolo foto Praha povídky kultura
TECH ▞▞ | twitter RSS
««« »»»

Jednoduché problémy mívají většinou složité řešení

29. 7. 2009 (před 10 lety) — k47 (CC by-nc-sa) (♪)

Děj­ství druhé: K. bude dlouze trou­sit moudra o pro­gra­mo­vání, op­ti­ma­li­zaci da­ta­bází, po­ma­losti dibi a další zcela zá­sadní ži­votní pravdy, pak za­po­mene, co chtěl říct dál, ale vysolí dva stripy na dřevo, aby se ne­řeklo a půjde spát.


Hm.

V po­slední době se stří­davě vracím k jed­nomu tri­vi­ál­nímu pro­blému, který nemá žádný prak­tický užitek. Před­stavte si, že mám nějaké stránky – třeba k47čku, kde je hodně po­ví­dek a já chci zjis­tit, jaká kon­krétní slova se v nich vy­sky­tují, ko­li­krát je které po­u­žito a taky si vést tyto zá­znamy v rámci jedné po­vídky (tedy jaké slovo, ko­li­krát ve které po­vídce) a všechno to uložit nějak ro­zumně do da­ta­báze. V tom není žádný pro­blém. Tedy… Vlastně je a rovnou dva. Zaprvé jak to vůbec udělat (na­pří­klad jak získat jed­not­livá slova a co to slovo vlastně je) a jak to udělat rychlé. Právě druhý pro­blém, ačkoli je čistě aka­de­mický – tedy zby­tečný, mi ne­dá­val spát a chtěl jsem ho vy­ře­šit a do­táh­nout k do­ko­na­losti. Chtěl jsem zkrátka aby to bylo kurva rychlý.

K dis­po­zici mám PHP a server, kde skript může běžet 60 vteřin a může alo­ko­vat si 64MB paměti. Sla­bota.

Naivní řešení co slovo to dotaz je po­ma­lou a bo­les­ti­vou vraž­dou da­ta­báze, která vám za ty stovky tisíc nebo do­konce mi­li­ony dotazů vy­lo­ženě po­dě­kuje (FYI: na k47čku bylo podle všeho po­u­žito 400.000 slov, no-ty-vole). Je po­třeba vy­mys­let něco ma­linko so­fis­ti­ko­va­něj­šího. Mohli bychom si do­ta­zem vybrat všechny texty a ty pak na­jed­nou zpra­co­vá­vat, ale na­ra­zíme na ne­do­sta­tek paměti. Můžeme je zpra­co­vá­vat po­stupně: načíst jeden text, roz­dě­lit na slova, načíst další, roz­dě­lit, slova spojit s před­cho­zími atd., ale pořád je po­třeba si třeba ucho­vá­vat in­for­maci, která slova patří ke kte­rému článku a takže si musíme vybrat mezi va­ri­an­tami všechno v paměti nebo hodně SQL dotazů za sebou atd. Není to sranda. Musí se vy­mys­let něco víc so­fis­ti­ko­va­něj­šího. Mnohem víc.

Na řešení této úplné zby­teč­nosti jsem (z hle­diska da­ta­báze) vy­zkou­šel už asi šest růz­ných pří­stupů, ale žádný nebyl dost rychlý. Zkou­šel jsem to řešit naivně, zkou­šel jsem použít pre­pa­red sta­te­ments, zkou­šel jsem tvořit hro­madné in­serty, zkou­šel jsem tvořit ještě hro­mad­nější in­serty, napsal jsem si ulo­že­nou pro­ceduru a další milion va­ri­ací na po­dobné téma. Schválně, zkuste si po­dob­nou věc vy­ře­šit, na­u­číte se spoustu věcí, se kte­rými byste se ne­se­tkali v nor­mál­ních blogo-apli­ka­cích, kde je ve­li­kost dat se kte­rými se pra­cuje tak nějak nor­mální (fajn, ne­ří­kám, že ta moje věc je nějaký extrém, ale stejně).

Já jsem na­pří­klad v prů­běhu ladění (mimo jiné) zjisti, že da­ta­bá­zový layer dibi je pomalý a ne­na­žrané a táhne celý pro­gram ke dnu. Tedy takhle: dibi je sa­mo­zřejmě vý­borná knihovna, jejíž režii v nor­mál­ních si­tu­a­cích ne­po­znáte. V nor­mál­ních. Ale si­tu­ace, ve které ji po­u­ží­vám já, není nor­mální. Na­pří­klad ge­ne­ruji ob­rov­ské ví­ce­ná­sobné in­serty a dibi nabízí mož­nost dotaz po­sklá­dat do pole, kdy se pak volá nějak takto: dibi::query(array('INSERT INTO [somewhere]', $data1, $data2, $data3)). Toto po­u­žití dibi je velice po­ho­dlné, ale při vel­kých datech skončí hláš­kou o vy­čer­pané paměti. Bo­hu­žel. Po­hy­buji se v ob­lasti spe­ci­ál­ních pří­padů: od pan­gejtu k pan­gejtu a po­zná­vám, kde jsou man­ti­nely. No nic, musím si od­pus­tit ten luxus, jít níž a zrych­lo­vat. Na mi­li­sekun­dách totiž záleží. Když se má pro­vést 2000 dotazů, pak je rozdíl mezi 40ms a 80ms přesně 80 vteřin. To není pro­blém když skript/pro­gram může běžet od ne­vi­dim do ne­vi­dim a alo­ko­vat paměť až do ale­luja. Ale je to pro­blém na ome­ze­ném ser­veru.

Sranda byla, když jsem zjis­til, že po­ma­lost může pra­me­nit jinde, než jsem pů­vodně myslel. Zjis­til jsem, že ně­které dotazy byly ne­če­kaně skoro za­darmo a ně­které žraly víc času, než se mi líbilo. Pro­fi­lo­vání je v tomto pří­padě nutné a poučné. Člověk pak zjistí, jaká kvanta času ušetří jeden starý laciný trik: před vlo­že­ním vel­kého množ­ství dat se z ta­bulky od­straní indexy a pri­mární klíče, pak se data vloží a klíče a indexy se znovu zapnou. Je to rych­lejší kvůli sku­teč­nosti, že se nemusí kon­t­ro­lo­vat a řadit klíče při každém in­sertu, ale pro­vede se to jenom jednou na konci. Pravda někdy to nejde a nedá se použít vý­borná MySQL vy­chy­távka INSERT … ON DU­PLI­CATE KEY UPDATE ….

K tomu všemu si při­počtěte, že žádná op­ti­ma­li­zace ne­zna­me­nala jenom malou změnu někde v kódu, ale kom­pletní (v lepším pří­padě jenom zcela zá­sadní) pře­for­mu­lo­vání stra­te­gie řešení pro­blému (na­pří­klad na­há­zet všechna slova do da­ta­báze a tam je pak nějak zpra­co­vá­vat). Kháááááán! Na twit­teru jsem se chlu­bil, že v jedné ite­raci pokus-omyl jsem jednu část úlohy, která by běžela celý den zop­ti­ma­li­zo­val na 3 vte­řiny, ale po­sléze jsem zjis­til, že to stejně nebude fun­go­vat. A zase pře­ko­pat a znova.

Život je pes.

Pro­blémy se začaly vy­sky­to­vat všude možně, ať už to byly zá­lud­nosti s po­rov­ná­ním ře­tězců (collation) nebo zjiš­tění, jak straš­livě může všechno táh­nout k zemi PHP kód, když se v něm ko­pí­rují velká pole nebo mají ne­vhod­nou struk­turu, která se pomalu pro­chází atd.

Ale stejně to není rychlé. Fun­guje to tak nějak ro­zumně, ale ne žá­doucí rych­lostí.

Po­u­čení z toho všeho zní: Nikdy ne­mů­žeme vyhrát.


A na závěr nějaké stripy, abych něčím přebil pachuť tohoto článku. I když jak se na to dívám, nevím jestli 669.280 něco sku­tečně pře­bije. Při výrobě toho stripu jsem nej­spíš trpěl ně­ja­kou mozko­vou cho­ro­bou. Zato číslo 669.281 sku­tečně ob­sa­huje humor k tématu.


PS: Když by někoho na­padlo nějaké ge­ni­ální řešení, jak pro­blém řešit ex­trémně rychle a ele­gantně, ať mě urych­leme kon­tak­tuje a bude mu po­ma­zána hlava.

píše k47 & hosté, ascii@k47.cz