{"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\/pt\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu","title":{"rendered":"Exibi\u00e7\u00e3o de resultados de busca e problemas de desempenho","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Um dos cen\u00e1rios t\u00edpicos em todos os aplicativos que conhecemos \u00e9 a busca de dados com base em crit\u00e9rios espec\u00edficos e a apresenta\u00e7\u00e3o desses dados de forma leg\u00edvel. Aqui tamb\u00e9m podem haver funcionalidades adicionais para ordena\u00e7\u00e3o, agrupamento e pagina\u00e7\u00e3o. A tarefa, em teoria, \u00e9 trivial, mas muitos desenvolvedores cometem uma s\u00e9rie de erros ao resolv\u00ea-la, o que prejudica o desempenho. Vamos explorar v\u00e1rias op\u00e7\u00f5es de solu\u00e7\u00e3o para essa tarefa e formular recomenda\u00e7\u00f5es sobre a escolha da implementa\u00e7\u00e3o mais eficaz.<\/p>\n<p><img decoding=\"async\" alt=\"Exibi\u00e7\u00e3o de resultados de busca e problemas de desempenho\" 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>Op\u00e7\u00e3o de pagina\u00e7\u00e3o #1<\/h2>\n<p>\nA op\u00e7\u00e3o mais simples que vem \u00e0 mente \u00e9 a pagina\u00e7\u00e3o dos resultados da busca de maneira cl\u00e1ssica.<\/p>\n<p><img decoding=\"async\" alt=\"Exibi\u00e7\u00e3o de resultados de busca e problemas de desempenho\" src=\"\/wp-content\/uploads\/2020\/03\/4957d9ad21a8a0c70390b224fe76cb33.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nSuponha que uma base de dados relacional esteja em uso no aplicativo. Nesse caso, para apresentar a informa\u00e7\u00e3o dessa forma, ser\u00e1 necess\u00e1rio executar duas consultas SQL:<\/p>\n<ul>\n<li>Obter as linhas para a p\u00e1gina atual.<\/li>\n<li>Contar o n\u00famero total de linhas que atendem aos crit\u00e9rios de busca \u2014 isso \u00e9 necess\u00e1rio para mostrar as p\u00e1ginas.<\/li>\n<\/ul>\n<p>\nVamos considerar a primeira consulta usando um banco de dados MS SQL de teste. <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/Microsoft\/sql-server-samples\/releases\/download\/adventureworks\/AdventureWorks2016_EXT.bak\">AdventureWorks <\/a><\/noindex>para o servidor 2016. Para isso, usaremos a tabela 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>\nA consulta acima retornar\u00e1 os 50 primeiros pedidos da lista, ordenados de forma decrescente pela data de adi\u00e7\u00e3o, ou seja, os 50 pedidos mais recentes.<\/p>\n<p>Ela \u00e9 executada rapidamente na base de teste, mas vamos examinar o plano de execu\u00e7\u00e3o e as estat\u00edsticas de entrada e sa\u00edda:<\/p>\n<p><img decoding=\"async\" alt=\"Exibi\u00e7\u00e3o de resultados de busca e problemas de desempenho\" src=\"\/wp-content\/uploads\/2020\/03\/248e4b64593c7000216b46777183d639.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabela 'SalesOrderHeader'. Contagem de scans 1, leituras l\u00f3gicas 698, leituras f\u00edsicas 0, leituras antecipadas 0, leituras l\u00f3gicas lob 0, leituras f\u00edsicas lob 0, leituras antecipadas lob 0.<\/code><\/pre>\n<p>\n<i>A estat\u00edstica de entrada\/sa\u00edda para cada consulta pode ser obtida executando o comando SET STATISTICS IO ON no ambiente de execu\u00e7\u00e3o de consultas.<\/i><\/p>\n<p>Como pode ser visto no plano de execu\u00e7\u00e3o, a opera\u00e7\u00e3o mais intensiva em recursos \u00e9 a ordena\u00e7\u00e3o de todas as linhas da tabela original pela data de adi\u00e7\u00e3o. E o problema \u00e9 que quanto mais linhas aparecerem na tabela, mais \"pesada\" se tornar\u00e1 a ordena\u00e7\u00e3o. Na pr\u00e1tica, essas situa\u00e7\u00f5es devem ser evitadas, ent\u00e3o vamos adicionar um \u00edndice na data de adi\u00e7\u00e3o e verificar se o consumo de recursos mudou:<\/p>\n<p><img decoding=\"async\" alt=\"Exibi\u00e7\u00e3o de resultados de busca e problemas de desempenho\" src=\"\/wp-content\/uploads\/2020\/03\/a33e1eadf6f8f8b516b9bb834ad7ad86.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabela 'SalesOrderHeader'. Contagem de scans 1, leituras l\u00f3gicas 165, leituras f\u00edsicas 0, leituras antecipadas 5, leituras l\u00f3gicas lob 0, leituras f\u00edsicas lob 0, leituras antecipadas lob 0.\n<\/code><\/pre>\n<p>\n\u00c9 evidente que melhorou muito. Mas ser\u00e1 que todos os problemas foram resolvidos? Vamos alterar a consulta para buscar pedidos em que o valor total dos produtos ultrapasse 100 d\u00f3lares:<\/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=\"Exibi\u00e7\u00e3o de resultados de busca e problemas de desempenho\" src=\"\/wp-content\/uploads\/2020\/03\/e057bfc89e11a31c6d2855b93ec3c747.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabela 'SalesOrderHeader'. Contagem de varreduras 1, leituras l\u00f3gicas 1081, leituras f\u00edsicas 0, leituras antecipadas 0, leituras l\u00f3gicas lob 0, leituras f\u00edsicas lob 0, leituras antecipadas lob 0.<\/code><\/pre>\n<p>\nTemos uma situa\u00e7\u00e3o curiosa: o plano da consulta n\u00e3o \u00e9 muito pior que o anterior, mas a quantidade real de leituras l\u00f3gicas \u00e9 quase o dobro da que teria em uma varredura completa da tabela. A solu\u00e7\u00e3o existe \u2014 se transformarmos o \u00edndice existente em composto e adicionarmos o campo de pre\u00e7o total dos produtos, novamente obteremos 165 leituras l\u00f3gicas:<\/p>\n<pre><code class=\"sql\">CREATE INDEX IX_SalesOrderHeader_OrderDate_SubTotal on Sales.SalesOrderHeader(OrderDate, SubTotal);\n<\/code><\/pre>\n<p>\nPodemos continuar essa s\u00e9rie de exemplos por muito tempo, mas duas ideias principais que quero expressar aqui s\u00e3o as seguintes:<\/p>\n<ul>\n<li>Adicionar qualquer novo crit\u00e9rio ou ordem de classifica\u00e7\u00e3o \u00e0 consulta pode impactar significativamente a velocidade de sua execu\u00e7\u00e3o.<\/li>\n<li>Mas se precisarmos extrair apenas uma parte dos dados, e n\u00e3o todos os resultados que atendem \u00e0s condi\u00e7\u00f5es de busca \u2014 h\u00e1 v\u00e1rias maneiras de otimizar essa consulta.<\/li>\n<\/ul>\n<p>\nAgora vamos para a segunda consulta mencionada no in\u00edcio \u2014 aquela que conta o n\u00famero de registros que atendem ao crit\u00e9rio de busca. Vamos usar o mesmo exemplo \u2014 buscar pedidos que custam mais de 100 d\u00f3lares:<\/p>\n<pre><code class=\"sql\">SELECT COUNT(1) FROM Sales.SalesOrderHeader\nWHERE SubTotal &gt; 100\n<\/code><\/pre>\n<p>\nCom o \u00edndice composto mencionado acima, obtemos:<\/p>\n<p><img decoding=\"async\" alt=\"Exibi\u00e7\u00e3o de resultados de busca e problemas de desempenho\" src=\"\/wp-content\/uploads\/2020\/03\/7beab0c83a50e2d68a83a965df555ec9.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabela 'SalesOrderHeader'. Contagem de scans 1, leituras l\u00f3gicas 698, leituras f\u00edsicas 0, leituras antecipadas 0, leituras l\u00f3gicas lob 0, leituras f\u00edsicas lob 0, leituras antecipadas lob 0.<\/code><\/pre>\n<p>\nO fato de a consulta percorrer todo o \u00edndice n\u00e3o \u00e9 surpreendente, j\u00e1 que o campo SubTotal n\u00e3o est\u00e1 na primeira posi\u00e7\u00e3o, portanto a consulta n\u00e3o pode se aproveitar disso. O problema \u00e9 resolvido adicionando outro \u00edndice ao campo SubTotal, resultando em apenas 48 leituras l\u00f3gicas.<\/p>\n<p>Podemos apresentar mais alguns exemplos de consultas para contagem, mas a ess\u00eancia permanecer\u00e1 a mesma: <b>obter um conjunto de dados e contar a quantidade total \u2014 s\u00e3o duas consultas fundamentalmente diferentes<\/b>, e cada uma requer suas pr\u00f3prias medidas de otimiza\u00e7\u00e3o. De maneira geral, n\u00e3o ser\u00e1 poss\u00edvel encontrar uma combina\u00e7\u00e3o de \u00edndices que funcione igualmente bem para ambas as consultas.<\/p>\n<p>Assim, uma das principais exig\u00eancias a serem esclarecidas ao desenvolver uma solu\u00e7\u00e3o de pesquisa como essa \u00e9 se, de fato, \u00e9 importante para a empresa ver o n\u00famero total de objetos encontrados. Muitas vezes, isso n\u00e3o \u00e9 necess\u00e1rio. E a navega\u00e7\u00e3o por n\u00fameros de p\u00e1gina espec\u00edficos, na minha opini\u00e3o, \u00e9 uma solu\u00e7\u00e3o com um campo de aplica\u00e7\u00e3o muito restrito, uma vez que a maioria dos cen\u00e1rios de pagina\u00e7\u00e3o se apresenta como 'ir para a pr\u00f3xima p\u00e1gina'.<\/p>\n<h2>Op\u00e7\u00e3o de pagina\u00e7\u00e3o #2<\/h2>\n<p>\nSuponha que os usu\u00e1rios n\u00e3o estejam interessados em saber o n\u00famero total de objetos encontrados. Vamos tentar simplificar a p\u00e1gina de pesquisa:<\/p>\n<p><img decoding=\"async\" alt=\"Exibi\u00e7\u00e3o de resultados de busca e problemas de desempenho\" src=\"\/wp-content\/uploads\/2020\/03\/66f5ee6dc1a52f14e55ae31e05c502b2.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nNa pr\u00e1tica, a \u00fanica diferen\u00e7a \u00e9 que n\u00e3o \u00e9 mais poss\u00edvel navegar para n\u00fameros de p\u00e1ginas espec\u00edficas, e agora essa tabela de exibi\u00e7\u00e3o n\u00e3o precisa saber quantas p\u00e1ginas existem ao todo. Mas surge a quest\u00e3o: como a tabela saber\u00e1 se h\u00e1 dados para a pr\u00f3xima p\u00e1gina (para exibir corretamente o link 'Pr\u00f3ximo')?<\/p>\n<p>A resposta \u00e9 muito simples: podemos ler da base de dados uma entrada a mais do que o necess\u00e1rio para exibi\u00e7\u00e3o, e a presen\u00e7a dessa 'entrada adicional' indicar\u00e1 se h\u00e1 um pr\u00f3ximo lote dispon\u00edvel. Assim, para obter uma p\u00e1gina de dados, ser\u00e1 necess\u00e1rio executar apenas uma consulta, o que melhora significativamente o desempenho e facilita a manuten\u00e7\u00e3o dessa funcionalidade. Eu tive um caso na pr\u00e1tica em que abrir m\u00e3o do c\u00e1lculo do n\u00famero total de registros acelerou a entrega dos resultados em 4-5 vezes.<\/p>\n<p>Para essa abordagem, existem v\u00e1rias op\u00e7\u00f5es de interface do usu\u00e1rio: comandos 'voltar' e 'avan\u00e7ar', como no exemplo acima, um bot\u00e3o 'carregar mais', que simplesmente adiciona um novo lote aos resultados exibidos, e 'rolagem infinita', que funciona no princ\u00edpio de 'carregar mais', mas o sinal para obter o pr\u00f3ximo lote \u00e9 a rolagem do usu\u00e1rio at\u00e9 o final de todos os resultados mostrados. Qualquer que seja a solu\u00e7\u00e3o visual, o princ\u00edpio de amostragem de dados permanece o mesmo.<\/p>\n<h2>Nuances da implementa\u00e7\u00e3o da pagina\u00e7\u00e3o<\/h2>\n<p>\nEm todos os exemplos de consultas apresentados acima, usa-se a abordagem 'deslocamento + quantidade', onde na pr\u00f3pria consulta \u00e9 especificado de qual linha da resultante e quantas linhas precisam ser retornadas. Primeiramente, vamos considerar como organizar melhor a transmiss\u00e3o de par\u00e2metros nesse caso. Na pr\u00e1tica, encontrei v\u00e1rias maneiras:<\/p>\n<ul>\n<li>N\u00famero sequencial da p\u00e1gina solicitada (pageIndex), tamanho da p\u00e1gina (pageSize).<\/li>\n<li>N\u00famero sequencial do primeiro registro que deve ser retornado (startIndex), n\u00famero m\u00e1ximo de registros no resultado (count).<\/li>\n<li>N\u00famero sequencial do primeiro registro que deve ser retornado (startIndex), n\u00famero sequencial do \u00faltimo registro que deve ser retornado (endIndex).<\/li>\n<\/ul>\n<p>\n\u00c0 primeira vista, pode parecer t\u00e3o elementar que n\u00e3o h\u00e1 diferen\u00e7a. Mas n\u00e3o \u00e9 assim\u2014o segundo (startIndex, count) \u00e9 a op\u00e7\u00e3o mais conveniente e universal. Existem v\u00e1rias raz\u00f5es para isso:<\/p>\n<ul>\n<li>Para a abordagem de ler +1 registro, como mencionado acima, a primeira op\u00e7\u00e3o com pageIndex e pageSize \u00e9 extremamente inconveniente. Por exemplo, queremos exibir 50 registros por p\u00e1gina. De acordo com o algoritmo apresentado, \u00e9 necess\u00e1rio ler um registro a mais do que o necess\u00e1rio. Se esse 'mais 1' n\u00e3o estiver configurado no servidor, obteremos que, para a primeira p\u00e1gina, devemos solicitar registros de 1 a 51, para a segunda\u2014de 51 a 101, e assim por diante. Se especificarmos o tamanho da p\u00e1gina como 51 e aumentarmos o pageIndex, a segunda p\u00e1gina retornar\u00e1 de 52 a 102 e assim por diante. Consequentemente, na primeira op\u00e7\u00e3o, a \u00fanica maneira de implementar corretamente o bot\u00e3o para a pr\u00f3xima p\u00e1gina seria preparar no servidor a leitura da 'linha extra', o que seria um detalhe muito obscuro.<\/li>\n<li>A terceira op\u00e7\u00e3o n\u00e3o faz sentido algum, uma vez que para executar consultas na maioria dos bancos de dados ainda ser\u00e1 necess\u00e1rio passar a quantidade, e n\u00e3o o \u00edndice do \u00faltimo registro. A subtra\u00e7\u00e3o de startIndex de endIndex e uma opera\u00e7\u00e3o aritm\u00e9tica elementar, mas ela \u00e9 desnecess\u00e1ria aqui.<\/li>\n<\/ul>\n<p>\nAgora devemos descrever as desvantagens da implementa\u00e7\u00e3o de pagina\u00e7\u00e3o atrav\u00e9s de 'offset + quantidade':<\/p>\n<ul>\n<li>Obter cada pr\u00f3xima p\u00e1gina ser\u00e1 mais dispendioso e mais lento do que a anterior, porque o banco de dados ainda precisar\u00e1 percorrer todos os registros 'do princ\u00edpio' de acordo com os crit\u00e9rios de busca e ordena\u00e7\u00e3o, e ent\u00e3o parar na parte necess\u00e1ria.<\/li>\n<li>Nem todos os SGBDs podem suportar essa abordagem.<\/li>\n<\/ul>\n<p>\nExistem alternativas, mas elas tamb\u00e9m n\u00e3o s\u00e3o ideais. A primeira dessas abordagens \u00e9 chamada de 'keyset paging' ou 'm\u00e9todo seek' e consiste no seguinte: ap\u00f3s obter um lote, pode-se memorizar os valores dos campos do \u00faltimo registro da p\u00e1gina, e depois utiliz\u00e1-los para obter o pr\u00f3ximo lote. Por exemplo, fizemos essa consulta:<\/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>\nE na \u00faltima entrada, obtivemos o valor da data do pedido '2014-06-29'. Ent\u00e3o, para obter a pr\u00f3xima p\u00e1gina, podemos tentar executar o seguinte:<\/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>\nO problema \u00e9 que OrderDate n\u00e3o \u00e9 um campo \u00fanico e a condi\u00e7\u00e3o mencionada acima provavelmente ir\u00e1 omitir muitas linhas necess\u00e1rias. Para trazer clareza a essa consulta, \u00e9 preciso adicionar um campo \u00fanico \u00e0 condi\u00e7\u00e3o (suponha que 75074 \u00e9 o \u00faltimo valor da chave prim\u00e1ria do primeiro lote):<\/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>\nEsta op\u00e7\u00e3o funcionar\u00e1 corretamente, mas, de forma geral, ser\u00e1 dif\u00edcil otimiz\u00e1-la, pois a condi\u00e7\u00e3o cont\u00e9m o operador OR. Se, com o crescimento do OrderDate, o valor da chave prim\u00e1ria tamb\u00e9m aumentar, a condi\u00e7\u00e3o pode ser simplificada, mantendo apenas o filtro pelo SalesOrderID. Mas se n\u00e3o houver correla\u00e7\u00e3o rigorosa entre os valores da chave prim\u00e1ria e o campo pelo qual os resultados foram ordenados, na maioria dos SGBDs, n\u00e3o ser\u00e1 poss\u00edvel evitar esse OR. Uma exce\u00e7\u00e3o conhecida para mim \u00e9 o PostgreSQL, onde a compara\u00e7\u00e3o de tuplas \u00e9 totalmente suportada, e a condi\u00e7\u00e3o mencionada acima pode ser escrita como 'WHERE (OrderDate, SalesOrderID) &lt; (&#039;2014-06-29&#039;, 75074)&#039;. Com uma chave composta contendo esses dois campos, essa consulta deve ser bastante leve.<\/p>\n<p>Uma segunda abordagem alternativa pode ser encontrada, por exemplo, em <noindex><a rel=\"nofollow\" href=\"https:\/\/www.elastic.co\/guide\/en\/elasticsearch\/reference\/6.8\/search-request-scroll.html\">ElasticSearch scroll API<\/a><\/noindex> ou <noindex><a rel=\"nofollow\" href=\"https:\/\/medium.com\/@gary.strange\/understanding-cosmosdb-continuation-tokens-hasmoreresults-and-connectionpolicy-requesttimeouts-3ed1fadfa81d\">Cosmos DB<\/a><\/noindex> \u2014 quando a consulta, al\u00e9m dos dados, retorna um identificador especial, que pode ser usado para obter o pr\u00f3ximo lote de dados. Se esse identificador tiver um tempo de vida ilimitado (como no Cosmos DB), \u00e9 uma \u00f3tima forma de implementar a pagina\u00e7\u00e3o com transi\u00e7\u00f5es sequenciais entre p\u00e1ginas (a op\u00e7\u00e3o #2 mencionada acima). Seus poss\u00edveis inconvenientes: n\u00e3o \u00e9 suportado em todos os SGBDs; o identificador obtido para o pr\u00f3ximo lote pode ter um tempo de vida limitado, o que, em geral, n\u00e3o \u00e9 adequado para realizar intera\u00e7\u00f5es com o usu\u00e1rio (como, por exemplo, na ElasticSearch scroll API).<\/p>\n<h2>Filtragem complexa<\/h2>\n<p>\nComplicamos um pouco mais a tarefa. Suponha que surge a necessidade de implementar uma chamada pesquisa facetada, muito familiar em lojas online. Os exemplos citados acima com base na tabela de pedidos n\u00e3o s\u00e3o muito representativos neste caso, ent\u00e3o vamos mudar para a tabela Product do banco de dados AdventureWorks:<\/p>\n<p><img decoding=\"async\" alt=\"Exibi\u00e7\u00e3o de resultados de busca e problemas de desempenho\" src=\"\/wp-content\/uploads\/2020\/03\/070890a3cb79de3edc69b60b5300efe7.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nQual \u00e9 a ideia da pesquisa facetada? \u00c9 que para cada elemento do filtro \u00e9 exibido o n\u00famero de registros que correspondem a esse crit\u00e9rio <i>considerando os filtros escolhidos nas demais categorias<\/i>.<\/p>\n<p>Por exemplo, se escolh\u00eassemos neste caso a categoria Bikes e a cor Black, a tabela mostraria apenas bicicletas da cor preta, mas ao mesmo tempo:<\/p>\n<ul>\n<li>Para cada crit\u00e9rio do grupo 'Categories' ser\u00e1 mostrado o n\u00famero de produtos dessa categoria na cor preta.<\/li>\n<li>Para cada crit\u00e9rio do grupo 'Colors' ser\u00e1 mostrado o n\u00famero de bicicletas dessa cor.<\/li>\n<\/ul>\n<p>\nAqui est\u00e1 um exemplo de exibi\u00e7\u00e3o do resultado para tais condi\u00e7\u00f5es:<\/p>\n<p><img decoding=\"async\" alt=\"Exibi\u00e7\u00e3o de resultados de busca e problemas de desempenho\" src=\"\/wp-content\/uploads\/2020\/03\/7c8ec4d8b427f1f59f0129afebe74b08.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nSe al\u00e9m disso marcarmos a categoria 'Clothing', a tabela mostrar\u00e1 tamb\u00e9m as roupas da cor preta que est\u00e3o dispon\u00edveis. O n\u00famero de produtos de cor preta na se\u00e7\u00e3o 'Color' tamb\u00e9m ser\u00e1 recalculado de acordo com as novas condi\u00e7\u00f5es, mas na se\u00e7\u00e3o 'Categories' nada mudar\u00e1\u2026 Espero que esses exemplos sejam suficientes para compreender o algoritmo familiar da pesquisa facetada.<\/p>\n<p>Agora vamos imaginar como isso pode ser implementado em um banco de dados relacional. Cada grupo de crit\u00e9rios, como Category e Color, exigir\u00e1 uma consulta separada:<\/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=\"Exibi\u00e7\u00e3o de resultados de busca e problemas de desempenho\" 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=\"Exibi\u00e7\u00e3o de resultados de busca e problemas de desempenho\" src=\"\/wp-content\/uploads\/2020\/03\/82e1e6ab0fae683d383e3fecf4e3a15a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nQual \u00e9 o problema com essa solu\u00e7\u00e3o? \u00c9 simples \u2014 ela n\u00e3o escala bem. Cada se\u00e7\u00e3o de filtro requer uma consulta separada para contar as quantidades e essas consultas n\u00e3o s\u00e3o das mais leves. Em lojas online, em algumas categorias, pode haver v\u00e1rias dezenas de se\u00e7\u00f5es de filtro, o que pode se tornar um s\u00e9rio problema de desempenho.<\/p>\n<p>Normalmente, ap\u00f3s essas afirma\u00e7\u00f5es, me s\u00e3o oferecidas algumas solu\u00e7\u00f5es, nomeadamente:<\/p>\n<ul>\n<li>Consolidar todas as contagens em uma \u00fanica consulta. Tecnicamente, isso \u00e9 poss\u00edvel usando a palavra-chave UNION, mas isso n\u00e3o ajuda muito em termos de desempenho \u2014 o banco de dados ainda precisar\u00e1 executar 'do zero' cada um dos fragmentos.<\/li>\n<li>Cachear quantidades. Isso \u00e9 algo que me sugerem praticamente toda vez que descrevo o problema. A nuance \u00e9 que, de maneira geral, isso \u00e9 imposs\u00edvel. Suponha que temos 10 'facetados', cada um com 5 valores. Essa \u00e9 uma situa\u00e7\u00e3o muito 'modesta' em compara\u00e7\u00e3o com o que pode ser visto em lojas online. A escolha de um item facetado influencia as quantidades em 9 outros; em outras palavras, para cada combina\u00e7\u00e3o de crit\u00e9rios, as quantidades podem ser diferentes. No total, em nosso exemplo, existem 50 crit\u00e9rios que o usu\u00e1rio pode escolher, portanto, as combina\u00e7\u00f5es poss\u00edveis seriam 250. N\u00e3o h\u00e1 mem\u00f3ria ou tempo suficientes para preencher tal matriz de dados. Pode-se argumentar que nem todas as combina\u00e7\u00f5es s\u00e3o reais e que o usu\u00e1rio raramente escolhe mais de 5-10 crit\u00e9rios. Sim, \u00e9 poss\u00edvel implementar o carregamento sob demanda e o cache de quantidades apenas para o que foi selecionado anteriormente, mas quanto mais op\u00e7\u00f5es de escolha houver, menos eficaz ser\u00e1 esse cache e mais evidentes se tornar\u00e3o os problemas de tempo de resposta (especialmente se o conjunto de dados mudar regularmente).<\/li>\n<\/ul>\n<p>\nFelizmente, essa tarefa j\u00e1 possui solu\u00e7\u00f5es bastante eficazes que funcionam de maneira previs\u00edvel em grandes volumes de dados. Para qualquer uma dessas op\u00e7\u00f5es, faz sentido separar o rec\u00e1lculo dos facets e a obten\u00e7\u00e3o da p\u00e1gina de resultados em duas solicita\u00e7\u00f5es paralelas ao servidor e organizar a interface do usu\u00e1rio de modo que o carregamento dos dados dos facets 'n\u00e3o atrapalhe' a exibi\u00e7\u00e3o dos resultados da pesquisa.<\/p>\n<ul>\n<li>Chamar a recontagem completa de \"facetas\" com a menor frequ\u00eancia poss\u00edvel. Por exemplo, em vez de recalcular tudo a cada altera\u00e7\u00e3o nos crit\u00e9rios de pesquisa, encontre a contagem total dos resultados que atendem \u00e0s condi\u00e7\u00f5es atuais e sugira ao usu\u00e1rio exibi-los \u2014 \"1425 registros encontrados, mostrar?\" O usu\u00e1rio pode continuar alterando as condi\u00e7\u00f5es de pesquisa ou clicar no bot\u00e3o \"mostrar\". Somente neste segundo caso ser\u00e3o executadas todas as consultas para obter os resultados e recalcular as quantidades em todas as \"facetas\". Nesse caso, como n\u00e3o \u00e9 dif\u00edcil perceber, ser\u00e1 necess\u00e1rio lidar com uma solicita\u00e7\u00e3o para obter o total de resultados e sua otimiza\u00e7\u00e3o. Este m\u00e9todo pode ser encontrado em muitas pequenas lojas online. \u00c9 \u00f3bvio que isso n\u00e3o \u00e9 uma panaceia para este problema, mas em casos simples pode ser um bom compromisso.<\/li>\n<li>Usar motores de busca para procurar resultados e contar facetas, como Solr, ElasticSearch, Sphinx e outros. Todos eles s\u00e3o projetados para construir \"facetas\" e fazem isso de forma bastante eficiente devido ao \u00edndice invertido. Como os motores de busca s\u00e3o estruturados, por que eles s\u00e3o mais eficazes nessas situa\u00e7\u00f5es do que bancos de dados de uso geral, quais s\u00e3o as melhores pr\u00e1ticas e armadilhas \u2014 este \u00e9 um tema para um artigo separado. Aqui, quero chamar a aten\u00e7\u00e3o para o fato de que o motor de busca n\u00e3o pode substituir o armazenamento de dados principal, mas \u00e9 utilizado como um complemento: quaisquer altera\u00e7\u00f5es na base principal, que s\u00e3o relevantes para a busca, s\u00e3o sincronizadas no \u00edndice de busca; o mecanismo de busca geralmente interage apenas com o motor de busca e n\u00e3o acessa a base principal. Um dos pontos mais importantes aqui \u00e9 como organizar essa sincroniza\u00e7\u00e3o de forma confi\u00e1vel. Tudo depende dos requisitos para o \"tempo de resposta\". Se o tempo entre uma altera\u00e7\u00e3o na base principal e sua \"apari\u00e7\u00e3o\" na busca n\u00e3o for cr\u00edtico, pode-se criar um servi\u00e7o que a cada alguns minutos busca os registros recentemente alterados e os indexa. Se for necess\u00e1rio um tempo de resposta o mais curto poss\u00edvel, pode-se implementar algo como <noindex><a rel=\"nofollow\" href=\"https:\/\/microservices.io\/patterns\/data\/transactional-outbox.html\">caixa de sa\u00edda transacional<\/a><\/noindex> para enviar atualiza\u00e7\u00f5es para o servi\u00e7o de busca.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Como resultado, obtemos um servi\u00e7o de \"monitoramento como servi\u00e7o\", automatizado na linha de produ\u00e7\u00e3o de integra\u00e7\u00e3o cont\u00ednua. O desenvolvedor ou testador precisa apenas definir a lista de m\u00e9tricas em um arquivo JSON, e tudo o mais acontece automaticamente. Obtemos um controle de qualidade transparente dos lan\u00e7amentos: todas as notifica\u00e7\u00f5es sobre desempenho, consumo de recursos ou regress\u00f5es arquitet\u00f4nicas.<\/h2>\n<p><\/p>\n<ol>\n<li>A implementa\u00e7\u00e3o de pagina\u00e7\u00e3o do lado do servidor \u00e9 uma complica\u00e7\u00e3o s\u00e9ria e deve ser aplicada apenas a conjuntos de dados que est\u00e3o crescendo rapidamente ou que s\u00e3o simplesmente grandes. N\u00e3o h\u00e1 uma receita absolutamente precisa para avaliar o que \u00e9 \"grande\" ou \"em crescimento r\u00e1pido\", mas eu seguiria esta abordagem:\n<ul>\n<li>Se a obten\u00e7\u00e3o da cole\u00e7\u00e3o completa de dados, considerando o tempo do servidor e a transfer\u00eancia pela rede, se encaixar normalmente nos requisitos de desempenho, n\u00e3o faz sentido implementar a pagina\u00e7\u00e3o do lado do servidor.<\/li>\n<li>Pode haver uma situa\u00e7\u00e3o em que, pelo menos por um tempo, n\u00e3o se prevejam problemas de desempenho, pois h\u00e1 poucos dados, mas a cole\u00e7\u00e3o de dados est\u00e1 em constante crescimento. Se um determinado conjunto de dados pode, a longo prazo, deixar de atender ao ponto anterior \u2014 \u00e9 melhor incluir a pagina\u00e7\u00e3o desde o in\u00edcio.<\/li>\n<\/ul>\n<\/li>\n<li>Se do lado dos neg\u00f3cios n\u00e3o houver uma exig\u00eancia r\u00edgida para mostrar o n\u00famero total de resultados ou exibir n\u00fameros de p\u00e1ginas, e al\u00e9m disso, seu sistema n\u00e3o possui um mecanismo de busca \u2014 \u00e9 melhor n\u00e3o implementar esses aspectos e considerar a op\u00e7\u00e3o #2.<\/li>\n<li>Se houver uma exig\u00eancia clara sobre busca facetada, voc\u00ea tem duas op\u00e7\u00f5es para n\u00e3o sacrificar o desempenho:\n<ul>\n<li>N\u00e3o recalcular todas as quantidades a cada altera\u00e7\u00e3o dos crit\u00e9rios de busca.<\/li>\n<li>Utilizar mecanismos de busca como Solr, ElasticSearch, Sphinx e outros. Mas \u00e9 preciso entender que ele n\u00e3o pode substituir o banco de dados principal e deve ser utilizado como um complemento ao armazenamento principal para resolver quest\u00f5es de busca. <\/li>\n<\/ul>\n<\/li>\n<li>Al\u00e9m disso, no caso de busca facetada, faz sentido dividir a obten\u00e7\u00e3o da p\u00e1gina de resultados de busca e a contagem de quantidades em duas requisi\u00e7\u00f5es paralelas. A contagem de quantidades pode levar mais tempo do que a obten\u00e7\u00e3o dos resultados, enquanto os resultados s\u00e3o mais importantes para o usu\u00e1rio.<\/li>\n<li>Se voc\u00ea estiver utilizando um banco de dados SQL para busca, qualquer altera\u00e7\u00e3o no c\u00f3digo relacionada a essa parte deve ser bem testada em termos de desempenho com o volume correspondente de dados (superior ao volume em um banco de dados \"vivo\"). Tamb\u00e9m \u00e9 desej\u00e1vel utilizar o monitoramento do tempo de execu\u00e7\u00e3o das requisi\u00e7\u00f5es em todas as inst\u00e2ncias do banco, e especialmente \u2014 no \"vivo\". Mesmo que na fase de desenvolvimento os planos de requisi\u00e7\u00f5es estejam funcionando bem, com o crescimento do volume de dados, a situa\u00e7\u00e3o pode mudar consideravelmente.<\/li>\n<\/ol>\n<p>Fonte: <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.3 - 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\/pt\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.3\" \/>\n\t\t<meta property=\"og:locale\" content=\"pt_PT\" \/>\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\/pt\/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\udd47Exibi\u00e7\u00e3o de resultados da pesquisa e problemas de desempenho | ProHoster","description":"Um dos cen\u00e1rios t\u00edpicos em todos os aplicativos que conhecemos \u00e9 a busca de dados com base em crit\u00e9rios espec\u00edficos e a apresenta\u00e7\u00e3o deles de maneira leg\u00edvel.","canonical_url":"https:\/\/prohoster.info\/pt\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"pt_PT","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\/pt\/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\/pt\/wp-json\/wp\/v2\/posts\/75530","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/comments?post=75530"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/posts\/75530\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/media\/75531"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/media?parent=75530"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/categories?post=75530"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/tags?post=75530"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}