valimised

Eile toimusid riigikogu valimised ja tegu oli juba teiste valimistega, kus valimiskommisioni leht mingil hetkel loobus värske info edastamisest.

Helmes, kes antud tarkvara teinud on tuli täna välja huvitava patuoinaga – jamades olevat süüdi avatud lähtekoodiga andmebaasimootor PostgreSQL, nende poolt tehtud tarkvara töötas perfektselt ja adekvaatset jõudlustesti ei tehtud kuna see olla võimatu.

Esiteks tundub siin äärmiselt kohatu PostgreSQLi süüdistamine, selle peal käib maailmas väga palju süsteeme, mille andmemahud ja koormused on võrratult suuremad sellest, mida see valimissüsteem oleks pidanud kannatama (no kasvõi näiteks Skypei kasutajate baas on PostgreSQLi peal). Mulle isiklikult tundub, et antud juhul oli Postgresi süüdistada lihtsalt palju mugavam, kui öelda, et me ei testinud ega seadistanud asja piisavalt, sest näiteks erinevalt Oraclest ei ole siin taga kedagi kes sind siinkohal laimu eest kohtusse kaebaks.

Teiseks öelda, et meie tarkvara töötas ideaalselt, ikaldus vahend X mida me kasutasime on üsna kohatu, kuna arendaja vastutab üldiselt ikka terviku eest. PostgreSQL on ennast maailmas piisavalt tõestanud, küsimus tundub olevat puhtalt rakenduse arhitektuuris ja/või serveri seadistustes. Siinkohal oleks tore kuulata Hannu Krosingu või mõne teise postgresi guru kommentaari.

No ja viimaseks jutt, et “Omalt poolt olime kõik ära testinud ja kontrollinud ning enam midagi teha ei saanud” – antud rakenduse testimine peaks täiesti reaalse koormuse juures olema üsna lihtne. Eesti oma ~600 000 häälega on ikka imepisike asi simuleerimiseks. Hiinlastel oleks ehk sutsu raskem 😛

Aga, et see ei jääks tühjaks targutamiseks, siis viskasin hommikul rongis tööle sõites kokku naiivse valimise rakenduse, et vaadata palju sellise baasi täitmine ja hilisem võitjate selgitamine sellise baasi pealt aega võtaks suvalisel desktop masinal.

Kõigepealt tuleb teha mõned eeldused:

Teen ainult häälte (votes) ja kandidaatide (candidates) tabelid. Tegelikult peaks tabeleid olema muidugi rohkem – valimisnimekirjad, erakonnad, ringkonnad, valimisjaoskonnad ja ilmselt veel mõned, mis esimese hooga pähe ei tule. Neid tabeleid võib aga rahus ignoreerida, kuna väljaarvatud häälte tabel peaks muu olema üsna konstantne ja eeltäidetud.

Teen eelduse, et iga hääl on eraldi kirje votes tabelis. Ilmselt praktikas nii ei ole ja pigem teatab valimisjaoskond häälte arvu ühe kirjena kandidaadi kohta a’la kandidaat_X sai 1000 häält. See oleks jõudluse mõttes oluliselt lihtsam, kuna 600 000 inserdi asemel oleks neid pigem kuskil 50 000 ringi. Teen sihilikult jõudluse mõttes oluliselt hullema variandi, et näha palju see aega võtaks.

Teen eelduse, et valimisjaoskond teatab kõik oma hääled korraga. St. iga hääle sisestamine ei ole omaette transaktsioon vaid pigem on seda kõigi ühe valimisjaoskonna häälte sisestamine.

Eeldan, et veebis kasutajale graafikute ja statsi näitamist ei tehta otse andmebaasi pealt vaid pigem genereeritakse staatiline leht näiteks kord minutis. Ei tundu olevat põhjust, miks peaks kasutajale näidatav leht üldse andmebaasi vastu käima ja ilmselt nii oligi tehtud sest probleemide ajal tuli leht endiselt kiirelt ette, lihtsalt vanade andmetega. See tähendab, et mul pole vaja emuleerida kuidas paarsada tuhat erinevat select päringut sekundis baasi pihta käivad.

Üldiselt nende selgitust lugedes jääb mulje, et küsimus oli selles et query planner tegi otsuseid vana tabeli statistika pealt (VACUUM ANALYZE’i polnud vahepeal käivitatud) mistõttu eelistati ebaefektiivsemat käivitusplaani. Näiteks, kui tabelis on mõnisada kirjet võib igati mõistlik olla kasutada tabeli käigi ridade läbikäimist (full scan) indeksi poole pöördumise asemel. Artiklist jääb mulje, et hetkel kui jama tekkis vaatasid adminid käimasolevaid päringuid ja nende execution plani ja andsid VACUUM ANALYZE ja siis ootasid tunnikese, et päringu käitusplaan muutuks. Selleks, peab andmebaasi IO ikka ülimalt ülekoormatud olema, et VACUUM ANALYZE sellise aja võtaks. Näiteks minu 600 000 kirjega häälte tabelil võttis tavalisel desktop masinal ~2s.

Schema niisiis selline:

CREATE TABLE candidates(
    candidate_id INT PRIMARY KEY, 
    name text
)
 
CREATE TABLE votes(
    electoral_district_id INT NOT NULL, 
    candidate_id INT NOT NULL REFERENCES candidates(candidate_id)
)
 
CREATE INDEX idx_candidate_id ON votes(candidate_id)

Ja script, mis “valimistulemusi” sisestab on siin.

Ja aega võtab sellega 600 000 hääle sisestamine veidi alla 4 minuti:

hadara@hadara-desktop:~$ python elections.py 
        candidates inserted
        votes inserted
tables filled in: 211.85s

Tegu niisiis tavalisel desktop masinal suht vaike seadistustega jooksva PostgreSQLiga (shared_buffers keeratud 256MB peale, reaalsetes serverites ilmselt pigem 4+GB).

Sellise baasi pealt võitjate pärimine võiks välja näha näiteks nii:

elections=# SELECT votes.candidate_id, COUNT(*) AS votecount,(SELECT name FROM candidates WHERE candidates.candidate_id=votes.candidate_id) AS candidate_name FROM votes GROUP BY votes.candidate_id ORDER BY votecount LIMIT 10; candidate_id | votecount | candidate_name 
--------------+-----------+----------------
          106 |       600 | candidate_106
          120 |       600 | candidate_120
          285 |       600 | candidate_285
          681 |       600 | candidate_681
          866 |       600 | candidate_866
          264 |       600 | candidate_264
          887 |       600 | candidate_887
          601 |       600 | candidate_601
          664 |       600 | candidate_664
          251 |       600 | candidate_251
(10 ROWS)

See päring võtab 146ms ja execution plan on selline:

elections=# EXPLAIN analyze SELECT votes.candidate_id, COUNT(*) AS votecount,(SELECT name FROM candidates WHERE candidates.candidate_id=votes.candidate_id) AS candidate_name FROM votes GROUP BY votes.candidate_id ORDER BY votecount DESC LIMIT 10;
                                                                     QUERY PLAN                                                                     
----------------------------------------------------------------------------------------------------------------------------------------------------
 LIMIT  (cost=19956.81..19956.83 ROWS=10 width=4) (actual TIME=146.603..146.605 ROWS=10 loops=1)
   ->  Sort  (cost=19956.81..19959.31 ROWS=1000 width=4) (actual TIME=146.602..146.603 ROWS=10 loops=1)
         Sort KEY: (COUNT(*))
         Sort Method:  top-N heapsort  Memory: 25kB
         ->  HashAggregate  (cost=11655.00..19935.20 ROWS=1000 width=4) (actual TIME=144.690..146.393 ROWS=1000 loops=1)
               ->  Seq Scan ON votes  (cost=0.00..8655.00 ROWS=600000 width=4) (actual TIME=0.005..36.190 ROWS=600000 loops=1)
               SubPlan 1
                 ->  INDEX Scan USING candidates_pkey ON candidates  (cost=0.00..8.27 ROWS=1 width=13) (actual TIME=0.001..0.001 ROWS=1 loops=1000)
                       INDEX Cond: (candidate_id = $0)
 Total runtime: 146.651 ms
(10 ROWS)

Valimistega ma kuidagi seotud pole ja PostgreSQLi näpisin viimati umbes 8 aastat tagasi. Seega üsna puusalt tulistamine.

Uuendus: Martin Rebane, kellele on antud minust oluliselt rohkem kirjanikuannet, on ka samal teemal kirjutanud.

8 thoughts on “valimised

  1. dim123

    Probleem on loogikaga see on kindel. sinu antud lahendus on lihtne kuid ei pelga reaalsust.
    Isiklikult arvan et nendel oli probleem andmete väljastamistega.
    Nimelt tabel votes ( kus on max 600k kirjet ) täitus ilusti ( nende väide )
    samujti summarne tabel oli pidevalt lukkus. sest seal ( minu arvamus ) käis kontrolli kas candidat olemas ja siis update
    Isiklik arvamus on see et kuna tabel oli pidevalt lukkus ei saanud neid andmeid väljastada.

    Kõik sõltub veel millised kontrollid need targad suutsid sinna peale ehitada.

    Muud midagi . Aga sinuga täitsa nõus süsteemi kontrolliks 2 või 3 kordse koormusega võttaks max 1 päeva testimist.

  2. hadara Post author

    Noh ma imestaks väga, kui neil reaalselt olekski selline naiivne votes tabel. Praktikas on pigem ikka nii et on valimisjaoskondade tabel ja siis seose tabel valimisjaoskonna ja kandidaadi vahel, mis ütleb mitu häält kandidaat antud jaoskonnast sai. Valimisjaoskondi on 625 ja kandidaate 789. Kõikidest jaoskondadest kaugeltki mitte kõigile kandidaatidele hääli ei anta (ega saagi anda, sest pole antud valimisringkonna kandidaat), seega tegelik kirjete arv tuleb ilmselt oluliselt pisem, kui 600k. Aga see polegi oluline.

    Minu näite eesmärk oli eeskätt näidata, et isegi, kui teeks kõige naiivsema ja lollima lahenduse, mis pähe tuleb toimib see postgresil täiesti tavalisel raual täiesti adekvaatselt. “tulemuste” väljanäitamine on kiire isegi juhul, kui query plan on selline kole nagu mul ja tuleb iga kord kõik 600k rida üle käia, et hääled kokku arvutada.

    Lukustamise osas – postgres üldiselt ei lukusta midagi (isegi mitte row level lukke ei võeta, rääkimata mingitest table level lukkudest) selle asemel kasutatakse andmete versioneerimist (MVCC – http://www.postgresql.org/docs/8.4/static/mvcc-intro.html). Muidugi saab ise peale sundida misiganes taseme luku võtmist aga sellist liigutust tehes peab tegija ka ikka väga täpselt teadma, mida ta teeb ja mis on selle tagajärjed. See ei ole kindlasti mingi igapäevane arendusvõte.

    Täiendasin oma scripti ka, nii et -r võtmega saab ette anda protsesside arvu, mis paralleelselt sisestamisega käivad nii kiirelt kui suudavad “tulemusi” küsimas. Kuna see päring jääb tänu pidevalt count(*) täitmisele puhtalt prose jõu taha siis peaks parima tulemuse andma sama arv lugevaid protsesse, kui on prose coreisid. Minu desktopil on 2 corei, seega 2 lugeva protsessiga suudetakse paralleelselt sisestamisega hoida tempot kumbki 10 req/s. Eeldan, et reaalse schema peal ei ole vaja häälte arvu saamiseks midagi enam arvutada ja tulemus oleks samal raual vähemalt 10k req/s. Nii või teisiti, isegi see 20 req/s oleks pidanud olema kordades rohkem, kui vaja. Seda kõike muidugi taaskord eeldusel et VVK lehe vaatamine on täiesti staatiline HTML ja ei tehta andmebaasi ühtki päringut. Kuna seal lehel ei ole midagi kasutaja spetsiifilist ja andmete värskuses minutine viide ilmselgelt aktsepteeritav siis oleks teisi tegemine arhitektuuri mõttes puhas endale augu kaevamine.

    Igatahes on VVK käinud nüüd välja lubaduse tellida kolmandalt osapoolelt ekspertiis juhtunud kohta @ http://www.epl.ee/artikkel/593885
    Loodetavasti see ka avaldatakse täies mahus ja sellest saab põnev reaalse elu case study mida üliõpilastele koolides tutvustada.

  3. Zahhar Kirillov

    Üldjoontes nõustun – ülesanne ise-enesest üsnagi triviaalne. Ka mySQL sellega saaks hakkama. Küsimus pigem terve rakenduse arhitektuuris ja testimisel. Ise arvan, et probleem oli presentatsiooni kihiga – nemad kasvõi ei saanud genereerida uusi staatilisi HTML-lehekülge, või siis http-server mis pidi külaliste päringutele vastama oli maas. Viimasel juhul põhjus võib olla üldse riistvaras – näiteks, oli server seebikarbi-tüübi switchi või ruuteri taga, mida on nüüd muidugi piinlik tunnistada 😉

    Aga kõige häbimisväärne see, et suur firma kohe toob vabanduseks lause stiilis “vabavara on odav ja ebakvaliteetne, pidi kasutama Oracle vms kommertsanmebaas, siis asi oleks kindel”. See on üsna absurdne väide, mis on mõeldud pigem võhikute lollitamiseks. Edaspidi rahvas hakkab spekuleerima teemal, et valitsus tellib odavad lahendused, säästab tarkvaralt jne. Häbi, Helmes!

  4. dim123

    heh kui nad avalikustaks koodi ja lisaks db struktuuri oleks tõesti väga huvitav seda näha.
    hetkel ma ei rääkinud tabeli lukkust vaid pigem seda et kui vote sisse tehakse insereid ja peale vote on ehitatud trigereid mis oluliselt hakkavad kontrollima ja pidurdama see võib olla probleemiks.

    Endal kahjuks kah on MS toode kus kood on vigane ja tekkivad dead lockid.

    Aga jah see andmete küsimine kuna see on meil online siis vabalt võis olla otsene baasi lugemine 🙂
    lisa sinna veel andmete pidev insert ja on gi kogu moos

  5. Toomas

    rk2011.vvk.ee lehe järgi peaks avaliku serveri infoks läbi käidama selline jada ja seosed:
    Mandaadid-ringkond-maakond-vald-jaoskond-kandidaadi hääled- erakonna hääled – mandaatide jaotuse arvutus

  6. hadara Post author

    Reeglite nimekiri on muidugi tõesti parajalt pikk, aga seal ei tundu iseenesest olevat midagi hullu. Arvesta, et seda arvutust tuleb käima lasta maksimaalselt valimisjaoskondade arv kordi, ehk siis 625 ja tabelid mille peal seda arvutust tehakse on staatilised ja imepisikesed.

    Muutub ilmselt ainult 1 tabel. Imestaks väga, kui kogu see arvutus üle mõnesaja ms võtma peaks. Pole ka kuigi selge kas see veebi jaoks andmeid näidanud süsteem üldse ise selle arvutusega tegeleski, sest VVKl endal oli ju väidetavalt värkete tulemuste pilt koguaeg olemas. Arvestades, et vahepeal ilmus VVK lehe asemele exceli tulpdiagramm tekib muidugi kerge kahtlus, et mis kujul see seisu ülevaade neil olemas oli 😛

    Endal pole küll viitsimist seda värki näpuharjutuseks läbi proovida, aga usun, et sobiks ideaalselt tudengitele, kellel pidevalt igasugu ainete tarvis nagunii mingeid lambi projekte välja mõelda.

  7. Bunter

    Ma ei saa aru, miks neil üldse midagi vaja oli insertida. Kõik komponendid välja arvatud häälte arv oli eelnevalt teada. Kõik kirjed oleks võinud baasis olemas olla ning häälte laekumisel oleks vaja olnud ainult update teha. Statistika oleks ilus ja korrektne olnud kogu aeg.

Leave a Reply

Your email address will not be published.


*