{"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\/fr\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu","title":{"rendered":"Affichage des r\u00e9sultats de recherche et probl\u00e8mes de performance","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>L'un des sc\u00e9narios typiques dans toutes les applications que nous connaissons est la recherche de donn\u00e9es selon certains crit\u00e8res et leur affichage dans un format lisible. Il peut \u00e9galement y avoir des fonctionnalit\u00e9s suppl\u00e9mentaires pour le tri, le regroupement et la pagination. La t\u00e2che, en th\u00e9orie, est triviale, mais lors de sa r\u00e9alisation, de nombreux d\u00e9veloppeurs commettent une s\u00e9rie d'erreurs qui affectent ensuite les performances. Examinons les diff\u00e9rentes options pour r\u00e9soudre ce probl\u00e8me et formulons des recommandations pour choisir la mise en \u0153uvre la plus efficace.<\/p>\n<p><img decoding=\"async\" alt=\"Affichage des r\u00e9sultats de recherche et probl\u00e8mes de performance\" 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>Option de pagination #1<\/h2>\n<p>\nLa solution la plus simple qui vient \u00e0 l'esprit est l'affichage des r\u00e9sultats de recherche page par page dans sa forme classique.<\/p>\n<p><img decoding=\"async\" alt=\"Affichage des r\u00e9sultats de recherche et probl\u00e8mes de performance\" src=\"\/wp-content\/uploads\/2020\/03\/4957d9ad21a8a0c70390b224fe76cb33.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nSupposons qu'une base de donn\u00e9es relationnelle soit utilis\u00e9e dans l'application. Dans ce cas, pour afficher l'information de cette mani\u00e8re, il faudra ex\u00e9cuter deux requ\u00eates SQL :<\/p>\n<ul>\n<li>Obtenir les lignes pour la page actuelle.<\/li>\n<li>Compter le nombre total de lignes correspondant aux crit\u00e8res de recherche \u2014 cela est n\u00e9cessaire pour afficher les pages.<\/li>\n<\/ul>\n<p>\nExaminons la premi\u00e8re requ\u00eate en prenant comme exemple la base de donn\u00e9es MS SQL de test <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/Microsoft\/sql-server-samples\/releases\/download\/adventureworks\/AdventureWorks2016_EXT.bak\">AdventureWorks <\/a><\/noindex>pour le serveur 2016. Pour cela, nous utiliserons la table 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>\nLa requ\u00eate ci-dessus retournera les 50 premi\u00e8res commandes de la liste, tri\u00e9es par date d'ajout dans l'ordre d\u00e9croissant, en d'autres termes \u2014 les 50 derni\u00e8res commandes.<\/p>\n<p>Elle s'ex\u00e9cute rapidement sur la base de donn\u00e9es de test, mais examinons le plan d'ex\u00e9cution et les statistiques d'entr\u00e9e-sortie :<\/p>\n<p><img decoding=\"async\" alt=\"Affichage des r\u00e9sultats de recherche et probl\u00e8mes de performance\" src=\"\/wp-content\/uploads\/2020\/03\/248e4b64593c7000216b46777183d639.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Table 'SalesOrderHeader'. Scan count 1, logical reads 698, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.<\/code><\/pre>\n<p>\n<i>Pour obtenir les statistiques d'entr\u00e9e\/sortie de chaque requ\u00eate, vous pouvez ex\u00e9cuter dans l'environnement d'ex\u00e9cution des requ\u00eates la commande SET STATISTICS IO ON.<\/i><\/p>\n<p>Comme on peut le voir dans le plan d'ex\u00e9cution, l'op\u00e9ration la plus consommatrice de ressources est le tri de toutes les lignes de la table source par date d'ajout. Et le probl\u00e8me est que plus il y aura de lignes dans la table, plus le tri sera \u00ab lourd \u00bb. Dans la pratique, il faut \u00e9viter de telles situations, c'est pourquoi nous ajouterons un index sur la date d'ajout et v\u00e9rifierons si la consommation de ressources a chang\u00e9 :<\/p>\n<p><img decoding=\"async\" alt=\"Affichage des r\u00e9sultats de recherche et probl\u00e8mes de performance\" src=\"\/wp-content\/uploads\/2020\/03\/a33e1eadf6f8f8b516b9bb834ad7ad86.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Table 'SalesOrderHeader'. Scan count 1, logical reads 165, physical reads 0, read-ahead reads 5, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.\n<\/code><\/pre>\n<p>\nIl est \u00e9vident que c'est beaucoup mieux maintenant. Mais tous les probl\u00e8mes sont-ils r\u00e9solus ? Changeons la requ\u00eate pour rechercher les commandes dont le montant total des produits d\u00e9passe 100 dollars :<\/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=\"Affichage des r\u00e9sultats de recherche et probl\u00e8mes de performance\" src=\"\/wp-content\/uploads\/2020\/03\/e057bfc89e11a31c6d2855b93ec3c747.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Table 'SalesOrderHeader'. Compte de scans 1, lectures logiques 1081, lectures physiques 0, lectures anticip\u00e9es 0, lectures logiques lob 0, lectures physiques lob 0, lectures anticip\u00e9es lob 0.<\/code><\/pre>\n<p>\nNous avons une situation amusante : le plan de la requ\u00eate n'est pas beaucoup plus mauvais que le pr\u00e9c\u00e9dent, mais le nombre r\u00e9el de lectures logiques est presque deux fois plus \u00e9lev\u00e9 que lors d'un scan complet de la table. Il existe une solution \u2014 si l'on cr\u00e9e un index composite \u00e0 partir de l'index existant et que l'on ajoute le montant total des produits comme deuxi\u00e8me champ, nous obtenons \u00e0 nouveau 165 lectures logiques :<\/p>\n<pre><code class=\"sql\">CREATE INDEX IX_SalesOrderHeader_OrderDate_SubTotal on Sales.SalesOrderHeader(OrderDate, SubTotal);\n<\/code><\/pre>\n<p>\nCette s\u00e9rie d'exemples peut \u00eatre poursuivie longtemps, mais les deux id\u00e9es principales que je souhaite exprimer ici sont les suivantes :<\/p>\n<ul>\n<li>Ajouter n'importe quel nouveau crit\u00e8re ou ordre de tri dans la requ\u00eate de recherche peut avoir un impact important sur la vitesse de son ex\u00e9cution.<\/li>\n<li>Mais si nous devons ne lire qu'une partie des donn\u00e9es, et non tous les r\u00e9sultats correspondant aux conditions de recherche \u2014 il existe de nombreuses fa\u00e7ons d'optimiser une telle requ\u00eate.<\/li>\n<\/ul>\n<p>\nPassons maintenant \u00e0 la deuxi\u00e8me requ\u00eate mentionn\u00e9e au d\u00e9but \u2014 celle qui compte le nombre d'enregistrements qui r\u00e9pondent au crit\u00e8re de recherche. Prenons le m\u00eame exemple \u2014 rechercher des commandes qui co\u00fbtent plus de 100 dollars :<\/p>\n<pre><code class=\"sql\">SELECT COUNT(1) FROM Sales.SalesOrderHeader\nWHERE SubTotal &gt; 100\n<\/code><\/pre>\n<p>\nAvec l'index composite mentionn\u00e9 ci-dessus, nous obtenons :<\/p>\n<p><img decoding=\"async\" alt=\"Affichage des r\u00e9sultats de recherche et probl\u00e8mes de performance\" src=\"\/wp-content\/uploads\/2020\/03\/7beab0c83a50e2d68a83a965df555ec9.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Table 'SalesOrderHeader'. Scan count 1, logical reads 698, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.<\/code><\/pre>\n<p>\nIl n'est pas surprenant que la requ\u00eate parcourt enti\u00e8rement l'index, car le champ SubTotal n'est pas en premi\u00e8re position, donc la requ\u00eate ne peut pas en tirer parti. Le probl\u00e8me se r\u00e9sout en ajoutant un autre index sur le champ SubTotal, et cela donne finalement seulement 48 lectures logiques.<\/p>\n<p>On peut donner encore quelques exemples de requ\u00eates de comptage, mais le principe reste le m\u00eame : <b>obtenir une portion de donn\u00e9es et compter le total sont deux requ\u00eates fondamentalement diff\u00e9rentes<\/b>, et chacune n\u00e9cessite ses propres mesures d'optimisation. En g\u00e9n\u00e9ral, il n'est pas possible de trouver une combinaison d'index qui fonctionne \u00e9galement bien pour les deux requ\u00eates.<\/p>\n<p>Par cons\u00e9quent, l'une des exigences importantes \u00e0 pr\u00e9ciser lors du d\u00e9veloppement d'une telle solution de recherche est de savoir s'il est vraiment important pour l'entreprise de voir le nombre total d'objets trouver. Souvent, ce n'est pas le cas. Quant \u00e0 la navigation par num\u00e9ros de page sp\u00e9cifiques, cela me semble \u00eatre une solution \u00e0 champ d'application tr\u00e8s limit\u00e9, car la plupart des sc\u00e9narios de pagination ressemblent \u00e0 \u00ab passer \u00e0 la page suivante \u00bb.<\/p>\n<h2>Option de pagination #2<\/h2>\n<p>\nSupposons que les utilisateurs ne se soucient pas de conna\u00eetre le nombre total d'objets trouv\u00e9s. Essayons de simplifier la page de recherche :<\/p>\n<p><img decoding=\"async\" alt=\"Affichage des r\u00e9sultats de recherche et probl\u00e8mes de performance\" src=\"\/wp-content\/uploads\/2020\/03\/66f5ee6dc1a52f14e55ae31e05c502b2.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nEn r\u00e9alit\u00e9, la seule chose qui a chang\u00e9 est qu'il n'est plus possible de naviguer par num\u00e9ros de pages sp\u00e9cifiques, et il n'est plus n\u00e9cessaire que cette table sache combien de pages peuvent exister. Mais la question se pose : comment la table sait-elle s'il y a des donn\u00e9es pour la page suivante (afin d'afficher correctement le lien \u00ab Suivant \u00bb) ?<\/p>\n<p>La r\u00e9ponse est tr\u00e8s simple : il est possible de lire dans la base une entr\u00e9e de plus que ce qui est n\u00e9cessaire pour l'affichage, et la pr\u00e9sence de cette entr\u00e9e \u00ab suppl\u00e9mentaire \u00bb indiquera s'il y a un lot suivant. Ainsi, pour obtenir une page de donn\u00e9es, il suffira d'ex\u00e9cuter une seule requ\u00eate, ce qui am\u00e9liore consid\u00e9rablement les performances et facilite la maintenance de cette fonctionnalit\u00e9. J'ai eu un cas dans ma pratique o\u00f9 l'abandon du comptage du nombre total d'enregistrements a acc\u00e9l\u00e9r\u00e9 le retour des r\u00e9sultats de 4 \u00e0 5 fois.<\/p>\n<p>Pour cette approche, il existe plusieurs options d'interface utilisateur : les commandes \u00ab pr\u00e9c\u00e9dent \u00bb et \u00ab suivant \u00bb, comme dans l'exemple ci-dessus, le bouton \u00ab charger plus \u00bb, qui ajoute simplement un nouveau lot aux r\u00e9sultats affich\u00e9s, la \u00ab d\u00e9filement infini \u00bb, qui fonctionne selon le principe de \u00ab charger plus \u00bb, mais le signal pour obtenir le prochain lot est le d\u00e9filement par l'utilisateur de tous les r\u00e9sultats affich\u00e9s jusqu'\u00e0 la fin. Quelle que soit la solution visuelle, le principe d'extraction des donn\u00e9es reste le m\u00eame.<\/p>\n<h2>D\u00e9tails de la mise en \u0153uvre de la pagination<\/h2>\n<p>\nDans tous les exemples de requ\u00eates donn\u00e9s ci-dessus, nous utilisons l'approche \u00ab d\u00e9calage + nombre \u00bb, o\u00f9 la requ\u00eate elle-m\u00eame indique \u00e0 partir de quel num\u00e9ro de ligne de r\u00e9sultat et combien de lignes retourner. Commen\u00e7ons par examiner comment organiser au mieux le passage des param\u00e8tres dans ce cas. Dans la pratique, j'ai rencontr\u00e9 plusieurs m\u00e9thodes :<\/p>\n<ul>\n<li>Num\u00e9ro de page demand\u00e9 (pageIndex), taille de la page (pageSize).<\/li>\n<li>Num\u00e9ro du premier enregistrement \u00e0 retourner (startIndex), nombre maximum d'enregistrements dans le r\u00e9sultat (count).<\/li>\n<li>Num\u00e9ro du premier enregistrement \u00e0 retourner (startIndex), num\u00e9ro du dernier enregistrement \u00e0 retourner (endIndex).<\/li>\n<\/ul>\n<p>\n\u00c0 premi\u00e8re vue, cela peut sembler si simple qu'il n'y a pas de diff\u00e9rence. Mais ce n'est pas le cas \u2014 la solution la plus pratique et universelle est la deuxi\u00e8me (startIndex, count). Cela s'explique par plusieurs raisons :<\/p>\n<ul>\n<li>Pour l'approche utilisant la lecture d'un enregistrement suppl\u00e9mentaire, le premier choix avec pageIndex et pageSize est extr\u00eamement peu pratique. Par exemple, si nous voulons afficher 50 enregistrements par page. Selon l'algorithme ci-dessus, il faut lire un enregistrement de plus que n\u00e9cessaire. Si ce \u00ab +1 \u00bb n'est pas pris en compte c\u00f4t\u00e9 serveur, cela signifie que pour la premi\u00e8re page, nous devrions demander les enregistrements de 1 \u00e0 51, pour la deuxi\u00e8me \u2014 de 51 \u00e0 101, etc. Si nous sp\u00e9cifions une taille de page de 51 et augmentons le pageIndex, la deuxi\u00e8me page renverra de 52 \u00e0 102, etc. Par cons\u00e9quent, dans la premi\u00e8re option, le seul moyen de mettre en \u0153uvre correctement un bouton pour passer \u00e0 la page suivante est de pr\u00e9voir c\u00f4t\u00e9 serveur la lecture de la ligne \u00ab suppl\u00e9mentaire \u00bb, ce qui est une nuance tr\u00e8s obscure.<\/li>\n<li>La troisi\u00e8me option n'a absolument aucun sens, car pour ex\u00e9cuter des requ\u00eates dans la plupart des bases de donn\u00e9es, il faudra quand m\u00eame transmettre le nombre d'enregistrements et non l'index du dernier enregistrement. Bien que soustraire startIndex de endIndex soit une op\u00e9ration arithm\u00e9tique \u00e9l\u00e9mentaire, elle est ici superflue.<\/li>\n<\/ul>\n<p>\nIl est maintenant n\u00e9cessaire de d\u00e9crire les inconv\u00e9nients de l'impl\u00e9mentation du paging via \u00ab d\u00e9calage + quantit\u00e9 \u00bb :<\/p>\n<ul>\n<li>Obtenir chaque page suivante sera plus co\u00fbteux et plus lent que la pr\u00e9c\u00e9dente, car la base de donn\u00e9es devra quand m\u00eame parcourir tous les enregistrements \u00ab depuis le d\u00e9but \u00bb selon les crit\u00e8res de recherche et de tri, puis s'arr\u00eater sur le segment requis.<\/li>\n<li>Toutes les SGBD ne peuvent pas prendre en charge cette approche.<\/li>\n<\/ul>\n<p>\nIl existe des alternatives, mais elles ne sont pas non plus id\u00e9ales. La premi\u00e8re de ces approches est appel\u00e9e \u00ab keyset paging \u00bb ou \u00ab m\u00e9thode de recherche \u00bb et consiste \u00e0 ce qui suit : apr\u00e8s avoir obtenu un lot, vous pouvez m\u00e9moriser les valeurs des champs dans le dernier enregistrement de la page, puis les utiliser pour obtenir le lot suivant. Par exemple, nous avons ex\u00e9cut\u00e9 une telle requ\u00eate :<\/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>\nEt dans la derni\u00e8re entr\u00e9e, nous avons obtenu la date de commande '2014-06-29'. Pour acc\u00e9der \u00e0 la page suivante, on peut essayer d'ex\u00e9cuter ce qui suit :<\/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>\nLe probl\u00e8me est que OrderDate n'est pas un champ unique et la condition mentionn\u00e9e ci-dessus risquerait de manquer beaucoup de lignes n\u00e9cessaires. Pour rendre cette requ\u00eate unique, il faut ajouter un champ unique \u00e0 la condition (supposons que 75074 est la derni\u00e8re valeur de la cl\u00e9 primaire de la premi\u00e8re portion) :<\/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>\nCette option fonctionnera correctement, mais en g\u00e9n\u00e9ral, elle sera difficile \u00e0 optimiser, car la condition contient l'op\u00e9rateur OR. Si, avec l'augmentation de OrderDate, la valeur de la cl\u00e9 primaire augmente, la condition peut \u00eatre simplifi\u00e9e en ne conservant que le filtre par SalesOrderID. Mais s'il n'y a pas de corr\u00e9lation stricte entre les valeurs de la cl\u00e9 primaire et le champ par lequel le r\u00e9sultat est tri\u00e9, il sera impossible d'\u00e9viter cet OR dans la plupart des SGBD. Une exception connue est PostgreSQL, qui prend pleinement en charge la comparaison de tuples, et la condition mentionn\u00e9e ci-dessus peut \u00eatre \u00e9crite comme \u00ab WHERE (OrderDate, SalesOrderID) &lt; (&#039;2014-06-29&#039;, 75074) \u00bb. Avec une cl\u00e9 composite contenant ces deux champs, une telle requ\u00eate devrait \u00eatre assez l\u00e9g\u00e8re.<\/p>\n<p>Une deuxi\u00e8me approche alternative peut \u00eatre rencontr\u00e9e, par exemple, dans <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 o\u00f9 la requ\u00eate renvoie, en plus des donn\u00e9es, un identifiant sp\u00e9cial qui permet d'obtenir la prochaine portion de donn\u00e9es. Si cet identifiant a une dur\u00e9e de vie illimit\u00e9e (comme dans Cosmos DB), cela constitue un excellent moyen de r\u00e9aliser un pagination avec un passage s\u00e9quentiel entre les pages (la variante #2 mentionn\u00e9e ci-dessus). Ses possibles inconv\u00e9nients : elle n'est pas prise en charge dans tous les SGBD ; l'identifiant de la prochaine portion peut avoir une dur\u00e9e de vie limit\u00e9e, ce qui, en g\u00e9n\u00e9ral, ne convient pas pour r\u00e9aliser une interaction avec l'utilisateur (comme, par exemple, l'ElasticSearch scroll API).<\/p>\n<h2>Filtrage complexe<\/h2>\n<p>\nNous compliquons encore la t\u00e2che. Supposons qu'il y ait une exigence de r\u00e9aliser ce qu'on appelle une recherche facett\u00e9e, famili\u00e8re \u00e0 tous dans les magasins en ligne. Les exemples ci-dessus bas\u00e9s sur la table des commandes ne sont pas tr\u00e8s repr\u00e9sentatifs dans ce cas, donc passons \u00e0 la table Product de la base AdventureWorks :<\/p>\n<p><img decoding=\"async\" alt=\"Affichage des r\u00e9sultats de recherche et probl\u00e8mes de performance\" src=\"\/wp-content\/uploads\/2020\/03\/070890a3cb79de3edc69b60b5300efe7.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nQuelle est l'id\u00e9e de la recherche facett\u00e9e ? Que pour chaque \u00e9l\u00e9ment de filtre, le nombre d'enregistrements correspondant \u00e0 ce crit\u00e8re soit affich\u00e9. <i>en tenant compte des filtres s\u00e9lectionn\u00e9s dans toutes les autres cat\u00e9gories.<\/i>.<\/p>\n<p>Par exemple, si nous s\u00e9lectionnons dans cet exemple la cat\u00e9gorie Bikes et la couleur Black, la table n'affichera que les v\u00e9los de couleur noire, mais en m\u00eame temps :<\/p>\n<ul>\n<li>Pour chaque crit\u00e8re du groupe \u00ab Categories \u00bb, le nombre de produits de cette cat\u00e9gorie de couleur noire sera affich\u00e9.<\/li>\n<li>Pour chaque crit\u00e8re du groupe \u00ab Colors \u00bb, le nombre de v\u00e9los de cette couleur sera affich\u00e9.<\/li>\n<\/ul>\n<p>\nVoici un exemple de sortie des r\u00e9sultats pour de telles conditions :<\/p>\n<p><img decoding=\"async\" alt=\"Affichage des r\u00e9sultats de recherche et probl\u00e8mes de performance\" src=\"\/wp-content\/uploads\/2020\/03\/7c8ec4d8b427f1f59f0129afebe74b08.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nSi en plus on coche la cat\u00e9gorie \u00ab Clothing \u00bb, la table affichera \u00e9galement des v\u00eatements de couleur noire disponibles. Le nombre de produits de couleur noire dans la section \u00ab Color \u00bb sera \u00e9galement recalcul\u00e9 selon les nouvelles conditions, mais rien ne changera dans la section \u00ab Categories \u00bb... J'esp\u00e8re que ces exemples suffisent pour comprendre l'algorithme habituel de fonctionnement de la recherche facett\u00e9e.<\/p>\n<p>Imaginons maintenant comment cela peut \u00eatre r\u00e9alis\u00e9 sur une base de donn\u00e9es relationnelle. Chaque groupe de crit\u00e8res, comme Category et Color, n\u00e9cessitera une requ\u00eate distincte :<\/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=\"Affichage des r\u00e9sultats de recherche et probl\u00e8mes de performance\" 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=\"Affichage des r\u00e9sultats de recherche et probl\u00e8mes de performance\" src=\"\/wp-content\/uploads\/2020\/03\/82e1e6ab0fae683d383e3fecf4e3a15a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nQu'est-ce qui ne va pas avec cette solution ? C'est tr\u00e8s simple : elle ne se scalpe pas bien. Chaque section de filtre n\u00e9cessite une requ\u00eate distincte pour compter les quantit\u00e9s et ces requ\u00eates ne sont pas les plus l\u00e9g\u00e8res. Dans les magasins en ligne, certaines cat\u00e9gories peuvent avoir plusieurs dizaines de sections de filtre, ce qui peut poser un probl\u00e8me s\u00e9rieux de performance.<\/p>\n<p>G\u00e9n\u00e9ralement, apr\u00e8s ces d\u00e9clarations, on me propose certaines solutions, \u00e0 savoir :<\/p>\n<ul>\n<li>Fusionner tous les comptages en une seule requ\u00eate. Techniquement, cela est possible avec le mot-cl\u00e9 UNION, mais cela n'aidera pas beaucoup en termes de performance \u2014 la base de donn\u00e9es devra tout de m\u00eame ex\u00e9cuter chaque fragment \u00ab \u00e0 partir de z\u00e9ro \u00bb.<\/li>\n<li>Mettre en cache les quantit\u00e9s. C'est ce que l'on me sugg\u00e8re presque \u00e0 chaque fois que je d\u00e9cris le probl\u00e8me. Le d\u00e9tail, c'est que cela est en g\u00e9n\u00e9ral impossible. Supposons que nous ayons 10 \u00ab facettes \u00bb, chacune contenant 5 valeurs. C'est une situation tr\u00e8s \u00ab modeste \u00bb par rapport \u00e0 ce que l'on peut voir dans les boutiques en ligne. Le choix d'un \u00e9l\u00e9ment de facette influence les quantit\u00e9s des 9 autres, en d'autres termes, pour chaque combinaison de crit\u00e8res, les quantit\u00e9s peuvent \u00eatre diff\u00e9rentes. Au total dans notre exemple, il y a 50 crit\u00e8res que l'utilisateur peut s\u00e9lectionner, donc le nombre de combinaisons possibles sera de 250. Il n'y aurait pas assez de m\u00e9moire ni de temps pour remplir un tel tableau de donn\u00e9es. On peut objecter que toutes les combinaisons ne sont pas r\u00e9alistes et qu'un utilisateur ne choisit que rarement plus de 5 \u00e0 10 crit\u00e8res. Oui, on peut mettre en place un chargement paresseux et un cache des quantit\u00e9s uniquement pour ce qui a d\u00e9j\u00e0 \u00e9t\u00e9 s\u00e9lectionn\u00e9, mais plus il y aura d'options de choix, moins ce cache sera efficace et plus les probl\u00e8mes de temps de r\u00e9ponse seront perceptibles (surtout si le jeu de donn\u00e9es change r\u00e9guli\u00e8rement).<\/li>\n<\/ul>\n<p>\nHeureusement, ce type de probl\u00e8me poss\u00e8de depuis longtemps des solutions assez efficaces qui fonctionnent de mani\u00e8re pr\u00e9visible sur de grands volumes de donn\u00e9es. Pour chacune de ces options, il est judicieux de diviser le recalcul des facettes et l'obtention de la page de r\u00e9sultats en deux appels parall\u00e8les au serveur et d'organiser l'interface utilisateur de fa\u00e7on \u00e0 ce que le chargement des donn\u00e9es concernant les facettes \u00ab ne g\u00eane pas \u00bb l'affichage des r\u00e9sultats de recherche.<\/p>\n<ul>\n<li>Appeler un recalcul complet des \u00ab facettes \u00bb le moins possible. Par exemple, ne pas recalculer tout \u00e0 chaque changement des crit\u00e8res de recherche, mais plut\u00f4t trouver le nombre total de r\u00e9sultats correspondant aux conditions actuelles et proposer \u00e0 l'utilisateur de les afficher \u2014 \u00ab 1425 enregistrements trouv\u00e9s, afficher ? \u00bb L'utilisateur peut soit continuer \u00e0 modifier les conditions de recherche, soit cliquer sur le bouton \u00ab afficher \u00bb. Ce n'est que dans le deuxi\u00e8me cas que toutes les requ\u00eates pour obtenir des r\u00e9sultats et recalculer les quantit\u00e9s pour toutes les \u00ab facettes \u00bb seront ex\u00e9cut\u00e9es. Il est \u00e9vident que cela n\u00e9cessite de g\u00e9rer la requ\u00eate pour obtenir le nombre total de r\u00e9sultats et son optimisation. Cette m\u00e9thode peut \u00eatre rencontr\u00e9e dans de nombreux petits magasins en ligne. \u00c9videmment, ce n'est pas une panac\u00e9e pour ce probl\u00e8me, mais dans des cas simples, cela peut \u00eatre un bon compromis.<\/li>\n<li>Utiliser un moteur de recherche pour trouver des r\u00e9sultats et compter les facettes, comme Solr, ElasticSearch, Sphinx et autres. Tous sont con\u00e7us pour construire des \u00ab facettes \u00bb et le font assez efficacement gr\u00e2ce \u00e0 un index invers\u00e9. Comment fonctionnent les moteurs de recherche, pourquoi ils sont plus efficaces dans ce cas que les bases de donn\u00e9es g\u00e9n\u00e9ralistes, quelles sont les pratiques et les \u00e9cueils \u2014 c'est un sujet pour un article s\u00e9par\u00e9. Ici, je veux souligner que le moteur de recherche ne peut pas remplacer le principal d\u00e9p\u00f4t de donn\u00e9es, il est utilis\u00e9 comme un compl\u00e9ment : tout changement dans la base de donn\u00e9es principale, pertinent pour la recherche, est synchronis\u00e9 avec l'index de recherche ; le m\u00e9canisme de recherche interagit g\u00e9n\u00e9ralement uniquement avec le moteur de recherche et ne s'adresse pas \u00e0 la base principale. L'un des points les plus importants ici est comment organiser cette synchronisation de mani\u00e8re fiable. Tout d\u00e9pend des exigences en mati\u00e8re de \u00ab temps de r\u00e9ponse \u00bb. Si le temps entre un changement dans la base principale et sa \u00ab manifestation \u00bb dans la recherche n'est pas critique, on peut cr\u00e9er un service qui, toutes les quelques minutes, cherche les enregistrements r\u00e9cemment modifi\u00e9s et les indexe. Si un temps de r\u00e9ponse minimale est requise, on peut mettre en \u0153uvre quelque chose comme <noindex><a rel=\"nofollow\" href=\"https:\/\/microservices.io\/patterns\/data\/transactional-outbox.html\">outbox transactionnelle<\/a><\/noindex> pour envoyer des mises \u00e0 jour au service de recherche.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Conclusions<\/h2>\n<p><\/p>\n<ol>\n<li>La mise en \u0153uvre du paging c\u00f4t\u00e9 serveur est une complexit\u00e9 s\u00e9rieuse, et elle n'a de sens que pour des ensembles de donn\u00e9es en forte croissance ou simplement volumineux. Il n'existe pas de recette pr\u00e9cise pour \u00e9valuer ce qui est \u00ab grand \u00bb ou \u00ab en forte croissance \u00bb, mais je suivrais une telle approche :\n<ul>\n<li>Si l'obtention de l'ensemble complet des donn\u00e9es, en tenant compte du temps serveur et de la transmission r\u00e9seau, respecte normalement les exigences de performance, il n'est pas utile de mettre en \u0153uvre le paging c\u00f4t\u00e9 serveur.<\/li>\n<li>Il peut arriver qu'\u00e0 court terme, il n'y ait pas de probl\u00e8mes de performance, car il y a peu de donn\u00e9es, mais que la collection de donn\u00e9es soit en constante augmentation. Si un certain ensemble de donn\u00e9es risque \u00e0 l'avenir de ne plus satisfaire le point pr\u00e9c\u00e9dent, il est pr\u00e9f\u00e9rable de pr\u00e9voir le paging d\u00e8s le d\u00e9part.<\/li>\n<\/ul>\n<\/li>\n<li>S'il n'y a pas d'exigence stricte de la part des affaires concernant l'affichage du nombre total de r\u00e9sultats ou des num\u00e9ros de pages, et que votre syst\u00e8me n'a pas de moteur de recherche, il vaut mieux ne pas mettre en \u0153uvre ces \u00e9l\u00e9ments et consid\u00e9rer l'option n\u00b02.<\/li>\n<li>S'il existe une exigence claire concernant la recherche par facettes, vous avez deux options pour ne pas compromettre la performance :\n<ul>\n<li>Ne pas recalculer tous les quantit\u00e9s \u00e0 chaque modification des crit\u00e8res de recherche.<\/li>\n<li>Utiliser des moteurs de recherche tels que Solr, ElasticSearch, Sphinx et autres. Mais il faut comprendre qu'il ne peut pas remplacer la base de donn\u00e9es principale et doit \u00eatre utilis\u00e9 comme un compl\u00e9ment au stockage principal pour r\u00e9soudre des probl\u00e8mes de recherche. <\/li>\n<\/ul>\n<\/li>\n<li>Dans le cas de la recherche par facettes, il est \u00e9galement judicieux de s\u00e9parer l'obtention de la page des r\u00e9sultats de recherche et le comptage des quantit\u00e9s en deux requ\u00eates parall\u00e8les. Le comptage des quantit\u00e9s peut prendre plus de temps que l'obtention des r\u00e9sultats, tandis que les r\u00e9sultats sont plus importants pour l'utilisateur.<\/li>\n<li>Si vous utilisez une base de donn\u00e9es SQL pour la recherche, tout changement de code li\u00e9 \u00e0 cette partie doit \u00eatre soigneusement test\u00e9 pour la performance sur un volume de donn\u00e9es appropri\u00e9 (sup\u00e9rieur \u00e0 celui de la base \u00ab en direct \u00bb). Il est \u00e9galement souhaitable d'utiliser un monitoring du temps d'ex\u00e9cution des requ\u00eates sur tous les instances de la base, et surtout \u2014 sur la base \u00ab en direct \u00bb. M\u00eame si, au stage de d\u00e9veloppement, les plans de requ\u00eates semblaient bons, la situation peut changer consid\u00e9rablement avec l'augmentation des donn\u00e9es.<\/li>\n<\/ol>\n<p>Source : <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.2 - 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\/fr\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.2\" \/>\n\t\t<meta property=\"og:locale\" content=\"fr_FR\" \/>\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\/fr\/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\udd47Affichage des r\u00e9sultats de recherche et probl\u00e8mes de performance | ProHoster","description":"Un des sc\u00e9narios typiques dans toutes les applications que nous connaissons est la recherche de donn\u00e9es selon certains crit\u00e8res et leur affichage dans un format lisible.","canonical_url":"https:\/\/prohoster.info\/fr\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"fr_FR","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\/fr\/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\/fr\/wp-json\/wp\/v2\/posts\/75530","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/comments?post=75530"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/posts\/75530\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/media\/75531"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/media?parent=75530"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/categories?post=75530"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/fr\/wp-json\/wp\/v2\/tags?post=75530"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}