Një nga skenarët tipikë në të gjitha aplikacionet që njohim është kërkimi i të dhënave sipas kritereve të caktuara dhe shfaqja e tyre në një format të lehtë për t'u lexuar. Këtu mund të jenë mundësi shtesë për renditje, grupim dhe shfaqje me faqe. Detyra, në parim, duket triviale, por gjatë zgjidhjes së saj, shumë zhvillues bëjnë një sërë gabimesh, të cilat më pas ndikon në performancën. Le të shqyrtojmë ndryshime të ndryshme të kësaj detyre dhe të formulojmë rekomandime për zgjedhjen e zgjidhjes më efikase.

Opsioni i paging #1
Opsioni më i thjeshtë që vjen në mendje është shfaqja me faqe e rezultateve të kërkimit në formën e tij më klasike.

Le të supozojmë se në aplikacion përdoret një bazë të dhënash relacionale. Në këtë rast, për të shfaqur informacionin në këtë formë, do të nevojiten dy kërkesa SQL:
- Merrni rreshtat për faqen aktuale.
- Numëroni numrin total të rreshtave që i përgjigjen kritereve të kërkimit — kjo është e nevojshme për të treguar faqet.
Le të shqyrtojmë kërkesën e parë për shembull në bazën e të dhënave testuese MS SQL për serverin 2016. Për këtë qëllim do të përdorim tabelën Sales.SalesOrderHeader:
SELECT * FROM Sales.SalesOrderHeader
ORDER BY OrderDate DESC
OFFSET 0 ROWS
FETCH NEXT 50 ROWS ONLY
Kërkesa e sipërme do të nxjerrë 50 porositë e para nga lista, të renditura në mënyrë dekreskuese sipas datës së shtimit, domethënë — 50 porositë e fundit.
Ajo ekzekutohet shpejt në bazën e të dhënave testuese, por le të shikojmë planin e ekzekutimit dhe statistikat e hyrje-daljes:

Tabela 'SalesOrderHeader'. Numri i skanimeve 1, lexime logjike 698, lexime fizike 0, lexime përpara 0, lexime logjike lob 0, lexime fizike lob 0, lexime përpara lob 0.Statistikat e hyrje-daljes për çdo kërkesë mund të merren duke ekzekutuar në mjedisin e ekzekutimit të kërkesave komandën SET STATISTICS IO ON.
Siç duket nga plani i ekzekutimit, procesi më frytshkurt është renditja e të gjithë rreshtave të tabelës origjinale sipas datës së shtimit. Problemi është se sa më shumë rreshta të shfaqen në tabelë, aq më 'e rëndë' bëhet renditja. Në praktikë, situata të tilla duhet të shmangen, prandaj le të shtojmë një indeks në datën e shtimit dhe të shohim nëse ka ndryshuar konsumi i burimeve:

Tabela 'SalesOrderHeader'. Numri i skanimeve 1, lexime logjike 165, lexime fizike 0, lexime përpara 5, lexime logjike lob 0, lexime fizike lob 0, lexime përpara lob 0.
Është e qartë se është përmirësuar ndjeshëm. Por a janë zgjidhur të gjitha problemet? Le të ndryshojmë kërkesën për të kërkuar porositë ku vlera e përgjithshme e produkteve kalon 100 dollarë:
SELECT * FROM Sales.SalesOrderHeader
WHERE SubTotal > 100
ORDER BY OrderDate DESC
OFFSET 0 ROWS
FETCH NEXT 50 ROWS ONLY

Tabela 'SalesOrderHeader'. Numri i skanimeve 1, lexime logjike 1081, lexime fizike 0, lexime përpara 0, lexime logjike lob 0, lexime fizike lob 0, lexime përpara lob 0.Kemi një situatë interesante: plani i kërkesës nuk është shumë më i keq se ai i mëparshmi, por numri real i leximeve logjike është pothuajse dy herë më i lartë se gjatë skanimit të plote të tabelës. Ka një zgjidhje — nëse krijojmë një indeks të përbërë dhe të shtojmë si fushë të dytë çmimin total të produkteve, përsëri do të kemi 165 lexime logjike:
CREATE INDEX IX_SalesOrderHeader_OrderDate_SubTotal on Sales.SalesOrderHeader(OrderDate, SubTotal);
Kjo seri shembujsh mund të vazhdojë për një kohë të gjatë, por dy mendimet kryesore që dua të shpreh këtu janë:
- Shtimi i çdo kriteri të ri apo renditjeje në kërkesën e kërkimit mund të ndikojë ndjeshëm në shpejtësinë e ekzekutimit të saj.
- Por nëse na nevojitet të nxjerrim vetëm një pjesë të të dhënave, dhe jo të gjithë rezultatet që përputhen me kriteret e kërkimit — ka shumë mënyra për të optimizuar një kërkesë të tillë.
Tani le të kalojmë në kërkesën e dytë, të përmendur në fillim — atë që numëron rekordet që përmbushin kriterin e kërkimit. Të marrim të njëjtin shembull — kërkimin e porosive që kalojnë 100 dollarë:
SELECT COUNT(1) FROM Sales.SalesOrderHeader
WHERE SubTotal > 100
Me një indeks të përbërë, siç u përmend më sipër, marrim:

Tabela 'SalesOrderHeader'. Numri i skanimeve 1, lexime logjike 698, lexime fizike 0, lexime përpara 0, lexime logjike lob 0, lexime fizike lob 0, lexime përpara lob 0.Që kërkesa kalon të gjithë indeksin nuk është çudi, pasi fusha SubTotal nuk është në pozita të para, prandaj kërkesa nuk mund ta shfrytëzojë atë. Problemi zgjidhet duke shtuar një indeks tjetër në fushën SubTotal, dhe në fund jep vetëm 48 lexime logjike.
Mund të jepen edhe disa shembuj të tjerë të kërkesave për numërimin e sasisë, por thelbi do të mbetet i njëjtë: përftimi i një sasie të dhënash dhe numërimi i numrit total është dy kërkesa esencialisht të ndryshme, dhe secila kërkon masat e saj për optimizim. Në përgjithësi, nuk do të mund të gjejmë një kombinim indeksesh që funksionon po njësoj mirë për të dy kërkesat.
Prandaj, një nga kërkesat e rëndësishme që duhet sqaruar gjatë zhvillimit të një zgjidhjeje të tillë kërkimi është nëse në të vërtetë biznesi ka rëndësi për të parë numrin e përgjithshëm të objekteve të gjetura. Shpesh ndodh që jo. Dhe navigimi për numra të caktuar faqesh, sipas mendimit tim, është një zgjidhje me një aplikacion shumë të ngushtë, pasi shumica e skenarëve me paginim duket si "kalim në faqen tjetër".
Opcioni i paginimit #2
Supozoni që përdoruesve nuk u intereson njohja e numrit të përgjithshëm të objekteve të gjetura. Le të përpiqemi ta thjeshtojmë faqen e kërkimit:

Në fakt, vetëm ka ndryshuar se nuk ka mundësi të kalosh në numra të caktuar faqesh, dhe tani kjo tabelë për shfaqje nuk ka nevojë të dijë se sa të gjitha mund të jenë. Por lind një pyetje — si do ta dijë tabela nëse ka të dhëna për faqen tjetër (për të shfaqur saktë lidhjen "Tjetër")?
Përgjigja është shumë e thjeshtë: mund të lexosh nga baza një regjistër më shumë se sa nevojitet për shfaqje, dhe prania e këtij regjistri "shtesë" do të tregohet për të treguar nëse ka një grup tjetër. Kështu, për të marrë një faqe të dhënash do të duhet të ekzekutosh vetëm një kërkesë, e cila ndjeshëm përmirëson performancën dhe lehtëson mbështetje të tillë të funksionalitetit. Kam pasur në praktikë rastin kur heqja dorë nga numërimi i përgjithshëm të regjistrave e ka përshpejtuar sjelljen e rezultateve me 4-5 herë.
Për këtë qasje ekzistojnë disa variante të ndërfaqes së përdoruesit: komandat "mbrapa" dhe "përpara", siç është shembulli më sipër, butoni "ngarko më shumë", i cili thjesht shton një grup të ri në rezultatet e shfaqura, "rrotullim i pafund", i cili funksionon sipas parimit "ngarko më shumë", por sinjali për marrjen e grupit tjetër është ngjitja nga përdoruesi në fund të të gjitha rezultateve të shfaqura. Si do të ishte zgjidhja vizuale, parimi i seleksionimit të të dhënave mbetet i njëjtë.
Nuancat në realizimin e paginimit
Në të gjithë shembujt e kërkesave të dhëna më lart, përdoret qasja "zhvendosje + numri", kur në vetë kërkesën tregohet nga cila rresht i rezultatit dhe sa rreshta duhet të kthehen. Së pari, le të shohim se si është më mirë të organizohet kalimi i parametrave në këtë rast. Në praktikë kam hasur disa mënyra:
- Numri rendor i faqes së kërkuar (pageIndex), madhësia e faqes (pageSize).
- Numri rendor i regjistrit të parë që duhet të kthehet (startIndex), numri maksimal i regjistrave në rezultat (count).
- Numri rendor i regjistrit të parë që duhet të kthehet (startIndex), numri rendor i regjistrit të fundit që duhet të kthehet (endIndex).
Në dukje mund të duket se është kaq elementar, sa nuk ka ndonjë diferencë. Por nuk është kështu — opsioni më i përshtatshëm dhe universalisht është i dyti (startIndex, count). Kjo ka disa arsye:
- Për qasjen me leximin +1 të regjistrave, e cila përmendet më lart, varianti i parë me pageIndex dhe pageSize është shumë i pakëndshëm. Për shembull, ne duam të shfaqim 50 regjistra në faqe. Sipas algoritmit të përmendur më sipër, duhet të lexojmë një regjistër më shumë se sa duhet. Nëse ky "+1" nuk është parashikuar në server, rezulton se për faqen e parë duhet të kërkojmë regjistra nga 1 në 51, për të dytën — nga 51 në 101 etj. Nëse përcaktojmë madhësinë e faqes 51 dhe rritim pageIndex, faqja e dytë do të kthejë nga 52 në 102 etj. Prandaj, në variantin e parë, mënyra e vetme për të realizuar normalisht butonin e kalimit në faqen tjetër është të parashikojmë në server leximin e "regjistrit të tepërt", që do të ishte një nuancë shumë e paqartë.
- Varianti i tretë nuk ka absolutisht kuptim, pasi për ekzekutimin e kërkesave në shumicën e bazave të të dhënave së bashku duhet të kalosh sasinë dhe jo indeksin e regjistrit të fundit. Le të jetë heqja e startIndex nga endIndex dhe një operacion themelor aritmetik, por është e panevojshme këtu.
Tani duhet të përshkruajmë disavantazhet e realizimit të paginimit përmes "zhvendosjes + numrit":
- Marrja e çdo faqe tjetër do të jetë më e kushtueshme dhe më e ngadaltë se e mëparshmja, pasi baza e të dhënave ende do të duhet të kalojë përmes të gjitha regjistrave nga "fillimi" sipas kritereve të kërkimit dhe renditjes, pas së cilës do të ndalojë në fragmentin e nevojshëm.
- Jo të gjitha DBMS mund ta mbështesin këtë qasje.
Ekzistojnë alternativa, por ato gjithashtu nuk janë ideale. I pari nga këto qasje quhet "keyset paging" ose "seek method" dhe përbëhet nga kjo: pas marrjes së një grupi mund të mbani mend vlerat e fushave në regjistrin e fundit në faqe, dhe më pas t'i përdorni ato për të marrë grupin tjetër. Për shembull, ne kemi ekzekutuar një të tillë kërkesë:
SELECT * FROM Sales.SalesOrderHeader
ORDER BY OrderDate DESC
OFFSET 0 ROWS
FETCH NEXT 50 ROWS ONLY
Dhe në regjistrimin e fundit morëm vlerën e datës së porosisë '2014-06-29'. Atëherë, për të marrë faqen e ardhshme, mund të përpiqemi të kryejmë një të tillë:
SELECT * FROM Sales.SalesOrderHeader
WHERE OrderDate < '2014-06-29'
ORDER BY OrderDate DESC
OFFSET 0 ROWS
FETCH NEXT 50 ROWS ONLY
Problemi është se OrderDate është një fushë jo unike dhe kushti i përmendur më sipër me shumë gjasa do të shpërfillë shumë rreshta të nevojshëm. Për të sjellë qartësi në këtë kërkesë, duhet të shtojmë në kusht një fushë unike (le të supozojmë që 75074 është vlera e fundit e çelësit primar nga grupi i parë):
SELECT * FROM Sales.SalesOrderHeader
WHERE (OrderDate = '2014-06-29' AND SalesOrderID < 75074)
OR (OrderDate < '2014-06-29')
ORDER BY OrderDate DESC, SalesOrderID DESC
OFFSET 0 ROWS
FETCH NEXT 50 ROWS ONLY
Ky variant do të funksionojë në mënyrë korrekte, por në përgjithësi do të jetë e vështirë për ta optimizuar, pasi kushti përmban operatorin OR. Nëse me rritjen e OrderDate rritet vlera e çelësit primar, atëherë kushti mund të thjeshtohet, duke lënë vetëm filtrin për SalesOrderID. Por nëse nuk ka një korrelacion strikte midis vlerave të çelësit primar dhe fushës, sipas së cilës renditet rezultati, në shumicën e DBMS-ve nuk do të është e mundur të shmanget ky OR. Një përjashtim i njohur është PostgreSQL, ku mbështetet plotësisht krahasimi i tuple-ve, dhe kushti i përmendur më lart mund të shkruhet si "WHERE (OrderDate, SalesOrderID) < ('2014-06-29', 75074)". Nëse ka një çelës të përbërë me këto dy fusha, një kërkesë e tillë duhet të jetë mjaft e lehtë.
Një qasje alternative e dytë mund të haset, për shembull, në ose — kur kërkesa përveç të dhënave kthen një identifikues special, me të cilin mund të marrësh grupin e ardhshëm të të dhënave. Nëse ky identifikues ka një afat të pakufizuar (si në Comsos DB), atëherë kjo është një mënyrë e shkëlqyer për të zbatuar paging me kalimin sekondar mes faqeve (varianti #2 i përmendur më lart). Disavantazhet e mundshme: mbështetet shumë pak nga të gjitha DBMS-të; identifikuesi i marrë për grupin e ardhshëm mund të ketë një afat të kufizuar, që në përgjithësi nuk është i përshtatshëm për realizimin e ndërveprimit me përdoruesin (si, për shembull, ElasticSearch scroll API).
Filtrimi i komplikuar
Po e vështirësojmë më tej detyrën. Supozoni se ka një kërkesë për të realizuar atë që quhet kërkesa faceted, e njohur mirë nga dyqanet Online. Shembujt e mësipërm mbi tabelën e porosive nuk janë shumë ilustrues në këtë rast, prandaj do të kalojmë në tabelën Product nga baza AdventureWorks:

Çfarë është ideja e kërkimit faceted? Është, që për çdo element filtri të tregohet numri i regjistrimeve që përputhen me këtë kriter me përjashtim të filtrave të zgjedhur në të gjitha kategoritë e tjera.
Për shembull, nëse në këtë shembull zgjedhim kategorinë Bikes dhe ngjyrën Black, tabela do të tregohet vetëm biçikleta në ngjyrë të zezë, por për këtë:
- Për çdo kriter të grupit "Categories" do të tregohen numri i produkteve nga kjo kategori që janë në ngjyrën e zezë.
- Për çdo kriter të grupit "Colors" do të tregohet numri i biçikletave në këtë ngjyrë.
Ja një shembull i daljes së rezultatit për këto kushte:

Nëse përveç këtij kategorizimi shënojmë kategorinë "Clothing", tabela do të tregojë gjithashtu edhe rrobat në ngjyrë të zezë që janë në dispozicion. Numri i produkteve në ngjyrë të zezë në seksionin "Color" gjithashtu do të rikalkulohet sipas kushteve të reja, vetëm në seksionin "Categories" asgjë nuk do të ndryshojë... Shpresoj që këto shembuj janë të mjaftueshëm për të kuptuar algoritmin e zakonshëm të funksionimit të kërkimit faceted.
Tani imagjinoni se si kjo mund të realizohet në një bazë të dhënash relacionale. Çdo grup kriteresh, siç janë Kategoritë dhe Ngjyra, do të kërkojë një kërkesë të veçantë:
SELECT pc.ProductCategoryID, pc.Name, COUNT(1) FROM Production.Product p
INNER JOIN Production.ProductSubcategory ps ON p.ProductSubcategoryID = ps.ProductSubcategoryID
INNER JOIN Production.ProductCategory pc ON ps.ProductCategoryID = pc.ProductCategoryID
WHERE p.Color = 'Black'
GROUP BY pc.ProductCategoryID, pc.Name
ORDER BY COUNT(1) DESC

SELECT Color, COUNT(1) FROM Production.Product p
INNER JOIN Production.ProductSubcategory ps ON p.ProductSubcategoryID = ps.ProductSubcategoryID
WHERE ps.ProductCategoryID = 1 --Bikes
GROUP BY Color
ORDER BY COUNT(1) DESC

Çfarë nuk shkon me këtë zgjidhje? Shumë thjesht — ajo nuk është e shkallëzuar mirë. Çdo seksion filtri kërkon një kërkesë të veçantë për numërimin e sasive dhe këto kërkesa nuk janë të lehta. Në dyqanet online, në disa kategori mund të ketë edhe disa dhjetëra sekcione filtrimi, që mund të kursejë një problem serioz për performancën.
Zakonsisht pas këtyre pohimeve, më ofrojnë disa zgjidhje, ndryshe:
- Të bashkohen të gjitha numërimet e sasive në një kërkesë. Teknikisht, kjo është e mundur me ndihmën e fjalës kyçe UNION, por kjo nuk do ta ndihmojë shumë në performancë — baza e të dhënave prapë do të duhet të kryejë "nga fillimi" çdo nga fragmentet.
- Memorizimi i numrave. Më propozojnë këtë pothuajse çdo herë kur përshkruaj një problem. Problemi është se kjo është në terma të përgjithshëm e pamundur. Le të supozojmë se kemi 10 "faset", secila me 5 vlera. Kjo është një situatë shumë "modeste" krahasuar me atë që mund të shihni në dyqanet online. Zgjedhja e një elementi në një faset ndikon në numrat në 9 të tjerë; me fjalë të tjera, për çdo kombinim kriteresh, numrat mund të jenë të ndryshëm. Në shembullin tonë, ka gjithsej 50 kritere që përdoruesi mund të zgjedhë, kështu që do të ketë 250 kombinime të mundshme. Nuk ka mjaftueshëm memorie dhe kohë për të mbushur një të tillë masë të dhënash. Të tjerët mund të argumentojnë se jo të gjitha kombinimet janë reale dhe përdoruesi shpesh nuk zgjedh më shumë se 5-10 kritere. Po, mund të implementoni ngarkim të ngadalshëm dhe memorizim të numrave vetëm për ato që janë zgjedhur ndonjëherë, por sa më shumë mundësi zgjedhjeje të ketë, aq më pak efikase do të jetë kjo memorje dhe aq më të dukshme do të jenë problemet me kohën e përgjigjes (sidomos nëse grumbulli i të dhënave ndryshon rregullisht).
Fatmirësisht, një problem i tillë ka zgjidhje të mjaftueshme efektive që funksionojnë parashikueshëm me sasi të mëdha të dhënash. Për çdo një nga këto opsione ka kuptim të ndahen rinovimi i fasetëve dhe marrja e faqes së rezultateve në dy kërkesa paralele ndaj serverit dhe të organizohet ndërfaqja e përdoruesit në një mënyrë që ngarkimi i të dhënave mbi fasetët "nuk pengon" shfaqjen e rezultateve të kërkimit.
- Thirrni rinovimin e plotë të "fasetëve" sa më rrallë që të jetë e mundur. P.sh., mos e rinovoni gjithçka me çdo ndryshim të kritereve të kërkimit, por në vend të kësaj, gjeni numrin total të rezultateve që përputhen me kushtet aktuale dhe ofroni përdoruesit që t'i shfaqë ato — "1425 të dhëna të gjetura, të shfaqim?" Përdoruesi mund të vazhdojë të ndryshojë kushtet e kërkimit ose të klikojë butonin "shfaqim". Vetëm në rastin e dytë do të kryhen të gjitha kërkesat për marrjen e rezultateve dhe rinovimin e numrave në të gjitha "fasetët". Në këtë rast, siç është e dukshme, do të duhet të merren në konsideratë kërkesat për të marrë numrin total të rezultateve dhe optimizimin e tij. Ky metod mund të shihet në shumë dyqane të vogla online. Është e qartë se kjo nuk është një zgjidhje për problemin, por në raste të thjeshta mund të jetë një kompromis i mirë.
- Përdorni motorë kërkimi për të gjetur rezultate dhe numëruar fasetët, si Solr, ElasticSearch, Sphinx dhe të tjerë. Të gjithë ata janë të dizajnuar për ndërtimin e "fasetëve" dhe e bëjnë këtë mjaft efektivisht duke përdorur indeksin e inverzuar. Si funksionojnë motorët e kërkimit, pse janë më efektivë në këto raste krahasuar me bazat e të dhënave të zakonshme, çfarë praktikash dhe pengesash ekzistojnë — kjo është një temë për një artikull të veçantë. Këtu dua të theksoj që motorët e kërkimit nuk mund të zëvendësojnë depozitën kryesore të të dhënave; përdoren si një përforcim: çdo ndryshim në bazën kryesore, i cili ka rëndësi për kërkimin, sinkronizohet në indeksin e kërkimit; mekanizmi i kërkimit ndikon zakonisht vetëm me motorin e kërkimit dhe nuk iu drejtohet bazës kryesore. Një nga piketat më të rëndësishme këtu është se si ta organizoni këtë sinkronizim në një mënyrë të sigurt. Gjithçka varet nga kërkesat për "kohën e reagimit". Nëse koha mes ndryshimit në bazën kryesore dhe "manifestimit" të tij në kërkim nuk është kritike, mund të krijoni një shërbim që çdo disa minuta kërkon regjistrimet e ndryshuara së fundi dhe i indeksin ato. Nëse kërkohet koha më e ulët e mundshme e reagimit, mund të implementoni diçka si për të dërguar përditësime në shërbimin e kërkimit.
Përfundimet
- Implementimi i paginimit në anën e serverit është një ndërlikim i rëndësishëm dhe ka kuptim ta aplikoni vetëm për grumbuj të dhënash që rriten shpejt ose thjesht janë të mëdhenj. Si ta vlerësoni një "të madh" ose "të shpejtë" — nuk ka një recetë të saktë, por do të ndiqja këtë qasje:
- Nëse marrja e koleksionit të plotë të të dhënave, duke marrë parasysh kohën e serverit dhe transmetimin nëpër rrjet, përshtatet normalisht me kërkesat për performancën — nuk ka kuptim të implementoni paginimin në anën e serverit.
- Mund të ndodhë një situatë ku në një të ardhme të afërt nuk parashikohen probleme me performancën, pasi të dhënat janë të pakta, por koleksioni i të dhënave është gjithnjë në rritje. Nëse ndonjë grup të dhënash në perspektivë mund të ndalojë përmbushjen e pikës së mëparshme — është më mirë ta planifikoni paginimin nga fillimi.
- Nëse nga ana e biznesit nuk ka një kërkesë strikte për shfaqjen e numrit total të rezultateve ose për shfaqjen e numrave të faqeve, dhe për më tepër në sistemin tuaj nuk ka motor kërkimi — është më mirë që këto pika të mos implementohen dhe të shqyrtohet opsioni #2.
- Nëse ka një kërkesë të qartë për kërkimin me faceta, keni dy mundësi për të mos sakrifikuar performancën:
- Mos e përllogaritni gjithë numrat për çdo ndryshim të kritereve të kërkimit.
- Përdorni motorë kërkimi si Solr, ElasticSearch, Sphinx dhe të tjerë. Por duhet të kuptoni se ai nuk mund të zëvendësojë bazën e të dhënave kryesore dhe duhet përdorur si një shtesë për depot kryesore për zgjidhjen e detyrave të kërkimit.
- Po ashtu, në rastin e kërkimit me faceta, ka kuptim të ndahen marrja e faqes së rezultateve të kërkimit dhe numërimi i sasisë në dy kërkesa paralele. Numërimi i sasisë mund të marrë më shumë kohë se marrja e rezultateve, ndërsa rezultatet janë më të rëndësishme për përdoruesin.
- Nëse po përdorni një bazë të dhënash SQL për kërkimin, çdo ndryshim të kodit që i përket kësaj pjese duhet të testohet mirë për performancën mbi volumet e dhënash të përshtatshme (duke tejkaluar volumin në bazën 'live'). Po ashtu, është e dëshirueshme të përdoret monitorimi i kohës së ekzekutimit të kërkesave në të gjitha instancat e bazës, dhe sidomos në atë 'live'. Edhe nëse në fazën e zhvillimit me planet e kërkesave gjithçka ishte mirë, me rritjen e volumit të të dhënave situata mund të ndryshojë ndjeshëm.
Burimi: habr.com
