2013-01-06 7 views
11

Ecco un pezzo di codice che inizializzare un TableBatchOperation progettato per recuperare le due file in un unico lotto:Il recupero di molte righe utilizzando un TableBatchOperation non è supportato?

TableBatchOperation batch = new TableBatchOperation(); 
batch.Add(TableOperation.Retrieve("somePartition", "rowKey1")); 
batch.Add(TableOperation.Retrieve("somePartition", "rowKey2")); 
//second call throws an ArgumentException: 
//"A batch transaction with a retrieve operation cannot contain 
//any other operation" 

Come citato, viene generata un'eccezione, e sembra non supportato per recuperare N righe in un unico lotto. Questo è un grosso problema per me, in quanto ho bisogno di recuperare circa 50 righe per richiesta. Questo problema è tanto più saggio quanto il costo. Come forse sapete, i prezzi di Archiviazione tabella di Azure si basano sulla quantità di transazioni, il che significa che 50 operazioni di recupero sono 50 volte più costose di una singola operazione batch.

Ho perso qualcosa?

Side note Sto utilizzando il nuovo Azure Storage api 2.0. Ho notato che questa domanda non è mai stata sollevata sul web. Questo vincolo potrebbe essere stato aggiunto di recente?

modificare

ho trovato una questione connessa qui: Very Slow on Azure Table Storage Query on PartitionKey/RowKey List. Sembra che l'uso di TableQuery con "o" su rowkey risulti con una scansione completa della tabella. Qui c'è davvero un problema serio ...

+0

Sono bloccato ... non riesco a trovare una soluzione accettabile ... non mi meraviglio perché le domande azzurre sullo stackoverflow sono così inattive: tuttavia Azure non è pronto per la produzione. – uzul

+0

Hai qualche esempio del tipo di dati che stai cercando di richiedere? – knightpfhor

+0

Sono semplici stringhe JSON, molto piccole e milioni di esse. Ho creato qualche entità generica con una proprietà "Data" contenente la stringa ... ma ora sto pensando che dovrei andare con i blob in termini di prestazioni ... ma non riesco ancora a recuperarli in un singolo round trip ... – uzul

risposta

0

Le operazioni "Get" batch non sono supportate da Archiviazione tabelle di Azure. Le operazioni supportate sono: Aggiungi, Elimina, Aggiorna e Unisci. Dovresti eseguire query come richieste separate. Per un'elaborazione più rapida, potresti voler eseguire queste query in parallelo.

+1

Grazie per la risposta, ma non posso considerarla una soluzione accettabile. – uzul

+0

Posso sapere perché? –

+3

la tua soluzione significa "50 thread + 50 round-trip" per richiesta dell'utente. E mi aspetto centinaia di richieste al secondo. Ti sembra scalabile? Sul serio! Non riesco a credere che il team di Azure non abbia pensato alla necessità di recuperare N righe sulla stessa partizione. Che peccato!!! – uzul

0

La soluzione migliore è creare una query di selezione Linq/OData ... che recuperi ciò che stai cercando.

Per prestazioni migliori, è necessario creare una query per partizione ed eseguire tali query contemporaneamente.

Non l'ho provato personalmente, ma penso che funzionerebbe.

0

Quante entità hai per partizione? Con un'operazione di recupero è possibile recuperare fino a 1000 record per query. Quindi è possibile eseguire il filtraggio della chiave di riga sul set di memoria e pagare solo per 1 operazione.

Un'altra opzione è fare un Row Key range query per recuperare parte di una partizione in una sola operazione. In sostanza, si specifica un limite superiore e inferiore per i tasti di riga da restituire, anziché un'intera partizione.

4

Quando si progetta lo schema Chiave di partizione (PK) e Riga chiave (RK) in Azure Table Storage (ATS), la considerazione principale dovrebbe essere come recuperare i dati. Come hai detto, ogni query eseguita costa sia denaro, ma soprattutto tempo, quindi è necessario recuperare tutti i dati in una query efficiente.Le query efficienti che è possibile eseguire su ATS sono di questi tipi:

  • esatta PK e RK
  • PK esatta, gamma RK
  • PK Gamma
  • Gamma PK, gamma RK

Sulla base dei vostri commenti, suppongo che tu abbia alcuni dati simili a questo:

PK RK  Data 
Guid1 A  {Data:{...}, RelatedRows: [{PK:"Guid2", RK:"B"}, {PK:"Guid3", RK:"C"}]} 
Guid2 B  {Data:{...}, RelatedRows: [{PK:"Guid1", RK:"A"}] 
Guid3 C  {Data:{...}, RelatedRows: [{PK:"Guid1", RK:"A"}];} 

e hai recuperato i dati su Guid1, e ora devi caricare Guid2 e Guid3. Presumo anche che queste righe non abbiano un comune denominatore come se fossero tutte per lo stesso utente. Con questo in mente mi piacerebbe creare un extra "tabella indice", che potrebbe assomigliare a questo:

PK  RK  Data 
Guid1-A Guid2-B {Data:{....}} 
Guid1-A Guid3-C {Data:{....}} 
Guid2-B Guid1-A {Data:{....}} 
Guid2-B Guid1-A {Data:{....}} 

Qualora il PK è il PK combinato e RK del genitore e l'RK è il PK combinato e RK del fila di bambini. È quindi possibile eseguire una query che dice restituire tutte le righe con PK = "Guida1-A" e otterrete tutti i dati relativi con una sola chiamata (o due chiamate in generale). L'overhead più grande che questo crea è nelle tue scritture, quindi ora quando hai ragione di una riga devi anche scrivere righe per ciascuna delle righe correlate e anche assicurarti che i dati siano aggiornati (questo potrebbe non essere un problema per te se questa è una scrittura una volta tipo di scenario).

Se una delle mie supposizioni è errata o se si dispone di dati di esempio, è possibile aggiornare questa risposta con esempi più pertinenti.

4

provare qualcosa di simile:

TableQuery<DynamicTableEntity> query = new TableQuery<DynamicTableEntity>() 
               .Where(TableQuery.CombineFilters(
                TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, "partition1"), 
                TableOperators.And, 
                TableQuery.CombineFilters(
                 TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, "row1"), 
                 TableOperators.Or, 
                 TableQuery.GenerateFilterCondition("RowKey", QueryComparisons.Equal, "row2")))); 
+0

Sai se esiste un limite al numero di condizioni di filtro che puoi avere? – TomSelleck

3

So che questa è una vecchia questione, ma come Azure ANCORA non supporta gli indici secondari, sembra che sarà rilevante per un certo tempo.

Ho riscontrato lo stesso tipo di problema. Nel mio scenario, avevo bisogno di cercare centinaia di elementi all'interno della stessa partizione, dove ci sono milioni di righe (immagina GUID come riga-chiave). Ho provato un paio di opzioni di ricercare 10.000 righe

  1. (PK & & RK)
  2. (PK & & RK1) || (PK & RK2) || ...
  3. PK & & (RK1 || RK2 || ...)

stavo usando l'API asincrona, con un massimo di 10 gradi di parallelismo (max 10 richieste in attesa). Ho anche testato un paio di lotti diversi (10 righe, 50, 100).

Test      Batch Size API calls Elapsed (sec) 
(PK && RK)     1   10000  95.76 
(PK && RK1) || (PK && RK2) 10   1000  25.94 
(PK && RK1) || (PK && RK2) 50   200   18.35 
(PK && RK1) || (PK && RK2) 100   100   17.38 
PK && (RK1 || RK2 || …) 10   1000  24.55 
PK && (RK1 || RK2 || …) 50   200   14.90 
PK && (RK1 || RK2 || …) 100   100   13.43 

NB: Questi sono tutti all'interno della stessa partizione - solo più tasti di selezione.

Sarei stato felice di ridurre il numero di chiamate API. Ma come ulteriore vantaggio, anche il tempo trascorso è significativamente inferiore, risparmiando sui costi di elaborazione (almeno alla fine!).

Non troppo sorprendente, i lotti di 100 righe hanno fornito le prestazioni migliori.Ci sono ovviamente altre considerazioni sulle prestazioni, in particolare l'utilizzo della rete (# 1 difficilmente utilizza la rete a tutti, ad esempio, mentre gli altri spingono molto più difficile)

EDIT Prestare attenzione durante la ricerca di molti rowkeys. Esiste (o ovviamente) una limitazione della lunghezza dell'URL alla query. Se si supera la lunghezza, la query continuerà comunque perché il servizio non è in grado di indicare che l'URL è stato troncato. Nel nostro caso, abbiamo limitato la lunghezza della query combinata a circa 2500 caratteri (URL codificato!)

+0

Questo limite si applica anche alle query C#, giusto? C'è un modo per dire quanto è lunga la query? L'ultimo test che hai qui è implementato nella risposta di @Kiran Madipally, giusto? – TomSelleck

0

Ok, quindi un'operazione di richiamo batch, il caso migliore è una query di tabella. Una situazione meno ottimale richiederebbe operazioni di recupero parallelo.

A seconda del progetto PK, RK che è possibile basare su un elenco (PK, RK), individuare quale sia l'insieme più piccolo/più efficiente di operazioni di recupero/query che è necessario eseguire. Poi recuperi tutte queste cose in parallelo e riordini la risposta esatta lato client.

IMAO, è stata una mancata progettazione da parte di Microsoft per aggiungere il metodo Retrieve alla classe TableBatchOperation perché trasmette semantica non supportata dall'API di archiviazione tabella.

In questo momento, non sono dell'umore di scrivere qualcosa di super efficiente, quindi lascerò questa soluzione super semplice qui.

var retrieveTasks = new List<Task<TableResult>>(); 

foreach (var item in list) 
{ 
    retrieveTasks.Add(table.ExecuteAsync(TableOperation.Retrieve(item.pk, item.rk))); 
} 

var retrieveResults = new List<TableResult>(); 

foreach (var retrieveTask in retrieveTasks) 
{ 
    retrieveResults.Add(await retrieveTask); 
} 

Questo blocco asincrono di codice preleverà le entità in list in parallelo e memorizzare il risultato nel retrieveResults preservare l'ordine. Se hai intervalli continui di entità che devi recuperare, puoi migliorarlo utilizzando una query rang.

C'è un punto debole (che dovrete trovare provando questo) è dove è probabilmente più veloce/più economico per interrogare più entità di quanto potrebbe essere necessario per un recupero di batch specifico quindi scartare i risultati recuperati che non si ' ho bisogno.

Se si dispone di una piccola partizione si potrebbe trarre beneficio da una query in questo modo:

where pk=partition1 and (rk=rk1 or rk=rk2 or rk=rk3) 

Se il lessicografico (vale a dire un sistema di classificazione) distanza è grande tra le chiavi si potrebbe desiderare di prendere in parallelo. Ad esempio, se si memorizza l'alfabeto nella memoria della tabella, è preferibile eseguire a e z che sono distanti tra le operazioni di recupero parallelo durante il recupero di a, e c che sono vicine tra loro. Recupero a, bc e z trarrebbero vantaggio da un approccio ibrido.

Se sai tutto questo in anticipo puoi calcolare qual è la cosa migliore da fare dato un set di PK e RK. Quanto più sai come vengono ordinati i dati sottostanti, migliori saranno i tuoi risultati. Consiglierei un approccio generale a questo e invece, provate ad applicare ciò che imparate da questi diversi modelli di query per risolvere il vostro problema.