masmavi tablo depolamasında partitionkey ile bir sorguyu hızlandırma


10

Bu sorgunun hızını nasıl artırabiliriz?

Yaklaşık sahip 100 tüketici dahilindeki s 1-2 minutesaşağıdaki sorgu yürütme. Bu çalışmaların her biri, bir tüketim fonksiyonunun 1 çalışmasını temsil eder.

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

Bu sorgu yaklaşık 5000 sonuç verecektir .

Tam kod:

    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);
    }

Bu yürütmeler sırasında, 100 tüketici olduğunda, gördüğünüz gibi, istekler kümelenecek ve ani artışlar oluşturacaktır:

resim açıklamasını buraya girin

Bu ani yükselişler sırasında talepler genellikle 1 dakikadan fazla sürer:

resim açıklamasını buraya girin

Bu sorgunun hızını nasıl artırabiliriz?


5000 sonuç, sorguda neredeyse yeterince filtrelemediğiniz gibi görünüyor. Sadece 5000 sonucu koda aktarmak bir ton ağ süresine mal olacaktır. Daha sonra hala filtreleme yapacağınızı boş verin. | Her zaman sorguda bir işleme kadar dosyalama yapın. İdeal olarak indeks alan ve / veya hesaplanmış bir görünümün sonucu olan satırlarda.
Christopher

Bu "Çeviri" nesneleri büyük mü? Neden bütün db gibi almak yerine bazı parametreleri almak istemiyorsunuz?
Hirasawa Yui

@HirasawaYui hayır onlar küçük
l --''''''--------- '' '' '' '' '' '' '

daha fazla filtreleme yapmalısınız, 5000 sonuç çekmek anlamsız görünüyor. Verilerinizi bilmeden söylemek imkansız, ancak daha anlamlı bir şekilde bölümlemek veya sorguda bir tür filtreleme tanıtmak için bir yol bulmanız gerektiğini söyleyebilirim
4c74356b41

Kaç farklı bölüm var?
Peter Bons11

Yanıtlar:


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

İşte sorunlardan biri, sorguyu çalıştırıyor ve sonra bu "nerede" kullanarak bellekten filtreleme. Çok yardımcı olması gereken sorgu çalışmadan önce filtreleri buraya taşıyın.

İkincisi, veritabanından almak için bazı satır sınırı sağlamanız gerekir


bu bir fark
yaratmadı

3

Düşünebileceğiniz 3 şey var:

1 . Her şeyden önce, Wheresorgu sonucunda yerine getirdiğiniz maddelerinizden kurtulun . Sorguları olabildiğince sorguya eklemek daha iyidir (tablolarınızda herhangi bir dizininiz varsa da daha iyi olabilir). Şimdilik, sorgunuzu aşağıdaki gibi değiştirebilirsiniz:

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))
));

Alınacak büyük miktarda veriye sahip olduğunuz için sorgularınızı paralel olarak çalıştırmak daha iyidir. Yani, değiştirmeniz gerekir do whileiçindeki döngü ExecuteQueryAsyncile yöntemle Parallel.ForEachI dayalı yazdım Stephen Toub Parallel.While ; Bu şekilde sorgu yürütme süresini azaltacaktır. Bu iyi bir seçimdir, çünkü Resultbu yönteme bir çağrı yaptığınızda kaldırabilirsiniz , ancak kodun bu bölümünden sonra konuşacağım küçük bir sınırlaması vardır:

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;
}

Ve sonra bunu Getyönteminizde çağırabilirsiniz :

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

Gördüğünüz gibi, bu kod async değildir (adını değiştirmelisiniz) ve Parallel.ForEachbir async yöntemine geçmeyle uyumlu değildir. Bunun için kullandım ExecuteQuerySegmented. Ancak, daha fazla ölçülebilir hale ve yukarıda yerini alabilecek asenkron yöntemin tüm avantajlarını kullanmak ForEachile döngü ActionBlockiçinde yöntemle veri akışı veya ParallelForEachAsyncgelen uzatma yöntemiyle AsyncEnumerator Nuget paketinin .

2 .Bu performans iyileştirme çoğu yüzde 10 olsa bile, bağımsız paralel sorguları yürütmek ve sonra sonuçları birleştirmek için iyi bir seçimdir. Bu, en iyi performans dostu sorguyu bulmak için size zaman kazandırır. Ancak, tüm kısıtlamalarınızı buna dahil etmeyi asla unutmayın ve sorunun hangisinin daha uygun olduğunu bilmek için her iki yolu da test edin.

3 . Bunun iyi bir öneri olup olmadığından emin değilim, Ama yapın ve sonuçları görün. MSDN'de açıklandığı gibi :

Tablo hizmeti, sunucu zaman aşımlarını aşağıdaki gibi zorlar:

  • Sorgu işlemleri: Zaman aşımı süresi boyunca, bir sorgu en fazla beş saniye boyunca çalıştırılabilir. Sorgu beş saniyelik aralıkta tamamlanmazsa, yanıt sonraki bir istekte kalan öğeleri almak için devam belirteçleri içerir. Daha fazla bilgi için bkz. Sorgu Zaman Aşımı ve Sayfalandırma.

  • Ekleme, güncelleme ve silme işlemleri: Maksimum zaman aşımı aralığı 30 saniyedir. Otuz saniye ayrıca tüm ekleme, güncelleme ve silme işlemleri için varsayılan aralıktır.

Hizmetin varsayılan zaman aşımından daha az bir zaman aşımı belirtirseniz, zaman aşımı aralığınız kullanılır.

Böylece zaman aşımı ile oynayabilir ve performans iyileştirmeleri olup olmadığını kontrol edebilirsiniz.


2

Ne yazık ki, aşağıdaki sorgu tam bir masa taraması sunar :

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

Bunu iki Bölüm Anahtarı filtresine ayırmalı ve ayrı olarak sorgulamalısınız, bu da iki bölüm taraması haline gelecek ve daha verimli çalışacaktır .


bununla belki% 10'luk bir gelişme gördük, ama yeterli değil
l --''''''--------- '' '' '' '' '' ''

1

Böylece sır sadece kodda değil, aynı zamanda Azure depolama tablolarınızı oluştururken de vardır.

a) Azure'daki sorgularınızı optimize etmek için öne çıkan seçeneklerden biri önbelleklemeyi tanıtmaktır. Bu, genel yanıt sürelerinizi büyük ölçüde azaltacak ve böylece bahsettiğiniz yoğun saatte darboğazdan kaçınacaktır.

b) Ayrıca, Azure dışındaki varlıkları sorgularken, bunu yapmanın en hızlı yolu hem PartitionKey hem de RowKey'dir. Bunlar, Tablo Deposu'ndaki tek dizinlenmiş alanlardır ve bunların her ikisini de kullanan herhangi bir sorgu birkaç milisaniye içinde döndürülür. Bu yüzden hem PartitionKey hem de RowKey kullandığınızdan emin olun.

Daha fazla ayrıntıyı burada bulabilirsiniz: https://docs.microsoft.com/tr-tr/azure/storage/tables/table-storage-design-for-query

Bu yardımcı olur umarım.


-1

not: Bu genel DB sorgu optimizasyonu önerisidir.

ORM'nin aptalca bir şey yapması mümkündür. Optimizasyon yaparken bir soyutlama katmanını düşürmek sorun olmaz. Bu yüzden, neler olup bittiğini daha kolay görmek ve optimize etmek için sorguyu sorgu dilinde (SQL?) Yeniden yazmanızı öneririm.

Aramaları optimize etmenin anahtarı sıralamadır! Bir tabloyu sıralı tutmak, her sorgudaki tüm tabloyu taramaya kıyasla genellikle çok daha ucuzdur! Bu nedenle mümkünse, tabloyu sorguda kullanılan anahtara göre sıralayın. Çoğu veritabanı çözümünde bu bir dizin anahtarı oluşturularak elde edilir.

Birkaç kombinasyon varsa iyi çalışan başka bir strateji, her sorguyu her zaman güncel olan ayrı (bellekte geçici) bir tablo olarak bulundurmaktır. Bir şey eklendiğinde, aynı zamanda "görünüm" tablolarına "eklenir". Bazı veritabanı çözümleri buna "görünümler" adını verir.

Daha kaba bir strateji ise, yükü dağıtmak için salt okunur kopyalar oluşturmaktır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.