{"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\/nl\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu","title":{"rendered":"Zoekresultaten weergeven en prestatieproblemen.","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Een van de typische scenario's in alle gebruikelijke toepassingen is het zoeken naar gegevens op basis van bepaalde criteria en het weergeven daarvan in een leesbaar formaat. Hier kunnen ook extra mogelijkheden zijn voor sorteren, groeperen en paginering. De taak lijkt triviaal, maar bij het oplossen ervan maken veel ontwikkelaars een aantal fouten waardoor de prestaties in gevaar komen. Laten we verschillende oplossingen voor deze taak onderzoeken en aanbevelingen formuleren voor de keuze van de meest effici\u00ebnte implementatie.<\/p>\n<p><img decoding=\"async\" alt=\"Zoekresultaten weergeven en prestatieproblemen.\" 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>Paginering optie #1<\/h2>\n<p>\nDe eenvoudigste optie die in je opkomt, is het pagineren van de zoekresultaten in de meest klassieke vorm.<\/p>\n<p><img decoding=\"async\" alt=\"Zoekresultaten weergeven en prestatieproblemen.\" src=\"\/wp-content\/uploads\/2020\/03\/4957d9ad21a8a0c70390b224fe76cb33.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nStel dat er een relationele database in de applicatie wordt gebruikt. In dat geval moeten er twee SQL-query's worden uitgevoerd om informatie op deze manier weer te geven:<\/p>\n<ul>\n<li>Haal de rijen op voor de huidige pagina.<\/li>\n<li>Tel het totale aantal rijen dat aan de zoekcriteria voldoet - dit is nodig voor het tonen van pagina's.<\/li>\n<\/ul>\n<p>\nLaten we de eerste query bekijken aan de hand van een 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>voor de 2016 server. Voor dit doel gebruiken we de tabel 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>\nDe bovenstaande query zal de eerste 50 bestellingen uit de lijst weergeven, gesorteerd op de datum van toevoeging in aflopende volgorde, met andere woorden - de laatste 50 bestellingen.<\/p>\n<p>Deze wordt snel uitgevoerd op de testdatabase, maar laten we naar het uitvoeringplan en de invoer-\/uitvoerstatistieken kijken:<\/p>\n<p><img decoding=\"async\" alt=\"Zoekresultaten weergeven en prestatieproblemen.\" src=\"\/wp-content\/uploads\/2020\/03\/248e4b64593c7000216b46777183d639.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabel '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>Statistieken voor invoer\/uitvoer voor elke query kunnen worden verkregen door de opdracht SET STATISTICS IO ON in de query-uitvoeringsomgeving uit te voeren.<\/i><\/p>\n<p>Zoals blijkt uit het uitvoeringplan, is de meest resource-intensievere taak het sorteren van alle rijen in de oorspronkelijke tabel op datum van toevoeging. Het probleem is dat naarmate er meer rijen in de tabel komen, de sortering 'zwaarder' wordt. In de praktijk moeten dergelijke situaties worden vermeden, laten we daarom een index op de datum van toevoeging toevoegen en kijken of het resourceverbruik is veranderd:<\/p>\n<p><img decoding=\"async\" alt=\"Zoekresultaten weergeven en prestatieproblemen.\" src=\"\/wp-content\/uploads\/2020\/03\/a33e1eadf6f8f8b516b9bb834ad7ad86.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabel '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>\nHet is duidelijk dat het veel beter is geworden. Maar zijn alle problemen opgelost? Laten we de zoekopdracht aanpassen naar bestellingen waarbij de totale waarde van de artikelen meer dan 100 dollar bedraagt:<\/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=\"Zoekresultaten weergeven en prestatieproblemen.\" src=\"\/wp-content\/uploads\/2020\/03\/e057bfc89e11a31c6d2855b93ec3c747.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabel 'SalesOrderHeader'. Scan aantal 1, logische leesbewerkingen 1081, fysieke leesbewerkingen 0, read-ahead leesbewerkingen 0, lob logische leesbewerkingen 0, lob fysieke leesbewerkingen 0, lob read-ahead leesbewerkingen 0.<\/code><\/pre>\n<p>\nWe hebben een interessante situatie: het plan van de query is niet veel beter dan het vorige, maar het daadwerkelijke aantal logische leesbewerkingen is bijna twee keer zoveel als bij een volledige scan van de tabel. Er is een oplossing \u2014 als we van de al bestaande index een samengestelde index maken en de totale prijs van de artikelen als tweede veld toevoegen, krijgen we weer 165 logische leesbewerkingen:<\/p>\n<pre><code class=\"sql\">CREATE INDEX IX_SalesOrderHeader_OrderDate_SubTotal on Sales.SalesOrderHeader(OrderDate, SubTotal);\n<\/code><\/pre>\n<p>\nDeze reeks voorbeelden kan nog lang doorgaan, maar er zijn twee belangrijke gedachten die ik hier wil uitdr\u00fccken:<\/p>\n<ul>\n<li>Het toevoegen van elke nieuwe voorwaarde of sorteervolgorde aan de zoekopdracht kan de snelheid van de uitvoering aanzienlijk be\u00efnvloeden.<\/li>\n<li>Maar als we alleen een deel van de gegevens moeten lezen, en niet alle resultaten die aan de zoekcriteria voldoen \u2014 zijn er veel manieren om zo'n query te optimaliseren.<\/li>\n<\/ul>\n<p>\nLaten we nu overgaan naar de tweede query die aan het begin werd genoemd \u2014 die welke het aantal records telt dat voldoet aan de zoekcriteria. Laten we hetzelfde voorbeeld nemen \u2014 het zoeken naar bestellingen die meer dan 100 dollar kosten:<\/p>\n<pre><code class=\"sql\">SELECT COUNT(1) FROM Sales.SalesOrderHeader\nWHERE SubTotal &gt; 100\n<\/code><\/pre>\n<p>\nMet de samengestelde index zoals hierboven vermeld, krijgen we:<\/p>\n<p><img decoding=\"async\" alt=\"Zoekresultaten weergeven en prestatieproblemen.\" src=\"\/wp-content\/uploads\/2020\/03\/7beab0c83a50e2d68a83a965df555ec9.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabel '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>\nDat de query de hele index doorloopt, is niet verwonderlijk, aangezien het veld SubTotal niet op de eerste positie staat, waardoor de query deze niet kan gebruiken. Het probleem wordt opgelost door een extra index op het veld SubTotal toe te voegen, wat uiteindelijk slechts 48 logische leesbewerkingen oplevert.<\/p>\n<p>We kunnen nog enkele voorbeelden geven van queries voor het tellen van aantallen, maar de essentie blijft dezelfde: <b>het verkrijgen van een gegevensportie en het tellen van het totale aantal \u2014 dit zijn twee fundamenteel verschillende queries<\/b>, en iedere query vereist zijn eigen maatregelen voor optimalisatie. In het algemeen is het niet mogelijk om een combinatie van indexen te vinden die voor beide queries even goed werkt.<\/p>\n<p>Een van de belangrijke vereisten die moet worden verduidelijkt bij het ontwikkelen van een zoekoplossing is of het voor het bedrijf echt belangrijk is om het totale aantal gevonden objecten te zien. Vaak is dat niet het geval. En de navigatie naar specifieke paginanummers is, naar mijn mening, een oplossing met een zeer beperkte toepassing, aangezien de meeste scenario's met paging eruitzien als 'ga naar de volgende pagina'.<\/p>\n<h2>Paging optie #2<\/h2>\n<p>\nStel dat het voor gebruikers niet belangrijk is om het totale aantal gevonden objecten te weten. Laten we proberen de zoekpagina te vereenvoudigen:<\/p>\n<p><img decoding=\"async\" alt=\"Zoekresultaten weergeven en prestatieproblemen.\" src=\"\/wp-content\/uploads\/2020\/03\/66f5ee6dc1a52f14e55ae31e05c502b2.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nFeitelijk is er alleen veranderd dat er geen mogelijkheid is om naar specifieke paginanummers te gaan, en nu hoeft deze tabel niet te weten hoeveel pagina's er in totaal kunnen zijn. Maar er rijst de vraag: hoe weet de tabel of er gegevens zijn voor de volgende pagina (om de link \"Volgende\" correct weer te geven)?<\/p>\n<p>Het antwoord is heel eenvoudig: je kunt \u00e9\u00e9n record meer ophalen uit de database dan nodig is voor weergave, en het bestaan van dit 'extra' record zal aangeven of er een volgende partij is. Op deze manier is er voor het verkrijgen van \u00e9\u00e9n pagina gegevens slechts \u00e9\u00e9n verzoek nodig, wat de prestaties aanzienlijk verbetert en het onderhoud van dergelijke functionaliteit vergemakkelijkt. Ik heb in de praktijk een geval meegemaakt waarin het afzien van het tellen van het totale aantal records de resultaten 4-5 keer versnelde.<\/p>\n<p>Voor deze aanpak zijn er verschillende opties voor de gebruikersinterface: \u2018terug\u2019 en \u2018verder\u2019 knoppen, zoals in het bovenstaande voorbeeld, een 'meer laden' knop die gewoon een nieuwe partij aan de weergegeven resultaten toevoegt, en 'oneindige scroll', die werkt op basis van 'meer laden', maar waarbij het signaal om de volgende partij op te halen wordt gegeven door de gebruiker die alle weergegeven resultaten tot het einde scrolt. Wat de visuele oplossing ook is, het principe van gegevensselectie blijft hetzelfde.<\/p>\n<h2>Nuances van implementatie van paging<\/h2>\n<p>\nIn alle voorbeelden van verzoeken die hierboven zijn gegeven, wordt de aanpak 'offset + aantal' gebruikt, waarbij in het verzoek wordt aangegeven vanaf welke volgorde van het resultaat en hoeveel rijen moeten worden geretourneerd. Laten we eerst bekijken hoe we de parameteroverdracht in dit geval het beste kunnen organiseren. In de praktijk ben ik verschillende manieren tegengekomen:<\/p>\n<ul>\n<li>Het volgnummer van de opgevraagde pagina (pageIndex), de paginagrootte (pageSize).<\/li>\n<li>Het volgnummer van de eerste record die moet worden teruggegeven (startIndex), het maximale aantal records in het resultaat (count).<\/li>\n<li>Het volgnummer van de eerste record die moet worden teruggegeven (startIndex), het volgnummer van de laatste record die moet worden teruggegeven (endIndex).<\/li>\n<\/ul>\n<p>\nIn eerste instantie kan het lijken alsof dit zo elementair is, dat er geen verschil is. Maar dat is niet zo \u2014 de meest handige en universele optie is de tweede (startIndex, count). Hier zijn verschillende redenen voor:<\/p>\n<ul>\n<li>Voor de aanpak met het afleiden van +1 record, zoals hierboven genoemd, is de eerste optie met pageIndex en pageSize uiterst onhandig. Bijvoorbeeld, we willen 50 records op een pagina weergeven. Volgens het hierboven beschreven algoritme moet er \u00e9\u00e9n record meer worden gelezen dan nodig. Als deze \u2018+1\u2019 niet op de server is ingebouwd, moeten we voor de eerste pagina records opvragen van 1 tot 51, voor de tweede van 51 tot 101 enzovoort. Als we de paginagrootte op 51 instellen en pageIndex verhogen, zal de tweede pagina records van 52 tot 102 teruggeven enzovoort. Bijgevolg is de enige manier om de knop voor het navigeren naar de volgende pagina correct te implementeren in de eerste optie, door \u2018de extra\u2019 regel op de server in te bouwen, wat een zeer onduidelijk punt zal zijn.<\/li>\n<li>De derde optie heeft helemaal geen zin, omdat voor het uitvoeren van aanvragen in de meeste databases nog steeds het aantal moet worden doorgegeven, en niet de index van de laatste record. Hoewel het aftrekken van startIndex van endIndex een elementaire wiskundige bewerking is, is deze hier overbodig.<\/li>\n<\/ul>\n<p>\nNu moeten we de nadelen van de implementatie van paging via \u2018offset + aantal\u2019 beschrijven:<\/p>\n<ul>\n<li>Het verkrijgen van elke volgende pagina zal kostbaarder en langzamer zijn dan de vorige, omdat de database alle records \u2018vanaf het begin\u2019 moet doorlopen volgens de zoek- en sorteerkriteria, waarna deze moet stoppen bij het juiste fragment.<\/li>\n<li>Niet alle DBMS kunnen deze aanpak ondersteunen.<\/li>\n<\/ul>\n<p>\nEr zijn alternatieven, maar die zijn ook niet perfect. De eerste van deze benaderingen wordt 'keyset paging' of de 'seek method' genoemd en bestaat uit het volgende: na het verkrijgen van een groep records kun je de waarden van de velden in de laatste record op de pagina onthouden, en deze vervolgens gebruiken om de volgende groep te verkrijgen. Bijvoorbeeld, we hebben zo'n aanvraag uitgevoerd:<\/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 de laatste invoer hebben we de besteldatum &#8216;2014-06-29&#8217; ontvangen. Voor de volgende pagina kan men proberen het volgende uit te voeren:<\/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>\nHet probleem is dat OrderDate een niet-uniek veld is en de bovenstaande voorwaarde waarschijnlijk veel benodigde rijen overslaat. Om de nauwkeurigheid van deze query te vergroten, moet een uniek veld aan de voorwaarde worden toegevoegd (laten we aannemen dat 75074 de laatste waarde van de primaire sleutel uit de eerste set is):<\/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>\nDeze optie werkt goed, maar in het algemeen is het moeilijk om deze te optimaliseren, omdat de voorwaarde de OR-operator bevat. Als de waarde van de primaire sleutel toeneemt met de groei van OrderDate, kan de voorwaarde worden vereenvoudigd door alleen te filteren op SalesOrderID. Maar als er geen strikte correlatie is tussen de waarden van de primaire sleutel en het veld waarop het resultaat is gesorteerd, is het in de meeste DBMS'en niet mogelijk om deze OR te vermijden. Een bekende uitzondering is PostgreSQL, waar het vergelijken van tuples volledig wordt ondersteund, en de bovenstaande voorwaarde kan worden geschreven als \"WHERE (OrderDate, SalesOrderID) &lt; (&#8216;2014-06-29&#8217;, 75074)\". Met een samengestelde sleutel met deze twee velden zou zo'n query vrij eenvoudig moeten zijn.<\/p>\n<p>Een tweede alternatieve benadering kan bijvoorbeeld worden gevonden 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> of <noindex><a rel=\"nofollow\" href=\"https:\/\/medium.com\/@gary.strange\/understanding-cosmosdb-continuation-tokens-hasmoreresults-and-connectionpolicy-requesttimeouts-3ed1fadfa81d\">Cosmos DB<\/a><\/noindex> \u2014 wanneer de query naast de gegevens een speciale identificator retourneert, waarmee je de volgende set gegevens kunt ophalen. Als deze identificator een onbeperkte levensduur heeft (zoals in Cosmos DB), dan is dit een uitstekende manier om paging met voortgang tussen pagina's te implementeren (de hierboven genoemde optie #2). Mogelijke nadelen zijn: het wordt lang niet door alle databases ondersteund; de verkregen identificator voor de volgende set kan een beperkte levensduur hebben, wat in het algemeen niet geschikt is voor gebruikersinteractie (zoals bijvoorbeeld de ElasticSearch scroll API).<\/p>\n<h2>Complexe filtering<\/h2>\n<p>\nWe complicate the task further. Let's assume there is a requirement to implement what is known as a faceted search, familiar to everyone from online stores. The above examples based on the orders table are not very illustrative in this case, so let\u2019s switch to the Product table from the AdventureWorks database:<\/p>\n<p><img decoding=\"async\" alt=\"Zoekresultaten weergeven en prestatieproblemen.\" 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 item, the number of records corresponding to this criterion is displayed. <i>taking into account the filters selected in all other categories.<\/i>.<\/p>\n<p>For example, if we select the Bikes category and the color Black in this instance, the table will only output black bicycles, but at the same time:<\/p>\n<ul>\n<li>For each criterion in the 'Categories' group, the number of products from that category in black will be shown.<\/li>\n<li>For each criterion in the 'Colors' group, the number of bicycles in that color will be shown.<\/li>\n<\/ul>\n<p>\nHere is an example output for such conditions:<\/p>\n<p><img decoding=\"async\" alt=\"Zoekresultaten weergeven en prestatieproblemen.\" src=\"\/wp-content\/uploads\/2020\/03\/7c8ec4d8b427f1f59f0129afebe74b08.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nIf we further mark the Clothing category, the table will also show black clothing items that are available. The number of black products in the 'Color' section will also be recalculated according to the new conditions, but in the 'Categories' section, nothing will change... I hope these examples are enough to understand the familiar algorithm of how faceted search works.<\/p>\n<p>Now let's imagine how this can be implemented in 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=\"Zoekresultaten weergeven en prestatieproblemen.\" 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=\"Zoekresultaten weergeven en prestatieproblemen.\" src=\"\/wp-content\/uploads\/2020\/03\/82e1e6ab0fae683d383e3fecf4e3a15a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nWhat is wrong with this solution? Very simply - it scales poorly. Each filter section requires a separate query to count quantities, and these queries are not the lightest. In online stores, some categories can have several dozen filter sections, 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>Alle tellingen combineren in \u00e9\u00e9n aanvraag. Technisch gezien is dit mogelijk met het sleutelwoord UNION, maar het zal de prestaties niet significant verbeteren \u2014 de database moet namelijk voor elk van de fragmenten opnieuw beginnen.<\/li>\n<li>Aantal cachen. Dit wordt me praktisch elke keer voorgesteld wanneer ik het probleem beschrijf. Het probleem is dat dit in het algemeen onmogelijk is. Stel dat we 10 'facetten' hebben, elk met 5 waarden. Dit is een zeer 'bescheiden' situatie vergeleken met wat we in dezelfde webwinkels kunnen zien. De keuze van \u00e9\u00e9n facette be\u00efnvloedt het aantal in 9 andere; met andere woorden, voor elke combinatie van criteria kunnen de aantallen verschillend zijn. In totaal zijn er in ons voorbeeld 50 criteria die de gebruiker kan kiezen, en daarmee zijn er 250 mogelijke combinaties. Voor het vullen van zo'n gegevensset is er niet genoeg geheugen of tijd. Hier kan men inbrengen dat niet alle combinaties realistisch zijn en dat een gebruiker zelden meer dan 5-10 criteria zal kiezen. Ja, het is mogelijk om luie ladingen en caching van aantallen alleen voor datgene wat ooit is gekozen te maken, maar hoe meer keuzemogelijkheden er zijn, hoe minder effectief die cache zal zijn en hoe merkbaarder de responstijden (vooral als de dataset regelmatig veranderd).<\/li>\n<\/ul>\n<p>\nGelukkig zijn er voor dergelijke taken al geruime tijd voldoende effici\u00ebnte oplossingen beschikbaar die voorspelbaar werken bij grote hoeveelheden data. Voor elk van deze opties is het zinvol om de herberekening van facetten en het verkrijgen van de resultatenpagina te splitsen in twee parallelle verzoeken aan de server, en de gebruikersinterface zo te organiseren dat het laden van gegevens over facetten de weergave van de zoekresultaten 'niet stoort'.<\/p>\n<ul>\n<li>Roep de volledige herberekening van \"facet\" zo min mogelijk aan. Bijvoorbeeld, herbereken niet alles bij elke wijziging van de zoekcriteria, maar vind in plaats daarvan het totale aantal resultaten dat voldoet aan de huidige voorwaarden en bied de gebruiker aan om deze te tonen \u2014 \"1425 records gevonden, tonen?\" De gebruiker kan ofwel doorgaan met het wijzigen van de zoekvoorwaarden of op de knop \"tonen\" klikken. Alleen in dat laatste geval zullen alle verzoeken voor het verkrijgen van resultaten en het herberekenen van aantallen op alle \"facetten\" worden uitgevoerd. Het is evident dat hierbij een verzoek voor het verkrijgen van het totale aantal resultaten en de optimalisatie ervan aan de orde komt. Deze methode komt vaak voor in veel kleine webwinkels. Het is duidelijk dat dit geen panacee is voor dit probleem, maar in eenvoudige gevallen kan het een redelijke compromislomp zijn.<\/li>\n<li>Gebruik zoekmachines om resultaten te vinden en facetten te tellen, zoals Solr, ElasticSearch, Sphinx en anderen. Ze zijn allemaal ontworpen voor het opbouwen van \"facetten\" en doen dit vrij effectief dankzij omgekeerde indexen. Hoe zoekmachines zijn ingericht, waarom ze in zulke gevallen effectiever zijn dan relationele databases, welke praktijken en aandachtspunten er zijn \u2014 dit is een onderwerp voor een apart artikel. Hier wil ik benadrukken dat een zoekmachine niet kan worden vervangen door de belangrijkste gegevensopslag, maar wordt gebruikt als aanvulling: alle veranderingen in de hoofd database die relevant zijn voor de zoekopdracht worden gesynchroniseerd in de zoekindex; het zoekmechanisme werkt normaal gesproken alleen met de zoekmachine en raadpleegt de hoofd database niet. Een van de belangrijkste momenten hier is hoe deze synchronisatie betrouwbaar te organiseren. Dit hangt volledig af van de vereisten voor \"reactietijd\". Als de tijd tussen een wijziging in de hoofd database en de \"weergave\" daarvan in de zoekresultaten niet kritiek is, kan er een service worden gemaakt die om de paar minuten onlangs gewijzigde records zoekt en indiceert. Als de minimale mogelijkheid van reactietijd vereist is, kan er iets worden ge\u00efmplementeerd als <noindex><a rel=\"nofollow\" href=\"https:\/\/microservices.io\/patterns\/data\/transactional-outbox.html\">transactionele outbox<\/a><\/noindex> voor het verzenden van updates naar de zoekservice.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Conclusies<\/h2>\n<p><\/p>\n<ol>\n<li>Implementatie van server-side paging is een ernstige complicatie en het is alleen zinvol om het toe te passen voor snelgroeiende of gewoon grote datasets. Er is geen absoluut nauwkeurige richtlijn om te bepalen wat 'groot' of 'snelgroeiend' is, maar ik zou de volgende aanpak aanhouden:\n<ul>\n<li>Als het verkrijgen van de volledige dataset rekening houdend met server tijd en netwerktransmissie normaal binnen de prestatie-eisen past, heeft server-side paging geen zin.<\/li>\n<li>Er kan zich een situatie voordoen waarin er voorlopig geen prestatieproblemen te verwachten zijn, omdat er weinig gegevens zijn, maar de dataset groeit voortdurend. Als een bepaalde dataset in de toekomst mogelijk niet meer aan het vorige punt voldoet, is het beter om paging direct in te plannen.<\/li>\n<\/ul>\n<\/li>\n<li>Als er vanuit de business geen strikte vereiste is voor het tonen van het totale aantal resultaten of voor het weergeven van paginanummers, en er daarnaast geen zoekmachine in uw systeem aanwezig is, is het beter om deze aspecten niet te implementeren en optie #2 te overwegen.<\/li>\n<li>Als er een duidelijke vereiste is voor faceted search, heeft u twee opties om de prestaties niet in gevaar te brengen:\n<ul>\n<li>Tel niet alle aantallen bij elke wijziging van de zoekcriteria.<\/li>\n<li>Gebruik zoekmachines zoals Solr, ElasticSearch, Sphinx en anderen. Maar het moet worden begrepen dat dit geen vervanging voor de hoofd-database kan zijn, en het moet worden gebruikt als aanvulling op de hoofdopslag om zoekproblemen op te lossen. <\/li>\n<\/ul>\n<\/li>\n<li>Ook in het geval van faceted search is het zinvol om het ophalen van de zoekresultaatpagina en het tellen van aantallen in twee parallelle aanvragen te splitsen. Het tellen van aantallen kan meer tijd in beslag nemen dan het verkrijgen van resultaten, terwijl de resultaten belangrijker zijn voor de gebruiker.<\/li>\n<li>Als u een SQL-database gebruikt voor zoekopdrachten, moet elke codewijziging die betrekking heeft op dit onderdeel goed getest worden op prestatie op het relevante gegevensvolume (dat groter is dan het volume in de 'live' database). Het is ook wenselijk om de uitvoeringstijden van aanvragen op alle database-exemplaren te monitoren, en vooral op de 'live' versie. Zelfs als alles er op ontwikkelingsniveau goed uitzag met de queryplannen, kan de situatie veranderen naarmate het gegevensvolume groeit.<\/li>\n<\/ol>\n<p>Bron: <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\/nl\/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=\"nl_NL\" \/>\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\/nl\/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\udd47 Zoekresultaten weergeven en prestatieproblemen | ProHoster","description":"Een van de gebruikelijke scenario's in alle vertrouwde applicaties is het zoeken naar gegevens op basis van bepaalde criteria en het presenteren ervan in een leesbaar formaat.","canonical_url":"https:\/\/prohoster.info\/nl\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"nl_NL","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\/nl\/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\/nl\/wp-json\/wp\/v2\/posts\/75530","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/comments?post=75530"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/posts\/75530\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/media\/75531"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/media?parent=75530"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/categories?post=75530"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/nl\/wp-json\/wp\/v2\/tags?post=75530"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}