I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Një ditë, në të ardhmen e largët, eliminimi automatik i të dhënave të panevojshme do të jetë një nga detyrat më të rëndësishme të DBMS [1]. Deri atëherë, na nevojitet të kujdesemi vetë për fshirjen ose zhvendosjen e të dhënave të panevojshme në sisteme më të lira ruajtjeje. Supozoni se keni vendosur të fshini disa milion rreshta. Një detyrë e thjeshtë, veçanërisht nëse dihet kushti dhe ka një indeks të përshtatshëm. "DELETE FROM table1 WHERE col1 = :value" — çfarë mund të jetë më e thjeshtë, apo jo?

Video:

Luaj videon

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

  • Unë jam në komitetin programues të Highload që nga viti i parë, pra nga viti 2007.

  • Dhe me Postgres jam që nga viti 2005. E kam përdorur në shumë projekte.

  • Grupi me RuPostges po ashtu që nga viti 2007.

  • Gjatë Meetup kemi arritur mbi 2100 pjesëmarrës. Kjo është vendi i dytë në botë pas New Yorkut, kemi tejkaluar San Franciskon që prej kohësh.

  • Kam jetuar disa vite në Kaliforni. Jam angazhuar më shumë me kompani amerikane, përfshirë të mëdha. Ato janë përdorues aktivë të Postgres. Dhe atje ndodhin gjëra interesante.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

https://postgres.ai/ – kjo është kompania ime. Ne merremi me automatizimin e detyrave që eliminojnë ngadalësimin e zhvillimit.

Kur bëni diçka, ndonjëherë përreth Postgres ndodhin disa pengesa. Le të themi se ju duhet të prisni derisa administratorit të ngritë një skenë testuese për ju, ose ju duhet të prisni derisa DBA të reagojë ndaj jush. Ne gjejmë këto ngushtica në procesin e zhvillimit, testimit dhe administratës dhe përpiqemi t'i eliminojmë ato nëpërmjet automatizimit dhe qasjeve të reja.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

https://www.seagate.com/files/www-content/our-story/trends/files/idc-seagate-dataage-whitepaper.pdf

Kohët e fundit isha në VLDB në Los Anxhelos. Kjo është konferenca më e madhe për bazat e të dhënave. Atje kishte një prezantim mbi faktin se në të ardhmen, DBMS do të ruajnë dhe automatikisht do të fshijnë të dhënat. Ky është një temë e re.

Sasia e të dhënave në botë po rritet duke arritur zetta-bajt – kjo është 1,000,000 petabajt. Tani është vlerësuar se kemi më shumë se 100 zetta-bajt të dhënash të ruajtura në botë. Dhe po bëhet gjithnjë e më shumë.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

https://vldb2019.github.io/files/VLDB19-keynote-2-slides.pdf

Dhe çfarë të bëjmë me këtë? Është e qartë se duhet të fshihemi. Ja lidhja për këtë prezantim interesant. Por për momentin, kjo nuk është realizuar në DBMS.

Ata që dinë të llogaritë paratë duan dy gjëra. Ata duan që ne të fshijmë, prandaj teknikisht duhet ta dimë si ta bëjmë këtë.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Ajo që do të tregoj tani është një situatë e përgjithshme, e cila përfshin shumë situata reale, pra një kompilim të asaj që më ka ndodhur me mua dhe me bazat e të dhënave për shumë vite, shumë herë. Ka pengesa kudo dhe të gjithë vazhdojnë të bien mbi to.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Supozoni se kemi një bazë ose disa baza të dhënash që po rriten. Disa regjistrime janë dukshëm të padobishme. Për shembull, përdoruesi ka filluar diçka, por nuk e ka përfunduar. Pas një kohe, ne e dimë se këto të papërfunduara mund të mos ruhen më. Pra, disa sende të padobishme do të doja të pastronim për të kursyer hapësirë dhe për të përmirësuar performancën, etj.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Në thelb, qëllimi është për të automatizuar heqjen e gjërave të caktuara, rreshtave të caktuar në një tabelë.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Dhe kemi një kërkesë të tillë, për të cilën do të flasim sot, pra për heqjen e mbeturinave.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

E kemi kërkuar një zhvillues të përvojshëm që ta bëjë këtë. Ai mori këtë kërkesë, e verifikoi – gjithçka funksionon. E testoi në staging – gjithçka ishte në rregull. E nxorrëm – gjithçka funksionon. Njëherë në ditë e nisim këtë – gjithçka është mirë.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

BD po arrin dhe arrin. DELETE ditor fillon të punojë pak më ngadalë.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Pastaj e kuptojmë se aktualisht kemi një kompani marketingu dhe trafiku do të jetë disa herë më i madh, prandaj vendosim të pezullojmë disa gjëra përkohësisht. Dhe harrojmë t'i kthejmë mbrapsht.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Pas disa muajsh, kujtuam. Dhe ai zhvillues kishte larguar ose ishte i zënë me diçka tjetër, ia besuam një tjetri për ta kthyer.

Ai kontrolloi në dev, në staging – gjithçka është në rregull. Natyrisht, duhet të pastrojmë atë që është grumbulluar. Ai kontrolloi, gjithçka funksionon.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Çfarë ndodh më pas? Më pas gjithçka bie. Bie në një mënyrë që në një moment gjithçka shkon poshtë. Të gjithë janë në shok, askush nuk kupton se çfarë po ndodh. Dhe më pas del që çështja ishte në këtë DELETE.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Çfarë shkoi keq? Këtu është një listë e asaj që mund të ketë shkuar keq. Cili nga këto është më i rëndësishëm?

  • Për shembull, nuk kishte rishikim, domethënë ekspertët DBA nuk e kontrolluan. Ai do të kishte gjetur problemin me një shikim të përvojës, për më tepër ai ka qasje në prodhim, ku janë grumbulluar disa milion rreshta.

  • Mund të ketë pasur një kontroll të gabuar.

  • Mund të ketë ndodhur që hardueri është i vjetër dhe duhet të bëhet një përmirësim për këtë bazë.

  • Ose është diçka e gabuar me bazën e të dhënave, dhe ne duhet të kalojmë nga Postgres në MySQL.

  • Ose, ndoshta, ka ndonjë problem me operacionin.

  • Ndoshta, ka ndonjë gabim në organizimin e punës dhe duhet të shkarkojmë dikë dhe të punsojmë njerëz më të mirë?

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Nuk kishte një kontroll DBA. Sikur të kishte një DBA, ai do ta kishte parë atë disa milion rreshta dhe madje pa eksperimente do thoshte: "Kështu nuk bëhet". Supozoni se nëse ky kod do të ishte në GitLab, GitHub dhe do të kishte një proces shqyrtimi të kodit dhe nuk do të ishte e mundur që kjo operacion të kalonte në prodhim pa miratimin e DBA, atëherë ishte e qartë që DBA do të thoshte: "Kështu nuk lejohet".

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Ai do të thoshte se do të keni probleme me disk IO dhe të gjitha proceset do të çmenden, mund të ketë bllokime, gjithashtu do të bllokoni auto-vakuumin për disa minuta, kështu që kjo nuk është e mirë.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

http://bit.ly/nancy-hl2018-2

Gabimi i dytë është se kontrolluam në vendin e gabuar. Pas faktit, ne e pamë që kishte shumë të dhëna të papastërta në prodhim, ndërsa zhvilluesit nuk kishin të dhëna të tilla në këtë bazë, dhe as në staging askush nuk e kishte krijuar këtë papastërti në mënyrë të veçantë. Si rrjedhim, aty kishte 1,000 rreshta që u përpunuan shpejt.

Ne e kuptojmë se testet tona janë të dobëta, dmth, procesi që kemi ndërtuar nuk kap problemet. Nuk kemi kryer një eksperiment të duhur me DB.

Eksperimenti ideal është të bëhet në pajisje të njëjtë. Në pajisje të njëjtë nuk është gjithmonë e mundur ta realizosh këtë, por është shumë e rëndësishme që të jetë një kopje e plotë e bazës së të dhënave. Kjo është ajo që kam predikuar për disa vite. Dhe një vit më parë kam folur për këtë, mund ta shihni në YouTube.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Ndoshta, ne kemi pajisje të këqija? Nëse e shohim, latenca ka shkaktuar një rritje. Ne pamë që aktiviteti ishte 100%. Sigurisht, nëse do të ishim me disqe moderne NVMe, ndoshta do të na ishte shumë më lehtë. Dhe ndoshta nuk do të kishim shpërthyer nga kjo.

Nëse keni në cloud, atëherë përmirësimi bëhet lehtë. Ngremë replikat e reja në pajisje të reja. Switchover. Dhe gjithçka shkon mirë. Mjaft lehtësisht.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

A është e mundur që të prekni disqet më pak? Dhe këtu përmes DBA-së, e hedhim dritë në një temë që quhet përshtatja e checkpoint-it. Bëhet e ditur që nuk është bërë përshtatja e checkpoint-it.

Çfarë është checkpoint? Kjo ndodh në çdo DBMS. Kur të dhënat tuaja në memory ndryshojnë, ato nuk shkruhen menjëherë në disqe. Informacioni se të dhënat janë ndryshuar fillimisht regjistrohet në një jurnal përpara, në write-ahead log. Dhe në një moment, DBMS vendos që është koha të shkarkojë faqe reale në disk, që në rast se ndodhi një dështim, të bëjë më pak REDO. Kjo është si në një lojë. Nëse na vrasin, do të fillojmë lojën nga checkpoint-i i fundit. Dhe çdo DBMS e realizon këtë.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Cilësimet në Postgres janë të prapambetura. Ato janë projektuar për volumet e të dhënave dhe operacioneve para 10-15 vitesh. Dhe checkpoint nuk është përjashtim.

Kjo informacion është nga raporti ynë të kontrolleve Postgres, dmth, kontrolli automatik i shëndetit. Dhe kjo është një bazë të dhënash disa terabajt nga ana tjetër. Dhe duket qartë se checkpoint-et e detyrueshme ndodhin në të thuajse 90% të rasteve.

Çfarë do të thotë kjo? Aty janë dy cilësime. Checkpoint mund të ndodhë përmes një kohe të caktuar, për shembull, në 10 minuta. Ose mund të ndodhë kur ka mbushur një numër të konsiderueshëm të dhënash.

Dhe me të zakonshmet max_wal_size është vendosur në 1 gigabajt. Në fakt, kjo ndodh realisht në Postgres pas 300-400 megabaytëve. Ju keni ndërruar kaq shumë të dhëna dhe keni një checkpoint.

Nëse askush nuk e ka modifikuar këtë dhe shërbimi është rritur ndërsa kompania fiton shumë para dhe ka shumë transaksione, atëherë checkpoint ndodh çdo minutë, ndonjëherë edhe çdo 30 sekonda, e madje ndonjëherë ato ndërthuren. Kjo është shumë keq.

Dhe na nevojitet që të ndodhë më rrallë. Kjo do të thotë se mund të rrisim max_wal_size. Dhe ai do të ndodhë më rrallë.

Por ne kemi zhvilluar një metodologji për këtë, domethënë si të marrim vendime për zgjedhjen e cilësimeve, duke u mbështetur qartë në të dhëna konkrete.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Për pasojë, ne bëjmë dy seri eksperimesh mbi bazat e të dhënave.

Seria e parë – ne ndryshojmë max_wal_size. Dhe kryejmë një operacion masiv. Fillimisht e bëjmë atë me cilësimin default prej 1 gigabajt. Dhe bëjmë një DELETE masiv të shumë miliona rreshtave.

E shohim se na është vështirë. Shikojmë që disk IO është shumë i keq. Shikojmë sa WAL kemi gjeneruar, sepse kjo është shumë e rëndësishme. Shikojmë sa herë ka ndodhur checkpoint. Dhe shohim që nuk është mirë.

Më pas, ne rrisim max_wal_size. E përsërisim. Rrisim, përsërisim. Dhe kështu shumë herë. Në parim, 10 pikë është mirë, ku 1, 2, 4, 8 gigabajt. Dhe shikojmë sjelljen e sistemit specifik. Është e qartë se këtu pajisja duhet të jetë si në prodhimin. Ju duhet të keni të njëjtat disqe, aq sa vende, dhe cilësimet e Postgres duhet të jenë të njëjta.

Dhe kështu do të shkëmbejmë sistemin tonë dhe do të dimë se si do të sillej DBMS gjatë një DELETE të keq në masë dhe si do të bëjë checkpoint.

Checkpoint në shqip – janë piketat e kontrollit.

Shembuj: DELETE disa milionë rreshta sipas indeksit, rreshtat janë ‘të shpërndara’ nëpër faqet.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Ja një shembull. Kjo është një bazë e caktuar. Dhe me konfigurimin e paracaktuar në 1 gigabajt për max_wal_size, është shumë e qartë se disqet tona për shkrim po shkojnë në pllakë. Ky është një simptomë tipike e një pacienti shumë të sëmurë, dmth. kishte vërtet probleme. Dhe këtu ishte një operacion i vetëm, aty kishte pikërisht DELETE të disa milionëve rreshta.

Nëse një operacion të tillë e lanë në prodhim, atëherë ne do të bjerë, sepse është e qartë se një DELETE na vret në pllakë.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Dhe, ku 16 gigabajt, duket se tashmë kemi filluar të shohim disa prekje. Prekthet janë një përmirësim, pra po godasim plafonin, por tashmë nuk është aq keq. Ka një lirinë të vogël që është shfaqur atje. Nga e djathta – kjo është regjistrimi. Dhe numri i operacioneve – grafiku i dytë. Është e qartë se po frymojmë pak më lehtë tani që kemi 16 gigabajt.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Aty ku kemi 64 gigabajt, është qartë se gjërat janë përmirësuar ndjeshëm. Tani prekthet janë shumë të theksuara, shfaqen më shumë mundësi për operacione të tjera dhe për të bërë diçka me disku.

Pse kështu?

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Do të përqendrohem pak në detaje, por kjo temë, si të kryejmë tuning të checkpoint-it, mund të shndërrohet në një prezantim të tërë, prandaj nuk do të ngarkoj shumë, por do të theksoj disi se çfarë vështirësish ka.

Nëse checkpoint-i ndodh shumë shpesh, dhe ne përditësojmë rreshtat tanë jo në një mënyrë të rregullt, por i gjejmë sipas indeksit, kjo është mirë, sepse nuk e fshijmë tërë tabelën, mund të ndodhë që fillimisht prekim faqen e parë, pastaj atë të mijtë, e më pas kthehemi përsëri në të parën. Dhe nëse midis këtyre hyrjeve në faqen e parë checkpoint-i e ka ruajtur atë në disk, ai do ta ruajë përsëri, sepse ne e prekim atë për herë të dytë.

Dhe ne do ta detyrojmë checkpoint-in ta ruajë atë shumë herë. Kështu, për të ndodhin operacione të tepruar.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Por kjo nuk është e gjitha. Në Postgres, faqet peshojnë 8 kilobajtë, ndërsa në Linux 4 kilobajtë. Dhe ka një konfigurim për full_page_writes. Në parazgjedhje, ajo është e aktivizuar. Dhe kjo është e drejtë, sepse, nëse e çaktivizojmë, ka rrezik që gjatë një dështimi të ruhet vetëm gjysma e faqes.

Shtypja në WAL (journali përpara) ka një sjellje që, kur ndodh checkpoint dhe ne e ndryshojmë faqen për herë të parë, të gjithë faqja shkruhet në journal, dmth. të gjithë 8 kilobajtë, edhe pse ne ndryshuam vetëm një rresht, i cili peshon 100 byte. Dhe jemi të detyruar ta shkruajmë të gjithë faqen.

Në ndryshimet e mëtejshme do të ketë vetëm një tuple specifik, por për herë të parë shkruajmë të gjithë.

Dhe, për rrjedhojë, nëse ndodh përsëri një checkpoint, ne duhet të fillojmë sërish nga fillimi dhe të dërgojmë të gjithë faqen. Me checkpoints të shpeshta, kur ne kalojmë përmes të njëjtave faqe, full_page_writes = on do të jetë më shumë se sa do të mund të ishte, dmth. ne gjenerojmë më shumë WAL. Më shumë dërgohet në replika, në arkiv, në disk.

Dhe, për rrjedhojë, ne kemi dy teprica që ndodhin.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Nëse rrisim max_wal_size, do të thotë që po lehtësojmë punën si për checkpoint ashtu edhe për wal writer. Dhe kjo është e shkëlqyer.

Le të vendosim një terabajt dhe të jetojmë me këtë. Çfarë ka të keqe në këtë? Kjo është keq, sepse në rast dështimi do të zgjohemi për orë të tëra, sepse checkpoint-i ishte kohë më parë dhe tashmë shumë gjëra kanë ndryshuar. Dhe për të gjitha këto duhet të bëjmë REDO. Prandaj po bëjmë një seri të dytë eksperimentesh.

Ne kryejmë një operacion dhe shikojmë, kur checkpoint-i është afër përfundimit, e bëjmë kill -9 Postgres qëllimisht.

Dhe pas kësaj e nisim atë përsëri, dhe shikojmë sa gjatë do të zgjohet në këtë pajisje, që do të thotë sa shumë REDO do të bëjë në këtë situatë të keqe.

Do ta theksoj dy herë se situata është e keqe. Së pari, ne u rrëzuam pikërisht para përfundimit të checkpoint-it, prandaj duhet të humbim shumë. Dhe së dyti, kishim një operacion masiv. Dhe po të ishin checkpoint-et me kohë të përcaktuar, ka gjasa që do të ishte gjeneruar më pak WAL nga momenti i fundit të checkpoint-it. Pra, kjo është dyfish dështim.

Ne analizojmë një situatë të tillë për madhësi të ndryshme max_wal_size dhe kuptojmë se, nëse max_wal_size është 64 gigabajt, në situatën më të keqe të dyfishtë do të kemi 10 minuta kohë ngritjeje. Dhe mendojmë – a na pëlqen kjo apo jo. Kjo është një çështje biznesi. Ne duhet të tregojmë këtë pamje atyre që janë përgjegjës për vendimet biznesore dhe të pyesim: "Sa mund të rrijë maksimumi në rast problemi? A mund të rrijë në situatën më të keqe për 3-5 minuta?" Dhe merrni një vendim.

Dhe këtu ka një moment interesant. Në konferencën tonë ka disa prezantime mbi Patroni. Dhe ndoshta e përdorni atë. Kjo është autofailover për Postgres. GitLab dhe Data Egret e kanë folur për këtë.

Dhe nëse keni autofailover, i cili ndodh pas 30 sekondash, ndoshta mund të rrijmë edhe 10 minuta? Sepse në atë kohë do të kalojmë në replikë, dhe gjithçka do të jetë në rregull. Kjo është një çështje e diskutueshme. Nuk e di një përgjigje të qartë. Ndihem vetëm se kjo temë nuk është vetëm rreth rikuperimit pas dështimit.

Nëse kemi rikuperim të gjatë pas dështimit, do të na ndodhë të mos jemi të rehatshëm në shumë situata të tjera. Për shembull, në eksperimentet e njëjta, kur bëjmë diçka dhe jemi të detyruar ndonjëherë të presim për 10 minuta.

Megjithatë, nuk do të shkoja shumë larg, edhe nëse kemi autofailover. Zakonisht, vlera si 64, 100 gigabajt janë vlera të mira. Ndonjëherë është e arsyeshme të zgjedhim më pak. Në përgjithësi, kjo është një shkencë delikate.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Për të bërë iteracione, për shembull, max_wal_size = 1, 8, duhet të përsërisni operacionin masiv shumë herë. E keni bërë atë. Dhe në atë bazë dëshironi ta bëni përsëri, por ju keni fshirë gjithçka. Çfarë të bëni?

Do t'ju tregoj më vonë për zgjidhjen tonë dhe se çfarë bëjmë për të iteruar në situata të tilla. Ky është qasja më e saktë.

Por në këtë rast kemi pasur fat. Nëse, siç shkruhet këtu, "BEGIN, DELETE, ROLLBACK", atëherë mund ta përsërisim DELETE. Domethënë, nëse e kemi anuluar vetë, atëherë mund ta përsërisim. Dhe fizikisht, të dhënat do të jenë aty. Nuk do të ketë asnjë bloat. Mund të iteroni në DELETE të tillë.

DELETE i tillë me ROLLBACK është ideal për tuning-un e checkpoint-it, edhe nëse nuk keni një database labs të vendosur siç duhet.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Kemi bërë një tabelë me një kolonë "i". Postgres ka kolona shërbimi. Ato janë të padukshme nëse nuk kërkohen posaçërisht. Këto janë: ctid, xmid, xmax.

Ctid është adresa fizike. Faqja zero, për të parin tuple në faqe.

Duket se pas ROOLBACK, rreshti mbeti në të njëjtin vend. Kështu që mund ta provojmë sërish, do të veprojë njësoj. Kjo është thelbësore.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Xmax është koha e vdekjes së rreshtit. Ai është vendosur, por Postgres e di që kjo transaksion është rikuperuar, prandaj, sa dhe 0 dhe transaksioni i rikuperuar – nuk ka rëndësi. Kjo tregon se mund të iterojmë mbi DELETE dhe të kontrollojmë sjelljen e sistemit në operacione masive. Mund të bëjmë laboratore database për të varfrit.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Kjo tashmë është për programuesit. Po ashtu, për DBA-të, ata gjithmonë i qortojnë programuesit: «Pse bëni operacione të tilla të gjata dhe të rënda?». Kjo është një temë krejt tjetër përpendikulare. Disa herë kishte administrim, tani do të kemi zhvillim.

Është e qartë se ne nuk e kemi ndarë në pjesë. Kjo është e kuptueshme. Nuk mund të bëjmë një DELETE të tillë për miliona rreshta pa e ndarë në pjesë. Do të zgjaste 20 minuta, dhe gjithçka do të ngecë. Por, fatkeqësisht, gabime ndodhin edhe nga zhvilluesit e përvojshkë, madje edhe në kompani shumë të mëdha.

Pse është e rëndësishme të ndahet?

  • Nëse e shohim se disku është i ngarkuar, atëherë le të ngadalësojmë. Dhe nëse kemi ndarë, mund të shtojmë pauza, mund të ngadalësojmë throttling.

  • Dhe ne nuk do të bllokojmë të tjerët për një kohë të gjatë. Në disa raste, kjo nuk ka rëndësi; nëse po fshini mbeturina reale, me të cilat askush nuk punon, përveç se punës autovacuum, sepse ai do të presë që transaksioni të përfundojë. Por nëse po fshini diçka që dikush mund ta kërkojë përsëri, atëherë ata do të bllokohen dhe do të ndodhi ndonjë efekt zinxhir. Në faqet e internetit dhe në aplikacionet mobile, duhet të shmangni transaksionet e gjata.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

https://postgres.ai/products/joe/

Kjo është interesante. Shumë shpesh has në zhvillues që pyesin: "Cila të jetë madhësia e paketës?".

Kuptohet, sa më e madhe të jetë madhësia e paketës, aq më pak është ngarkesa e transaksionit, pra shpenzimet shtesë nga transaksionet. Por në të njëjtën kohë, koha për këtë transaksion rritet.

Kam një rregull shumë të thjeshtë: merrni sa më shumë të jetë e mundur, por mos e kaloni ekzekutimin për sekondë.

Pse një sekondë? Shpjegimi është shumë i thjeshtë dhe i kuptueshëm për të gjithë, madje edhe për ata që nuk janë teknikë. Ne shohim reagimin. Le të marim 50 milisekonda. Nëse diçka ka ndryshuar, syri ynë reagon. Nëse është më pak, bëhet më e vështirë. Nëse diçka përgjigjet pas 100 milisekondash, për shembull, kur e klikoni me miun dhe ju përgjigjet pas 100 milisekondash, tashmë e ndieni atë vonesë të vogël. Një sekondë perceptohet si vonesë.

Për rrjedhojë, nëse e ndajmë operacionin tonë me masa në grupe prej 10 sekondash, kemi rreziqe që të bllokojmë dikë. Ai do të funksionojë për disa sekonda dhe kjo do të vihet re nga njerëzit. Prandaj, preferoj të mos bëj më shumë se një sekondë. Por njëkohësisht, nuk do të doja ta ndaja në grupe shumë të vogla, sepse do të ishte e dukshme ngarkesa e transaksionit. Baza do të kishte vështirësi, mund të shfaqen edhe probleme të tjera të ndryshme.

Ne po përshtatim madhësinë e grupit. Në secilin rast mund ta bëjmë ndryshe. Mund ta automatizojmë. Dhe sigurohemi për efikasitetin e përpunimit të një grupi. Të themi, bëjmë DELETE për një grup ose UPDATE.

Dhe, për ta thënë këtë, gjithçka që po tregoj është jo vetëm për DELETE. Siç mund ta keni kuptuar, është për çdo operacion masiv mbi të dhënat.

Dhe ne e shohim se plani është i shkëlqyer. Dallohet indeksi i skanimit, madje edhe skanimi i vetëm i indeksit është më i mirë. Dhe kemi një sasi të vogël të dhënash të angazhuara. Dhe gjithçka përfundon në pak më pak se një sekondë. Super.

Dhe ne gjithashtu duhet të sigurohemi që nuk ka degradim. Ka raste kur paketat e para përfundimisht shpejt, dhe pastaj gjithçka bëhet gjithnjë e më keq. Procesi është i tillë që kërkon shumë testime. Për këtë ashtu siç është e nevojshme laboratori i bazës së të dhënave.

Dhe ne gjithashtu duhet të përgatisim diçka që do të na lejojë të monitorojmë këtë siç duhet në prodhim. Për shembull, mund të shkruajmë kohën në log, mund të shkruajmë se ku jemi tani dhe kë kemi fshirë tani. Dhe kjo do të na ndihmojë të kuptojmë se çfarë po ndodh. Dhe në rast se ndodh diçka keq, ta gjejmë shpejt atë problem.

Nëse na nevojitet të kontrollojmë efikasitetin e pyetjeve dhe na nevojitet të iterojmë shumë herë, atëherë ka një gjë si shoku bot. Ai është gati. Përdoret nga dhjetëra zhvillues çdo ditë. Ai ka një bazë të dhënash të madhe një terabajt që mund ta ofrojë për kërkesë brenda 30 sekondash, kopjen tuaj personale. Dhe ju mund të hiqni ndonjë gjë atje dhe të thoni RESET, dhe të hiqni përsëri. Mund ta eksperimentoni në këtë mënyrë. Unë shoh një të ardhme për këtë. Dhe ne e bëjmë tashmë këtë.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

https://docs.gitlab.com/ee/development/background_migrations.html

Cilat janë strategjitë për ndarjen? Unë shoh 3 strategji të ndryshme ndarjeje që përdorin zhvilluesit në grup.

E para është shumë e thjeshtë. Ne kemi një ID numerik. Dhe le të ndajmë në intervale të ndryshme dhe të punojmë me këtë. Disavantazhi është i qartë. Në segmentin e parë, ne mund të kemi 100 rreshta realisht të pavlefshëm, në të dytin 5 rreshta ose mund edhe të mos kemi asnjë rresht, ose të gjithë 1,000 rreshta mund të jenë të pavlefshëm. Është një punë shumë e paekuilibruar, por është e lehtë të ndahen. E mora ID më të madhe dhe e ndava. Ky është një qasje naive.

Stratégjia e dytë është një qasje e balancuar. Ajo përdoret në Gitlab. Kemi marrë dhe skanuar tabelën. Kemi zbuluar kufijtë e grupeve të ID-së në mënyrë që secila grup të ketë saktësisht 10,000 regjistra. Dhe i vendosëm në një radhë. Pastaj vazhdojmë përpunimin. Kjo mund të bëhet në disa disa, të dhëna.

Në strategjinë e parë, gjithashtu, mund ta bëjmë këtë në disa disa. Nuk është e vështirë.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

https://medium.com/@samokhvalov/how-partial-indexes-affect-update-performance-in-postgres-d05e0052abc

Por ka një qasje më të mirë dhe më optimale. Kjo është strategjia e tretë. Kur është e mundur, është më mirë ta zgjedhim atë. Ne e bëjmë këtë mbi një indeks të veçantë. Në këtë rast, do të jetë, për sa duket, indeksi sipas kushteve tona të mbeturinave dhe ID-së. Ne do të përfshijmë ID-në, në mënyrë që të jetë një skan i vetëm i indeksit, për të mos shkuar në heap.

Si rregull, skanimi i vetëm i indeksit është më i shpejtë se skanimi i indeksit.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Dhe ne shpejt e gjejmë ID-të që duam t'i heqim. BATCH_SIZE e zgjedhim paraprakisht. Dhe jo vetëm i marrim, por i marrim në një mënyrë speciale dhe menjëherë i bllokojmë. Por e bllokojmë në atë mënyrë që, nëse ata tashmë janë të bllokuar, ne nuk i bllokojmë dhe kalojmë më tej për të marrë të tjerët. Kjo është për azhurnimin që anashkalon të bllokuarit. Kjo superfuqi e Postgres na lejon të punojmë në disa rrjedha, nëse duam. Mund të punojmë në një rrjedhë. Dhe këtu kemi CTE - ky është një kërkesë. Dhe në katin e dytë të këtij CTE ndodh me të vërtetë heqja - kthimi *. Mund të kthehet id, por është më mirë *, nëse keni pak të dhëna në çdo rresht.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Përse na nevojitet kjo? Na duhet për të raportuar. Ne tani kemi hequr kaq shumë rreshta në fakt. Dhe kufijtë tanë sipas ID-së ose sipas created_at janë të tilla. Mund të bëhet min, max. Mund të bëhet edhe diçka tjetër. Këtu mund të përfshihet shumë. Dhe kjo është shumë e dobishme për monitorim.

Për indeksin ka një vërejtje tjetër. Nëse ne kemi vendosur që na nevojitet një indeks i veçantë pikërisht për këtë detyrë, duhet të sigurohemi që ai nuk do të prishet deri te azhurnimet e vetme të heap. Kështu, në Postgres ka një statistikë të tillë. Mund ta shihni atë në pg_stat_user_tables për tabelën tuaj. Mund të shihni nëse përdoren azhurnime të nxehta apo jo.

Ka situata kur indeksi juaj i ri mund ta ngadalësojë thjesht procesin. Dhe të gjitha azhurnimet e tjera që tashmë funksionojnë do të ngadalësohen. Jo thjesht sepse është shtuar indeksi (çdo indeks e ngadalëson pak azhurnimin, por pak), por këtu ai gjithashtu do ta dëmtojë. Dhe nuk është e mundur të bëhet një optimizim i veçantë për këtë tabelë. Kjo ndodh herë pas here. Është një hollësi që pak njerëz e mbajnë mend. Dhe është e lehtë të biesh në këtë kurth. Ndonjëherë ndodh që duhet të gjejmë një qasje tjetër dhe të arrijmë të shmangim këtë indeks të ri, ose të krijojmë një indeks tjetër, ose ndryshe, për shembull, mund të përdorim metodën e dytë.

Por kjo është strategjia më optimale, si të ndani në grupe dhe të godisni me një kërkesë në grupe, të fshini pak nga pak, etj.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Transaksione të gjata — https://gitlab.com/snippets/1890447

Autovacuum i bllokuar — https://gitlab.com/snippets/1889668

Çështje bllokuese — https://gitlab.com/snippets/1890428

Gabim numër 5 është i madh. Nikolai nga Okmeter foli për monitorimin e Postgres. Fatkeqësisht, nuk ekziston monitorim ideal i Postgres-it. Disa janë më afër, disa më larg. Okmeter është mjaft afër për t'u konsideruar ideal, por ka shumë gjëra që i mungojnë dhe duhet të shtohen. Këtë duhet ta keni parasysh.

Për shembull, është më mirë të monitoroni tuples të vdekur. Nëse keni shumë të tilla në tabelë, diçka nuk është në rregull. Është më mirë të reagoni tani, përndryshe mund të ketë degradim, dhe mund të rrëzohemi. Kjo ndodh.

Nëse ka IO të madh, është e qartë që kjo nuk është mirë.

Transaksionet e gjata gjithashtu. Në OLTP, nuk duhet lejuar transaksione të gjata. Dhe këtu është një lidhje për një snippet, i cili lejon që të merret ky snippet dhe të bëhet ndjekje e disa transaksioneve të gjata.

Pse janë të këqija transaksionet e gjata? Sepse të gjitha lokat do të lëshohen vetëm në fund. Dhe ne bllokojmë të gjithë. Për më tepër, bllokojmë punën e autovacuum për të gjitha tabelat. Kjo nuk është aspak e mirë. Edhe nëse në replikë keni hot standby të aktivizuar, kjo është prapë e keqe. Në përgjithësi, nuk duhet lejuar kurrë transaksione të gjata.

Nëse kemi shumë tabela të cilat nuk po vakuumohen, atëherë duhet të kemi një alert. Këtu mund të ketë një situatë të tillë. Ne mund të ndikojmë në funksionimin e autovacuum. Ky është një snippet nga Avito, i cili e përmirësova pak. Dhe doli një instrument interesant për të parë se si qëndrojnë gjërat me autovacuum. Për shembull, ka disa tabela që janë duke pritur, por nuk po e presin radhën e tyre. Kjo gjithashtu duhet futur në monitorim dhe të kemi një alert.

Dhe blloqet e çështjeve. Pylli i pemëve të bllokimeve. Më pëlqen të marr diçka nga dikush dhe ta përmirësoj. Këtu kam marrë një CTE rekursiv të shkëlqyer nga Data Egret, i cili tregon pyllin e pemëve të bllokimeve. Kjo është një gjë e mirë për diagnostikimin. Dhe mbi të mund të ndërtohet gjithashtu monitorimi. Por kjo duhet bërë me kujdes. Duhet të vendosim një statement_timeout të vogël për veten tonë. Dhe padyshim që lock_timeout është i preferueshëm.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Ndonjëherë, të gjitha këto gabime ndodhin në përmbledhje.

Sipas mendimit tim, gabimi më i rëndësishëm këtu është organizativ. Është organizativ, sepse teknologjia nuk mbulon. Kjo është numri 2 – u kontrollua në vendin e gabuar.

Ne u kontrolluam në vendin e gabuar, sepse nuk kishim një klon të production-it, në të cilin ishte e lehtë të kontrollonim. Një zhvillues madje mund të mos ketë akses në production.

Dhe ne e kontrolluam në vendin e gabuar. Po të kishim kontrolluar aty, do ta kishim parë vetë. Zhvilluesi do ta kishin vërejtur gjithçka edhe pa DBA, nëse e kishte kontrolluar atë në një ambient të mirë, ku ka të dhëna të njëjta dhe vendosje identike. Ai do të kishte parë gjithë këtë degradim dhe do t'i ishte turp.

Një fjalë edhe për avtokombinimin. Pasi bëmë një pastrim masiv të disa miliona rreshtave, duhet bërë edhe REPACK. Kjo është veçanërisht e rëndësishme për indeksat. Ata do të ndihen keq pasi ne ta pastrojmë gjithçka aty.

Dhe nëse dëshironi të riktheni punën e përditshme të pastrimit, unë do të sugjeroja ta bëni këtë më shpesh, por në doza të vogla. Mund të bëhet çdo minutë ose më shpesh pak nga pak. Dhe është e nevojshme të vendosni monitorimin e dy gjërave: që nuk ka gabime në këtë punë dhe se ajo nuk ngec. Ai truku që kam treguar do ta zgjidhë këtë.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Ajo që ne bëjmë është open source. Kjo është e vendosur në GitLab. Dhe ne bëjmë që njerëzit të mund të kontrollojnë edhe pa DBA. Ne bëjmë një laboratori për databazat, pra ky është komponenti bazë mbi të cilin aktualisht punon Joe. Dhe ju mund të merrni një kopje të prodhimit. Tani ka një implementim të Joe për slack, ku mund të thoni: «shpjego këtë kërkesë» dhe menjëherë merrni rezultatin për kopjen tuaj të databazës. Aty mund të bëni madje edhe DELETE dhe askush nuk do ta vërejë.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Supozoni se keni 10 terabajt, ne bëjmë laboratorin për databazat gjithashtu 10 terabajt. Dhe me databaza 10 terabajt që punojnë në të njëjtën kohë, mund të punojnë 10 zhvillues njëkohësisht. Çdo njëri mund të bëjë atë që dëshiron. Mund të fshijë, të lëshojë dhe të tjera. Kjo është një fantastikë. Për këtë do të flasim nesër.

I dashur DELETE. Nikolai Samohvalov (Postgres.ai)

Kjo quhet thin provisioning. Kjo është një provizionim i hollë. Kjo është një fantastikë që redukton ndjeshëm vonesat në zhvillim, në testim dhe e bën botën më të mirë. Pra, sapo ju lejon të shmangni problemet me operacionet masive.

Shembull: një databazë prej 5 terabajt, marrja e një kopjeje për më pak se 30 sekonda. Dhe kjo nuk varet as nga madhësia, pra nuk ka rëndësi sa terabajt.

Sot mund të hyni në Postgres.ai dhe të shqyrtoni mjetet tona. Mund të regjistroheni, të shikoni se çfarë ka atje. Mund ta vendosni këtë bot, është falas. Shkruani.

Pyetje

Shpesh në situata reale ndodh që të dhënat që duhet të mbeten në tabelë janë shumë më pak se ato që duhet të fshihen. Pra, në një situatë të tillë shpesh është më e lehtë të përdorësh një qasje të tillë, kur është më e thjeshtë të krijosh një objekt të ri, të kopjosh atje vetëm të dhënat e nevojshme dhe të përfundosh tabelën e vjetër. Është e qartë se është e nevojshme një qasje programore për këtë moment, derisa të ndodhë kalimi. Si është kjo qasje?

Kjo është një qasje shumë e mirë dhe një detyrë shumë e mirë. Ajo është shumë e ngjashme me atë që bën pg_repack, është shumë e ngjashme me atë që ju duhet të bëni kur keni bërë ID-të me 4 byte. Shumë framework-e e bënë këtë disa vjet më parë, dhe pikërisht tabelat u rritën dhe duhet t'i konvertoni në 8 byte.

Ky është një detyrë mjaft e vështirë. Ne e kemi bërë këtë. Dhe duhet të jeni shumë të kujdesshëm. Aty ka shkallë etj. Por është e realizueshme. Pra, qasja standarde është si në pg_repack. Ju shpallni një tabelë të tillë. Dhe para se të filloni të derdhni të dhënat në të nga snapshoti, ju gjithashtu shpallni një tabelë tjetër, e cila ndjek të gjitha ndryshimet. Ka një hile, që disa ndryshime mund të mos i ndjekim fare. Ka nuanca. Pas kësaj, ju kaloni duke aplikuar ndryshimet. Do të ketë një pauzë të shkurtër kur të gjithë do të jetë të bllokuar, por në përgjithësi, kjo realizohet.

Nëse shqyrtoni pg_repack në GitHub, kur ishte detyra për të konvertuar ID-në nga int 4 në int 8, kishte një ide për të përdorur pg_repack vetë. Kjo është gjithashtu e mundur, por është një metodë paksa haker, por është e përshtatshme për këtë qëllim. Ju mund të ndërhyni në trigger-in që përdor pg_repack dhe atje të thoni: 'Ne nuk kemi nevojë për këto të dhëna', që do të thotë se ne derdhim vetëm atë që na nevojitet. Dhe pastaj ai thjesht do të kalojë dhe mbaroi.

Me këtë qasje, ne gjithashtu marrim një kopje të dytë të tabelës, ku të dhënat tashmë janë të indeksuara dhe të rregulluara shumë bukur me indekse të bukura.

Bloat jo, është një qasje e mirë. Por e di që ka përpjekje për të zhvilluar automatizimin për këtë, dmth të bëjnë një zgjidhje universale. Mund t'ju lidh me këtë automatizim. Është shkruar në Python, nje gjë e mirë.

Unë vetëm pak e njoh botën e MySQL, prandaj erdha të dëgjoj. Dhe ne përdorim një qasje të tillë.

Por ai është vetëm nëse kemi 90%. Nëse kemi 5%, atëherë nuk është shumë e mirë për ta aplikuar.

Faleminderit për prezantimin! Nëse s'ka burime për të bërë një kopje të plotë të prodhimit, a ka ndonjë algoritëm apo formulë për të llogaritur ngarkesën ose madhësinë?

Pyetje e mirë. Deri tani kemi arritur të gjejmë baza shumë terabajtësh. Edhe nëse hardware-i nuk është i njëjtë, për shembull, më pak memorie, më pak procesor dhe disqet nuk janë krejtësisht të njëjta, ne akoma e bëjmë këtë. Nëse nuk ka mundësi, duhet të mendojmë. Le të mendoj deri nesër, ju erdhët, do të bisedojmë, kjo është një pyetje e mirë.

Faleminderit për raportin! Filluat me faktin se ekziston një Postgres fantastik, i cili ka disa kufizime, por vazhdon të zhvillohet. Megjithatë, këto janë vetëm zgjidhje të përkohshme. A është e mundur që kjo të jetë në kundërshtim me zhvillimin e vetë Postgres, ku do të shfaqen ndonje DELETE deferent ose diçka tjetër që duhet të mbështesë në nivelin e ulët atë që po përpiqemi ta zgjidhim me mjete të çuditshme të krijuara prej nesh?

Nëse ne themi në SQL se duam të fshijmë ose të përditësojmë shumë regjistra në një transaksion, si mund ta shpërndajë Postgres këtë? Ne jemi fizikisht të kufizuar në operacione. Ajo çfarë do të bëjmë është ende e ngadaltë. Dhe në këtë kohë do të jemi në bllokim etj.

Kjo është bërë me indeksat.

Mund të supozoj se tuning i checkpoint-it të njëjtë mund të automatizohet. Ndoshta një ditë do të ndodhë. Por atëherë nuk e kuptoj shumë pyetjen.

Pyetja është nëse ka ndonjë drejtim të tillë zhvillimi që po ndodh atje, ndërsa këtu vazhdon paralel zhvillimi juaj? Domethënë, a nuk po mendojnë për këtë akoma?

Unë flas për parimet që mund të përdoren tani. Ka një bot tjetër Nancy, me këtë mund të bëni tuning automatizuar të checkpoint. A do të jetë ndonjëherë kjo në Postgres? Nuk e di, kjo nuk po diskutohet akoma. Ne jemi akoma larg kësaj. Por ka shkencëtarë që po krijojnë sisteme të reja. Dhe ata na fusin në indekset automatike. Ka zhvillime. Për shembull, mund të shikoni auto tuning. Ai përcakton parametrat automatikisht. Por ai nuk do t'ju bëjë tuning të checkpoint për momentin. Këtu, ai do të përcaktojë për performance, shell buffer etj.

Dhe për tuning të checkpoint mund të bëni një gjë të tillë: nëse keni mijëra klasterë dhe harduerë të ndryshëm, makina virtuale të ndryshme në cloud, mund të përdorni botin tonë Nancy për të bërë automatizimin. Dhe do të përcaktohet max_wal_size sipas konfigurimeve tuaja objektivisht automatikisht. Por për momentin, kjo nuk është as në kernel, për fat të keq.

Mirë se vini! Flisnit për dëmshmërinë e transaksioneve të gjata. Flisnit se autovacuum bllokohet në rastet e fshirjeve. Çfarë tjetër na dëmton kjo? Sepse ne flasim më shumë për çlirimin e hapësirës dhe mundësinë për ta përdorur atë. Çfarë tjetër po humbim?

Autovacuum – ndoshta nuk është problemi më i madh këtu. Problemi është se një transaksion i gjatë mund të bllokojë transaksionet e tjera, dhe kjo është më e rrezikshme. Ajo mund të ndodhi, por mund edhe të mos ndodhë. Nëse ndodh, mund të jetë shumë keq. Madje, autovacuum gjithashtu përbën një problem. Ka dy probleme me transaksionet e gjata në OLTP: bllokimet dhe autovacuum. Dhe nëse keni aktivizuar feedback-in e hot standby në replikë, atëherë do të keni edhe bllokimin e autovacuum që do të vijë në master, dhe do të vijë nga replikat. Por, të paktën, atje nuk do të ketë bllokime. Ndërsa këtu do të ketë bllokime. Po flasim për ndryshimet e të dhënave, prandaj bllokimet janë një moment i rëndësishëm këtu. Dhe nëse gjithçka zgjat gjatë, gjithnjë e më shumë transaksione do të bllokohen. Ato mund të bllokojnë të tjera. Dhe krijohen pemë bllokimi. Unë kam dhënë një lidhje për snippet-in. Dhe ky problem bëhet më i dukshëm më shpejt se problemi me autovacuum, i cili mund vetëm të akumulohet.

Faleminderit për raportimin! E filluat raportin tuaj duke thënë që testoni gabim. E vazhduat idenë tuaj se duhet të merrni pajisje të njëjta, me një bazë gjithashtu të tillë. Le të themi se i dhamë zhvilluesit bazën. Dhe ai e përfundoi kërkesën. Dhe duket se gjithçka është mirë. Por ai nuk kontrollon në live, dhe në live, për shembull, kemi ngarkesë prej 60-70%. Edhe nëse e përdorim këtë rregullim, rezultati është jo edhe aq i mirë.

Të kesh në ekip një ekspert dhe të përdorësh ekspertët DBA, që mund të parashikojnë se çfarë do të ndodhë në ngarkesë reale, është e rëndësishme. Kur thjesht kalojmë ndryshimet e pastra, ne shohim një pamje. Por një qasje më e avancuar, kur bëjmë gjithçka një herë, por edhe me ngarkesë të simuluar nga prodhimi, është krejtësisht fantastike. Para kësaj duhet të rritemi. Kjo është për të rritur. Ne e pamë thjesht që çfarë ka dhe ne shikuam gjithashtu – nëse na mjaftojnë burimet. Kjo është një pyetje e mirë.

Kur ne tashmë po bëjmë garbage select dhe kemi, për shembull, një flag të fshirë

Kjo është ajo që autovacuum bën automatikisht në Postgres.

Ah, e bën këtë?

Autovacuum është në të vërtetë garbage collector.

Faleminderit!

Faleminderit për raportin! A ka ndonjë mundësi që të projektojmë menjëherë bazën e të dhënave me ndarjen e tillë, që çdo pleh të ndahen nga tabela kryesore ndonjëherë në anë?

Sigurisht, ekziston.

A është e mundur të sigurohemi, nëse bllokojmë tabelën, që nuk duhet të përdoret?

Sigurisht, ka. Por kjo është një pyetje si ajo e vezës dhe pulës. Nëse të gjithë e dimë se çfarë do të ndodhë në të ardhmen, sigurisht, do të bëjmë gjithçka mrekullisht. Por biznesi ndryshon, aty shfaqen kolona të reja, kërkesa të reja. Dhe pastaj – si befas, duam ta fshijmë këtë. Por kjo është situata perfekte, në jetën reale ndodh gjithmonë, por jo gjithmonë. Megjithatë, në përgjithësi, është një ide e mirë. Thjesht shkurto dhe kaq.

Burimi: habr.com

Bli një hosting të besueshëm për faqet me mbrojtje DDoS, VPS VDS serverë 🔥 Bli një hosting të besueshëm për faqet me mbrojtje DDoS, VPS VDS serverë | ProHoster