cara mempercepat kueri dengan partisi di penyimpanan tabel biru


10

Bagaimana cara meningkatkan kecepatan kueri ini?

Kami memiliki sekitar 100 konsumen dalam rentang 1-2 minutespelaksanaan kueri berikut. Masing-masing menjalankan ini mewakili 1 menjalankan fungsi konsumsi.

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

Kueri ini akan menghasilkan sekitar 5.000 hasil.

Kode lengkap:

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

Selama eksekusi ini, ketika ada 100 konsumen, seperti yang Anda lihat permintaan akan mengelompok dan membentuk lonjakan:

masukkan deskripsi gambar di sini

Selama lonjakan ini, permintaan seringkali memakan waktu lebih dari 1 menit:

masukkan deskripsi gambar di sini

Bagaimana cara meningkatkan kecepatan kueri ini?


5000 hasil sepertinya Anda tidak memfilter hampir cukup dalam kueri. Hanya mentransfer 5.000 hasil ke kode akan menghabiskan banyak waktu jaringan. Tidak masalah bahwa Anda masih akan melakukan pemfilteran setelahnya. | Selalu lakukan sebanyak pengajuan pemrosesan dalam kueri. Idealnya pada baris yang mendapat indeks dan / atau merupakan hasil dari tampilan yang dihitung.
Christopher

Apakah objek "Terjemahan" itu besar? Mengapa Anda tidak suka mendapatkan beberapa parameter alih-alih gettin` seperti seluruh db?
Hirasawa Yui

@HirasawaYui tidak ada mereka kecil
l --''''''--------- '' '' '' '' '' ''

Anda harus melakukan lebih banyak penyaringan, menarik 5.000 hasil tampaknya tidak ada artinya. tidak mungkin untuk mengetahui tanpa mengetahui data Anda, tetapi saya akan mengatakan Anda harus mencari cara untuk mempartisi dengan cara yang lebih bermakna atau memperkenalkan semacam penyaringan dalam permintaan
4c74356b41

Ada berapa partisi yang berbeda?
Peter Bons

Jawaban:


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

Berikut adalah salah satu masalah, Anda menjalankan kueri dan kemudian menyaringnya dari memori menggunakan "wheres" ini. Pindahkan filter sebelum query berjalan yang seharusnya banyak membantu.

Kedua, Anda harus memberikan beberapa batasan baris untuk diambil dari basis data


ini tidak membuat perbedaan
l

3

Ada 3 hal yang dapat Anda pertimbangkan:

1 . Pertama-tama, singkirkan Whereklausa yang Anda lakukan pada hasil kueri. Lebih baik untuk memasukkan klausa dalam permintaan sebanyak mungkin (bahkan lebih baik jika Anda memiliki indeks pada tabel Anda memasukkannya juga). Untuk saat ini, Anda dapat mengubah kueri Anda seperti di bawah ini:

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

Karena Anda memiliki sejumlah besar data untuk diambil, lebih baik menjalankan kueri Anda secara paralel. Jadi, Anda harus mengganti metode do whileloop di dalam ExecuteQueryAsyncdengan Parallel.ForEachsaya menulis berdasarkan Stephen Toub Parallel.While ; Dengan cara ini akan mengurangi waktu eksekusi permintaan. Ini adalah pilihan yang baik karena Anda dapat menghapus Resultketika Anda melakukan panggilan pada metode ini, Tetapi ada sedikit batasan yang akan saya bicarakan setelah bagian kode ini:

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

Dan kemudian Anda bisa menyebutnya dalam Getmetode Anda :

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

Seperti yang Anda lihat metode itselft bukan async (Anda harus mengubah namanya) dan Parallel.ForEachtidak kompatibel dengan meneruskan metode async. Inilah mengapa saya menggunakan ExecuteQuerySegmentedsebagai gantinya. Tetapi, untuk membuatnya lebih berkinerja dan menggunakan semua manfaat metode asinkron Anda dapat mengganti ForEachloop di atas dengan ActionBlockmetode dalam Dataflow atau ParallelForEachAsyncmetode ekstensi dari paket AsyncEnumerator Nuget .

2. Ini adalah pilihan yang baik untuk menjalankan kueri paralel independen dan kemudian menggabungkan hasilnya, bahkan jika peningkatan kinerjanya paling banyak 10 persen. Ini memberi Anda waktu untuk dapat menemukan kueri ramah kinerja terbaik. Tapi, jangan pernah lupa untuk memasukkan semua kendala Anda di dalamnya, dan uji kedua cara untuk mengetahui mana yang lebih cocok untuk masalah Anda.

3 . Saya tidak yakin itu saran yang bagus atau tidak, tapi lakukan dan lihat hasilnya. Seperti yang dijelaskan dalam MSDN :

Layanan Tabel memberlakukan batas waktu server sebagai berikut:

  • Operasi kueri: Selama interval waktu habis, kueri dapat mengeksekusi hingga maksimum lima detik. Jika kueri tidak selesai dalam interval lima detik, respons mencakup token kelanjutan untuk mengambil item yang tersisa pada permintaan berikutnya. Lihat Batas Waktu Kueri dan Paginasi untuk informasi lebih lanjut.

  • Sisipkan, perbarui, dan hapus operasi: Interval batas waktu maksimum adalah 30 detik. Tiga puluh detik juga merupakan interval default untuk semua operasi penyisipan, pembaruan, dan penghapusan.

Jika Anda menentukan batas waktu yang kurang dari batas waktu standar layanan, interval waktu habis Anda akan digunakan.

Jadi, Anda dapat bermain dengan batas waktu dan memeriksa apakah ada peningkatan kinerja.


2

Sayangnya, kueri di bawah ini memperkenalkan pemindaian tabel lengkap :

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

Anda harus membaginya menjadi dua filter Partition Key dan meminta mereka secara terpisah, yang akan menjadi dua scan partisi dan melakukan lebih efisien.


kami melihat mungkin peningkatan 10% dengan ini, tapi itu tidak cukup
l

1

Jadi rahasianya tidak hanya dalam kode tetapi juga dalam menyiapkan tabel penyimpanan Azure Anda.

a) Salah satu opsi utama untuk mengoptimalkan kueri Anda di Azure adalah memperkenalkan caching. Ini akan secara drastis mengurangi waktu respons keseluruhan Anda dan dengan demikian menghindari kemacetan selama jam sibuk yang telah Anda sebutkan.

b) Juga, Saat meminta entitas keluar dari Azure, cara tercepat yang mungkin untuk melakukannya adalah dengan PartitionKey dan RowKey. Ini adalah satu-satunya bidang yang diindeks di Penyimpanan Tabel dan permintaan apa pun yang menggunakan keduanya akan dikembalikan dalam hitungan beberapa milidetik. Jadi pastikan Anda menggunakan PartitionKey & RowKey.

Lihat detail lebih lanjut di sini: https://docs.microsoft.com/en-us/azure/storage/tables/table-storage-design-for-query

Semoga ini membantu.


-1

Catatan: Ini adalah saran umum optimasi permintaan DB.

Mungkin saja ORM melakukan sesuatu yang bodoh. Ketika melakukan optimasi, tidak apa-apa untuk mengecilkan lapisan abstraksi. Jadi saya sarankan menulis ulang permintaan dalam bahasa query (SQL?) Untuk membuatnya lebih mudah untuk melihat apa yang terjadi, dan juga lebih mudah untuk mengoptimalkan.

Kunci untuk mengoptimalkan pencarian adalah penyortiran! Menjaga tabel diurutkan biasanya jauh lebih murah dibandingkan dengan memindai seluruh tabel pada setiap permintaan! Jadi jika memungkinkan, biarkan tabel diurutkan berdasarkan kunci yang digunakan dalam kueri. Dalam sebagian besar solusi basis data, ini dicapai dengan membuat kunci indeks.

Strategi lain yang berfungsi dengan baik jika ada beberapa kombinasi, adalah membuat setiap kueri sebagai tabel terpisah (sementara dalam memori) yang selalu terkini. Jadi ketika sesuatu dimasukkan, itu juga "dimasukkan" ke dalam tabel "view". Beberapa solusi basis data menyebut ini "tampilan".

Strategi yang lebih kasar adalah membuat replika read-only untuk mendistribusikan beban.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.