{"id":75530,"date":"2020-03-26T19:42:23","date_gmt":"2020-03-26T17:42:23","guid":{"rendered":"https:\/\/prohoster.info\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu"},"modified":"2020-03-26T19:42:23","modified_gmt":"2020-03-26T17:42:23","slug":"vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu","status":"publish","type":"post","link":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu","title":{"rendered":"Wyniki wyszukiwania i problemy z wydajno\u015bci\u0105","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Jednym z typowych scenariuszy w znanych nam aplikacjach jest wyszukiwanie danych wed\u0142ug okre\u015blonych kryteri\u00f3w i wy\u015bwietlanie ich w czytelnej formie. Mog\u0105 pojawi\u0107 si\u0119 dodatkowe mo\u017cliwo\u015bci sortowania, grupowania czy paginacji. Zadanie z pozoru jest trywialne, jednak wielu programist\u00f3w pope\u0142nia b\u0142\u0119dy, kt\u00f3re wp\u0142ywaj\u0105 na wydajno\u015b\u0107. Spr\u00f3bujmy przyjrze\u0107 si\u0119 r\u00f3\u017cnym rozwi\u0105zaniom tego problemu i sformu\u0142owa\u0107 zalecenia dotycz\u0105ce wyboru najbardziej efektywnej realizacji.<\/p>\n<p><img decoding=\"async\" alt=\"Wyniki wyszukiwania i problemy z wydajno\u015bci\u0105\" src=\"\/wp-content\/uploads\/2020\/03\/364ab29c4a6117ff441b933a38ec8401.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>Opcja paginacji #1<\/h2>\n<p>\nNajprostsza opcja, kt\u00f3ra przychodzi do g\u0142owy, to paginacja wynik\u00f3w wyszukiwania w jej najklasyczniejszej formie.<\/p>\n<p><img decoding=\"async\" alt=\"Wyniki wyszukiwania i problemy z wydajno\u015bci\u0105\" src=\"\/wp-content\/uploads\/2020\/03\/4957d9ad21a8a0c70390b224fe76cb33.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nZa\u0142\u00f3\u017cmy, \u017ce w aplikacji u\u017cywana jest relacyjna baza danych. W takim przypadku, aby wy\u015bwietli\u0107 informacje w takiej formie, nale\u017cy wykona\u0107 dwa zapytania SQL:<\/p>\n<ul>\n<li>Pobierz wiersze dla bie\u017c\u0105cej strony.<\/li>\n<li>Policz ca\u0142kowit\u0105 liczb\u0119 wierszy odpowiadaj\u0105cych kryteriom wyszukiwania \u2014 jest to potrzebne do pokazania stron.<\/li>\n<\/ul>\n<p>\nRozwa\u017cmy pierwsze zapytanie na przyk\u0142adzie testowej bazy MS SQL <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/Microsoft\/sql-server-samples\/releases\/download\/adventureworks\/AdventureWorks2016_EXT.bak\">AdventureWorks <\/a><\/noindex>dla serwera 2016. W tym celu skorzystamy z tabeli Sales.SalesOrderHeader:<\/p>\n<pre><code class=\"sql\">SELECT * FROM Sales.SalesOrderHeader\nORDER BY OrderDate DESC\nOFFSET 0 ROWS\nFETCH NEXT 50 ROWS ONLY\n<\/code><\/pre>\n<p>\nPodane powy\u017cej zapytanie wy\u015bwietli pierwsze 50 zam\u00f3wie\u0144 z listy, posortowane malej\u0105co wed\u0142ug daty dodania, innymi s\u0142owy \u2014 50 ostatnich zam\u00f3wie\u0144.<\/p>\n<p>Zapytanie to wykonuje si\u0119 szybko na testowej bazie, ale przyjrzyjmy si\u0119 planowi wykonania i statystykom wej\u015bcia-wyj\u015bcia:<\/p>\n<p><img decoding=\"async\" alt=\"Wyniki wyszukiwania i problemy z wydajno\u015bci\u0105\" src=\"\/wp-content\/uploads\/2020\/03\/248e4b64593c7000216b46777183d639.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabela 'SalesOrderHeader'. Liczba skan\u00f3w 1, odczyty logiczne 698, odczyty fizyczne 0, odczyty wyprzedzaj\u0105ce 0, odczyty logiczne LOB 0, odczyty fizyczne LOB 0, odczyty wyprzedzaj\u0105ce LOB 0.<\/code><\/pre>\n<p>\n<i>Statystyki wej\u015bcia\/wyj\u015bcia dla ka\u017cdego zapytania mo\u017cna uzyska\u0107, wykonuj\u0105c w \u015brodowisku wykonawczym polecenie SET STATISTICS IO ON.<\/i><\/p>\n<p>Jak wida\u0107 z planu wykonania, najbardziej zasobo\u017cerna jest sortowanie wszystkich wierszy w tabeli \u017ar\u00f3d\u0142owej wed\u0142ug daty dodania. Problem polega na tym, \u017ce im wi\u0119cej wierszy pojawi si\u0119 w tabeli, tym bardziej \u201aci\u0119\u017ckie\u2018 b\u0119dzie sortowanie. W praktyce nale\u017cy unika\u0107 takich sytuacji, dlatego dodamy indeks na dat\u0119 dodania i sprawdzimy, czy zu\u017cycie zasob\u00f3w si\u0119 zmieni\u0142o:<\/p>\n<p><img decoding=\"async\" alt=\"Wyniki wyszukiwania i problemy z wydajno\u015bci\u0105\" src=\"\/wp-content\/uploads\/2020\/03\/a33e1eadf6f8f8b516b9bb834ad7ad86.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabela 'SalesOrderHeader'. Liczba skan\u00f3w 1, odczyty logiczne 165, odczyty fizyczne 0, odczyty wyprzedzaj\u0105ce 5, odczyty logiczne LOB 0, odczyty fizyczne LOB 0, odczyty wyprzedzaj\u0105ce LOB 0.\n<\/code><\/pre>\n<p>\nOczywi\u015bcie, sta\u0142o si\u0119 znacznie lepiej. Ale czy wszystkie problemy zosta\u0142y rozwi\u0105zane? Zmieniamy zapytanie do wyszukiwania zam\u00f3wie\u0144, gdzie \u0142\u0105czna warto\u015b\u0107 towar\u00f3w przekracza 100 dolar\u00f3w:<\/p>\n<pre><code class=\"sql\">SELECT * FROM Sales.SalesOrderHeader\nWHERE SubTotal &gt; 100\nORDER BY OrderDate DESC\nOFFSET 0 ROWS\nFETCH NEXT 50 ROWS ONLY\n<\/code><\/pre>\n<p>\n<img decoding=\"async\" alt=\"Wyniki wyszukiwania i problemy z wydajno\u015bci\u0105\" src=\"\/wp-content\/uploads\/2020\/03\/e057bfc89e11a31c6d2855b93ec3c747.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabela 'SalesOrderHeader'. Liczba skan\u00f3w 1, logiczne odczyty 1081, fizyczne odczyty 0, odczyty z wyprzedzeniem 0, logiczne odczyty LOB 0, fizyczne odczyty LOB 0, odczyty LOB z wyprzedzeniem 0.<\/code><\/pre>\n<p>\nMamy zabawn\u0105 sytuacj\u0119: plan zapytania nieco gorszy od poprzedniego, ale faktyczna liczba logicznych odczyt\u00f3w prawie dwa razy wi\u0119ksza ni\u017c przy pe\u0142nym skanowaniu tabeli. Wyj\u015bciem jest \u2014 je\u015bli z ju\u017c istniej\u0105cego indeksu stworzymy z\u0142o\u017cony, dodaj\u0105c jako drugie pole sumaryczn\u0105 cen\u0119 towar\u00f3w, to zn\u00f3w uzyskamy 165 logicznych odczyt\u00f3w:<\/p>\n<pre><code class=\"sql\">CREATE INDEX IX_SalesOrderHeader_OrderDate_SubTotal on Sales.SalesOrderHeader(OrderDate, SubTotal);\n<\/code><\/pre>\n<p>\nT\u0119 seri\u0119 przyk\u0142ad\u00f3w mo\u017cna kontynuowa\u0107 jeszcze d\u0142ugo, ale dwie g\u0142\u00f3wne my\u015bli, kt\u00f3re chc\u0119 tutaj wyrazi\u0107, s\u0105 nast\u0119puj\u0105ce:<\/p>\n<ul>\n<li>Dodanie jakiegokolwiek nowego kryterium lub porz\u0105dku sortowania do zapytania mo\u017ce znacz\u0105co wp\u0142yn\u0105\u0107 na szybko\u015b\u0107 jego wykonania.<\/li>\n<li>Ale je\u015bli musimy odczyta\u0107 tylko cz\u0119\u015b\u0107 danych, a nie wszystkie wyniki pasuj\u0105ce do warunk\u00f3w wyszukiwania \u2014 istnieje wiele sposob\u00f3w na optymalizacj\u0119 takiego zapytania.<\/li>\n<\/ul>\n<p>\nTeraz przejd\u017amy do drugiego zapytania, wspomnianego na samym pocz\u0105tku \u2014 tego, kt\u00f3re liczy liczb\u0119 rekord\u00f3w spe\u0142niaj\u0105cych kryteria wyszukiwania. We\u017amy ten sam przyk\u0142ad \u2014 wyszukiwanie zam\u00f3wie\u0144, kt\u00f3re kosztuj\u0105 wi\u0119cej ni\u017c 100 dolar\u00f3w:<\/p>\n<pre><code class=\"sql\">SELECT COUNT(1) FROM Sales.SalesOrderHeader\nWHERE SubTotal &gt; 100\n<\/code><\/pre>\n<p>\nPrzy z\u0142o\u017conym indeksie, kt\u00f3ry zosta\u0142 wskazany powy\u017cej, otrzymujemy:<\/p>\n<p><img decoding=\"async\" alt=\"Wyniki wyszukiwania i problemy z wydajno\u015bci\u0105\" src=\"\/wp-content\/uploads\/2020\/03\/7beab0c83a50e2d68a83a965df555ec9.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabela 'SalesOrderHeader'. Liczba skan\u00f3w 1, odczyty logiczne 698, odczyty fizyczne 0, odczyty wyprzedzaj\u0105ce 0, odczyty logiczne LOB 0, odczyty fizyczne LOB 0, odczyty wyprzedzaj\u0105ce LOB 0.<\/code><\/pre>\n<p>\nTo, \u017ce zapytanie przesz\u0142o ca\u0142y indeks w ca\u0142o\u015bci \u2014 nie dziwi, poniewa\u017c pole SubTotal nie jest na pierwszej pozycji, wi\u0119c zapytanie nie mo\u017ce z niego skorzysta\u0107. Problem mo\u017cna rozwi\u0105za\u0107, dodaj\u0105c kolejny indeks na pole SubTotal, co ostatecznie daje ju\u017c tylko 48 logicznych odczyt\u00f3w.<\/p>\n<p>Mo\u017cna poda\u0107 jeszcze kilka przyk\u0142ad\u00f3w zapyta\u0144 o zliczanie, ale istota pozostaje ta sama: <b>uzyskanie porcji danych i zliczenie \u0142\u0105cznej liczby \u2014 to dwa zasadniczo r\u00f3\u017cne zapytania<\/b>, i ka\u017cde wymaga swoich \u015brodk\u00f3w na optymalizacj\u0119. W og\u00f3lnym przypadku nie uda si\u0119 znale\u017a\u0107 kombinacji indeks\u00f3w, kt\u00f3ra r\u00f3wnie dobrze dzia\u0142a dla obu zapyta\u0144.<\/p>\n<p>W zwi\u0105zku z tym, jednym z wa\u017cnych wymaga\u0144, kt\u00f3re nale\u017cy wyja\u015bni\u0107 podczas opracowywania takiego rozwi\u0105zania wyszukiwania, jest to, czy rzeczywi\u015bcie biznesowi zale\u017cy na tym, aby zobaczy\u0107 ca\u0142kowit\u0105 liczb\u0119 znalezionych obiekt\u00f3w. Cz\u0119sto tak nie jest. A nawigacja po konkretnych numerach stron, moim zdaniem, jest rozwi\u0105zaniem o bardzo w\u0105skim zakresie zastosowania, poniewa\u017c wi\u0119kszo\u015b\u0107 scenariuszy z paginacj\u0105 wygl\u0105da jak 'przejd\u017a do nast\u0119pnej strony'.<\/p>\n<h2>Opcja paginacji #2<\/h2>\n<p>\nPrzypu\u015b\u0107my, \u017ce u\u017cytkownikom nie zale\u017cy na wiedzy o ca\u0142kowitej liczbie znalezionych obiekt\u00f3w. Spr\u00f3bujmy upro\u015bci\u0107 stron\u0119 wynik\u00f3w wyszukiwania:<\/p>\n<p><img decoding=\"async\" alt=\"Wyniki wyszukiwania i problemy z wydajno\u015bci\u0105\" src=\"\/wp-content\/uploads\/2020\/03\/66f5ee6dc1a52f14e55ae31e05c502b2.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nW rzeczywisto\u015bci zmieni\u0142o si\u0119 tylko to, \u017ce nie ma mo\u017cliwo\u015bci przechodzenia do konkretnych numer\u00f3w stron, a teraz ta tabela do wy\u015bwietlania nie musi zna\u0107, ile ich mo\u017ce by\u0107. Pojawia si\u0119 jednak pytanie \u2014 jak tabela dowiaduje si\u0119, czy s\u0105 dane dla nast\u0119pnej strony (aby poprawnie wy\u015bwietli\u0107 link 'Nast\u0119pna')?<\/p>\n<p>Odpowied\u017a jest bardzo prosta: mo\u017cna odczyta\u0107 z bazy o jeden rekord wi\u0119cej, ni\u017c potrzebne do wy\u015bwietlenia, a obecno\u015b\u0107 tego 'dodatkowego' rekordu b\u0119dzie wskazywa\u0107, czy jest nast\u0119pna porcja. W ten spos\u00f3b, aby uzyska\u0107 jedn\u0105 stron\u0119 danych, trzeba b\u0119dzie wykona\u0107 tylko jedno zapytanie, co znacznie poprawia wydajno\u015b\u0107 i u\u0142atwia wsparcie takiej funkcjonalno\u015bci. Mia\u0142em na praktyce przypadek, kiedy rezygnacja z obliczania ca\u0142kowitej liczby rekord\u00f3w przyspieszy\u0142a wydanie wynik\u00f3w 4-5 razy.<\/p>\n<p>Dla tego podej\u015bcia istnieje kilka opcji interfejsu u\u017cytkownika: przyciski 'cofnij' i 'dalej', jak w powy\u017cszym przyk\u0142adzie, przycisk 'za\u0142aduj wi\u0119cej', kt\u00f3ry po prostu dodaje now\u0105 porcj\u0119 do wy\u015bwietlanych wynik\u00f3w, 'niesko\u0144czone przewijanie', kt\u00f3re dzia\u0142a na zasadzie 'za\u0142aduj wi\u0119cej', ale sygna\u0142em do pobrania nast\u0119pnej porcji jest przewini\u0119cie przez u\u017cytkownika wszystkich wy\u015bwietlonych wynik\u00f3w do ko\u0144ca. Niezale\u017cnie od wizualnego rozwi\u0105zania, zasada pobierania danych pozostaje taka sama.<\/p>\n<h2>Aspekty realizacji paginacji<\/h2>\n<p>\nWe wszystkich przyk\u0142adach zapyta\u0144 podanych powy\u017cej zastosowano podej\u015bcie 'offset + limit', kiedy w samym zapytaniu wskazuje si\u0119, od kt\u00f3rego wiersza wyniku oraz ile wierszy nale\u017cy zwr\u00f3ci\u0107. Najpierw rozwa\u017cmy, jak najlepiej zorganizowa\u0107 przekazywanie parametr\u00f3w w tym przypadku. W praktyce spotka\u0142em kilka sposob\u00f3w:<\/p>\n<ul>\n<li>Numer porz\u0105dku \u017c\u0105danej strony (pageIndex), rozmiar strony (pageSize).<\/li>\n<li>Numer porz\u0105dku pierwszego rekordu, kt\u00f3ry ma by\u0107 zwr\u00f3cony (startIndex), maksymalna liczba rekord\u00f3w w wyniku (count).<\/li>\n<li>Numer porz\u0105dku pierwszego rekordu, kt\u00f3ry ma by\u0107 zwr\u00f3cony (startIndex), numer porz\u0105dku ostatniego rekordu, kt\u00f3ry ma by\u0107 zwr\u00f3cony (endIndex).<\/li>\n<\/ul>\n<p>\nNa pierwszy rzut oka mo\u017ce si\u0119 wydawa\u0107, \u017ce to tak elementarne, \u017ce nie ma wi\u0119kszej r\u00f3\u017cnicy. Ale nie jest to prawda \u2014 najbardziej wygodn\u0105 i uniwersaln\u0105 opcj\u0105 jest druga (startIndex, count). Istnieje kilka powod\u00f3w:<\/p>\n<ul>\n<li>Podej\u015bcie z odczytem +1 rekordu, o kt\u00f3rym mowa powy\u017cej, w przypadku pierwszej opcji z pageIndex i pageSize jest niezwykle niewygodne. Na przyk\u0142ad chcemy wy\u015bwietli\u0107 50 rekord\u00f3w na stronie. Zgodnie z powy\u017cszym algorytmem, musimy odczyta\u0107 jeden rekord wi\u0119cej ni\u017c to konieczne. Je\u015bli ten \u201e+1\u201d nie jest oddany na serwerze, okazuje si\u0119, \u017ce dla pierwszej strony musimy \u017c\u0105da\u0107 rekord\u00f3w od 1 do 51, dla drugiej \u2014 od 51 do 101, itd. Je\u015bli wska\u017anik rozmiaru strony wynosi 51 i zwi\u0119kszamy pageIndex, to druga strona zwr\u00f3ci od 52 do 102 itd. W zwi\u0105zku z tym jedynym sposobem sensownego zrealizowania przycisku przej\u015bcia do nast\u0119pnej strony w pierwszej opcji jest zaimplementowanie na serwerze odczytu \u201enadmiarowego\u201d wiersza, co b\u0119dzie bardzo niejasnym niuansem.<\/li>\n<li>Trzecia opcja w og\u00f3le nie ma sensu, poniewa\u017c do wykonania zapyta\u0144 w wi\u0119kszo\u015bci baz danych i tak trzeba b\u0119dzie przekaza\u0107 liczb\u0119, a nie indeks ostatniego rekordu. Niech odejmowanie startIndex od endIndex b\u0119dzie elementarn\u0105 operacj\u0105 arytmetyczn\u0105, ale jest tutaj zb\u0119dne.<\/li>\n<\/ul>\n<p>\nTeraz nale\u017cy opisa\u0107 wady realizacji paginacji poprzez \u201eprzesuni\u0119cie + ilo\u015b\u0107\u201d:<\/p>\n<ul>\n<li>Uzyskiwanie ka\u017cdej kolejnej strony b\u0119dzie bardziej kosztowne i wolniejsze ni\u017c poprzedniej, poniewa\u017c baza danych i tak musi przej\u015b\u0107 przez wszystkie rekordy \u201eod pocz\u0105tku\u201d zgodnie z kryteriami wyszukiwania i sortowania, po czym zatrzyma\u0107 si\u0119 na odpowiednim fragmencie.<\/li>\n<li>Nie wszystkie SGBD mog\u0105 wspiera\u0107 to podej\u015bcie.<\/li>\n<\/ul>\n<p>\nAlternatywy istniej\u0105, ale r\u00f3wnie\u017c nie s\u0105 idealne. Pierwsze z tych podej\u015b\u0107 nazywa si\u0119 \u201epaginacja przez klucz\u201d lub \u201emetoda seek\u201d i polega na tym, \u017ce po uzyskaniu partii mo\u017cna zapami\u0119tywa\u0107 warto\u015bci p\u00f3l w ostatnim rekordzie na stronie, a nast\u0119pnie u\u017cywa\u0107 ich do uzyskania nast\u0119pnej partii. Na przyk\u0142ad wykonywali\u015bmy takie zapytanie:<\/p>\n<pre><code class=\"sql\">SELECT * FROM Sales.SalesOrderHeader\nORDER BY OrderDate DESC\nOFFSET 0 ROWS\nFETCH NEXT 50 ROWS ONLY\n<\/code><\/pre>\n<p>\nW ostatnim wpisie otrzymano warto\u015b\u0107 daty zam\u00f3wienia '2014-06-29'. W takim razie, aby uzyska\u0107 nast\u0119pn\u0105 stron\u0119, mo\u017cna spr\u00f3bowa\u0107 wykona\u0107 nast\u0119puj\u0105ce zapytanie:<\/p>\n<pre><code class=\"sql\">SELECT * FROM Sales.SalesOrderHeader\nWHERE OrderDate &lt; &#039;2014-06-29&#039;\nORDER BY OrderDate DESC\nOFFSET 0 ROWS\nFETCH NEXT 50 ROWS ONLY\n<\/code><\/pre>\n<p>\nProblem polega na tym, \u017ce OrderDate nie jest unikalnym polem, a wspomniane powy\u017cej warunki prawdopodobnie pomijaj\u0105 wiele potrzebnych wierszy. Aby wprowadzi\u0107 jednoznaczno\u015b\u0107 w to zapytanie, trzeba doda\u0107 unikalne pole (przyjmijmy, \u017ce 75074 to ostatnia warto\u015b\u0107 klucza podstawowego z pierwszej partii):<\/p>\n<pre><code class=\"sql\">SELECT * FROM Sales.SalesOrderHeader\nWHERE (OrderDate = '2014-06-29' AND SalesOrderID &lt; 75074)\n   OR (OrderDate &lt; &#039;2014-06-29&#039;)\nORDER BY OrderDate DESC, SalesOrderID DESC\nOFFSET 0 ROWS\nFETCH NEXT 50 ROWS ONLY\n<\/code><\/pre>\n<p>\nTa opcja b\u0119dzie dzia\u0142a\u0107 poprawnie, ale og\u00f3lnie b\u0119dzie trudno j\u0105 zoptymalizowa\u0107, poniewa\u017c warunek zawiera operator OR. Je\u015bli wraz ze wzrostem OrderDate ro\u015bnie warto\u015b\u0107 klucza podstawowego, warunek mo\u017cna upro\u015bci\u0107, zostawiaj\u0105c tylko filtr na SalesOrderID. Jednak je\u015bli mi\u0119dzy warto\u015bciami klucza podstawowego a polem, wed\u0142ug kt\u00f3rego posortowano wyniki, nie ma \u015bcis\u0142ej korelacji \u2014 w wi\u0119kszo\u015bci system\u00f3w baz danych unikni\u0119cie tego OR nie b\u0119dzie mo\u017cliwe. Znanym wyj\u0105tkiem jest PostgreSQL, kt\u00f3ry w pe\u0142ni wspiera por\u00f3wnanie krotek, a powy\u017cszy warunek mo\u017cna zapisa\u0107 jako \u201eWHERE (OrderDate, SalesOrderID) &lt; (&#039;2014-06-29&#039;, 75074)\u201d. Posiadaj\u0105c z\u0142o\u017cony klucz z tymi dwoma polami, takie zapytanie powinno by\u0107 wystarczaj\u0105co proste.<\/p>\n<p>Drugie alternatywne podej\u015bcie mo\u017cna spotka\u0107 na przyk\u0142ad w <noindex><a rel=\"nofollow\" href=\"https:\/\/www.elastic.co\/guide\/en\/elasticsearch\/reference\/6.8\/search-request-scroll.html\">ElasticSearch scroll API<\/a><\/noindex> lub <noindex><a rel=\"nofollow\" href=\"https:\/\/medium.com\/@gary.strange\/understanding-cosmosdb-continuation-tokens-hasmoreresults-and-connectionpolicy-requesttimeouts-3ed1fadfa81d\">Cosmos DB<\/a><\/noindex> \u2014 kiedy zapytanie opr\u00f3cz danych zwraca specjalny identyfikator, za pomoc\u0105 kt\u00f3rego mo\u017cna uzyska\u0107 nast\u0119pn\u0105 parti\u0119 danych. Je\u015bli ten identyfikator ma nieograniczony czas \u017cycia (jak w Comsos DB), to jest to doskona\u0142y spos\u00f3b na realizacj\u0119 paginacji z sekwencyjnym przechodzeniem mi\u0119dzy stronami (wariant #2 wspomniany powy\u017cej). Mo\u017cliwe wady: nie jest wspierane w wielu systemach DBMS; uzyskany identyfikator nast\u0119pnej partii mo\u017ce mie\u0107 ograniczony czas \u017cycia, co w og\u00f3lnym przypadku nie nadaje si\u0119 do realizacji interakcji z u\u017cytkownikami (jak na przyk\u0142ad ElasticSearch scroll API).<\/p>\n<h2>Skomplikowana filtracja<\/h2>\n<p>\nZwi\u0119kszamy trudno\u015b\u0107. Za\u0142\u00f3\u017cmy, \u017ce pojawi\u0142o si\u0119 wymaganie wdro\u017cenia tzw. wyszukiwania z u\u017cyciem faceted search, dobrze znanego ze sklep\u00f3w internetowych. Powy\u017csze przyk\u0142ady oparte na tabeli zam\u00f3wie\u0144 nie s\u0105 w tym przypadku najbardziej reprezentatywne, dlatego przejd\u017amy do tabeli Product z bazy AdventureWorks:<\/p>\n<p><img decoding=\"async\" alt=\"Wyniki wyszukiwania i problemy z wydajno\u015bci\u0105\" src=\"\/wp-content\/uploads\/2020\/03\/070890a3cb79de3edc69b60b5300efe7.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nNa czym polega idea faceted search? Polega ona na tym, \u017ce dla ka\u017cdego elementu filtru wy\u015bwietlana jest liczba rekord\u00f3w odpowiadaj\u0105cych temu kryterium. <i>z uwzgl\u0119dnieniem filtr\u00f3w wybranych w pozosta\u0142ych kategoriach.<\/i>.<\/p>\n<p>Na przyk\u0142ad, je\u015bli w tym przypadku wybierzemy kategori\u0119 Bikes i kolor Black, tabela wy\u015bwietli tylko czarne rowery, ale:<\/p>\n<ul>\n<li>Dla ka\u017cdego kryterium grupy \u201eCategories\u201d poka\u017ce liczb\u0119 produkt\u00f3w w tej czarnej kategorii.<\/li>\n<li>Dla ka\u017cdego kryterium grupy \u201eColors\u201d poka\u017ce liczb\u0119 rower\u00f3w w tym kolorze.<\/li>\n<\/ul>\n<p>\nOto przyk\u0142ad wynik\u00f3w dla takich warunk\u00f3w:<\/p>\n<p><img decoding=\"async\" alt=\"Wyniki wyszukiwania i problemy z wydajno\u015bci\u0105\" src=\"\/wp-content\/uploads\/2020\/03\/7c8ec4d8b427f1f59f0129afebe74b08.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nJe\u017celi dodatkowo zaznaczymy kategori\u0119 \u201eClothing\u201d, tabela poka\u017ce r\u00f3wnie\u017c czarne ubrania dost\u0119pne w sprzeda\u017cy. Liczba produkt\u00f3w czarnego koloru w sekcji \u201eColor\u201d r\u00f3wnie\u017c zostanie przeliczona zgodnie z nowymi warunkami, tylko w sekcji \u201eCategories\u201d nic si\u0119 nie zmieni\u2026 Mam nadziej\u0119, \u017ce tych przyk\u0142ad\u00f3w wystarczy, aby zrozumie\u0107 typowy algorytm dzia\u0142ania faceted search.<\/p>\n<p>Teraz wyobra\u017amy sobie, jak to mo\u017cna zrealizowa\u0107 w bazie danych relacyjnej. Ka\u017cda grupa kryteri\u00f3w, taka jak Kategoria i Kolor, b\u0119dzie wymaga\u0142a oddzielnego zapytania:<\/p>\n<pre><code class=\"sql\">SELECT pc.ProductCategoryID, pc.Name, COUNT(1) FROM Production.Product p\n  INNER JOIN Production.ProductSubcategory ps ON p.ProductSubcategoryID = ps.ProductSubcategoryID\n  INNER JOIN Production.ProductCategory pc ON ps.ProductCategoryID = pc.ProductCategoryID\nWHERE p.Color = 'Black'\nGROUP BY pc.ProductCategoryID, pc.Name\nORDER BY COUNT(1) DESC\n<\/code><\/pre>\n<p>\n<img decoding=\"async\" alt=\"Wyniki wyszukiwania i problemy z wydajno\u015bci\u0105\" src=\"\/wp-content\/uploads\/2020\/03\/3b59b68ce934c4ec1630ec649e68ccdb.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"sql\">SELECT Color, COUNT(1) FROM Production.Product p\n  INNER JOIN Production.ProductSubcategory ps ON p.ProductSubcategoryID = ps.ProductSubcategoryID\nWHERE ps.ProductCategoryID = 1 --Bikes\nGROUP BY Color\nORDER BY COUNT(1) DESC\n<\/code><\/pre>\n<p>\n<img decoding=\"async\" alt=\"Wyniki wyszukiwania i problemy z wydajno\u015bci\u0105\" src=\"\/wp-content\/uploads\/2020\/03\/82e1e6ab0fae683d383e3fecf4e3a15a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nCo jest nie tak z tym rozwi\u0105zaniem? Jest to bardzo proste \u2014 s\u0142abo si\u0119 skaluj\u0105. Ka\u017cda sekcja filtra wymaga oddzielnego zapytania do zliczenia ilo\u015bci, a te zapytania s\u0105 do\u015b\u0107 ci\u0119\u017ckie. W sklepach internetowych w niekt\u00f3rych kategoriach mo\u017ce by\u0107 nawet kilka dziesi\u0105tek sekcji filtr\u00f3w, co mo\u017ce stanowi\u0107 powa\u017cny problem dla wydajno\u015bci.<\/p>\n<p>Zwykle po tych stwierdzeniach przedstawiane s\u0105 mi pewne rozwi\u0105zania, a mianowicie:<\/p>\n<ul>\n<li>Po\u0142\u0105czy\u0107 wszystkie zliczenia w jednym zapytaniu. Technicznie jest to mo\u017cliwe za pomoc\u0105 s\u0142owa kluczowego UNION, ale nie przyczyni si\u0119 to znacz\u0105co do poprawy wydajno\u015bci \u2014 baza danych nadal b\u0119dzie musia\u0142a wykona\u0107 \u201eod pocz\u0105tku\u201d ka\u017cdy z fragment\u00f3w.<\/li>\n<li>Kategoryzowa\u0107 ilo\u015bci. To jest mi proponowane praktycznie za ka\u017cdym razem, gdy opisuj\u0119 problem. Niestety, w og\u00f3lnym przypadku jest to niemo\u017cliwe. Za\u0142\u00f3\u017cmy, \u017ce mamy 10 \u201efaset\u00f3w\u201d, z kt\u00f3rych ka\u017cdy ma 5 warto\u015bci. To bardzo \u201eskromna\u201d sytuacja, w por\u00f3wnaniu do tego, co mo\u017cna zobaczy\u0107 w tych samych sklepach internetowych. Wyb\u00f3r jednego elementu fasetu wp\u0142ywa na ilo\u015bci w 9 innych, innymi s\u0142owy, dla ka\u017cdej kombinacji kryteri\u00f3w ilo\u015bci mog\u0105 by\u0107 r\u00f3\u017cne. \u0141\u0105cznie w naszym przyk\u0142adzie mamy 50 kryteri\u00f3w, kt\u00f3re u\u017cytkownik mo\u017ce wybra\u0107, co oznacza, \u017ce mo\u017cliwych kombinacji b\u0119dzie 250. Na wype\u0142nienie takiej masy danych nie wystarczy ani pami\u0119ci, ani czasu. Mo\u017cna tu wtr\u0105ci\u0107, \u017ce nie wszystkie kombinacje s\u0105 realne i u\u017cytkownik rzadko wybiera wi\u0119cej ni\u017c 5-10 kryteri\u00f3w. Tak, mo\u017cna zrobi\u0107 leniwe \u0142adowanie i kategoryzowanie ilo\u015bci tylko dla tego, co kiedykolwiek zosta\u0142o wybrane, ale im wi\u0119cej b\u0119dzie opcji wyboru, tym mniej efektywny b\u0119dzie taki cache i tym bardziej zauwa\u017calne b\u0119d\u0105 problemy z czasem reakcji (zw\u0142aszcza je\u015bli zbi\u00f3r danych regularnie si\u0119 zmienia).<\/li>\n<\/ul>\n<p>\nNa szcz\u0119\u015bcie podobne zadanie ma ju\u017c od dawna wystarczaj\u0105co efektywne rozwi\u0105zania, kt\u00f3re przewidywalnie dzia\u0142aj\u0105 na du\u017cych zbiorach danych. Dla ka\u017cdej z tych opcji ma sens podzieli\u0107 przeliczenie faset i pobieranie strony wynik\u00f3w na dwa r\u00f3wnoleg\u0142e zapytania do serwera i zorganizowa\u0107 interfejs u\u017cytkownika w taki spos\u00f3b, aby \u0142adowanie danych z faset \u201enie przeszkadza\u0142o\u201d w wy\u015bwietlaniu wynik\u00f3w wyszukiwania.<\/p>\n<ul>\n<li>Wywo\u0142uj pe\u0142ne przeliczenie \u00abfaset\u00f3w\u00bb tak rzadko, jak to mo\u017cliwe. Na przyk\u0142ad, nie przeliczaj wszystkiego przy ka\u017cdej zmianie kryteri\u00f3w wyszukiwania, zamiast tego znajd\u017a \u0142\u0105czn\u0105 liczb\u0119 wynik\u00f3w odpowiadaj\u0105cych obecnym warunkom i zaproponuj u\u017cytkownikowi ich wy\u015bwietlenie \u2014 \u00abznaleziono 1425 wpis\u00f3w, wy\u015bwietli\u0107?\u00bb U\u017cytkownik mo\u017ce kontynuowa\u0107 zmian\u0119 warunk\u00f3w wyszukiwania lub nacisn\u0105\u0107 przycisk \u00abwy\u015bwietli\u0107\u00bb. Tylko w drugim przypadku zostan\u0105 wykonane wszystkie zapytania o uzyskanie wynik\u00f3w i przeliczenie ilo\u015bci na wszystkich \u00abfasetach\u00bb. Nale\u017cy zauwa\u017cy\u0107, \u017ce trzeba b\u0119dzie zaj\u0105\u0107 si\u0119 zapytaniem o uzyskanie \u0142\u0105cznej liczby wynik\u00f3w i jego optymalizacj\u0105. Takie podej\u015bcie mo\u017cna spotka\u0107 w wielu ma\u0142ych sklepach internetowych. Oczywiste jest, \u017ce to nie panaceum na ten problem, ale w prostych przypadkach mo\u017ce by\u0107 niez\u0142ym kompromisem.<\/li>\n<li>U\u017cywaj silnik\u00f3w wyszukiwania do wyszukiwania wynik\u00f3w i liczenia faset\u00f3w, takich jak Solr, ElasticSearch, Sphinx i inne. Wszystkie s\u0105 zaprojektowane do budowania \u00abfaset\u00f3w\u00bb i robi\u0105 to do\u015b\u0107 efektywnie dzi\u0119ki odwr\u00f3conemu indeksowi. Jak dzia\u0142aj\u0105 systemy wyszukiwania, dlaczego w takich przypadkach s\u0105 skuteczniejsze ni\u017c bazy danych og\u00f3lnego przeznaczenia, jakie s\u0105 praktyki i pu\u0142apki \u2014 to temat na osobny artyku\u0142. Chcia\u0142bym jednak zwr\u00f3ci\u0107 uwag\u0119, \u017ce silnik wyszukiwania nie mo\u017ce zast\u0105pi\u0107 g\u0142\u00f3wnego repozytorium danych; jest u\u017cywany jako dodatek: wszelkie zmiany w g\u0142\u00f3wnej bazie, maj\u0105ce znaczenie dla wyszukiwania, s\u0105 synchronizowane z indeksem wyszukiwania; mechanizm wyszukiwania zazwyczaj wsp\u00f3\u0142dzia\u0142a tylko z silnikiem wyszukiwania i nie odnosi si\u0119 do g\u0142\u00f3wnej bazy. Jednym z najwa\u017cniejszych punkt\u00f3w jest tutaj, jak zorganizowa\u0107 t\u0119 synchronizacj\u0119 niezawodnie. Wszystko zale\u017cy od wymaga\u0144 dotycz\u0105cych \u00abczasu reakcji\u00bb. Je\u015bli czas mi\u0119dzy zmian\u0105 w g\u0142\u00f3wnej bazie a jej \u00abmanifestacj\u0105\u00bb w wyszukiwaniu nie jest krytyczny, mo\u017cna stworzy\u0107 serwis, kt\u00f3ry co kilka minut przeszukuje niedawno zmienione wpisy i je indeksuje. Je\u015bli wymagana jest minimalna mo\u017cliwa reakcja, mo\u017cna wdro\u017cy\u0107 co\u015b w rodzaju <noindex><a rel=\"nofollow\" href=\"https:\/\/microservices.io\/patterns\/data\/transactional-outbox.html\">transactional outbox<\/a><\/noindex> do wysy\u0142ania aktualizacji do serwisu wyszukiwania.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Wnioski<\/h2>\n<p><\/p>\n<ol>\n<li>Realizacja paginacji po stronie serwera to powa\u017cne skomplikowanie, a jej zastosowanie ma sens tylko dla szybko rosn\u0105cych lub po prostu du\u017cych zbior\u00f3w danych. Jak oceni\u0107, co oznacza \u00bbdu\u017cy\u00ab lub \u00bbszybko rosn\u0105cy\u00ab \u2014 nie ma absolutnie dok\u0142adnej recepty, ale trzyma\u0142bym si\u0119 takiego podej\u015bcia:\n<ul>\n<li>Je\u015bli uzyskanie pe\u0142nej kolekcji danych z uwzgl\u0119dnieniem czasu serwera i transferu sieciowego mie\u015bci si\u0119 w wymaganiach dotycz\u0105cych wydajno\u015bci \u2014 nie ma sensu wdra\u017ca\u0107 paginacji po stronie serwera.<\/li>\n<li>Mo\u017ce zdarzy\u0107 si\u0119 sytuacja, \u017ce przez najbli\u017cszy czas nie przewiduje si\u0119 problem\u00f3w z wydajno\u015bci\u0105, poniewa\u017c danych jest ma\u0142o, ale kolekcja danych stale ro\u015bnie. Je\u015bli jaki\u015b zbi\u00f3r danych w przysz\u0142o\u015bci mo\u017ce przesta\u0107 spe\u0142nia\u0107 powy\u017cszy punkt \u2014 lepiej od razu za\u0142o\u017cy\u0107 paginacj\u0119.<\/li>\n<\/ul>\n<\/li>\n<li>Je\u015bli ze strony biznesu nie ma sztywnego wymogu dotycz\u0105cego pokazania og\u00f3lnej liczby wynik\u00f3w lub wy\u015bwietlania numer\u00f3w stron, a w Twoim systemie nie ma silnika wyszukiwania \u2014 lepiej tych kwestii nie wdra\u017ca\u0107 i rozwa\u017cy\u0107 wariant nr 2.<\/li>\n<li>Je\u015bli istnieje wyra\u017ane wymaganie dotycz\u0105ce wyszukiwania fasetowego, masz dwa sposoby, aby nie po\u015bwi\u0119ca\u0107 wydajno\u015bci:\n<ul>\n<li>Nie przelicza\u0107 wszystkich ilo\u015bci przy ka\u017cdej zmianie kryteri\u00f3w wyszukiwania.<\/li>\n<li>U\u017cywa\u0107 silnik\u00f3w wyszukiwania, takich jak Solr, ElasticSearch, Sphinx i inne. Nale\u017cy jednak pami\u0119ta\u0107, \u017ce nie mog\u0105 one zast\u0105pi\u0107 g\u0142\u00f3wnej bazy danych i powinny by\u0107 u\u017cywane jako uzupe\u0142nienie g\u0142\u00f3wnego repozytorium do rozwi\u0105zywania zada\u0144 wyszukiwania. <\/li>\n<\/ul>\n<\/li>\n<li>R\u00f3wnie\u017c w przypadku wyszukiwania fasetowego sensowne jest rozdzielenie uzyskiwania strony wynik\u00f3w wyszukiwania i liczenia ilo\u015bci na dwa r\u00f3wnoleg\u0142e zapytania. Liczenie ilo\u015bci mo\u017ce zaj\u0105\u0107 wi\u0119cej czasu ni\u017c uzyskiwanie wynik\u00f3w, podczas gdy wyniki s\u0105 wa\u017cniejsze dla u\u017cytkownika.<\/li>\n<li>Je\u015bli u\u017cywasz bazy danych SQL do wyszukiwania, wszelkie zmiany w kodzie dotycz\u0105ce tej cz\u0119\u015bci powinny by\u0107 dobrze testowane pod k\u0105tem wydajno\u015bci na odpowiedniej obj\u0119to\u015bci danych (przekraczaj\u0105cej obj\u0119to\u015b\u0107 w \u00bb\u017cywej\u00ab bazie). Po\u017c\u0105dane jest r\u00f3wnie\u017c monitorowanie czasu wykonania zapyta\u0144 na wszystkich instancjach bazy, a szczeg\u00f3lnie \u2014 na \u00bb\u017cywej\u00ab. Nawet je\u015bli na etapie rozwoju z planami zapyta\u0144 wszystko wygl\u0105da\u0142o dobrze, w miar\u0119 wzrostu obj\u0119to\u015bci danych sytuacja mo\u017ce si\u0119 znacz\u0105co zmieni\u0107.<\/li>\n<\/ol>\n<p>\u0179r\u00f3d\u0142o: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/epam_systems\/blog\/493438\/\">habr.com<\/a> <\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u041e\u0434\u0438\u043d \u0438\u0437 \u0442\u0438\u043f\u043e\u0432\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u0432 \u0432\u043e \u0432\u0441\u0435\u0445 \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u044b\u0445 \u043d\u0430\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0445 \u2014 \u043f\u043e\u0438\u0441\u043a \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u043c \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u044f\u043c \u0438 \u0432\u044b\u0432\u043e\u0434 \u0438\u0445 \u0432 \u0443\u0434\u043e\u0431\u043d\u043e\u043c \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0432\u0438\u0434\u0435. \u0422\u0443\u0442 \u0436\u0435 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043f\u043e \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0435, \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u043a\u0435, \u043f\u043e\u0441\u0442\u0440\u0430\u043d\u0438\u0447\u043d\u043e\u043c\u0443 \u0432\u044b\u0432\u043e\u0434\u0443. \u0417\u0430\u0434\u0430\u0447\u0430, \u043f\u043e \u0438\u0434\u0435\u0435, \u0442\u0440\u0438\u0432\u0438\u0430\u043b\u044c\u043d\u0430\u044f, \u043d\u043e \u043f\u0440\u0438 \u0435\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0438 \u043c\u043d\u043e\u0433\u0438\u0435 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u0434\u0435\u043b\u0430\u044e\u0442 \u0440\u044f\u0434 \u043e\u0448\u0438\u0431\u043e\u043a, \u0438\u0437-\u0437\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043f\u043e\u0442\u043e\u043c \u0441\u0442\u0440\u0430\u0434\u0430\u0435\u0442 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c. \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":75531,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-75530","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.1.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u041e\u0434\u0438\u043d \u0438\u0437 \u0442\u0438\u043f\u043e\u0432\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u0432 \u0432\u043e \u0432\u0441\u0435\u0445 \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u044b\u0445 \u043d\u0430\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0445 \u2014 \u043f\u043e\u0438\u0441\u043a \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u043c \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u044f\u043c \u0438 \u0432\u044b\u0432\u043e\u0434 \u0438\u0445 \u0432 \u0443\u0434\u043e\u0431\u043d\u043e\u043c \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0432\u0438\u0434\u0435.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"pl_PL\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47\u0412\u044b\u0432\u043e\u0434 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u043f\u043e\u0438\u0441\u043a\u0430 \u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c\u044e | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u041e\u0434\u0438\u043d \u0438\u0437 \u0442\u0438\u043f\u043e\u0432\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u0432 \u0432\u043e \u0432\u0441\u0435\u0445 \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u044b\u0445 \u043d\u0430\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0445 \u2014 \u043f\u043e\u0438\u0441\u043a \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u043c \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u044f\u043c \u0438 \u0432\u044b\u0432\u043e\u0434 \u0438\u0445 \u0432 \u0443\u0434\u043e\u0431\u043d\u043e\u043c \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0432\u0438\u0434\u0435.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2020-03-26T17:42:23+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-03-26T17:42:23+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47Wy\u015bwietlanie wynik\u00f3w wyszukiwania i problemy z wydajno\u015bci\u0105 | ProHoster","description":"Jednym z typowych scenariuszy w naszych znanych aplikacjach jest wyszukiwanie danych wed\u0142ug okre\u015blonych kryteri\u00f3w i wy\u015bwietlanie ich w czytelnej formie.","canonical_url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"pl_PL","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47\u0412\u044b\u0432\u043e\u0434 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u043f\u043e\u0438\u0441\u043a\u0430 \u0438 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b \u0441 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c\u044e | ProHoster","og:description":"\u041e\u0434\u0438\u043d \u0438\u0437 \u0442\u0438\u043f\u043e\u0432\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u0432 \u0432\u043e \u0432\u0441\u0435\u0445 \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u044b\u0445 \u043d\u0430\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0445 \u2014 \u043f\u043e\u0438\u0441\u043a \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u043c \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u044f\u043c \u0438 \u0432\u044b\u0432\u043e\u0434 \u0438\u0445 \u0432 \u0443\u0434\u043e\u0431\u043d\u043e\u043c \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0432\u0438\u0434\u0435.","og:url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2020-03-26T17:42:23+00:00","article:modified_time":"2020-03-26T17:42:23+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"75530","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":null,"breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 17:55:26","updated":"2022-10-02 02:12:16","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/75530","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/comments?post=75530"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/75530\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media\/75531"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media?parent=75530"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/categories?post=75530"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/tags?post=75530"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}