come velocizzare una query con keykey nella memoria della tabella azzurra


10

Come possiamo aumentare la velocità di questa query?

Abbiamo circa 100 consumatori entro il termine 1-2 minutesdell'esecuzione della seguente query. Ognuna di queste corse rappresenta 1 corsa di una funzione di consumo.

        TableQuery<T> treanslationsQuery = new TableQuery<T>()
         .Where(
          TableQuery.CombineFilters(
            TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, sourceDestinationPartitionKey)
           , TableOperators.Or,
            TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, anySourceDestinationPartitionKey)
          )
         );

Questa query produrrà circa 5000 risultati.

Codice completo:

    public static async Task<IEnumerable<T>> ExecuteQueryAsync<T>(this CloudTable table, TableQuery<T> query) where T : ITableEntity, new()
    {
        var items = new List<T>();
        TableContinuationToken token = null;

        do
        {
            TableQuerySegment<T> seg = await table.ExecuteQuerySegmentedAsync(query, token);
            token = seg.ContinuationToken;
            items.AddRange(seg);
        } while (token != null);

        return items;
    }

    public static IEnumerable<Translation> Get<T>(string sourceParty, string destinationParty, string wildcardSourceParty, string tableName) where T : ITableEntity, new()
    {
        var acc = CloudStorageAccount.Parse(Environment.GetEnvironmentVariable("conn"));
        var tableClient = acc.CreateCloudTableClient();
        var table = tableClient.GetTableReference(Environment.GetEnvironmentVariable("TableCache"));
        var sourceDestinationPartitionKey = $"{sourceParty.ToLowerTrim()}-{destinationParty.ToLowerTrim()}";
        var anySourceDestinationPartitionKey = $"{wildcardSourceParty}-{destinationParty.ToLowerTrim()}";

        TableQuery<T> treanslationsQuery = new TableQuery<T>()
         .Where(
          TableQuery.CombineFilters(
            TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, sourceDestinationPartitionKey)
           , TableOperators.Or,
            TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, anySourceDestinationPartitionKey)
          )
         );

        var over1000Results = table.ExecuteQueryAsync(treanslationsQuery).Result.Cast<Translation>();
        return over1000Results.Where(x => x.expireAt > DateTime.Now)
                           .Where(x => x.effectiveAt < DateTime.Now);
    }

Durante queste esecuzioni, quando ci sono 100 consumatori, come puoi vedere, le richieste si raggrupperanno e formeranno picchi:

inserisci qui la descrizione dell'immagine

Durante questi picchi, le richieste spesso richiedono più di 1 minuto:

inserisci qui la descrizione dell'immagine

Come possiamo aumentare la velocità di questa query?


5000 risultati sembrano non filtrare abbastanza nella query. Il solo trasferimento di 5000 risultati nel codice costerà un sacco di tempo in rete. Non importa che dopo continuerai a fare i filtri. | Effettua sempre la stessa quantità di elaborazione di una query. Idealmente su righe che hanno ottenuto un indice e / o sono il risultato di una vista calcolata.
Christopher

Quegli oggetti "di traduzione" sono grandi? Perché non ti piace ottenere alcuni parametri invece di ottenere come l'intero db?
Hirasawa Yui

@HirasawaYui no sono piccoli
l --''''''--------- '' '' '' '' '' ''

dovresti fare più filtri, tirando 5000 risultati sembra insignificante. è impossibile dirlo senza conoscere i tuoi dati, ma direi che dovresti trovare un modo per dividerli in modo più significativo o introdurre una sorta di filtro nella query
4c74356b41

Quante diverse partizioni ci sono?
Peter Bons,

Risposte:


3
  var over1000Results = table.ExecuteQueryAsync(treanslationsQuery).Result.Cast<Translation>();
        return over1000Results.Where(x => x.expireAt > DateTime.Now)
                           .Where(x => x.effectiveAt < DateTime.Now);

Ecco uno dei problemi, stai eseguendo la query e quindi filtrandola dalla memoria usando queste "location". Sposta i filtri prima dell'esecuzione della query, il che dovrebbe essere di grande aiuto.

In secondo luogo, è necessario fornire un limite di righe da recuperare dal database


questo non ha fatto differenza
l

3

Ci sono 3 cose che puoi considerare:

1 . Prima di tutto, elimina le Whereclausole che esegui sul risultato della query. È meglio includere le clausole nelle query il più possibile (ancora meglio se si hanno indici anche nelle tabelle). Per ora, puoi modificare la query come di seguito:

var translationsQuery = new TableQuery<T>()
.Where(TableQuery.CombineFilters(
TableQuery.CombineFilters(
    TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, sourceDestinationPartitionKey),
    TableOperators.Or,
    TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, anySourceDestinationPartitionKey)
    ),
TableOperators.And,
TableQuery.CombineFilters(
    TableQuery.GenerateFilterConditionForDate("affectiveAt", QueryComparisons.LessThan, DateTime.Now),
    TableOperators.And,
    TableQuery.GenerateFilterConditionForDate("expireAt", QueryComparisons.GreaterThan, DateTime.Now))
));

Poiché hai una grande quantità di dati da recuperare, è meglio eseguire le tue query in parallelo. Quindi, dovresti sostituire il metodo do whileloop inside ExecuteQueryAsynccon quello che Parallel.ForEachho scritto basato su Stephen Toub Parallel.While ; In questo modo ridurrà i tempi di esecuzione delle query. Questa è una buona scelta perché puoi rimuovere Resultquando fai una chiamata su questo metodo, ma ha un piccolo limite che ne parlerò dopo questa parte di codice:

public static IEnumerable<T> ExecuteQueryAsync<T>(this CloudTable table, TableQuery<T> query) where T : ITableEntity, new()
{
    var items = new List<T>();
    TableContinuationToken token = null;

    Parallel.ForEach(new InfinitePartitioner(), (ignored, loopState) =>
    {
        TableQuerySegment<T> seg = table.ExecuteQuerySegmented(query, token);
        token = seg.ContinuationToken;
        items.AddRange(seg);

        if (token == null) // It's better to change this constraint by looking at https://www.vivien-chevallier.com/Articles/executing-an-async-query-with-azure-table-storage-and-retrieve-all-the-results-in-a-single-operation
            loopState.Stop();
    });

    return items;
}

E poi puoi chiamarlo nel tuo Getmetodo:

return table.ExecuteQueryAsync(translationsQuery).Cast<Translation>();

Come puoi vedere, il suo metodo non è asincrono (dovresti cambiarne il nome) e Parallel.ForEachnon è compatibile con il passaggio di un metodo asincrono. Questo è il motivo per cui l'ho usato ExecuteQuerySegmentedinvece. Ma, per renderlo più performante e utilizzare tutti i vantaggi del metodo asincrono, è possibile sostituire il ForEachciclo sopra con il ActionBlockmetodo nel flusso di dati o il ParallelForEachAsyncmetodo di estensione dal pacchetto Nuget di AsyncEnumerator .

2. È una buona scelta eseguire query parallele indipendenti e quindi unire i risultati, anche se il miglioramento delle prestazioni è al massimo del 10 percento. Ciò ti dà il tempo di essere in grado di trovare la query più adatta alle prestazioni. Ma non dimenticare mai di includere tutti i tuoi vincoli e testare entrambi i modi per sapere quale si adatta meglio al tuo problema.

3 . Non sono sicuro che sia un buon suggerimento o meno, ma fallo e vedi i risultati. Come descritto in MSDN :

Il servizio tabelle impone i timeout del server come segue:

  • Operazioni di query: durante l'intervallo di timeout, una query può essere eseguita per un massimo di cinque secondi. Se la query non viene completata entro l'intervallo di cinque secondi, la risposta include i token di continuazione per il recupero degli elementi rimanenti su una richiesta successiva. Vedere Timeout e impaginazione delle query per ulteriori informazioni.

  • Operazioni di inserimento, aggiornamento ed eliminazione: l'intervallo di timeout massimo è di 30 secondi. Trenta secondi è anche l'intervallo predefinito per tutte le operazioni di inserimento, aggiornamento ed eliminazione.

Se si specifica un timeout inferiore al timeout predefinito del servizio, verrà utilizzato l'intervallo di timeout.

Quindi puoi giocare con il timeout e verificare se ci sono miglioramenti delle prestazioni.


2

Sfortunatamente, la query seguente introduce una scansione completa della tabella :

    TableQuery<T> treanslationsQuery = new TableQuery<T>()
     .Where(
      TableQuery.CombineFilters(
        TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, sourceDestinationPartitionKey)
       , TableOperators.Or,
        TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, anySourceDestinationPartitionKey)
      )
     );

Dovresti dividerlo in due filtri della chiave di partizione e interrogarli separatamente, che diventeranno due scansioni delle partizioni e funzioneranno in modo più efficiente.


forse abbiamo visto un miglioramento del 10% con questo, ma non è abbastanza
l --''''''--------- '' '' '' '' '' ''

1

Quindi il segreto non è solo nel codice ma anche nella configurazione delle tabelle di archiviazione di Azure.

a) Una delle opzioni importanti per ottimizzare le query in Azure è introdurre la memorizzazione nella cache. Ciò ridurrà drasticamente i tuoi tempi di risposta complessivi, evitando così colli di bottiglia nelle ore di punta che hai citato.

b) Inoltre, quando si eseguono query su entità al di fuori di Azure, il modo più rapido possibile è utilizzare sia PartitionKey che RowKey. Questi sono gli unici campi indicizzati in Archiviazione tabelle e qualsiasi query che li utilizza entrambi verrà restituita nel giro di pochi millisecondi. Quindi assicurati di utilizzare sia PartitionKey che RowKey.

Vedi maggiori dettagli qui: https://docs.microsoft.com/en-us/azure/storage/tables/table-storage-design-for-query

Spero che sia di aiuto.


-1

nota: questo è un consiglio generale sull'ottimizzazione delle query del DB.

È possibile che l'ORM stia facendo qualcosa di stupido. Quando si eseguono le ottimizzazioni, è OK abbassare un livello di astrazione. Quindi suggerisco di riscrivere la query nel linguaggio di query (SQL?) Per rendere più facile vedere cosa sta succedendo e anche più facile da ottimizzare.

La chiave per ottimizzare le ricerche è l'ordinamento! Mantenere una tabella ordinata è in genere molto più economico rispetto alla scansione dell'intera tabella su ogni query! Pertanto, se possibile, mantenere la tabella ordinata in base alla chiave utilizzata nella query. Nella maggior parte delle soluzioni di database, ciò si ottiene creando una chiave di indice.

Un'altra strategia che funziona bene se ci sono poche combinazioni è quella di avere ogni query come una tabella separata (temporanea in memoria) sempre aggiornata. Quindi, quando viene inserito qualcosa, viene anche "inserito" nelle tabelle "view". Alcune soluzioni di database chiamano queste "viste".

Una strategia più bruta è quella di creare repliche di sola lettura per distribuire il carico.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.