{"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 di ricerca e problemi di prestazioni","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Uno degli scenari comuni in tutte le applicazioni che conosciamo \u00e8 la ricerca di dati in base a criteri specifici e la loro visualizzazione in un formato leggibile. Qui possono esserci ulteriori possibilit\u00e0 di ordinamento, raggruppamento e visualizzazione paginata. L'obiettivo, in teoria, \u00e8 semplice, ma nella sua soluzione molti sviluppatori fanno una serie di errori che compromettono le prestazioni. Proviamo a esaminare diverse opzioni per risolvere questo problema e a formulare raccomandazioni per scegliere l'implementazione pi\u00f9 efficace.<\/p>\n<p><img decoding=\"async\" alt=\"Risultati di 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>\nL'opzione pi\u00f9 semplice che viene in mente \u00e8 la visualizzazione paginata dei risultati della ricerca nella sua forma pi\u00f9 classica.<\/p>\n<p><img decoding=\"async\" alt=\"Risultati di ricerca e problemi di prestazioni\" src=\"\/wp-content\/uploads\/2020\/03\/4957d9ad21a8a0c70390b224fe76cb33.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nSupponiamo che nell'applicazione sia 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 \u2014 questo \u00e8 necessario per visualizzare le pagine.<\/li>\n<\/ul>\n<p>\nEsaminiamo la prima query utilizzando un esempio di database di prova 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 restituir\u00e0 i primi 50 ordini dall'elenco, ordinati in base alla data di inserimento in modo decrescente, in altre parole, gli ultimi 50 ordini.<\/p>\n<p>Viene eseguita rapidamente su un 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 di 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 degli scan 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>Per ottenere statistiche di input\/output per ogni query, \u00e8 possibile eseguire nel contesto di esecuzione della query il comando SET STATISTICS IO ON.<\/i><\/p>\n<p>Come si evince dal piano di esecuzione, la parte pi\u00f9 dispendiosa in termini di risorse \u00e8 l'ordinamento di tutte le righe della tabella originale in base alla data di inserimento. E il problema \u00e8 che pi\u00f9 righe verranno aggiunte alla tabella, pi\u00f9 pesante diventer\u00e0 l'ordinamento. In pratica, situazioni di questo tipo dovrebbero essere evitate, quindi aggiungeremo un indice sulla data di inserimento e vedremo se il consumo di risorse \u00e8 cambiato:<\/p>\n<p><img decoding=\"async\" alt=\"Risultati di 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 degli scan 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 le cose siano migliorate notevolmente. 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 di 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 scansioni 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 curiosa: il piano della query non \u00e8 molto diverso da quello precedente, ma il numero effettivo di letture logiche \u00e8 quasi raddoppiato rispetto a una scansione completa della tabella. La soluzione c'\u00e8: se creiamo un indice composito aggiungendo come secondo campo il totale dei prezzi dei prodotti, torneremo a ottenere 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 potrebbe continuare a lungo, ma due idee principali che voglio esprimere qui sono le seguenti:<\/p>\n<ul>\n<li>Aggiungere qualsiasi nuovo criterio o ordine di ordinamento in una query pu\u00f2 influenzare notevolmente la velocit\u00e0 di esecuzione.<\/li>\n<li>Tuttavia, se abbiamo bisogno di leggere solo una parte dei dati, anzich\u00e9 tutti i risultati che soddisfano i criteri di ricerca, esistono molti modi per ottimizzare tale query.<\/li>\n<\/ul>\n<p>\nPassiamo ora alla seconda query, menzionata all'inizio \u2014 quella che conta il numero di record che soddisfano il criterio di ricerca. Prendiamo lo stesso esempio \u2014 la ricerca degli ordini superiori a 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 composto indicato sopra, otteniamo:<\/p>\n<p><img decoding=\"async\" alt=\"Risultati di 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 degli scan 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>\nChe la query scorra completamente attraverso l'indice non sorprende, dato che il campo SubTotal non \u00e8 in prima posizione e quindi la query non pu\u00f2 avvalersene. Il problema si risolve aggiungendo un altro indice sul campo SubTotal, portando cos\u00ec il totale a 48 letture logiche.<\/p>\n<p>Posso fornire ulteriori esempi di query di conteggio, ma il principio rimane lo stesso: <b>ottenere un insieme di dati e contare il numero totale sono due query fondamentalmente diverse<\/b>, e ciascuna richiede misure specifiche per l'ottimizzazione. In generale, non sar\u00e0 possibile trovare una combinazione di indici che funzioni altrettanto bene per entrambe le query.<\/p>\n<p>Pertanto, una delle importanti richieste da chiarire durante lo sviluppo di tale soluzione di ricerca \u00e8 se sia davvero importante per l'azienda vedere il numero totale di oggetti trovati. Spesso non lo \u00e8. E la navigazione per 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 si presenta come \u00abvai alla pagina successiva\u00bb.<\/p>\n<h2>Opzione di paging #2<\/h2>\n<p>\nSupponiamo che agli utenti non interessi conoscere il numero totale di oggetti trovati. Proviamo a semplificare la pagina di ricerca:<\/p>\n<p><img decoding=\"async\" alt=\"Risultati di ricerca e problemi di prestazioni\" src=\"\/wp-content\/uploads\/2020\/03\/66f5ee6dc1a52f14e55ae31e05c502b2.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nInfatti, \u00e8 cambiato solo il fatto che non \u00e8 possibile passare a numeri di pagina specifici, e ora a questa tabella non \u00e8 necessario sapere quante pagine ci possono essere in totale. Ma sorge la domanda: come far\u00e0 la tabella a sapere se ci sono dati per la pagina successiva (per mostrare correttamente il link \u00abSuccessivo\u00bb)?<\/p>\n<p>La risposta \u00e8 molto semplice: \u00e8 possibile leggere dal database un record in pi\u00f9 rispetto a quello necessario per visualizzare, e la presenza di questo \"record aggiuntivo\" indicher\u00e0 se ci sono ulteriori dati da mostrare. In questo modo, per ottenere una singola pagina di dati sar\u00e0 sufficiente eseguire una sola query, il che migliora notevolmente le prestazioni e semplifica il supporto di tale funzionalit\u00e0. Nella mia esperienza, ho avuto un caso in cui rinunciare al conteggio totale dei record ha accelerato l'emissione dei risultati di 4-5 volte.<\/p>\n<p>Per questo approccio esistono diverse opzioni di interfaccia utente: i pulsanti \"indietro\" e \"avanti\", come nell'esempio sopra, il pulsante \"carica altro\", che semplicemente aggiunge nuovi risultati visualizzati, e lo \"scroll infinito\", che funziona sul principio del \"carica altro\", ma il segnale per recuperare il prossimo set di dati \u00e8 lo scorrimento da parte dell'utente fino alla fine dei risultati visualizzati. Qualunque sia la soluzione visiva, il principio di campionamento dei dati rimane lo stesso.<\/p>\n<h2>Aspetti da considerare per l'implementazione del paging<\/h2>\n<p>\nIn tutti gli esempi di richieste sopra indicati, si utilizza l'approccio \"offset + limit\", in cui nella richiesta si specifica da quale riga del risultato e quante righe restituire. Innanzitutto, vediamo come organizzare al meglio il passaggio dei parametri in questo caso. Nella pratica, ho incontrato diversi metodi:<\/p>\n<ul>\n<li>Numero della pagina richiesta (pageIndex), dimensione della pagina (pageSize).<\/li>\n<li>Numero della prima registrazione da restituire (startIndex), numero massimo di registrazioni nel risultato (count).<\/li>\n<li>Numero della prima registrazione da restituire (startIndex), numero dell'ultima registrazione da restituire (endIndex).<\/li>\n<\/ul>\n<p>\nA prima vista potrebbe sembrare cos\u00ec elementare che non ci siano differenze. Ma non \u00e8 cos\u00ec: l'opzione pi\u00f9 comoda e universale \u00e8 la seconda (startIndex, count). Ci sono diverse ragioni per questo:<\/p>\n<ul>\n<li>Per l'approccio con la lettura di +1 record, come descritto sopra, la prima opzione con pageIndex e pageSize \u00e8 estremamente scomoda. Ad esempio, vogliamo visualizzare 50 record per pagina. Secondo l'algoritmo sopra esposto, \u00e8 necessario leggere un record in pi\u00f9 rispetto al necessario. Se questo \u00ab+1\u00bb non \u00e8 previsto sul server, risulta che per la prima pagina dobbiamo richiedere i record da 1 a 51, per la seconda da 51 a 101 e cos\u00ec via. Se si imposta la dimensione della pagina a 51 e si aumenta pageIndex, la seconda pagina restituir\u00e0 da 52 a 102 e cos\u00ec via. Pertanto, nella prima opzione, l'unico modo per implementare correttamente il pulsante per passare alla pagina successiva \u00e8 prevedere sul server la lettura di una \u00abriga extra\u00bb, il che sarebbe un dettaglio poco chiaro.<\/li>\n<li>La terza opzione non ha senso, poich\u00e9 per eseguire richieste nella maggior parte dei database \u00e8 comunque necessario passare il numero, non l'indice dell'ultimo record. Sebbene sottrarre startIndex da endIndex sia un'operazione aritmetica elementare, \u00e8 superflua in questo contesto.<\/li>\n<\/ul>\n<p>\nOra \u00e8 opportuno descrivere le carenze dell'implementazione del paging tramite \u00aboffset + quantit\u00e0\u00bb:<\/p>\n<ul>\n<li>Il recupero di ciascuna pagina successiva sar\u00e0 pi\u00f9 costoso e pi\u00f9 lento rispetto a quella precedente, poich\u00e9 il database deve comunque esaminare tutte le voci \u00abdall\u2019inizio\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 anche esse non sono perfette. Il primo di questi approcci \u00e8 chiamato \u00abkeyset paging\u00bb o \u00abmetodo di ricerca\u00bb e consiste nel seguente: dopo aver ottenuto un batch, \u00e8 possibile memorizzare i valori dei campi nell'ultima riga della pagina e poi usarli per ottenere il batch successivo. Ad esempio, abbiamo eseguito una query 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 riga abbiamo ottenuto il valore della data dell'ordine '2014-06-29'. Quindi, per recuperare la pagina successiva, possiamo tentare di eseguire una query del genere:<\/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 univoco e la condizione indicata sopra probabilmente salter\u00e0 molte righe necessarie. Per rendere univoco questa query, \u00e8 necessario aggiungere un campo univoco alla condizione (supponiamo che 75074 sia l'ultimo valore della chiave primaria della prima porzione):<\/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 un operatore OR. Se con l'aumento di OrderDate cresce anche il valore della chiave primaria, la condizione pu\u00f2 essere semplificata, mantenendo solo il filtro su SalesOrderID. Tuttavia, se tra i valori della chiave primaria e il campo secondo cui \u00e8 ordinato il risultato non c'\u00e8 una rigorosa correlazione, in molti DBMS non sar\u00e0 possibile evitare questo OR. L'unica eccezione a mia conoscenza \u00e8 PostgreSQL, dove viene supportato appieno il confronto di tuple, e la condizione sopra indicata pu\u00f2 essere scritta come \"WHERE (OrderDate, SalesOrderID) &lt; (&#039;2014-06-29&#039;, 75074)&quot;. Con una chiave composta contenente questi due campi, una query simile dovrebbe essere sufficientemente leggera.<\/p>\n<p>Un secondo approccio alternativo si pu\u00f2 trovare, 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 richiesta restituisce, oltre ai dati, un identificatore speciale che permette di ottenere il successivo set di dati. Se questo identificatore ha una durata illimitata (come nel caso di Cosmos DB), \u00e8 un ottimo modo per implementare il paging con passaggi sequenziali tra le pagine (opzione #2 menzionata sopra). I suoi possibili svantaggi: non \u00e8 supportato da tutti i DBMS; l'identificatore ottenuto per il prossimo set pu\u00f2 avere una durata limitata, il che non \u00e8 generalmente adatto per realizzare interazioni con l'utente (come nel caso di ElasticSearch scroll API).<\/p>\n<h2>Filtraggio complesso<\/h2>\n<p>\nComplichiamo ulteriormente la questione. Supponiamo che ci sia la necessit\u00e0 di implementare quello che viene chiamato faceted search, particolarmente noto negli e-commerce. Gli esempi precedenti basati sulla tabella degli ordini non sono molto significativi in questo caso, quindi passiamo alla tabella Product del database AdventureWorks:<\/p>\n<p><img decoding=\"async\" alt=\"Risultati di 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? Quella di mostrare, per ogni elemento del filtro, il numero di record che corrispondono a quel criterio. <i>tenendo conto dei filtri selezionati in tutte le altre categorie<\/i>.<\/p>\n<p>Ad esempio, se scegliamo in questo esempio la categoria Biciclette e il colore Nero, la tabella mostrer\u00e0 solo biciclette di colore nero, ma:<\/p>\n<ul>\n<li>Per ogni criterio del gruppo \"Categories\" sar\u00e0 mostrato il numero di prodotti in questa categoria di colore nero.<\/li>\n<li>Per ogni criterio del gruppo \"Colors\" sar\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 di ricerca e problemi di prestazioni\" src=\"\/wp-content\/uploads\/2020\/03\/7c8ec4d8b427f1f59f0129afebe74b08.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nSe oltre a questo selezioniamo la categoria \"Abbigliamento\", la tabella mostrer\u00e0 anche indumenti di colore nero disponibili. Il numero di prodotti di colore nero nella sezione \"Color\" verr\u00e0 nuovamente calcolato in base alle nuove condizioni, ma nella sezione \"Categories\" nulla cambier\u00e0... Spero che questi esempi siano sufficienti per capire il consueto algoritmo di funzionamento della ricerca per faccette.<\/p>\n<p>Immaginiamo ora come ci\u00f2 pu\u00f2 essere implementato in un database relazionale. Ogni gruppo di criteri, come Categoria e Colore, richieder\u00e0 una query separata:<\/p>\n<pre><code class=\"sql\">SELEZIONA pc.ProductCategoryID, pc.Name, COUNT(1) DA 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\nDOVE p.Color = 'Nero'\nRAGGRUPPA PER pc.ProductCategoryID, pc.Name\nORDINA PER COUNT(1) DISC\n<\/code><\/pre>\n<p>\n<img decoding=\"async\" alt=\"Risultati di 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\">SELEZIONA Colore, COUNT(1) DA Production.Product p\n  INNER JOIN Production.ProductSubcategory ps ON p.ProductSubcategoryID = ps.ProductSubcategoryID\nDOVE ps.ProductCategoryID = 1 --Biciclette\nRAGGRUPPA PER Colore\nORDINA PER COUNT(1) DISC\n<\/code><\/pre>\n<p>\n<img decoding=\"async\" alt=\"Risultati di 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? Molto semplice: non scala bene. Ogni sezione del filtro richiede una richiesta separata per contare le quantit\u00e0 e queste richieste non sono tra le pi\u00f9 leggere. Nei negozi online in alcune categorie possono esserci anche decine di sezioni del filtro, il che pu\u00f2 diventare un serio problema per le prestazioni.<\/p>\n<p>Di solito, dopo queste affermazioni, mi vengono proposte alcune soluzioni, ossia:<\/p>\n<ul>\n<li>Unire tutti i conteggi in un'unica richiesta. Tecnologicamente \u00e8 possibile utilizzare la parola chiave UNION, ma questo non aiuter\u00e0 molto le prestazioni: il database dovr\u00e0 comunque eseguire \"da zero\" ognuno dei frammenti.<\/li>\n<li>Memorizzare le quantit\u00e0. Questo \u00e8 ci\u00f2 che mi viene proposto praticamente ogni volta che descrivo un problema. La nuance \u00e8 che, in generale, ci\u00f2 \u00e8 impossibile. Supponiamo di avere 10 \"faccette\", ognuna con 5 valori. Questa \u00e8 una situazione molto \"umile\" rispetto a ci\u00f2 che si pu\u00f2 vedere nei negozi online. La selezione di un elemento della faccetta influisce sulle quantit\u00e0 nelle altre 9, in altre parole, per ogni combinazione di criteri le quantit\u00e0 possono variare. In tutto, nel nostro esempio, ci sono 50 criteri che l'utente pu\u00f2 selezionare, rispettivamente le combinazioni possibili saranno 250. Non ci sar\u00e0 abbastanza memoria o tempo per riempire 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, si pu\u00f2 implementare un caricamento pigro e memorizzare nella cache le quantit\u00e0 solo per quanto \u00e8 stato scelto in passato, ma pi\u00f9 opzioni di scelta ci saranno, meno efficace sar\u00e0 questa cache e pi\u00f9 evidenti saranno i problemi con i tempi di risposta (soprattutto se il set di dati cambia regolarmente).<\/li>\n<\/ul>\n<p>\nFortunatamente, questa sfida ha ormai diverse soluzioni efficaci, che funzionano in modo prevedibile su grandi volumi di dati. Per ciascuna di queste opzioni, ha senso separare il ricalcolo dei faccetti 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 sui faccetti \"non interferisca\" con la visualizzazione dei risultati di ricerca.<\/p>\n<ul>\n<li>Eseguire il ricalcolo completo dei \"facets\" il meno possibile. Ad esempio, non ricalcolare tutto ad ogni modifica dei criteri di ricerca, ma piuttosto trovare il numero totale di risultati che corrispondono alle attuali condizioni e offrire all'utente la possibilit\u00e0 di mostrarli: \"Trovati 1425 risultati, mostrare?\" L'utente pu\u00f2 continuare a modificare i criteri di ricerca oppure cliccare sul pulsante \"mostra\". Solo in quest'ultimo caso verranno eseguite tutte le richieste per ottenere i risultati e ricalcolare i quantitativi su tutti i \"facets\". \u00c8 evidente che sar\u00e0 necessario affrontare una richiesta per ottenere il numero totale dei risultati e la sua ottimizzazione. Questo approccio \u00e8 comune in molti piccoli negozi online. Ovviamente, non \u00e8 una soluzione miracolosa per questo problema, ma nei casi semplici pu\u00f2 rappresentare un buon compromesso.<\/li>\n<li>Utilizzare un motore di ricerca per trovare risultati e calcolare i faccetti, come Solr, ElasticSearch, Sphinx e altri. Tutti sono progettati per costruire 'facette' e lo fanno in modo abbastanza efficace grazie all'indice invertito. Come sono strutturati i motori di ricerca, perch\u00e9 sono pi\u00f9 efficaci in questi casi rispetto ai database generali, quali sono le pratiche e le insidie \u2014 questo \u00e8 un tema per un articolo a parte. Qui, voglio sottolineare che il motore di ricerca non pu\u00f2 sostituire il principale archivio dati; \u00e8 utilizzato come complemento: qualsiasi modifica nel database principale che \u00e8 rilevante per la ricerca viene sincronizzata nell'indice di ricerca; il meccanismo di ricerca interagisce di solito solo con il motore di ricerca e non si rivolge al database principale. Uno dei punti pi\u00f9 importanti qui \u00e8 come organizzare questa sincronizzazione in modo affidabile. Tutto dipende dalle esigenze relative ai 'tempi di risposta'. Se il tempo tra la modifica nel database principale e la sua 'manifestazione' nella ricerca non \u00e8 critico, si pu\u00f2 creare un servizio che ogni pochi minuti cerca le registrazioni recentemente modificate e le indicizza. Se \u00e8 necessario un tempo di risposta minimo, si pu\u00f2 implementare qualcosa di simile. <noindex><a rel=\"nofollow\" href=\"https:\/\/microservices.io\/patterns\/data\/transactional-outbox.html\">cassetta di transazione<\/a><\/noindex> per l'invio di aggiornamenti al servizio di ricerca.<\/li>\n<\/ul>\n<p><\/p>\n<h2>Conclusioni<\/h2>\n<p><\/p>\n<ol>\n<li>Implementare il paging sul lato server \u00e8 un notevole complicazione e ha senso applicarlo solo per set di dati in rapida crescita o semplicemente grandi. Non c'\u00e8 una ricetta assolutamente precisa per valutare cosa significhi 'grande' o 'in rapida crescita', ma seguirei questo approccio:\n<ul>\n<li>Se l'ottenimento dell'intera collezione di dati, tenendo conto del tempo di server e del trasferimento attraverso la rete, rientra normalmente nei requisiti di prestazione, non ha senso implementare il paging sul lato server.<\/li>\n<li>Potrebbe esserci una situazione in cui nel prossimo futuro non ci si prevede problemi di prestazioni, poich\u00e9 ci sono pochi dati, ma la collezione di dati continua a crescere. Se qualche set di dati potrebbe non soddisfare pi\u00f9 il punto precedente in futuro, \u00e8 meglio considerare subito l'implementazione del paging.<\/li>\n<\/ul>\n<\/li>\n<li>Se non c'\u00e8 una rigorosa richiesta aziendale per mostrare il numero totale di risultati o per visualizzare i numeri di pagina, e nel contempo 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 una chiara esigenza per la ricerca con facce, hai due opzioni per non compromettere le prestazioni:\n<ul>\n<li>Non ricalcolare tutti i quantitativi ad ogni modifica dei criteri di ricerca.<\/li>\n<li>Utilizzare motori di ricerca come Solr, ElasticSearch, Sphinx e altri. Ma \u00e8 importante comprendere che non pu\u00f2 sostituire il database principale e dovrebbe essere usato come complemento al repository principale per affrontare le problematiche di ricerca. <\/li>\n<\/ul>\n<\/li>\n<li>Inoltre, nel caso della ricerca con facce, ha senso separare l'ottenimento della pagina dei risultati di ricerca e il conteggio dei quantitativi in due richieste parallele. Il conteggio dei quantitativi potrebbe richiedere pi\u00f9 tempo rispetto all'ottenimento dei risultati, mentre i risultati sono pi\u00f9 importanti per l'utente.<\/li>\n<li>Se utilizzi un database SQL per le ricerche, ogni modifica al codice relativa a questa parte deve essere testata approfonditamente in termini di prestazioni su un volume di dati adeguato (superiore a quello presente nel database \"live\"). \u00c8 consigliabile anche monitorare il tempo di esecuzione delle query su tutti gli istanze del database, e in particolare su quella \"live\". Anche se durante la fase di sviluppo i piani delle query sembravano corretti, 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.0.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.0.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 della ricerca e problemi di prestazioni | ProHoster","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.","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}]}}