{"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\/en\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu","title":{"rendered":"Search results output and performance issues","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>One of the typical scenarios in all the applications we are familiar with is searching for data based on specific criteria and displaying it in a readable format. There may also be additional options for sorting, grouping, and pagination. The task, in theory, is trivial, but many developers make a number of mistakes in solving it, which then affects performance. Let's explore various solutions to this problem and formulate recommendations for choosing the most efficient implementation.<\/p>\n<p><img decoding=\"async\" alt=\"Search results output and performance issues\" 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>Paging Option #1<\/h2>\n<p>\nThe simplest option that comes to mind is to paginate search results in its most classic form.<\/p>\n<p><img decoding=\"async\" alt=\"Search results output and performance issues\" src=\"\/wp-content\/uploads\/2020\/03\/4957d9ad21a8a0c70390b224fe76cb33.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nSuppose a relational database is used in the application. In this case, to output the information in such a form, two SQL queries will need to be executed:<\/p>\n<ul>\n<li>Retrieve the rows for the current page.<\/li>\n<li>Count the total number of rows that match the search criteria \u2014 this is needed for displaying pages.<\/li>\n<\/ul>\n<p>\nLet's consider the first query using a test MS SQL database. <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/Microsoft\/sql-server-samples\/releases\/download\/adventureworks\/AdventureWorks2016_EXT.bak\">AdventureWorks <\/a><\/noindex>for the 2016 server. For this purpose, we will use the Sales.SalesOrderHeader table:<\/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>\nThe above query will output the first 50 orders from the list, sorted by the descending date of addition, in other words \u2014 the 50 most recent orders.<\/p>\n<p>It executes quickly on the test database, but let's look at the execution plan and I\/O statistics:<\/p>\n<p><img decoding=\"async\" alt=\"Search results output and performance issues\" 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>You can get the I\/O statistics for each query by executing the command SET STATISTICS IO ON in the query execution environment.<\/i><\/p>\n<p>As seen from the execution plan, the most resource-intensive operation is sorting all the rows of the source table by the date of addition. The issue is that the more rows appear in the table, the 'heavier' the sorting will be. In practice, such situations should be avoided, so we will add an index on the date of addition and see if resource consumption has changed:<\/p>\n<p><img decoding=\"async\" alt=\"Search results output and performance issues\" 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>\nClearly, things have improved significantly. But have all the issues been resolved? Let's modify the order search query to find orders where the total value of goods exceeds 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=\"Search results output and performance issues\" src=\"\/wp-content\/uploads\/2020\/03\/e057bfc89e11a31c6d2855b93ec3c747.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Table 'SalesOrderHeader'. Scan count 1, logical reads 1081, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.<\/code><\/pre>\n<p>\nWe have an amusing situation: the query plan is not much worse than the previous one, but the actual number of logical reads is almost twice as high as with a full table scan. There is a solution \u2014 if we create a composite index from the existing index and add the total product price as a second field, we'll get back to 165 logical reads:<\/p>\n<pre><code class=\"sql\">CREATE INDEX IX_SalesOrderHeader_OrderDate_SubTotal on Sales.SalesOrderHeader(OrderDate, SubTotal);\n<\/code><\/pre>\n<p>\nThis series of examples can go on for a long time, but the two main points I want to express here are:<\/p>\n<ul>\n<li>Adding any new criteria or sorting order to a search query can significantly affect its execution speed.<\/li>\n<li>But if we only need to read a part of the data, not all results that meet the search criteria \u2014 there are many ways to optimize such a query.<\/li>\n<\/ul>\n<p>\nNow, let's move on to the second query mentioned at the very beginning \u2014 the one that counts the number of records that meet the search criteria. We'll take the same example \u2014 searching for orders that cost more than 100 dollars:<\/p>\n<pre><code class=\"sql\">SELECT COUNT(1) FROM Sales.SalesOrderHeader\nWHERE SubTotal &gt; 100\n<\/code><\/pre>\n<p>\nWith the composite index mentioned above, we get:<\/p>\n<p><img decoding=\"async\" alt=\"Search results output and performance issues\" 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>\nIt\u2019s not surprising that the query goes through the entire index, as the SubTotal field is not in the first position, so the query cannot benefit from it. The problem is solved by adding another index on the SubTotal field, resulting in only 48 logical reads.<\/p>\n<p>We can provide several more examples of count queries, but the essence remains the same: <b>fetching a portion of data and counting the total number \u2014 these are two fundamentally different queries<\/b>, and each requires its own measures for optimization. In general, it will not be possible to find a combination of indexes that works equally well for both queries.<\/p>\n<p>Accordingly, one of the important requirements to clarify when developing such a search solution is whether it is really important for the business to see the total number of found objects. Often, it is not. And navigating through specific page numbers, in my opinion, is a solution with a very narrow application area, as most paging scenarios look like 'go to the next page.'<\/p>\n<h2>Paging Option #2<\/h2>\n<p>\nLet's assume users do not need to know the total number of found objects. We will try to simplify the search page:<\/p>\n<p><img decoding=\"async\" alt=\"Search results output and performance issues\" src=\"\/wp-content\/uploads\/2020\/03\/66f5ee6dc1a52f14e55ae31e05c502b2.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nIn fact, the only change is that it is no longer possible to go to specific page numbers, and now this table does not need to know how many there might be in total. But the question arises \u2014 how does the table know if there is data for the next page (to correctly display the 'Next' link)?<\/p>\n<p>The answer is very simple: you can read from the database one record more than needed for display, and the presence of this 'extra' record will indicate whether there is the next batch. Thus, to obtain one page of data, you only need to perform a single query, which significantly improves performance and simplifies the support of such functionality. In my practice, there was a case when abandoning the count of total records accelerated result output by 4-5 times.<\/p>\n<p>For this approach, there are several user interface options: 'back' and 'forward' buttons, like in the example above, a 'load more' button that simply adds a new batch to the displayed results, and 'infinite scrolling', which works on the principle of 'load more' but signals to retrieve the next batch when the user scrolls through all the displayed results to the end. Whatever the visual solution, the principle of data selection remains the same.<\/p>\n<h2>Subtlety of Paging Implementation<\/h2>\n<p>\nIn all the request examples provided above, the 'offset + count' approach is used, where the request specifies the order of the result rows and how many rows need to be returned. First, let's consider how to best organize the parameter transmission in this case. In practice, I have encountered several methods:<\/p>\n<ul>\n<li>The sequential number of the requested page (pageIndex) and the page size (pageSize).<\/li>\n<li>The sequential number of the first record to return (startIndex) and the maximum number of records in the result (count).<\/li>\n<li>The sequential number of the first record to return (startIndex) and the sequential number of the last record to return (endIndex).<\/li>\n<\/ul>\n<p>\nAt first glance, it may seem so elementary that there is no difference. However, this is not the case \u2014 the most convenient and universal option is the second one (startIndex, count). There are several reasons for this:<\/p>\n<ul>\n<li>For the approach with reading +1 record, as mentioned above, the first option with pageIndex and pageSize is extremely inconvenient. For example, we want to display 50 records on a page. According to the algorithm described above, we need to read one record more than necessary. If this '+1' is not accounted for on the server, it results in needing to request records from 1 to 51 for the first page, from 51 to 101 for the second page, and so on. If we specify a page size of 51 and increase pageIndex, the second page will return records from 52 to 102, and so forth. Thus, in the first option, the only way to properly implement the button for moving to the next page is to account for the 'extra' line on the server, which is a very implicit detail.<\/li>\n<li>The third option makes no sense at all, as executing queries in most databases will still require the amount, not the index of the last record to be passed. Although subtracting startIndex from endIndex is a basic arithmetic operation, it is superfluous here.<\/li>\n<\/ul>\n<p>\nNow it is time to describe the drawbacks of implementing paging through 'offset + count':<\/p>\n<ul>\n<li>Fetching each subsequent page will be more costly and slower than the previous one because the database will still need to traverse all records 'from the beginning' according to the search and sort criteria, and then stop at the necessary fragment.<\/li>\n<li>Not all DBMS can support this approach.<\/li>\n<\/ul>\n<p>\nThere are alternatives, but they are not ideal either. The first of these approaches is called 'keyset paging' or 'seek method' and is as follows: after fetching a batch, one can remember the field values in the last record on the page and then use them to fetch the next batch. For instance, we executed such a request:<\/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>\nIn the last entry, we obtained the order date value '2014-06-29'. Then to get the next page, we can attempt to execute the following:<\/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>\nThe issue is that OrderDate is not a unique field, and the condition mentioned above is likely to skip many necessary rows. To add clarity to this query, we need to add a unique field to the condition (let's assume that 75074 is the last primary key value from the first batch):<\/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>\nThis option will work correctly, but it will generally be difficult to optimize, as the condition contains an OR operator. If the primary key value increases with the growth of OrderDate, the condition can be simplified by leaving only the filter by SalesOrderID. However, if there is no strict correlation between the primary key values and the field by which the result is sorted, it will generally not be possible to avoid this OR in most DBMSs. The only exception I know is PostgreSQL, where tuple comparison is fully supported, and the condition above can be written as \"WHERE (OrderDate, SalesOrderID) &lt; (&#039;2014-06-29&#039;, 75074)&quot;. When there is a composite key with these two fields, such a query should be quite lightweight.<\/p>\n<p>A second alternative approach can be found, for example, in <noindex><a rel=\"nofollow\" href=\"https:\/\/www.elastic.co\/guide\/en\/elasticsearch\/reference\/6.8\/search-request-scroll.html\">ElasticSearch scroll API<\/a><\/noindex> or <noindex><a rel=\"nofollow\" href=\"https:\/\/medium.com\/@gary.strange\/understanding-cosmosdb-continuation-tokens-hasmoreresults-and-connectionpolicy-requesttimeouts-3ed1fadfa81d\">Cosmos DB<\/a><\/noindex> \u2014 where the query, in addition to data, returns a special identifier, which can be used to retrieve the next batch of data. If this identifier has an unlimited lifespan (as in Cosmos DB), it is an excellent way to implement paging with sequential navigation between pages (option #2 mentioned above). Its potential downsides include: it is not supported by all DBMS; the retrieved identifier for the next batch may have a limited lifespan, which generally does not suit user interaction (as, for example, in the ElasticSearch scroll API).<\/p>\n<h2>Complex filtering<\/h2>\n<p>\nLet's complicate the task further. Suppose there is a requirement to implement what is known as faceted search, something that is well-known from online stores. The examples above based on the orders table are not very illustrative in this case, so let's switch to the Product table from the AdventureWorks database:<\/p>\n<p><img decoding=\"async\" alt=\"Search results output and performance issues\" src=\"\/wp-content\/uploads\/2020\/03\/070890a3cb79de3edc69b60b5300efe7.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nWhat is the idea behind faceted search? It is that for each filter element, the number of records corresponding to that criterion is shown. <i>taking into account the filters chosen in all other categories.<\/i>.<\/p>\n<p>For example, if we select in this case the category Bikes and the color Black, the table will display only black bicycles, but at the same time:<\/p>\n<ul>\n<li>For each criterion in the 'Categories' group, the number of products from this category in black color will be shown.<\/li>\n<li>For each criterion in the 'Colors' group, the number of bicycles of this color will be displayed.<\/li>\n<\/ul>\n<p>\nHere is an example of the results output for such conditions:<\/p>\n<p><img decoding=\"async\" alt=\"Search results output and performance issues\" src=\"\/wp-content\/uploads\/2020\/03\/7c8ec4d8b427f1f59f0129afebe74b08.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAdditionally, if the 'Clothing' category is marked, the table will also show black clothing that is in stock. The number of black products in the 'Color' section will also be recalculated according to the new conditions, but nothing will change in the 'Categories' section... I hope these examples are sufficient to understand the familiar algorithm of how faceted search works.<\/p>\n<p>Now let's imagine how this can be implemented on a relational database. Each group of criteria, such as Category and Color, will require a separate query:<\/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=\"Search results output and performance issues\" 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=\"Search results output and performance issues\" src=\"\/wp-content\/uploads\/2020\/03\/82e1e6ab0fae683d383e3fecf4e3a15a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nSo, what is wrong with this solution? Quite simply \u2014 it doesn't scale well. Each filter section requires a separate query to count the quantities, and these queries are not the lightest. In online stores, there can be several dozen filter sections in some categories, which can become a serious performance issue.<\/p>\n<p>Usually, after these statements, I am offered some solutions, namely:<\/p>\n<ul>\n<li>Combine all quantity counts into a single query. Technically, this is possible using the UNION keyword, but it won't significantly improve performance \u2013 the database will still have to execute each fragment \"from scratch.\"<\/li>\n<li>Cache quantities. This is suggested to me practically every time I describe the problem. The nuance is that this is generally impossible. Suppose we have 10 \"facets,\" each with 5 values. This is a very \"modest\" situation compared to what can be seen in online stores. Choosing one facet item affects the quantities in the other 9; in other words, for every combination of criteria, the quantities can be different. In total, in our example, there are 50 criteria that users can select, which means there could be 250 possible combinations. There won't be enough memory or time to fill such a data array. One might argue that not all combinations are real and that users rarely choose more than 5-10 criteria. Yes, lazy loading and caching quantities only for what has ever been selected can be done, but the more options there are, the less effective such caching will be, and the more noticeable the response time problems will become (especially if the data set is regularly changing).<\/li>\n<\/ul>\n<p>\nFortunately, this type of task has long had sufficiently effective solutions that work predictably with large volumes of data. For any of these options, it makes sense to separate facet recalculation and retrieving the results page into two parallel requests to the server and to organize the user interface in such a way that loading facet data \"does not interfere\" with displaying search results.<\/p>\n<ul>\n<li>Triggering a full recalculation of facets should be done as infrequently as possible. For instance, not recalculating everything with every change in search criteria, but rather finding the total number of results that match the current conditions and offering the user to display them \u2014 \"1425 records found, show?\" The user can either continue changing the search conditions or click the \"show\" button. It's only in the latter case that all requests for retrieving results and recalculating counts for all facets will be executed. Notably, this requires dealing with a request for obtaining the total number of results and optimizing it. This method can be found in many small online stores. Clearly, it's not a panacea for this problem, but it can serve as a decent compromise in simple cases.<\/li>\n<li>Use search engines for retrieving results and counting facets, such as Solr, ElasticSearch, Sphinx, and others. All of them are designed for building facets and do so quite effectively through inverted indexing. How search engines are structured, why they are more efficient in such cases compared to general-purpose databases, what best practices and pitfalls exist \u2014 this is a topic for a separate article. Here, I want to emphasize that a search engine cannot replace the main data storage; it is used as an addition: any changes in the main database that matter for search are synchronized into the search index; the search mechanism typically interacts only with the search engine and does not query the main database. One of the most important aspects here is how to organize this synchronization reliably. Everything depends on the requirements for \"response time.\" If the time between a change in the main database and its \"manifestation\" in search is not critical, a service can be set up that checks for recently modified records and indexes them every few minutes. If a minimally possible response time is required, something like <noindex><a rel=\"nofollow\" href=\"https:\/\/microservices.io\/patterns\/data\/transactional-outbox.html\">transactional outbox<\/a><\/noindex> for sending updates to the search service.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Conclusions<\/h2>\n<p><\/p>\n<ol>\n<li>Implementing server-side paging is a significant complication and should only be applied for rapidly growing or simply large datasets. There is no absolute recipe for assessing what constitutes 'large' or 'rapidly growing', but I would adhere to this approach:\n<ul>\n<li>If obtaining the complete collection of data, considering server time and network transmission, fits within performance requirements, there is no sense in implementing server-side paging.<\/li>\n<li>It's possible that for the near future, there may be no performance issues, as there is little data, but the dataset is continuously growing. If a certain dataset may soon fail to meet the previous point, it's better to establish paging from the start.<\/li>\n<\/ul>\n<\/li>\n<li>If there is no strict business requirement for displaying the total number of results or page numbers, and your system lacks a search engine, it's best not to implement these aspects and consider option #2.<\/li>\n<li>If there is a clear requirement for faceted search, you have two options to avoid sacrificing performance:\n<ul>\n<li>Do not recalculate all counts with every change in search criteria.<\/li>\n<li>Use search engines such as Solr, ElasticSearch, Sphinx, and others. However, it's essential to understand that they cannot replace the main database and should be used as a complement to the primary storage for solving search tasks. <\/li>\n<\/ul>\n<\/li>\n<li>In the case of faceted search, it also makes sense to separate obtaining the search results page and counting quantities into two parallel queries. The counting of quantities may take longer than retrieving results, while results are more crucial for the user.<\/li>\n<li>If you are using an SQL database for search, any code changes related to this part should be well-tested concerning performance on the corresponding data volume (exceeding the volume in the 'live' database). It is also advisable to monitor query execution times on all database instances, especially on 'live'. Even if everything was good during the development stage with query plans, the situation may change significantly with the growing data volume.<\/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\/en\/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=\"en_US\" \/>\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\/en\/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\udd47Search Result Output and Performance Issues | ProHoster","description":"One of the standard scenarios in all the applications we are used to is searching for data based on specific criteria and displaying it in a readable format.","canonical_url":"https:\/\/prohoster.info\/en\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"en_US","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\/en\/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\/en\/wp-json\/wp\/v2\/posts\/75530","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/comments?post=75530"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/posts\/75530\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/media\/75531"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/media?parent=75530"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/categories?post=75530"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/en\/wp-json\/wp\/v2\/tags?post=75530"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}