{"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\/it\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu","title":{"rendered":"Risultati della ricerca e problemi di prestazioni","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Uno degli scenari tipici in tutte le applicazioni familiari \u00e8 la ricerca di dati in base a criteri specifici e la loro visualizzazione in un formato leggibile. Qui possono esserci funzionalit\u00e0 aggiuntive per l'ordinamento, il raggruppamento e la paginazione. L'obiettivo, in teoria, \u00e8 banale, ma nella sua risoluzione molti sviluppatori commettono diversi errori che influiscono negativamente sulle prestazioni. Cerchiamo di esaminare varie soluzioni a questo problema e formulare raccomandazioni per la scelta dell'implementazione pi\u00f9 efficiente.<\/p>\n<p><img decoding=\"async\" alt=\"Risultati della ricerca e problemi di prestazioni\" 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>Opzione di paginazione #1<\/h2>\n<p>\nLa soluzione pi\u00f9 semplice che viene in mente \u00e8 la paginazione dei risultati della ricerca nella sua forma pi\u00f9 classica.<\/p>\n<p><img decoding=\"async\" alt=\"Risultati della ricerca e problemi di prestazioni\" src=\"\/wp-content\/uploads\/2020\/03\/4957d9ad21a8a0c70390b224fe76cb33.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nSupponiamo che nell'applicazione venga utilizzato un database relazionale. In questo caso, per visualizzare le informazioni in questo modo sar\u00e0 necessario eseguire due query SQL:<\/p>\n<ul>\n<li>Ottenere le righe per la pagina corrente.<\/li>\n<li>Contare il numero totale di righe che corrispondono ai criteri di ricerca: questo \u00e8 necessario per mostrare le pagine.<\/li>\n<\/ul>\n<p>\nEsaminiamo la prima query utilizzando un esempio di un database di test MS SQL <noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/Microsoft\/sql-server-samples\/releases\/download\/adventureworks\/AdventureWorks2016_EXT.bak\">AdventureWorks <\/a><\/noindex>per il server 2016. A questo scopo utilizzeremo la tabella 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 query sopra riportata restituir\u00e0 i primi 50 ordini dall'elenco, ordinati in base alla data di aggiunta in ordine decrescente, in altre parole, gli ultimi 50 ordini.<\/p>\n<p>Viene eseguita rapidamente nel database di test, ma diamo un'occhiata al piano di esecuzione e alle statistiche di input\/output:<\/p>\n<p><img decoding=\"async\" alt=\"Risultati della ricerca e problemi di prestazioni\" src=\"\/wp-content\/uploads\/2020\/03\/248e4b64593c7000216b46777183d639.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabella 'SalesOrderHeader'. Count di scansione 1, letture logiche 698, letture fisiche 0, letture anticipate 0, letture logiche lob 0, letture fisiche lob 0, letture anticipate lob 0.<\/code><\/pre>\n<p>\n<i>\u00c8 possibile ottenere le statistiche di input\/output per ogni query eseguendo il comando SET STATISTICS IO ON nell'ambiente di esecuzione delle query.<\/i><\/p>\n<p>Come si pu\u00f2 vedere dal piano di esecuzione, l'operazione pi\u00f9 dispendiosa in termini di risorse \u00e8 l'ordinamento di tutte le righe della tabella originale per data di aggiunta. E il problema \u00e8 che pi\u00f9 righe vengono aggiunte alla tabella, pi\u00f9 sar\u00e0 \"pesante\" l'ordinamento. Nella pratica, si dovrebbero evitare tali situazioni, quindi aggiungiamo un indice sulla data di aggiunta e vediamo se il consumo di risorse \u00e8 cambiato:<\/p>\n<p><img decoding=\"async\" alt=\"Risultati della ricerca e problemi di prestazioni\" src=\"\/wp-content\/uploads\/2020\/03\/a33e1eadf6f8f8b516b9bb834ad7ad86.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabella 'SalesOrderHeader'. Count di scansione 1, letture logiche 165, letture fisiche 0, letture anticipate 5, letture logiche lob 0, letture fisiche lob 0, letture anticipate lob 0.\n<\/code><\/pre>\n<p>\n\u00c8 evidente che \u00e8 migliorato molto. Ma sono stati risolti tutti i problemi? Cambiamo la query per cercare gli ordini in cui il valore totale dei prodotti supera i 100 dollari:<\/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=\"Risultati della ricerca e problemi di prestazioni\" src=\"\/wp-content\/uploads\/2020\/03\/e057bfc89e11a31c6d2855b93ec3c747.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabella 'SalesOrderHeader'. Conteggio scansionato 1, letture logiche 1081, letture fisiche 0, letture anticipate 0, letture logiche lob 0, letture fisiche lob 0, letture anticipate lob 0.<\/code><\/pre>\n<p>\nAbbiamo una situazione divertente: il piano della query \u00e8 di poco inferiore a quello precedente, ma il numero effettivo di letture logiche \u00e8 quasi il doppio rispetto a una scansione completa della tabella. C'\u00e8 una soluzione: se trasformiamo l'indice esistente in un indice composito e aggiungiamo il prezzo totale dei prodotti come secondo campo, otteniamo di nuovo 165 letture logiche:<\/p>\n<pre><code class=\"sql\">CREATE INDEX IX_SalesOrderHeader_OrderDate_SubTotal on Sales.SalesOrderHeader(OrderDate, SubTotal);\n<\/code><\/pre>\n<p>\nQuesta serie di esempi pu\u00f2 continuare a lungo, ma le due idee principali che voglio esprimere qui sono:<\/p>\n<ul>\n<li>Aggiungere qualsiasi nuovo criterio o ordine di ordinamento nella query di ricerca pu\u00f2 influenzare notevolmente la velocit\u00e0 della sua esecuzione.<\/li>\n<li>Ma se dobbiamo estrarre solo una parte dei dati, e non tutti i risultati che soddisfano i criteri di ricerca, ci sono molti modi per ottimizzare tale query.<\/li>\n<\/ul>\n<p>\nOra passiamo alla seconda query, menzionata all'inizio - quella che conta il numero di record che soddisfano il criterio di ricerca. Prendiamo lo stesso esempio - cercare ordini che costano pi\u00f9 di 100 dollari:<\/p>\n<pre><code class=\"sql\">SELECT COUNT(1) FROM Sales.SalesOrderHeader\nWHERE SubTotal &gt; 100\n<\/code><\/pre>\n<p>\nCon l'indice composito indicato sopra, otteniamo:<\/p>\n<p><img decoding=\"async\" alt=\"Risultati della ricerca e problemi di prestazioni\" src=\"\/wp-content\/uploads\/2020\/03\/7beab0c83a50e2d68a83a965df555ec9.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<pre><code class=\"plaintext\">Tabella 'SalesOrderHeader'. Count di scansione 1, letture logiche 698, letture fisiche 0, letture anticipate 0, letture logiche lob 0, letture fisiche lob 0, letture anticipate lob 0.<\/code><\/pre>\n<p>\nNon sorprende che la query scorra l'intero indice, poich\u00e9 il campo SubTotal non \u00e8 in prima posizione, quindi la query non pu\u00f2 utilizzarlo. Il problema si risolve aggiungendo un ulteriore indice sul campo SubTotal, e alla fine otteniamo solo 48 letture logiche.<\/p>\n<p>Si possono presentare ulteriori esempi di query contabili, ma il concetto rimane lo stesso: <b>ottenere un insieme di dati e contare il totale sono due query fondamentalmente diverse<\/b>, e ciascuna richiede misure proprie per l'ottimizzazione. In generale, non \u00e8 possibile trovare una combinazione di indici che funzioni altrettanto bene per entrambe le query.<\/p>\n<p>Pertanto, uno dei requisiti importanti da chiarire durante lo sviluppo di una tale soluzione di ricerca \u00e8 se per l'azienda sia davvero importante vedere il numero totale degli oggetti trovati. Spesso non \u00e8 cos\u00ec. E la navigazione tramite numeri di pagina specifici, a mio avviso, \u00e8 una soluzione con un ambito di applicazione molto ristretto, poich\u00e9 la maggior parte degli scenari di paging appare come \"passare alla pagina successiva\".<\/p>\n<h2>Opzione di paging n. 2<\/h2>\n<p>\nSupponiamo che agli utenti non importi conoscere il numero totale degli oggetti trovati. Proviamo a semplificare la pagina di ricerca:<\/p>\n<p><img decoding=\"async\" alt=\"Risultati della ricerca e problemi di prestazioni\" src=\"\/wp-content\/uploads\/2020\/03\/66f5ee6dc1a52f14e55ae31e05c502b2.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nIn effetti, \u00e8 cambiato solo il fatto che non c'\u00e8 la possibilit\u00e0 di passare a numeri di pagina specifici, e ora questa tabella per la visualizzazione non ha bisogno di sapere quanti ce ne possono essere in totale. Ma sorge la domanda: come fa la tabella a sapere se ci sono dati per la pagina successiva (per visualizzare correttamente il link \"Successivo\")?<\/p>\n<p>La risposta \u00e8 molto semplice: si pu\u00f2 leggere dal database un record in pi\u00f9 rispetto a quanto necessario per la visualizzazione, e la presenza di questo record \"aggiuntivo\" indicher\u00e0 se c'\u00e8 un'altra porzione di dati. In questo modo, per ottenere una pagina di dati sar\u00e0 necessaria solo una query, il che migliora notevolmente le prestazioni e semplifica il supporto di tale funzionalit\u00e0. Ho avuto un caso nella mia pratica in cui l'abbandono del conteggio totale dei record ha accelerato il rilascio dei risultati di 4-5 volte.<\/p>\n<p>Per questo approccio esistono diverse varianti di interfaccia utente: i comandi \"indietro\" e \"avanti\", come nell'esempio sopra, il pulsante \"carica di pi\u00f9\", che aggiunge semplicemente una nuova porzione ai risultati visualizzati, e \"scorrimento infinito\", che funziona secondo il principio di \"carica di pi\u00f9\", ma il segnale per ottenere la prossima porzione \u00e8 quando l'utente scorre fino alla fine di tutti i risultati visualizzati. Qualunque sia la soluzione visiva, il principio di campionamento dei dati rimane lo stesso.<\/p>\n<h2>Aspetti dell'implementazione del paging<\/h2>\n<p>\nIn tutti gli esempi di query sopra riportati, viene utilizzato l'approccio \"offset + numero\", in cui nella richiesta viene specificato da quale riga dell'insieme dei risultati e quante righe devono essere restituite. Prima vediamo come organizzare meglio la trasmissione dei parametri in questo caso. Nella pratica, ho trovato diversi modi:<\/p>\n<ul>\n<li>Numero ordinario della pagina richiesta (pageIndex), dimensione della pagina (pageSize).<\/li>\n<li>Numero ordinario del primo record da restituire (startIndex), numero massimo di record nel risultato (count).<\/li>\n<li>Numero ordinario del primo record da restituire (startIndex), numero ordinario dell'ultimo record da restituire (endIndex).<\/li>\n<\/ul>\n<p>\nA prima vista potrebbe sembrare che sia cos\u00ec elementare da non esserci differenze. Ma non \u00e8 cos\u00ec: l'opzione pi\u00f9 comoda e versatile \u00e8 la seconda (startIndex, count). Ci sono diverse ragioni per questo:<\/p>\n<ul>\n<li>Per l'approccio con lettura di +1 record riportato sopra, la prima opzione con pageIndex e pageSize \u00e8 estremamente scomoda. Ad esempio, vogliamo visualizzare 50 record per pagina. Secondo l'algoritmo sopra riportato, \u00e8 necessario leggere un record in pi\u00f9 di quello richiesto. Se questo \u00ab+1\u00bb non \u00e8 implementato sul server, risulta che per la prima pagina dobbiamo richiedere i record da 1 a 51, per la seconda \u2014 da 51 a 101, e cos\u00ec via. Se specifichiamo una dimensione della pagina di 51 e aumentiamo pageIndex, la seconda pagina restituir\u00e0 da 52 a 102, e cos\u00ec via. Di conseguenza, nel primo caso l'unico modo per implementare correttamente il pulsante per passare alla pagina successiva \u00e8 includere sul server la lettura di una \u201criga in eccesso\u201d, che risulta essere una sfumatura poco chiara.<\/li>\n<li>Il terzo approccio non ha senso, poich\u00e9 per eseguire le richieste, nella maggior parte dei database, sar\u00e0 comunque necessario passare il numero, non l'indice dell'ultimo record. Anche se sottrarre startIndex da endIndex \u00e8 un'operazione aritmetica elementare, qui \u00e8 superflua.<\/li>\n<\/ul>\n<p>\nOra \u00e8 importante descrivere gli svantaggi della realizzazione della paginazione tramite \u00aboffset + quantit\u00e0\u00bb:<\/p>\n<ul>\n<li>Ottenere ogni pagina successiva richieder\u00e0 pi\u00f9 tempo e sar\u00e0 pi\u00f9 lento rispetto alla precedente, poich\u00e9 il database dovr\u00e0 comunque esaminare tutti i record \u00abdall'inizio\u00bb in base ai criteri di ricerca e ordinamento, per poi fermarsi sul frammento desiderato.<\/li>\n<li>Non tutti i DBMS possono supportare questo approccio.<\/li>\n<\/ul>\n<p>\nCi sono alternative, ma non sono ideali. Il primo di questi approcci si chiama \u00abkeyset paging\u00bb o \u00abseek method\u00bb e consiste nel: dopo aver ottenuto un lotto, \u00e8 possibile memorizzare i valori dei campi nell'ultimo record della pagina, e poi utilizzarli per ottenere il lotto successivo. Ad esempio, abbiamo eseguito una richiesta del genere:<\/p>\n<pre><code class=\"sql\">SELECT * FROM Sales.SalesOrderHeader\nORDER BY OrderDate DESC\nOFFSET 0 ROWS\nFETCH NEXT 50 ROWS ONLY\n<\/code><\/pre>\n<p>\nE nell'ultima registrazione abbiamo ottenuto il valore della data dell'ordine '2014-06-29'. Quindi, per ottenere la pagina successiva, si pu\u00f2 provare a eseguire quanto segue:<\/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>\nIl problema \u00e8 che OrderDate non \u00e8 un campo unico e la condizione sopra indicata ha una grande probabilit\u00e0 di saltare molte righe necessarie. Per rendere univoco questa query, \u00e8 necessario aggiungere alla condizione un campo unico (si supponga che 75074 sia l'ultimo valore della chiave primaria del primo lotto):<\/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>\nQuesta opzione funzioner\u00e0 correttamente, ma in generale sar\u00e0 difficile ottimizzarla, poich\u00e9 la condizione contiene l'operatore OR. Se con l'aumento di OrderDate aumentano i valori della chiave primaria, la condizione pu\u00f2 essere semplificata, mantenendo solo il filtro su SalesOrderID. Ma se non c'\u00e8 una correlazione rigorosa tra i valori della chiave primaria e il campo su cui il risultato \u00e8 ordinato, in molti DBMS sar\u00e0 impossibile evitare questo OR. L'unica eccezione che conosco \u00e8 PostgreSQL, dove il confronto dei tuple \u00e8 supportato in modo completo, e la condizione sopra indicata pu\u00f2 essere scritta come \"WHERE (OrderDate, SalesOrderID) &lt; (&#039;2014-06-29&#039;, 75074)&quot;. In presenza di una chiave composta con questi due campi, una richiesta simile dovrebbe essere sufficientemente leggera.<\/p>\n<p>Un secondo approccio alternativo pu\u00f2 essere trovato, ad esempio, 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> o <noindex><a rel=\"nofollow\" href=\"https:\/\/medium.com\/@gary.strange\/understanding-cosmosdb-continuation-tokens-hasmoreresults-and-connectionpolicy-requesttimeouts-3ed1fadfa81d\">Cosmos DB<\/a><\/noindex> \u2014 quando la query oltre ai dati restituisce un identificatore speciale, con cui \u00e8 possibile ottenere il lotto successivo di dati. Se questo identificatore ha una durata illimitata (come in Comsos DB), \u00e8 un ottimo modo per implementare il paging con transizioni sequenziali tra le pagine (il secondo metodo menzionato sopra). I suoi possibili svantaggi: non \u00e8 supportato da tutte le DBMS; l'identificatore ottenuto per il lotto successivo pu\u00f2 avere una durata limitata, il che in generale non \u00e8 adatto per l'interazione con l'utente (come, ad esempio, l'ElasticSearch scroll API).<\/p>\n<h2>Filtraggio complesso<\/h2>\n<p>\nComplichiamo ulteriormente il compito. Supponiamo di avere la richiesta di implementare il cosiddetto faceted search, ben noto a tutti dagli e-commerce. Gli esempi forniti sopra basati sulla tabella degli ordini non sono molto significativi in questo caso, quindi passeremo alla tabella Product del database AdventureWorks:<\/p>\n<p><img decoding=\"async\" alt=\"Risultati della ricerca e problemi di prestazioni\" src=\"\/wp-content\/uploads\/2020\/03\/070890a3cb79de3edc69b60b5300efe7.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nQual \u00e8 l'idea del faceted search? Consiste nel mostrare il numero di record corrispondenti a ciascun criterio per ogni elemento del filtro. <i>tenendo conto dei filtri selezionati in tutte le altre categorie.<\/i>.<\/p>\n<p>Ad esempio, se in questo caso scegliamo la categoria Bikes e il colore Black, la tabella mostrer\u00e0 solo biciclette di colore nero, ma:<\/p>\n<ul>\n<li>Per ogni criterio del gruppo \u00abCategories\u00bb verr\u00e0 mostrato il numero di prodotti di questa categoria di colore nero.<\/li>\n<li>Per ogni criterio del gruppo \u00abColors\u00bb verr\u00e0 mostrato il numero di biciclette di quel colore.<\/li>\n<\/ul>\n<p>\nEcco un esempio di output del risultato per tali condizioni:<\/p>\n<p><img decoding=\"async\" alt=\"Risultati della ricerca e problemi di prestazioni\" src=\"\/wp-content\/uploads\/2020\/03\/7c8ec4d8b427f1f59f0129afebe74b08.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nSe in aggiunta selezioniamo la categoria \u00abClothing\u00bb, la tabella mostrer\u00e0 anche i capi d'abbigliamento di colore nero disponibili. Il numero di prodotti di colore nero nella sezione \u00abColor\u00bb sar\u00e0 anch'esso ricalcolato secondo le nuove condizioni, mentre nella sezione \u00abCategories\u00bb non cambier\u00e0 nulla\u2026 Spero che questi esempi siano sufficienti per comprendere il consueto algoritmo di funzionamento del faceted search.<\/p>\n<p>Ora immaginiamo come potrebbe essere implementato su un database relazionale. Ogni gruppo di criteri, come Category e Color, richieder\u00e0 una query separata:<\/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=\"Risultati della ricerca e problemi di prestazioni\" 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=\"Risultati della ricerca e problemi di prestazioni\" src=\"\/wp-content\/uploads\/2020\/03\/82e1e6ab0fae683d383e3fecf4e3a15a.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nQual \u00e8 il problema con questa soluzione? \u00c8 semplice: non scala bene. Ogni sezione del filtro richiede una query separata per contare le quantit\u00e0 e queste query non sono proprio leggere. Negli e-commerce, in alcune categorie ci possono essere diverse decine di sezioni di filtro, il che pu\u00f2 diventare un grave problema per le prestazioni.<\/p>\n<p>Di solito, dopo queste affermazioni, mi vengono proposte alcune soluzioni, ovvero:<\/p>\n<ul>\n<li>Unire tutti i conteggi in una sola richiesta. Tecnicamente, \u00e8 possibile farlo utilizzando la parola chiave UNION, ma in termini di prestazioni non sar\u00e0 di grande aiuto: il database dovr\u00e0 comunque eseguire \"da zero\" ciascuno dei frammenti.<\/li>\n<li>Memorizzare nella cache i conteggi. Questo \u00e8 ci\u00f2 che mi viene suggerito praticamente ogni volta che descrivo il problema. Il punto \u00e8 che, in generale, \u00e8 impossibile. Supponiamo di avere 10 \"facette\", ognuna con 5 valori. Questa \u00e8 una situazione molto \"modesta\" rispetto a ci\u00f2 che si pu\u00f2 vedere negli stessi negozi online. La scelta di un elemento della facetta influisce sui conteggi in 9 altre facette; in altre parole, per ogni combinazione di criteri, i conteggi possono variare. Nel nostro esempio ci sono 50 criteri che l'utente pu\u00f2 scegliere, quindi ci saranno 250 combinazioni possibili. Non ci sar\u00e0 abbastanza memoria o tempo per popolare un tale array di dati. Si potrebbe obiettare che non tutte le combinazioni sono reali e che l'utente raramente seleziona pi\u00f9 di 5-10 criteri. S\u00ec, \u00e8 possibile fare un caricamento pigro e memorizzare nella cache i conteggi solo per ci\u00f2 che \u00e8 stato selezionato in precedenza, ma pi\u00f9 opzioni di scelta ci sono, meno efficace sar\u00e0 questa cache e pi\u00f9 evidenti saranno i problemi di tempo di risposta (soprattutto se il set di dati cambia regolarmente).<\/li>\n<\/ul>\n<p>\nFortunatamente, una tale esigenza ha gi\u00e0 soluzioni abbastanza efficienti che funzionano in modo prevedibile su grandi volumi di dati. Per ognuna di queste opzioni ha senso separare il ricalcolo delle facette e l'ottenimento della pagina dei risultati in due richieste parallele al server e organizzare l'interfaccia utente in modo che il caricamento dei dati delle facette non \"disturbi\" la visualizzazione dei risultati della ricerca.<\/p>\n<ul>\n<li>Evitare di eseguire un ri-calcolo completo dei \"facets\" il pi\u00f9 raramente possibile. Ad esempio, non calcolare tutto ad ogni modifica dei criteri di ricerca, ma piuttosto trovare il numero totale di risultati che corrispondono alle condizioni attuali e proporre all'utente di mostrarli \u2014 \"1425 risultati trovati, mostrare?\" L'utente pu\u00f2 continuare a modificare le condizioni di ricerca o premere il pulsante \"mostra\". Solo nel secondo caso verranno eseguite tutte le richieste per ottenere risultati e ricalcolare i numeri su tutti i \"facets\". Inoltre, come si pu\u00f2 facilmente notare, sar\u00e0 necessario gestire una richiesta per ottenere il numero totale di risultati e la sua ottimizzazione. Questo metodo pu\u00f2 essere trovato in molti piccoli negozi online. \u00c8 ovvio che non \u00e8 una panacea per questo problema, ma nei casi semplici pu\u00f2 essere un buon compromesso.<\/li>\n<li>Utilizzare motori di ricerca per trovare risultati e calcolare i facets, come Solr, ElasticSearch, Sphinx e altri. Tutti sono progettati per costruire \"facets\" e lo fanno in modo piuttosto efficace grazie all'indice invertito. Come sono strutturati i motori di ricerca, perch\u00e9 in questi casi sono pi\u00f9 efficaci rispetto ai database generali, quali sono le pratiche e gli aspetti da considerare \u2014 questo \u00e8 un argomento per un articolo a parte. Qui voglio sottolineare che un motore di ricerca non pu\u00f2 sostituire il principale archivio di dati, viene utilizzato come complemento: qualsiasi modifica nel database principale che abbia un impatto sulla ricerca viene sincronizzata nell'indice di ricerca; il meccanismo di ricerca interagisce normalmente solo con il motore di ricerca e non si rivolge al database principale. Uno degli aspetti pi\u00f9 importanti qui \u00e8 come organizzare questa sincronizzazione in modo affidabile. Tutto dipende dai requisiti riguardanti il \"tempo di risposta\". Se il tempo tra una modifica nel database principale e la sua \"manifestazione\" nella ricerca non \u00e8 critico, \u00e8 possibile implementare un servizio che ogni pochi minuti cerchi le voci recentemente modificate e le indicizzi. Se \u00e8 necessario il tempo di risposta minimo possibile, \u00e8 possibile implementare qualcosa come <noindex><a rel=\"nofollow\" href=\"https:\/\/microservices.io\/patterns\/data\/transactional-outbox.html\">transactional outbox<\/a><\/noindex> per inviare aggiornamenti al motore di ricerca.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Conclusioni<\/h2>\n<p><\/p>\n<ol>\n<li>L'implementazione del paging lato server \u00e8 un serio compito complesso e dovrebbe essere utilizzata solo per set di dati in rapida crescita o semplicemente ampi. Non esiste una ricetta esatta per valutare cosa sia \"grande\" o \"in rapida crescita\", ma io adotterei questo approccio:\n<ul>\n<li>Se l'ottenimento di un'intera raccolta di dati, considerando il tempo server e il trasferimento di rete, rientra normalmente nei requisiti di prestazione, non ha senso implementare il paging lato server.<\/li>\n<li>Potrebbe verificarsi una situazione in cui, per il momento, non ci sono problemi di prestazioni poich\u00e9 ci sono pochi dati, ma la raccolta di dati continua a crescere. Se un certo set di dati potrebbe nel tempo smettere di soddisfare il punto precedente, \u00e8 meglio implementare subito il paging.<\/li>\n<\/ul>\n<\/li>\n<li>Se non ci sono requisiti rigorosi da parte dell'azienda riguardo alla visualizzazione del numero totale di risultati o alla visualizzazione dei numeri di pagina, e nello stesso tempo nel vostro sistema non c'\u00e8 un motore di ricerca, \u00e8 meglio non implementare questi aspetti e considerare l'opzione #2.<\/li>\n<li>Se c'\u00e8 un chiaro requisito per la ricerca facettata, avete due opzioni per non compromettere le prestazioni:\n<ul>\n<li>Non ricalcolare tutti i numeri ad ogni cambiamento dei criteri di ricerca.<\/li>\n<li>Usare motori di ricerca come Solr, ElasticSearch, Sphinx e altri. Ma bisogna capire che non pu\u00f2 sostituire il database principale e deve essere utilizzato come complemento per risolvere compiti di ricerca. <\/li>\n<\/ul>\n<\/li>\n<li>Inoltre, nel caso di ricerca facettata, ha senso separare l'ottenimento della pagina dei risultati della ricerca e il conteggio dei numeri in due richieste parallele. Il conteggio dei numeri potrebbe richiedere pi\u00f9 tempo rispetto all'ottenimento dei risultati, mentre i risultati sono pi\u00f9 importanti per l'utente.<\/li>\n<li>Se utilizzate un database SQL per la ricerca, qualsiasi modifica del codice riguardante questa parte deve essere testata accuratamente in relazione alle prestazioni su un volume di dati corrispondente (superiore al volume del database \"in produzione\"). \u00c8 inoltre consigliabile utilizzare il monitoraggio del tempo di esecuzione delle query su tutte le istanze del database, e in particolare su quella \"in produzione\". Anche se nella fase di sviluppo con i piani delle query tutto andava bene, con l'aumento del volume dei dati la situazione potrebbe cambiare notevolmente.<\/li>\n<\/ol>\n<p>Fonte: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/epam_systems\/blog\/493438\/\">habr.com<\/a> <\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u041e\u0434\u0438\u043d \u0438\u0437 \u0442\u0438\u043f\u043e\u0432\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u0432 \u0432\u043e \u0432\u0441\u0435\u0445 \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u044b\u0445 \u043d\u0430\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0445 \u2014 \u043f\u043e\u0438\u0441\u043a \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u043c \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u044f\u043c \u0438 \u0432\u044b\u0432\u043e\u0434 \u0438\u0445 \u0432 \u0443\u0434\u043e\u0431\u043d\u043e\u043c \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0432\u0438\u0434\u0435. \u0422\u0443\u0442 \u0436\u0435 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043f\u043e \u0441\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0435, \u0433\u0440\u0443\u043f\u043f\u0438\u0440\u043e\u0432\u043a\u0435, \u043f\u043e\u0441\u0442\u0440\u0430\u043d\u0438\u0447\u043d\u043e\u043c\u0443 \u0432\u044b\u0432\u043e\u0434\u0443. \u0417\u0430\u0434\u0430\u0447\u0430, \u043f\u043e \u0438\u0434\u0435\u0435, \u0442\u0440\u0438\u0432\u0438\u0430\u043b\u044c\u043d\u0430\u044f, \u043d\u043e \u043f\u0440\u0438 \u0435\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0438 \u043c\u043d\u043e\u0433\u0438\u0435 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u0434\u0435\u043b\u0430\u044e\u0442 \u0440\u044f\u0434 \u043e\u0448\u0438\u0431\u043e\u043a, \u0438\u0437-\u0437\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043f\u043e\u0442\u043e\u043c \u0441\u0442\u0440\u0430\u0434\u0430\u0435\u0442 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c. \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0435\u043c \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0435 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":75531,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-75530","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.1.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u041e\u0434\u0438\u043d \u0438\u0437 \u0442\u0438\u043f\u043e\u0432\u044b\u0445 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0435\u0432 \u0432\u043e \u0432\u0441\u0435\u0445 \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u044b\u0445 \u043d\u0430\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f\u0445 \u2014 \u043f\u043e\u0438\u0441\u043a \u0434\u0430\u043d\u043d\u044b\u0445 \u043f\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u043c \u043a\u0440\u0438\u0442\u0435\u0440\u0438\u044f\u043c \u0438 \u0432\u044b\u0432\u043e\u0434 \u0438\u0445 \u0432 \u0443\u0434\u043e\u0431\u043d\u043e\u043c \u0434\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0432\u0438\u0434\u0435.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/it\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"it_IT\" \/>\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\/it\/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\udd47Risultati di ricerca e problemi di prestazioni | ProHoster","description":"Uno degli scenari tipici in tutte le applicazioni a noi familiari \u00e8 la ricerca di dati in base a criteri specifici e la loro presentazione in una forma leggibile.","canonical_url":"https:\/\/prohoster.info\/it\/blog\/administrirovanie\/vyvod-rezultatov-poiska-i-problemy-s-proizvoditelnostyu","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"it_IT","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\/it\/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\/it\/wp-json\/wp\/v2\/posts\/75530","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/comments?post=75530"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/posts\/75530\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/media\/75531"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/media?parent=75530"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/categories?post=75530"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/it\/wp-json\/wp\/v2\/tags?post=75530"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}