Bruce Momjian'i 2020. aasta ettekande "Unlocking the Postgres Lock Manager" tÔlge.

(MÀrkus: KÔik SQL pÀringud slaididelt on saadaval sellelt lingilt: )
Tere! On suurepĂ€rane olla taas Venemaal. Vabandan, et ei saanud eelmisel aastal tulla, kuid sel aastal on Ivanil ja mul suured plaanid. Loodan, et olen siin palju tihedamini. Ma armastan Venemaal kĂ€ia. Kavas on kĂŒlastada Tjumenit ja Tveri. Olen vĂ€ga rÔÔmus, et mul on vĂ”imalus nendes linnades viibida.
Minu nimi on Bruce Momjian. Töötan EnterpriseDB-s ja olen Postgresiga tegelenud enam kui 23 aastat. Elan Philadelphias, USA-s. Reisin umbes 90 pĂ€eva aastas. KĂŒlastan ligikaudu 40 konverentsi. Minu , mis sisaldab slaide, mida ma teile praegu nĂ€itan. Seega pĂ€rast konverentsi saate need minu isiklikult veebisaidilt alla laadida. Seal on ka umbes 30 esitlust. Samuti leidub videosid ja ĂŒle 500 blogipostituse. See on tĂ”eliselt sisutihe ressurss. Ja kui see materjal teid huvitab, kutsun teid seda kasutama.
Olen varem olnud Ă”petaja, professor, enne kui hakkasin Postgresiga tegelema. Olen vĂ€ga rÔÔmus, et mul on vĂ”imalus rÀÀkida teile sellest, mida ma kavatsen rÀÀkida. See on ĂŒks minu kĂ”ige huvitavamaid ettekandeid. See ettekande koosneb 110 slaidist. Alustame lihtsate asjadega, aga ettekande lĂ”pus muutub see jĂ€rjest keerulisemaks.

See on ĂŒsna ebameeldiv vestlus. Lukustamine ei ole just populaarne teema. Me tahame, et see kaoks. See on nagu hambaarsti juures kĂ€imine.

- Lukustamine on probleem paljudele, kes töötavad andmebaasidega ja kellel on samaaegselt mitu protsessi. Nad vajavad lukustamist. Seega, tÀna annan ma teile pÔhiteadmised lukustamisest.
- Tehinguidentifikaatorid. See on ĂŒsna igav osa esitlusest, kuid see on hĂ€davajalik mĂ”ista.
- Ja edasi rÀÀgime lukustamise liikidest. See on piisavalt mehaaniline osa.
- Ja seejĂ€rel tutvustame mĂ”ningaid lukustamise nĂ€iteid. See on kĂŒllaltki keeruline teema.

RÀÀgime lukustamisest.

Meie terminoloogia on piisavalt keeruline. Kui palju teist teab, kust see katkend pĂ€rineb? Kaks inimest. See on mĂ€ngust nimega âKolossaalsed seiklused koopasâ. See oli teksti pĂ”hine arvutimĂ€ng 80-ndatel, ma arvan. Seal pidi minema koopasse, labĂŒrinti ja tekst muutus, kuid sisu oli iga kord umbes sama. Nii mĂ€letan ma seda mĂ€ngu.

Siin nÀeme blokeeringute nimetusi, mis on meile tulnud Oracle'ist. Me kasutame neid.

Siin on terminid, mis mind hĂ€irivad. NĂ€iteks SHARE UPDATE ECXLUSIVE. JĂ€rgmine on SHARE RAW ECXLUSIVE. Ausalt öeldes, need nimed pole vĂ€ga arusaadavad. PĂŒĂŒame neid ĂŒksikasjalikumalt vaadata. MĂ”ned sisaldavad sĂ”na âshareâ, mis tĂ€hendab â eralduda. MĂ”ned sisaldavad sĂ”na âexclusiveâ â eksklusiivne. MĂ”ned sisaldavad mĂ”lemat sĂ”na. Tahaksin alustada sellest, kuidas need blokeeringud töötavad.

Samuti on vĂ€ga oluline sĂ”na âdostupâ â access. Ja sĂ”na ârowâ â rida. Ehk siis juurdepÀÀsu ja ridade jaotamine.

Veel ĂŒks probleem, mida Postgreses mĂ”ista on vajalik, kuid millest ma kahjuks oma ettekandes rÀÀkida ei saa, on MVCC. Mul on sellel teemal eraldi esitlus minu veebisaidil. Ja kui arvate, et see esitlus on keeruline, siis MVCC on tĂ”enĂ€oliselt minu kĂ”ige keerulisem teema. Kui olete huvitatud, vĂ”ite selle veebisaidilt vaadata. Saate vaadata ka videot.

Veel ĂŒks punkt, mida peame mĂ”istma â on tehinguidentifikaatorid. Paljud tehingud ei saa töötada ilma ainulaadsete identifikaatoriteta. Ja siin anname selgituse, mis on tehing. Postgreses on kaks tehingu numeratsiooni sĂŒsteemi. Ma tean, et see ei ole vĂ€ga ilus lahendus.

Pange tÀhele, et slaidid on suhteliselt keerulised, seega tuleks pöörata erilist tÀhelepanu sellele, mis on punase vÀrviga esile tÔstetud.

Vaadake. Punase vÀrviga on esile tÔstetud tehingu number. Siin on nÀidatud funktsioon SELECT pg_back. See tagastab minu tehingu ja selle tehingu ID.
Veel on oluline, kui teile meeldib see esitlus ja soovite selle oma andmebaasis kÀivitada, siis saate klikata sel roosaga esile tÔstetud lingil ning alla laadida selle esituse SQL. Ja te saate lihtsalt kÀivitada selle oma PSQL-is ning kogu esitlus ilmub teile ekraanile viivitamatult. See ei sisalda vÀrve, kuid vÀhemalt saame selle siiski nÀha.

Selles olukorras nĂ€eme tehingu ID-d. See on number, mille me sellele mÀÀrasime. Ja Postgresis on veel ĂŒks tehingu ID tĂŒĂŒp, mida kutsutakse virtuaalseks tehingu ID-ks.
Ja me peame seda mÔistma. See on vÀga oluline, vastasel juhul ei suuda me Postgresis lukustamist mÔista.
Virtuaalne tehingu ID on tehingu ID, mis ei sisalda pĂŒsivaid vÀÀrtusi. NĂ€iteks kui ma kasutan SELECT kĂ€sku, siis tĂ”enĂ€oliselt ei muuda ma andmebaasi, ma ei blokeeri midagi. Seega, kui kĂ€ivitame lihtsa SELECT-i, ei anna me sellele tehingule pĂŒsivat ID-d. Anname sellele vaid virtuaalse ID.
Ja see suurendab Postgresi jÔudlust, parandades puhastamisvÔimalusi, seega koosneb virtuaalne tehingu ID kahest numbrist. Esimene number enne kaldkriipsu on bakenidi ID. Ja paremal pool nÀeme lihtsalt loendurit.

Seega, kui ma kĂ€ivitangi pĂ€ringu, ĂŒtleb see, et bakenidi ID on 2.

Ja kui ma kÀitan selliseid tehingute seeriat, siis nÀeme, et loendur suureneb iga kord, kui ma pÀringu kÀivitada. NÀiteks, kui ma kÀitan pÀringut 2/10, 2/11, 2/12 jne.

Pange tĂ€hele, et siin on kaks veergu. Vasakul nĂ€eme virtuaalset tehingu ID-d â 2/12. Ja paremal on meil pĂŒsiv tehingu ID. Ja see vĂ€li on tĂŒhi. Ja see tehing ei muuda andmebaasi. Seega ei anna ma sellele pĂŒsivat tehingu ID-d.

Niipea kui ma kĂ€itan analĂŒĂŒsi kĂ€sku (ANALYZE), annab sama pĂ€ring mulle pĂŒsiva tehingu ID. Vaadake, kuidas see on muutunud. Varem ei olnud mul seda ID-d, nĂŒĂŒd on see olemas.

Nii et siin on veel ĂŒks pĂ€ring, veel ĂŒks tehing. Virtuaalne tehingu number on 2/13. Ja kui ma kĂŒsin pĂŒsivat tehingu ID-d, siis kui kĂ€ivitan pĂ€ringu, saan selle.

Nii et veel kord. Meil on virtuaalne tehingu ID ja pĂŒsiv tehingu ID. Lihtsalt mĂ”istke seda, et mĂ”ista Postgresi kĂ€itumist.

Liigume kolmandasse jaossa. Siin vaatleme erinevaid lukustustĂŒĂŒpe Postgresis. See ei ole vĂ€ga huvitav. Kuid viimane jaos on palju huvitavam. Siiski peame vaatama pĂ”hilisi asju, vastasel korral ei mĂ”ista me seda, mis tuleb edasi.
Me vaatame seda jaotust, iga lukustustĂŒĂŒbi vaatlemiseks. NĂ€itan teile nĂ€iteid, kuidas need seadistatakse, kuidas need töötavad, ning nĂ€itan teile mĂ”ningaid pĂ€ringuid, mida saab kasutada lukustuse toimimise jĂ€lgimiseks Postgresis.

KĂŒsimuse esitamiseks ja Postgresis toimuva jĂ€lgimiseks peame kĂŒsima sĂŒsteemi vaate kaudu. Antud juhul on meil punase vĂ€rviga esile toodud pg_lock. Pg_lock on sĂŒsteemitabel, mis ĂŒtleb meile, milliseid lukustusi kasutatakse Postgresis.
Kuid mul on vĂ€ga keeruline nĂ€idata teile pg_lock'i iseenesest, kuna see on ĂŒsna keeruline. SeetĂ”ttu olen loonud vaate, mis nĂ€itab pg_locks'i. Samuti tĂ€idab see minu jaoks teatud ĂŒlesannet, mis aitab mul paremini mĂ”ista. St see jĂ€tab vĂ€lja minu lukustused, minu enda seansi jne. See on lihtsalt tavaline SQL ja aitab mul nĂ€idata, mis toimub.

Teine probleem on see, et see vaade on vĂ€ga lai, seetĂ”ttu pean looma teise â lockview2.
Ja see nĂ€itab mulle veel veerge tabelist. Veel ĂŒks, mis nĂ€itab mulle teisi veerge. See on piisavalt keeruline, seega pĂŒĂŒdsin seda esitada vĂ”imalikult lihtsalt.

Nii et lĂ”ime tabeli nimega Lockdemo. Ja seal on ĂŒks rida. See on meie nĂ€idistabel. Loomeme jaotisi lihtsalt selleks, et nĂ€idata teile lukustuse nĂ€iteid.

Nii et ĂŒks rida, ĂŒks veerg. Esimene lukustustĂŒĂŒp on ACCESS SHARE. See on kĂ”ige vĂ€hem piirav lukustus. See tĂ€hendab, et see ei konfliktis praktiliselt teiste lukustustega.
Ja kui me tahame selgelt mÀÀratleda blokeerimise, siis kĂ€ivitame kĂ€su «lock table». Ja see blokeerib selgelt, st ACCESS SHARE reĆŸiimis kĂ€ivitame lock table. Ja kui ma kĂ€ivitan PSQL taustal, siis kĂ€ivitangi sel viisil teise seansi oma esimesest seansist. St mida ma siin teen? Ma lĂ€hen teise seanssi ja ĂŒtlen talle «nĂ€ita mulle lockview selle pĂ€ringu jaoks». Ja siin on mul AccessShareLock selles tabelis. See on tĂ€pselt see, mida ma kĂŒsisin. Ja ta ĂŒtleb, et blokeering on antud. VĂ€ga lihtne.

Edasi, kui vaatame teist veergu, siis seal ei ole midagi. Need on tĂŒhjad.

Ja kui ma kĂ€ivitun kĂ€su «SELECT», siis see on implitsiitne (selge) viis kĂŒsida AccessShareLocki. SeetĂ”ttu vabastan oma tabeli ja kĂ€ivitun pĂ€ringu, ja pĂ€ring tagastab mitu rida. Ja ĂŒhes ridast nĂ€eme AccessShareLocki. Nii et SELECT kutsub tabelis esile AccessShareLocki. Ja see ei konflikti praktiliselt millegagi, sest see on madala taseme blokeering.

Mis juhtub, kui kĂ€ivitan SELECT ja mul on kolm erinevat tabelit? Varem kĂ€ivitasin ainult ĂŒhe tabeli, nĂŒĂŒd kĂ€ivitan kolm: pg_class, pg_namespace ja pg_attribute.

Ja nĂŒĂŒd, kui vaatan pĂ€ringut, nĂ€en kolme tabeli peale 9 AccessShareLocki. Miks? Sinise vĂ€rviga on esile tĂ”stetud kolm tabelit: pg_attribute, pg_class, pg_namespace. Kuid vĂ”ite nĂ€ha, et kĂ”ik indeksid, mis on mÀÀratletud nende tabelite kaudu, omavad samuti AccessShareLocki.
Ja see on blokeering, mis praktiliselt ei konflikti teistega. Ja kĂ”ik, mida ta teeb, on lihtsalt see, et ta ei luba meil tabelit kustutada, kuni me seda vaatame. See on mĂ”istetav. St kui me valime tabeli, siis see hetkel kaob, see on vale, seega AccessShare â see on madala taseme blokeering, mis ĂŒtleb meile "Ă€rge kustutage seda tabelit, kuni ma töötan".. Sisuliselt on see kĂ”ik, mida ta teeb.

ROW SHARE â see blokeering on veidi erinev.

VÔtame nÀite. SELECT ROW SHARE viib iga rea blokeerimise eraldi.. Nii et keegi ei saa neid kustutada ega muuta, kuni me neid vaatame.
Nii et, mida teeb SHARE LOCK? Me nĂ€eme, et tehingu ID 681 on SELECT jaoks. Ja see on huvitav. Mis meil siin juhtus? Esmakordselt nĂ€eme numbrit vĂ€li "Lock". Me vĂ”tame tehingu ID, ja see ĂŒtleb, et see lukustab selle eksklusiivses reĆŸiimis. KĂ”ik, mida ta teeb, on ĂŒtlemine, et mul on rida, mis on tehniliselt kuskil tabelis lukustatud. Aga ei ĂŒtle, kus tĂ€pselt. Veidi hiljem vaatame seda lĂ€hemalt.

Siin rÀÀgime, et lukustus on meie poolt kasutusel.

Nii et, eksklusiivne lukustus ĂŒtleb selgelt, et see on eksklusiivne. Ja samuti, kui kustutate rea selles tabelis, siis just see juhtub, nagu te vĂ”ite nĂ€ha.

SHARE EXCLUSIVE â see on pikem lukustus.

See (ANALYZE) on analĂŒĂŒsi kĂ€sk, mida kasutatakse.

SHARE LOCK â vĂ”ite ekspressiivselt lukustada jagamisreĆŸiimis.

Saate luua ka unikaalse indeksi. Ja seal nÀete SHARE LOCKi, mis on osa sellest. Ja see lukustab tabeli ning seab sellele SHARE LOCK-i.
Vaikimisi tÀhendab SHARE LOCK tabelis, et teised inimesed vÔivad tabelit lugeda, kuid keegi ei saa seda muuta. Ja just see juhtub, kui loote unikaalse indeksi.
Kui ma loon unikaalse concurrently indeksi, siis mul on teine tĂŒĂŒpi lukustus, sest nagu te mĂ€letate, vĂ€hendab concurrently indeksite kasutamine lukustusnĂ”udeid. Ja kui kasutan tavalist lukustust, tavalist indeksit, siis takistan nii indeksi kirjutamist tabelisse selle loomise ajal. Kui kasutan concurrently indeksit, siis pean kasutama teist tĂŒĂŒpi lukustust.

SHARE ROW EXCLUSIVE â jĂ€lle saab seda mÀÀrata ekspressiivselt.

VÔi saame luua reegli, st vÔtta mingi kindla juhu, mille puhul seda kasutatakse.

EXCLUSIVE lukustus tÀhendab, et keegi teine ei saa tabelit muuta.

Siin nÀeme erinevaid lukustusi.

ACCESS EXCLUSIVE, nÀiteks, on lukustus kÀsk. NÀiteks, kui teete CLUSTER table, siis see tÀhendab, et keegi ei saa sinna kirjutada. Ja see lukustab mitte ainult tabeli, vaid ka indeksid.

See on ACCESS EXCLUSIVE lukustuse teine leht, kus me nÀeme konkreetselt, mida see tabelis lukustab. See lukustab eraldi rea tabelis, mis on pÀris huvitav.
See kĂ”ik pĂ”hiteave, mida ma tahtsin anda. Oleme rÀÀkinud lukudest, tehingu ID-dest, virtuaalsetest tehingu ID-dest ja pĂŒsivatest tehingu ID-dest.

NĂŒĂŒd vaatame lukke nĂ€idete kaudu. See on kĂ”ige huvitavam osa. Uurime vĂ€ga huvitavaid juhtumeid. Minu ĂŒlesanne selles esituses on anda teile parem arusaam sellest, mida Postgres tĂ”eliselt teeb, kui ta ĂŒritab lukustada erinevaid asju. Minu arvates oskab ta vĂ€ga hĂ€sti lukustada ĂŒksikuid osi.
Vaatame konkreetseid nÀiteid.

Alustame tabelitest ja ĂŒhest reast tabelis. Kui ma midagi sisestan, kuulen ExclusiveLock'i, tehingu ID-d ja ExclusiveLock'i tabelil.

Aga mis juhtub, kui sisestan veel kaks rida? NĂŒĂŒd on meie tabelis kolm rida. Kui ma sisestan ĂŒhe rea ja saan sellise vĂ€ljundi. Ja kui ma sisestan veel kaks rida, mis siin on veider? Siin on imelikkus, kuna ma lisasin kolm rida sellele tabelile, kuid mul on endiselt kaks rida lukustustabelis. Ja see on pĂ”himĂ”tteliselt Postgresi aluseline kĂ€itumine.
Paljud arvavad, et kui andmebaasis lukustad 100 rida, siis pead looma 100 lukustuse sissekannet. Kui ma lukustan korraga 1 000 rida, siis tahan 1 000 sellist pĂ€ringut. Ja kui ma pean lukustama miljon vĂ”i miljard. Kuid kui me seda teeme, siis see ei tööta hĂ€sti. Kui oled kasutanud sĂŒsteemi, mis loob lukustuse sissekande iga ĂŒksiku rea jaoks, siis nĂ€ed, et see on keeruline. Sest pead kohe mÀÀrama lukustustabeli, mis vĂ”ib tĂ€ituda, aga Postgres nii ei tee.
Ja slaidil on vĂ€ga oluline, et siin nĂ€idatakse selgelt, et on olemas veel ĂŒks sĂŒsteem, mis töötab MVCC sees, mis lukustab ĂŒksikuid reade. Seega, kui lukustate miljardeid reade, ei loo Postgres miljardit eraldi lukustamise kĂ€sku. Ja see mĂ”jub vĂ€ga hĂ€sti jĂ”udlusele.

Aga kuidas on uuendustega? Ma praegu uuendan rida ja te vÔite mÀrkida, et see tegi kohe kaks erinevat operatsiooni. Ta blokeeris tabeli samal ajal, aga ta blokeeris ka indeksi. Ja tal oli vaja blokeerida indeksi, kuna sellele tabelile on seatud unikaalsed piirangud. Ja me tahame veenduda, et keegi seda ei muuda, seetÔttu me selle blokeerime.

Aga mis juhtub, kui ma tahan uuendada kahte rida? Ja me nÀeme, et see kÀitub samuti. Me teeme kaks korda rohkem uuendusi, kuid tÀpselt sama palju ridade blokeeringuid.
Kui teid huvitab, kuidas Postgres seda teeb, peate kuulama mu ettekandeid MVCC-st, et teada saada, kuidas Postgres sisemiselt tÀhistab neid rive, mida ta muudab. Ja Postgresel on viis, kuidas ta seda teeb, kuid ta ei tee seda tabelite blokeerimise tasemel, ta teeb seda madalamal ja efektiivsemal tasemel.

Aga kui ma tahan midagi kustutada? Kui ma kustutan nĂ€iteks ĂŒhe rida ja mul on ikkagi minu kaks algset blokeeringut, ning isegi kui ma tahaksin need kĂ”ik eemaldada, on need ikkagi seal.

Ja nĂ€iteks, kui ma tahan lisada 1 000 rida ja siis kustutada vĂ”i lisada veel 1 000 rida, siis need individuaalsed read, mida ma lisasin vĂ”i muutsin, ei salvestata siia. Need salvestatakse madalamal tasemel seesama rida. Ja MVCC ettekandes rÀÀkisin ma sellest ĂŒksikasjalikult. Kuid on vĂ€ga oluline, et analĂŒĂŒsite blokeeringuid, et tagada, et teil on tabeli tasemel blokeering ja et siin ei ole nĂ€ha, kuidas mĂ”ned ĂŒksikud read salvestatakse.

Aga kuidas on eksplicitse blokeerimisega?

Kui ma vajutan 'uuenda', siis mul on kaks blokeeritud rida. Ja kui ma valin need kÔik ja vajutan 'uuenda kÔikjal', siis mul jÀÀb endiselt kaks blokeerimisrekordit.

Me ei loo eraldi kirjeid igaĂŒhe jaoks eraldi rida. Sest siis langeb jĂ”udlus, seal vĂ”ib olla liiga palju. Ja me vĂ”ime sattuda ebamugavasse olukorda.

Ja sama kehtib, kui me teeme shared, me vÔime teha kÔigi 30 korda.

Me taastame oma tabeli, kĂ”ik eemaldame, siis lisame uuesti ĂŒhe rida.

Teine kĂ€itumisviis, mida nĂ€ete Postgres'is, on vĂ€ga hĂ€sti tuntud ja soovitud kĂ€itumine â saate teha update vĂ”i select. Ja te saate neid teha samaaegselt. Select ei blokeeri update'id ja sama kehtib ka vastupidiselt. Me ĂŒtleme, et lugeja ei blokeeri kirjutajat ja kirjutaja ei blokeeri lugejat.
Ma nĂ€itan teile selle nĂ€idet. Ma teen praegu valiku. SeejĂ€rel teeme INSERT. Ja te saate nĂ€ha â 694. Te nĂ€ete tehingu ID-d, mis tegi selle insert'i. Ja see on see, kuidas see töötab.

Ja kui ma nĂŒĂŒd vaatan oma tagaosa ID-d, siis see on â 695.

Ja ma nÀen, et 695 ilmub minu tabelisse.

Ja kui ma teen siin uuenduse, siis saan teise juhtumi. Sellisel juhul on 695 eksklusiivne lukustus ja update'l on sama kÀitumine, kuid nende vahel ei teki konflikte, mis on piisavalt ebatavaline.
Ja vĂ”ite mĂ€rgata, et ĂŒleval on ShareLock ja all on ExclusiveLock. Ja mĂ”lemad tehingud said lĂ€bi.
Ja peate kuulama minu ettekannet MVCC teemal, et mÔista, kuidas see toimub. Kuid see on illustratsioon sellele, et saate neid samaaegselt teha, st samaaegselt teha SELECT ja UPDATE.

Laske meil lĂ€htestada ja teha veel ĂŒks tehing.

Kui proovite samaaegselt kĂ€ivitada kaks update'i samal real, siis see lukustub. Ja pidage meeles, et ma ĂŒtlesin, et lugeja ei blokeeri kirjutajat, vaid kirjutaja blokeerib lugeja, kuid ĂŒks kirjutaja blokeerib teist kirjutajat. St me ei saa lasta kahte inimest samaaegselt uuendada sama rida. Peab ootama, kuni ĂŒks neist lĂ”petab.

Ja selle illustreerimiseks vaatan Lockdemo tabelit. Ja vaatame ĂŒhte rida. Tehingut 698.
Me uuendasime selle 2 peale. 699 â see on esimene uuendus. Ja see lĂ€ks edukalt lĂ€bi vĂ”i see on ootel tehingus ja ootab, kuni me selle kinnitame vĂ”i tĂŒhistame.

Aga vaadake teist â 2/51 â see on meie esimene tehing, meie esimene sessioon. 3/112 â see on teine pĂ€ring, mis ilmus ĂŒlal ja mis muutsid selle vÀÀrtuse kolme peale. Ja kui te mĂ€rkate, siis ĂŒlemine jĂ€ttis ennast lukku, millel on ID 699. Kuid 3/112 ei saanud lukku. Veerus Lock_mode on kirjutatud, et ta ootab. Ta ootab 699. Ja kui te vaatate, kus on 699, on ta ĂŒleval. Ja mida esimest sessiooni tegi? Ta lĂ”i eksklusiivse lukku omaenda tehingu ID-l. Just nii teeb Postgres. Ta blokeerib oma tehingu ID. Ja kui te soovite Oodata, kuni keegi kinnitab vĂ”i tĂŒhistab, siis peate ootama, kuni seal on ootel tehing. Ja seetĂ”ttu saame nĂ€ha kummalist rida.
Vaadakem veel kord. Vasakul nĂ€eme meie töötlemise ID-d. Teises veerus nĂ€eme meie virtuaalse tehingu ID-d ja kolmandas nĂ€eme lock_type'i. Mida see tĂ€hendab? Fondamentaalselt ĂŒtleb, et see blokeerib tehingu ID-d. Kuid mĂ€rkige, et kĂ”ikides ridades ĂŒleval on kirjutatud relation. Ja seetĂ”ttu on teil tabelis kaks tĂŒĂŒpi blokeeringut. On relation-lukustus. Samuti on tehingu ID lukustus, kus me blokeerime ise, see on just see, mis toimub esimeses reas vĂ”i kĂ”ige alumises, kus transationid, kus me ootame, et 699 lĂ”petaks oma operatsiooni.
Mina vaatan, kuidas asjad lÀhevad. Ja siin toimub samaaegselt kaks asja. Te vaatate lukustamist tehingu ID-l esimeses reas, mis blokeerib iseennast. Ja ta blokeerib iseennast, et sundida inimesi ootama.
Kui te vaatate 6. rida, siis see sama kirje, mis on esimeses. Ja seetÔttu on tehing 699 blokeeritud. Samuti blokeerib 700 ennast. Ja siis nÀete alumises reas, et ootame, kuni 699 lÔpetab oma operatsiooni.

Ja lock_type, tuple, nÀete numbreid.

Te nÀete, et see on 0/10. Ja see on lehe number, samuti ka selle konkreetse rea offset.

Ja te nÀete, et see saab 0/11, kui me uuendame.

Aga tegelikult â see on 0/10, kuna ootamine toimib selle operatsiooni kallal. Meil on vĂ”imalus vaadata, et see on see rida, mida ma ootan, et kinnitada.

Kui me selle kinnitasime ja commit-nuppu vajutasime, ning vĂ€rskendus lĂ”petas, saame me jĂ€lle sama. Tehing 700 on ainus lukustus, ta ei oota enam kedagi, kuna see on commititud. Ta ootab vaid, kuni tehing lĂ”petatakse. Kui 699 lĂ”peb, ei oota me enam kedagi. Ja nĂŒĂŒd ĂŒtleb tehing 700, et kĂ”ik on korras, et kĂ”ik vajalikud lukustused on tal kĂ”ikides lubatud tabelites.

Ja et seda kĂ”ike veel keerulisemaks teha, loome me veel ĂŒhe vaate, mis seekord annab meile hierarhia. Ma ei oota, et te seda pĂ€ringut mĂ”istate. Kuid see annab meile selgema ĂŒlevaate, mis toimub.

See on rekursiivne vaade, millel on veel ĂŒks sektsioon. Ja see toob kĂ”ik tagasi kokku. Kasutame seda.

Mis juhtub, kui me teeme kolm samaaegset vĂ€rskendust ja ĂŒtleme, et rida on nĂŒĂŒd kolme vĂ”rra suurem. Ja vahetame 3 4 vastu.

Ja nĂŒĂŒd nĂ€eme 4. Ja tehingu ID 702.

Ja siis vahetan 4 5 vastu. Ja 5 6, ja 6 7. Ja ma panen ritta inimesed, kes ootavad, et see ĂŒks tehing Ă”nnestuks.

Ja kÔik muutub selgeks. Mis on esimene rida? See on 702. See on tehingu ID, mis algselt selle vÀÀrtuse seadmiseks mÀÀrati. Ja mis mul on kirjas veerus Granted? Mul on seal mÀrgid. f. Need on minu vÀrskendused (5, 6, 7), mis ei saa kinnitust, sest me ootame, et tehingu ID 702 lÔpeks. Seal on meil tehingu ID lukustus. Ja kokku tuleb 5 tehingu lukustus ID-d.
Ja kui te vaatate 704, 705, siis seal ei ole veel midagi kirjas, sest nad ei tea veel, mis toimub. Nad lihtsalt kirjutavad, et ei tea, mis toimub. Ja nad lÀhevad lihtsalt unne, sest nad ootavad, kuni keegi lÔpetab ja Àratab nad, kui tekib vÔimalus rida vahetada.

Nii see vÀlja nÀeb. Selge on see, et nad kÔik ootavad 12. rida.

See on see, mida me siin nÀgime. Siin on 0/12.

Nii et niipea, kui esimene tehing kinnitatakse, saate siin nĂ€ha, kuidas hierarhia töötab. Ja nĂŒĂŒd on kĂ”ik selge. Nad kĂ”ik saavad puhtaks. Ja nad on tegelikult endiselt ootamas.

Nii see lĂ€heb. 702 on toimetamisel. NĂŒĂŒd saab 703 selle rea lukustuse ja pĂ€rast seda hakkab 704 ootama, kuni 703 lĂ”petab toimetamise. Ja 705 ootab samuti. Kui kĂ”ik see on lĂ”pule viidud, siis nad puhastavad end ise. Tahaksin mĂ€rkida, et kĂ”ik seisavad jĂ€rjekorras. See meenutab olukorda ummikus, kus kĂ”ik ootavad esimest autot. Esimene auto peatus ja kĂ”ik jĂ€rgnevad seisavad pikalt reas. Siis see liigub edasi, jĂ€rgmine auto saab siis edasi liikuda ja oma lukustuse jne.

Ja kui see tundub teile ebapiisavalt keeruline, rÀÀgime nĂŒĂŒd deadlockidest. Ma ei tea, kui palju teist on nende vastu puutunud. See on piisavalt levinud probleem andmebaasisĂŒsteemides. Deadlockid tekivad siis, kui ĂŒks sessioon ootab, et teine sessioon midagi teeks. Samal ajal ootab teine sessioon, et esimene sessioon midagi teeks.
Ja nĂ€iteks, kui Ivan ĂŒtleb: "Anna mulle midagi" ja mina ĂŒtlen: "Ei, ma annan sulle seda ainult siis, kui sa annad mulle midagi teist." Ja ta ĂŒtleb: "Ei, ma ei anna seda, kui sa ei anna mulle." JĂ”uame murtud lukustuse olukorda. Olen kindel, et Ivan ei teeks nii, kuid te mĂ”istate mĂ”tte, et meil on kaks inimest, kes tahavad midagi saada ja nad ei ole valmis seda andma, kuni teine inimene neile seda ei anna. Ja siin ei ole lahendust.
Eessential on, et teie andmebaas tuvastaks need olukorrad. SeejĂ€rel tuleb eemaldada vĂ”i lĂ”petada ĂŒks sessioon, sest vastasel juhul jÀÀvad nad sinna igavesti. Me nĂ€eme seda andmebaasides, operatsioonisĂŒsteemides ja kĂ”ikjal, kus meil on paralleelsed protsessid.

Ja nĂŒĂŒd paigutame kaks deadlocki. Asetame 50 ja 80. Esimeses real loon vĂ€rskenduse 50-le 50-ks. See toob vĂ€lja tehingu numbri 710.

SeejÀrel muudan 80 81-ks ja 50 51-ks.

Ja nii see vÀlja nÀeb. SeetÔttu on 710-l rea lukustus ja 711 ootab kinnitust. Oleme seda varem nÀinud, kui vÀrskendused toimusid. 710 on meie rea omanik. Ja 711 ootab, kuni 710 lÔpetab tehingu.

Seal on isegi kirjas, millisel tÀpselt real meil deadlock toimub. Ja siin hakkab see kummaliseks muutuma.

NĂŒĂŒd vĂ€rskendame 80 80-ks.

Ja nĂŒĂŒd saavad alguse ummikud. 710 ootab 711 vastust, samas kui 711 ootab 710-d. See ei lĂ”pe hĂ€sti. Ja sellest pole vĂ€ljapÀÀsu. Nad ootavad ĂŒksteiselt vastust.

See lihtsalt hakkab kÔike aeglustama. Me ei soovi seda.

Postgresis on viise, kuidas mĂ€rkida, kui see juhtub. Ja kui see juhtub, siis saad sellise veateate. Selgub, et ĂŒks protsess ootab SHARED LOCK-i teiselt protsessilt, st see, mis blokeerib 711. Ja see protsess ootas SHARED LOCK-i teatud tehingu ID jaoks ning oli blokeeritud mingi protsessi poolt. Nii et siin on olukord ummikus.

Kas on olemas ka kolmeosalisi ummikuid? Kas see on vÔimalik? Jah.

Sisestame need numbrid tabelisse. Muudame 40 40-ks, teeme blokeeringu.

Muudame 60 61-ks, 80 81-ks.

K seejÀrel muudame 80, ja siis - bum!

NĂŒĂŒd ootab 714 715-d. 716 ootab 715-d. Ja seda ei saa enam parandada.

Siin pole enam kahte inimesena, vaid kolm inimest. Ma tahan sinult midagi, see tahab kolmandalt inimeselt midagi, ja kolmas inimene tahab minult midagi. Ja meil on kolmepoolne ootus, sest kÔik ootame, kuni teine inimene lÔpetab selle, mida ta peab tegema.

Postgres teab, millisel real see juhtub. Seega annab see teile jĂ€rgmise sĂ”numi, kus nĂ€idatakse, et teil on probleem, kus kolm sisendit blokeerivad ĂŒksteist. Ja siin pole piiranguid. See vĂ”ib juhtuda, kus 20 kirjet blokeerivad ĂŒksteist.

JĂ€rgmine probleem on serialiseeritavus.

Kui on eriline serialiseeritav blokeering.

Ja naaseme 719 juurde. Sellel on tÀiesti normaalne vÀljund.

Ja saate vajutada, et teha tehing serialiseeritava formaadiga.

NĂŒĂŒd mĂ”istate, et teil on teine blokk SA - see tĂ€hendab serialiseeritav.


Seega on meil uus blokeeringu liik, mida nimetatakse SARieadLock, mis on jÀrjekindel blokeering ja lubab sisestada seerianumbrid.

Samuti vÔite sisestada unikaalseid indekseid.

Selles tabelis on meil unikaalsed indeksid.

Seega, kui ma sisestan siia numbri 2, siis mul on 2. Kuid kĂ”ige ĂŒlal oleval joonisel sisestan veel ĂŒhe 2. NĂ€ete, et 721-l on eksklusiivne blokeering. Kuid nĂŒĂŒd ootab 722, et 721 lĂ”petaks oma operatsiooni, sest ta ei saa 2-d sisestada, enne kui ei tea, mis juhtub 721-ga.

Ja kui teeme alatehingu.

Siin on meil 723.

Ja kui me sĂ€ilitame punkti ja siis vĂ€rskendame seda, siis saame uue tehingu ID. See on veel ĂŒks kĂ€itumismuster, millest peate teadma. Kui me selle tagasi anname, siis tehingu ID kaob. 724 kaob. Kuid nĂŒĂŒd on meil 725.
Ja mida ma siin pĂŒĂŒan teha? PĂŒĂŒan nĂ€idata teile nĂ€iteid ebatavalisest lukustamisest, mida vĂ”ite kohata: olgu need serializable lukud vĂ”i SAVEPOINT - need on erinevad lukustamisvĂ”imalused, mis ilmuvad lukustamisdiagrammi.

See on eksplicitsete (selgete) lukustamiste loomine, kus asub pg_advisory_lock.

Ja nĂ€ete, et lukustamise tĂŒĂŒp on siin loetletud kui advisory. Ja siin on punasega kirjutatud âadvisoryâ. Ja te saate samaaegselt lukustada pg_advisory_unlock.

Ja lĂ”petuseks tahaksin teile nĂ€idata veel ĂŒhte hĂ€mmastavat asja. Loome veel ĂŒhe tĂŒĂŒbi. Kuid vormin pg_locks tabeli pg_stat_activity tabeliga. Ja miks ma seda teha tahan? Sest see vĂ”imaldab mul vaadata ja nĂ€ha kĂ”iki praegusi sessioone ning nĂ€ha, milliseid lukke nad ootavad. Ja see on piisavalt huvitav, kui me kogume lukustamisdiagrammi ja pĂ€ringute diagrammi kokku.

Ja siin loome pg_stat_view.

Ja me vĂ€rskendame rida ĂŒhe vĂ”rra. Ja siin nĂ€eme 724. SeejĂ€rel vĂ€rskendame meie rea kolme peale. Ja mida te siit praegu nĂ€ete? Need on pĂ€ringud, st nĂ€ete kogu pĂ€ringute nimekirja, mis on loetletud vasakus veerus. Ja siis paremal pool nĂ€ete lukustusi ja seda, mida need loovad. See vĂ”ib olla teile arusaadav, et pole vajadust iga kord tagasi minna igasse sessiooni ja vaadata - kas on vaja liituda vĂ”i mitte. Meie eest teevad nad seda.
Veel ĂŒks vĂ€ga kasulik funktsioon on pg_blocking_pids. Te ei pruugi sellest kunagi kuulnud olla. Mida see teeb? See vĂ”imaldab meil öelda, et seansi 11740 jaoks ootab see tĂ€pselt neid protsessi ID'sid. Ja te nĂ€ete, et 11740 ootab 724. Ja 724 on kĂ”ige ĂŒleval. Ja 11306 on teie protsessi ID. Sisuliselt lĂ€bib see funktsioon teie lukutustabelit. Ja ma tean, et see on veidi keeruline, kuid te mĂ”istate seda. Sisuliselt lĂ€bib see funktsioon selle lukutustabeli ja pĂŒĂŒab leida, kus see protsessi ID on, arvestades neid lukustusi, mida ta ootab. Samuti pĂŒĂŒab ta arvutada, milline protsessi ID on selle protsessi puhul, mis ootab lukustusi. Nii et te saate selle funktsiooni kĂ€ivitada pg_blocking_pids.
Ja see on vÀga kasulik. Me lisasime selle alles versioonist 9.6, seega on see funktsioon vaid 5 aastat vana, kuid see on vÀga ja vÀga kasulik. Sama kehtib ka teise pÀringu kohta. See nÀitab tÀpselt seda, mida me peame nÀgema.

See on see, millest ma tahtsin teiega rÀÀkida. Nagu ma ootasin, kulutasime kogu meie aja, sest slaide oli nii palju. Slaidid on allalaadimist ootamas. Ma tahan teid tĂ€nada, et olite siin. Olen kindel, et teile meeldib konverentsi ĂŒlejÀÀnud osa, suur tĂ€nu!
KĂŒsimused:
NĂ€iteks, kui ma proovin ridu uuendada, ja teine sessioon proovib kogu tabelit kustutada. Niikaua kui ma aru saan, peaks seal olema midagi nagu intent lock. Kas Postgreses on midagi sellist?

Naaseme algusesse. VĂ”ib-olla mĂ€letate, et kui teete midagi, nĂ€iteks SELECT, siis anname AccessShareLock'i. Ja see takistab tabeli kustutamist. Seega, kui soovite nĂ€iteks tabelis rida uuendada vĂ”i rida kustutada, ei saa keegi samal ajal kogu tabelit kustutada, sest te hoiate selle AccessShareLock'i kogu tabeli ja rea ĂŒle. Ja niipea kui olete lĂ”petanud, saavad nad selle kustutada. Kuid seni, kuni te seal midagi muutate, ei saa nad seda teha.
Teeme veel kord. Liigume kustutamise nĂ€ite juurde. Ja te nĂ€ete, kuidas reaal on eksklusiivne lukustus kogu tabeli ĂŒle.
See nÀeb vÀlja nagu eksklusiivne lukustus, eks ole?
Jah, see nĂ€eb vĂ€lja nagu see. Ma mĂ”istan, millest te rÀÀgite. Te ĂŒtlete, et kui ma teen SELECT-i, siis on mul ShareExclusive, ja kui ma muudan selle Row Exclusive-iks, kas see tekib probleem? Kuid ĂŒllatavalt see ei tekita probleemi. See on nagu lukustustaseme tĂ”stmine, kuid tegelikult on mul lukustus, mis takistab kustutamist. Ja nĂŒĂŒd, kui ma teen selle luku tugevamaks, takistab see endiselt kustutamist. Seega ei ole see nii, et ma tĂ”usen ĂŒles. St. see takistas seda ja kui see oli madalamal tasemel, seega, kui ma tĂ”stsin selle taset, takistab see endiselt tabeli kustutamist.
Ma mĂ”istan, millest te rÀÀgite. Siin ei ole olukorda, kus lukustustaset tĂ”stetakse, kus te proovite loobuda ĂŒhest lukustusest, et tutvustada vĂ”imsamat. Siin see lihtsalt suurendab selle takistuse ulatust, seega see ei pĂ”hjusta mingit konflikti. Aga see on hea kĂŒsimus. Suur tĂ€nu, et selle kĂŒsisite!
Mida peame tegema, et vÀltida deadlocki olukorda, kui meil on palju sessioone, suur kasutajate arv?
Postgres mĂ€rkab automaatselt deadlocki olukordi. Ja automaatselt eemaldab ĂŒhe sessioonidest. Ainus viis, kuidas aidata vĂ€ltida deadlocki, on blokeerida inimesi samas jĂ€rjestuses. Seega, kui vaatate oma rakendust, on tihti deadlockide pĂ”hjus... Oletame, et ma tahan blokeerida kaks erinevat asja. Ăks rakendus blokeerib tabeli 1, teine rakendus blokeerib 2 ja seejĂ€rel tabeli 1. Ja kĂ”ige lihtsam viis deadlockide vĂ€ltimiseks on vaadata oma rakendust ja pĂŒĂŒda tagada, et blokeerimine toimub kĂ”igis rakendustes samas jĂ€rjekorras. Ja see tavaliselt eemaldab 80% probleemidest, sest erinevad inimesed kirjutavad neid rakendusi. Ja kui blokite neid samas jĂ€rjekorras, ei satu te deadlocki olukorda.
Suur tÀnu teie ettekande eest! Te rÀÀkisite vacuum full-st ja kui ma Ôigesti mÔistan, siis vacuum full deformeerib kirje jÀrjekorda eraldi hoidlas, seega sÀilib praegune kirje muutumatuna. Aga miks vacuum full nÔuab eksklusiivset lukustust ja miks see konflikteerub kirjutamisoperatsioonidega?
See on hea kĂŒsimus. PĂ”hjuseks on see, et vacuum full vĂ”tab tabeli. Ja me sisuliselt loome uue versiooni tabelist. Ja tabel on uus. Seega on tegemist tĂ€iesti uue versiooniga tabelist. Probleem seisneb selles, et kui me seda teeme, ei taha me, et inimesed seda loeksid, sest me vajame, et nad nĂ€eksid uut tabelit. SeetĂ”ttu on see seotud eelmise kĂŒsimusega. Kui me saaksime lugeda samaaegselt, siis ei saaks me seda liigutada ja suunata inimesi uuele tabelile. Me peaksime ootama, kuni kĂ”ik lĂ”petavad selle tabeli lugemise, ja seetĂ”ttu on tegelikult tegemist exclusiivse lukustusolukorraga.
Me lihtsalt ĂŒtleme, et blokeerime alguses, kuna me teame, et lĂ”puks vajame eksklusiivset lukustust, et liigutada kĂ”ik uue koopia juurde. SeetĂ”ttu saame me seda potentsiaalselt lahendada. Ja me teeme seda koos samaaegse indekseerimisega. Kuid see on palju keerulisem teha. Ja see on vĂ€ga seotud teie eelmise kĂŒsimusega exclusiivse lukustuse kohta.
Kas Postgres'is on vÔimalik lisada lukustamise katkestusaega? Oracle'is vÔin ma nÀiteks kirjutada 'vali uuendamiseks' ja oodata 50 sekundit uuendamiseks. See oli rakenduse jaoks hea. Kuid Postgres'is pean ma selle kas kohe tegema ja mitte ootama, vÔi ootama kuni teatud ajani.
Jah, saate valida lukustamise katkestuaja oma lukustustele. Samuti saate anda kĂ€su no way, mis âŠ, kui te ei saa kohe lukustust. Seega kas lukustamise katkestusaja vĂ”i muu, mis vĂ”imaldab teil seda teha. Seda ei tehta sĂŒntaktilisel tasemel. Seda tehakse serveri muutuja tasemel. MĂ”nikord ei saa seda kasutada.
Kas saad avada 75. slaidi?
Jah.

Ja minu kĂŒsimus on jĂ€rgmine. Miks ootavad mĂ”lemad uuendusprotsessid 703?
Ja see on tĂ”esti hea kĂŒsimus. Ma ei saa aru, miks Postgres seda teeb. Aga kui 703 loodi, oodati 702. Ja kui 704 ja 705 ilmuvad, siis nĂ€ib, et nad ei tea, mida nad ootavad, sest seal pole veel midagi. Ja Postgres teeb seda nii: kui sa ei saa lukku, siis ta kirjutab âMilleks sind töödelda?â, sest sa ikkagi ootad kedagi. Nii et anname tal lihtsalt Ă”hus rippuda, ta ei uuenda seda ĂŒldse. Aga mis siin toimub? Kui 702 on protsessi lĂ”petanud ja 703 on saanud oma luku, siis sĂŒsteem naaseb tagasi. Ja ĂŒtleb, et nĂŒĂŒd ootavad kaks inimest. Siis uuendame neid koos. Ja mĂ€rkime, et mĂ”lemad ootavad.
Ma ei tea, miks Postgres nii teeb. Aga on probleem, mis on tuntud kui fâŠ. Tundub, et see ei ole vene termin. See on siis, kui kĂ”ik ootavad ĂŒhte lukku, isegi kui 20 instantsi ootab lukku. Ja Ă€kki Ă€rkavad nad kĂ”ik ĂŒles korraga. Ja kĂ”ik hakkavad pĂŒĂŒdma reageerida. Aga sĂŒsteem teeb nii, et kĂ”ik ootavad 703. Sest nad kĂ”ik ootavad ja me panevad nad kohe ritta. Ja kui tuleb mĂ”ni muu uus pĂ€ring, mis on pĂ€rast seda loodud, nĂ€iteks 707, siis seal on taas tĂŒhjus.
Ja mulle tundub, et see on tehtud selleks, et saaks öelda, et sellel etapil ootab 702 703, ja kÔigil, kes pÀrast seda tulevad, ei ole selles valdkonnas mitte mingit kirjet. Aga niipea, kui esimene ootaja lahkub, saavad kÔik, kes sel hetkel ootasid enne uuendust, sama mÀrgise. Ja seetÔttu arvan, et see on tehtud selleks, et saaksime töödelda jÀrjekorras, et nad oleksid Ôigesti jÀrjestatud.
Ma olen alati vaadanud seda kui ĂŒsna kummalist fenomeni. Sest siin, nĂ€iteks, ei loetle neid ĂŒldse. Aga mulle tundub, et iga kord, kui anname uue luku, vaatame me kĂ”iki, kes on ooteprotsessis. Siis paneme nad kĂ”ik jĂ€rjekorda. Ja seejĂ€rel, iga uus, mis tuleb, satub jĂ€rjekorda ainult siis, kui jĂ€rgmine inimene on lĂ”petanud töötlemise. VĂ€ga hea kĂŒsimus. AitĂ€h kĂŒsimise eest!
Mulle tundub, et see on palju loogilisem, kui 705 ootab 704.
Aga probleem on jĂ€rgmine. Tehniliselt saate Ă€ratada kas ĂŒhe vĂ”i teise. Seega Ă€ratame kas ĂŒhe vĂ”i teise. Kuid mis toimub sĂŒsteemi töös? NĂ€ete, et 703 on ĂŒlal blokeerinud omaenda tehingu ID. Nii töötab Postgres. Ja 703 blokeerib enda tehingu ID, seega kui keegi tahab oodata, siis ootab see 703. Ja pĂ”himĂ”tteliselt lĂ”petab 703. Ja ainult pĂ€rast selle lĂ”petamist Ă€rkab mĂ”ni protsess. Ja me ei tea, milline neist protsessidest on. Siis töötame jĂ€rk-jĂ€rgult kĂ”ik lĂ€bi. Kuid pole selge, milline protsess Ă€rkab esimesena, sest see vĂ”ib olla ĂŒkskĂ”ik milline neist protsessidest. PĂ”himĂ”tteliselt oli meil ajastaja, mis ĂŒtles, et nĂŒĂŒd saame Ă€ratada ĂŒkskĂ”ik millise neist protsessidest. Valime lihtsalt ĂŒhe juhuslikult. SeetĂ”ttu tuleb mĂ”lemad ĂŒles mĂ€rkida, sest me saan Ă€ratada mĂ”lemat.
Ja probleem on see, et meil on CP-lĂ”pmatus. Seega on tĂ€iesti tĂ”enĂ€oline, et saame Ă€ratada hilisema. Ja kui nĂ€iteks Ă€ratame hilisema, siis ootame seda, kes just sai blokeeringu, seega me ei mÀÀra, kes tĂ€pselt esimesena Ă€ratatakse. Loome lihtsalt sellise olukorra, ja sĂŒsteem Ă€ratab neid juhuslikus jĂ€rjekorras.
Jah . Vaadake, need on samuti huvitavad ja kasulikud. Teema on muidugi kohutavalt keeruline. AitÀh palju, Bruce!
Allikas: habr.com
