Azure 테이블 저장소에서 파티션 키를 사용하여 쿼리 속도를 높이는 방법


10

이 쿼리의 속도를 어떻게 높이나요?

다음 쿼리 를 실행하는 범위 내에 약 100 명의 소비자가1-2 minutes 있습니다. 이 러한 각 실행은 1 회 실행 기능을 나타냅니다.

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

이 쿼리는 약 5000 개의 결과를 생성합니다.

전체 코드 :

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

이러한 실행 중에 소비자가 100 명인 경우 요청이 클러스터되어 스파이크가 형성되는 것을 볼 수 있습니다.

여기에 이미지 설명을 입력하십시오

이러한 스파이크 동안 요청은 종종 1 분 이상이 소요됩니다.

여기에 이미지 설명을 입력하십시오

이 쿼리의 속도를 어떻게 높이나요?


쿼리에서 거의 필터링하지 않은 5000 개의 결과가 나타납니다. 5000 개의 결과를 코드로 전송하는 데는 상당한 네트워크 시간이 소요됩니다. 이후에도 필터링을 계속 수행 할 것이라는 점에 유의하지 마십시오. | 항상 쿼리에서 처리하는만큼 많은 파일을 작성하십시오. 인덱스를 얻거나 계산 된 뷰의 결과 행에 이상적입니다.
Christopher

"번역"개체가 큰가요? 왜 전체 DB와 같이 gettin` 대신 일부 매개 변수를 얻는 것이 좋지 않습니까?
히라사와 유이

@HirasawaYui 아니 그들은 작습니다
l --''''''--------- '' '' '' '' '' '' ''11

더 많은 필터링을 수행해야합니다. 5000 개의 결과를 가져 오는 것은 의미가없는 것 같습니다. 데이터를 모른 채 말할 수는 없지만 더 의미있는 방식으로 데이터를 분할하거나 쿼리에 일종의 필터링을 도입하는 방법을 찾아야한다고 말하고 싶습니다.
4c74356b41

몇 개의 다른 파티션이 있습니까?
Peter Bons

답변:


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

다음은 문제 중 하나입니다. 쿼리를 실행 한 다음 이러한 "wheres"를 사용하여 메모리에서 쿼리합니다. 쿼리가 실행되기 전에 필터를 이동하면 많은 도움이됩니다.

두 번째로 데이터베이스에서 검색 할 행의 한계를 제공해야합니다.


이것은 차이를 만들지
않았습니다

3

고려할 수있는 3 가지 사항이 있습니다.

1 . 우선 Where, 쿼리 결과에서 수행하는 절을 제거하십시오 . 쿼리에 절을 최대한 많이 포함시키는 것이 좋습니다 (테이블에 인덱스가있는 경우에도 더 좋습니다). 지금은 아래와 같이 쿼리를 변경할 수 있습니다.

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

검색 할 데이터가 많으므로 쿼리를 병렬로 실행하는 것이 좋습니다. 따라서 교체해야합니다do while 내부 루프 ExecuteQueryAsync와 방법을 Parallel.ForEachI에 따라 쓴 스티븐 Toub Parallel.While ; 이렇게하면 쿼리 실행 시간이 줄어 듭니다. 이 Result방법을 호출 할 때 제거 할 수 있기 때문에 이것은 좋은 선택 이지만,이 코드 부분 이후에 이야기 할 약간의 제한이 있습니다.

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

그런 다음 Get메소드 에서 호출 할 수 있습니다 .

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

보시다시피 메소드 자체는 비동기가 아니며 (이름을 변경해야 함) Parallel.ForEach비동기 메소드 전달과 호환되지 않습니다. 이것이 내가 ExecuteQuerySegmented대신 사용한 이유 입니다. 그러나 성능을 높이고 비동기 방식의 모든 이점을 사용하려면 위의 내용을 대체 할 수 있습니다ForEach높이고 루프 를 AsyncEnumerator Nuget 패키지의 Dataflow 또는 확장 메소드의 ActionBlock메소드로 .ParallelForEachAsync

2. 성능 향상이 최대 10 % 인 경우에도 독립적 인 병렬 쿼리를 실행 한 다음 결과를 병합하는 것이 좋습니다. 이를 통해 성능에 가장 적합한 쿼리를 찾을 수 있습니다. 그러나 모든 제약 조건을 포함하는 것을 잊지 말고 어떤 방법으로 문제를 더 잘 해결할 수 있는지 두 가지 방법을 테스트하십시오.

3 . 나는 그것이 좋은 제안인지 확실하지 않지만, 그것을하고 결과를보십시오. MSDN에 설명 된대로 :

테이블 서비스는 다음과 같이 서버 시간 초과를 시행합니다.

  • 쿼리 작업 : 시간 초과 간격 동안 쿼리가 최대 5 초 동안 실행될 수 있습니다. 쿼리가 5 초 간격 내에 완료되지 않으면 후속 요청에서 나머지 항목을 검색하기위한 연속 토큰이 응답에 포함됩니다. 자세한 내용은 쿼리 시간 초과 및 페이지 매김을 참조하십시오.

  • 삽입, 업데이트 및 삭제 작업 : 최대 시간 초과 간격은 30 초입니다. 또한 모든 삽입, 업데이트 및 삭제 작업의 기본 간격은 30 초입니다.

서비스의 기본 시간 초과보다 적은 시간 초과를 지정하면 시간 초과 간격이 사용됩니다.

따라서 시간 초과로 게임을하고 성능이 개선되었는지 확인할 수 있습니다.


2

불행히도 아래 쿼리는 전체 테이블 스캔을 소개합니다 .

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

이를 두 개의 파티션 키 필터로 분할하여 별도로 쿼리해야합니다.이 필터는 두 개의 파티션 스캔 이되어 보다 효율적으로 수행됩니다.


우리는 어쩌면이와 10 %의 개선을 보았다,하지만 충분하지 않습니다
리터 --''''''--------- '' '' '' '' '' ''

1

따라서 비밀은 코드뿐만 아니라 Azure 저장소 테이블 설정에도 있습니다.

a) Azure에서 쿼리를 최적화하는 탁월한 옵션 중 하나는 캐싱을 도입하는 것입니다. 이렇게하면 전체 응답 시간이 크게 줄어들어 언급 한 피크 시간 동안 병목 현상을 피할 수 있습니다.

b) 또한 Azure에서 엔터티를 쿼리 할 때 가장 빠른 방법은 PartitionKey와 RowKey를 사용하는 것입니다. 이들은 테이블 스토리지에서 유일하게 인덱싱 된 필드이며이 둘을 모두 사용하는 쿼리는 몇 밀리 초 내에 리턴됩니다. 따라서 PartitionKey와 RowKey를 모두 사용해야합니다.

자세한 내용은 여기를 참조하십시오 : https://docs.microsoft.com/en-us/azure/storage/tables/table-storage-design-for-query

도움이 되었기를 바랍니다.


-1

참고 : 이것은 일반적인 DB 쿼리 최적화 조언입니다.

ORM이 멍청한 짓을하고있을 수도 있습니다. 최적화를 수행 할 때 추상화 계층을 단계적으로 내리는 것이 좋습니다. 따라서 쿼리 언어 (SQL?)로 쿼리를 다시 작성하여 진행 상황을 쉽게보고 최적화하는 것이 좋습니다.

조회 최적화의 핵심은 정렬입니다! 테이블을 정렬하여 유지하는 것은 일반적으로 모든 쿼리에서 전체 테이블을 스캔하는 것에 비해 훨씬 저렴합니다! 따라서 가능하면 쿼리에 사용 된 키를 기준으로 테이블을 정렬하십시오. 대부분의 데이터베이스 솔루션에서 이는 인덱스 키를 작성하여 수행됩니다.

조합이 적을 때 잘 작동하는 또 다른 전략은 각 쿼리를 항상 최신 상태의 별도 (메모리의 임시) 테이블로 만드는 것입니다. 따라서 무언가가 삽입되면 "보기"테이블에도 "삽입"됩니다. 일부 데이터베이스 솔루션은이를 "보기"라고합니다.

보다 무차별 한 전략은로드를 분산시키기 위해 읽기 전용 복제본을 만드는 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.