Làm thế nào để tăng tốc truy vấn với phân vùng trong bộ lưu trữ bảng azure


10

Làm thế nào để chúng tôi tăng tốc độ của truy vấn này?

Chúng tôi có khoảng 100 người tiêu dùng trong khoảng thời gian 1-2 minutesthực hiện truy vấn sau. Mỗi một trong những lần chạy này đại diện cho 1 lần chạy của hàm tiêu dùng.

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

Truy vấn này sẽ mang lại khoảng 5000 kết quả.

Mã đầy đủ:

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

Trong các lần thực hiện này, khi có 100 người tiêu dùng, như bạn có thể thấy các yêu cầu sẽ co cụm và hình thành các đột biến:

nhập mô tả hình ảnh ở đây

Trong những lần tăng đột biến này, các yêu cầu thường mất hơn 1 phút:

nhập mô tả hình ảnh ở đây

Làm thế nào để chúng tôi tăng tốc độ của truy vấn này?


5000 kết quả có vẻ như bạn không lọc gần đủ trong truy vấn. Chỉ cần chuyển 5000 kết quả vào mã sẽ tốn một tấn thời gian mạng. Đừng bận tâm rằng bạn vẫn sẽ lọc sau đó. | Luôn luôn thực hiện càng nhiều việc xử lý trong truy vấn. Lý tưởng nhất là các hàng có chỉ mục và / hoặc là kết quả của chế độ xem được tính toán.
Christopher

Là những đối tượng "Dịch" lớn? Tại sao bạn không muốn nhận một số tham số thay vì gettin` như toàn bộ db?
Hirasawa Yui

@HirasawaYui không có họ nhỏ
l --''''''--------- '' '' '' '' '' '' '

bạn nên lọc nhiều hơn, kéo 5000 kết quả dường như vô nghĩa. Không thể biết nếu không biết dữ liệu của bạn, nhưng tôi muốn nói rằng bạn cần tìm ra cách phân vùng dữ liệu theo cách có ý nghĩa hơn hoặc giới thiệu một số loại lọc trong truy vấn
4c74356b41

Có bao nhiêu phân vùng khác nhau?
Peter Bons

Câu trả lời:


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

Đây là một trong những vấn đề, bạn đang chạy truy vấn và sau đó lọc nó khỏi bộ nhớ bằng cách sử dụng các "địa điểm" này. Di chuyển các bộ lọc đến trước khi truy vấn chạy sẽ giúp ích rất nhiều.

Thứ hai, bạn phải cung cấp một số giới hạn của các hàng để lấy từ cơ sở dữ liệu


didnt này tạo sự khác biệt
l --''''''--------- '' '' '' '' '' ''

3

Có 3 điều bạn có thể cân nhắc:

1 . Trước hết, hãy loại bỏ các Wheremệnh đề mà bạn thực hiện trên kết quả truy vấn. Tốt hơn là bao gồm các mệnh đề trong truy vấn càng nhiều càng tốt (thậm chí tốt hơn nếu bạn có bất kỳ chỉ mục nào trên các bảng của bạn cũng bao gồm chúng). Hiện tại, bạn có thể thay đổi truy vấn của mình như dưới đây:

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

Bởi vì bạn có một lượng lớn dữ liệu để truy xuất song song nên chạy các truy vấn của bạn. Vì vậy, bạn nên thay thế phương thức do whileloop bên trong ExecuteQueryAsyncbằng Parallel.ForEachI write dựa trên Stephen Toub Parallel.While ; Bằng cách này, nó sẽ giảm thời gian thực hiện truy vấn. Đây là một lựa chọn tốt vì bạn có thể loại bỏ Resultkhi bạn thực hiện cuộc gọi trên phương thức này, nhưng có một hạn chế nhỏ là tôi sẽ nói về nó sau phần mã này:

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

Và sau đó bạn có thể gọi nó trong Getphương thức của bạn :

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

Như bạn có thể thấy phương thức của nó không đồng bộ (bạn nên thay đổi tên của nó) và Parallel.ForEachkhông tương thích với việc truyền trong một phương thức async. Đây là lý do tại sao tôi đã sử dụng ExecuteQuerySegmentedthay thế. Nhưng, để làm cho nó hiệu quả hơn và sử dụng tất cả các lợi ích của phương pháp không đồng bộ, bạn có thể thay thế ForEachvòng lặp trên bằng ActionBlockphương thức trong Dataflow hoặc ParallelForEachAsyncphương thức mở rộng từ gói Nuget của AsyncEnumerator .

2. Đây là một lựa chọn tốt để thực hiện các truy vấn song song độc lập và sau đó hợp nhất các kết quả, ngay cả khi cải thiện hiệu suất của nó nhiều nhất là 10 phần trăm. Điều này cho bạn thời gian để có thể tìm thấy truy vấn thân thiện hiệu suất tốt nhất. Nhưng, đừng bao giờ quên bao gồm tất cả các ràng buộc của bạn trong đó, và kiểm tra cả hai cách để biết cái nào giải quyết vấn đề của bạn tốt hơn.

3 . Tôi không chắc đó có phải là một gợi ý hay không, nhưng hãy thực hiện và xem kết quả. Như được mô tả trong MSDN :

Dịch vụ Bảng thực thi thời gian chờ của máy chủ như sau:

  • Hoạt động truy vấn: Trong khoảng thời gian chờ, một truy vấn có thể thực hiện tối đa năm giây. Nếu truy vấn không hoàn thành trong khoảng thời gian năm giây, phản hồi bao gồm các mã thông báo tiếp tục để truy xuất các mục còn lại theo yêu cầu tiếp theo. Xem Thời gian chờ truy vấn và phân trang để biết thêm thông tin.

  • Thao tác chèn, cập nhật và xóa: Khoảng thời gian chờ tối đa là 30 giây. Ba mươi giây cũng là khoảng thời gian mặc định cho tất cả các hoạt động chèn, cập nhật và xóa.

Nếu bạn chỉ định thời gian chờ nhỏ hơn thời gian chờ mặc định của dịch vụ, khoảng thời gian chờ của bạn sẽ được sử dụng.

Vì vậy, bạn có thể chơi với thời gian chờ và kiểm tra xem có bất kỳ cải thiện hiệu suất.


2

Thật không may, bên dưới truy vấn giới thiệu quét toàn bộ bảng :

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

Bạn nên chia nó thành hai bộ lọc Khóa phân vùng và truy vấn chúng riêng rẽ, chúng sẽ trở thành hai lần quét phân vùng và thực hiện hiệu quả hơn.


chúng tôi thấy có thể là một sự cải thiện 10% với điều này, nhưng nó không đủ
l --''''''--------- '' '' '' '' '' ''

1

Vì vậy, bí mật không chỉ nằm ở mã mà còn trong việc thiết lập các bảng lưu trữ Azure của bạn.

a) Một trong những tùy chọn nổi bật để tối ưu hóa các truy vấn của bạn trong Azure là giới thiệu bộ đệm. Điều này sẽ làm giảm đáng kể thời gian phản hồi tổng thể của bạn và do đó tránh tắc nghẽn trong giờ cao điểm bạn đã đề cập.

b) Ngoài ra, khi truy vấn các thực thể ra khỏi Azure, cách nhanh nhất có thể để làm điều đó là với cả Phân vùng và RowKey. Đây là các trường được lập chỉ mục duy nhất trong Bộ lưu trữ bảng và bất kỳ truy vấn nào sử dụng cả hai trường này sẽ được trả về trong vài mili giây. Vì vậy, đảm bảo bạn sử dụng cả PartitionKey & RowKey.

Xem thêm chi tiết tại đây: https://docs.microsoft.com/en-us/azure/st Storage / tests / table-st Storage-design-for-query

Hi vọng điêu nay co ich.


-1

lưu ý: Đây là lời khuyên tối ưu hóa truy vấn DB chung.

Có thể là ORM đang làm điều gì đó ngu ngốc. Khi thực hiện tối ưu hóa, bạn có thể bước xuống một lớp trừu tượng. Vì vậy, tôi khuyên bạn nên viết lại truy vấn bằng ngôn ngữ truy vấn (SQL?) Để dễ dàng xem những gì đang diễn ra và cũng dễ dàng tối ưu hóa hơn.

Chìa khóa để tối ưu hóa tra cứu là sắp xếp! Giữ một bảng được sắp xếp thường rẻ hơn nhiều so với quét toàn bộ bảng trên mỗi truy vấn! Vì vậy, nếu có thể, hãy giữ bảng được sắp xếp theo khóa được sử dụng trong truy vấn. Trong hầu hết các giải pháp cơ sở dữ liệu, điều này đạt được bằng cách tạo khóa chỉ mục.

Một chiến lược khác hoạt động tốt nếu có ít kết hợp, là để mỗi truy vấn dưới dạng bảng riêng biệt (tạm thời trong bộ nhớ) luôn cập nhật. Vì vậy, khi một cái gì đó được chèn, nó cũng "chèn" vào các bảng "xem". Một số giải pháp cơ sở dữ liệu gọi đây là "lượt xem".

Một chiến lược tàn bạo hơn là tạo các bản sao chỉ đọc để phân phối tải.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.