Azure Cosmos DB와 함께 쿼리 성능 튜닝

적용 대상: NoSQL

Azure Cosmos DB에서는 스키마 또는 보조 인덱스를 요구하지 않고도 데이터를 쿼리하기 위한 NoSQL용 API를 제공합니다. 이 문서에서는 개발자를 위한 다음 정보를 제공합니다.

  • Azure Cosmos DB의 SQL 쿼리 실행 작동 방식에 대한 개략적인 세부 정보
  • 쿼리 성능에 대한 팁 및 모범 사례
  • SQL 쿼리 실행 메트릭을 활용하여 쿼리 성능을 디버그하는 방법의 예

SQL 쿼리 실행 정보

Azure Cosmos DB 데이터는 컨테이너에 저장되며 스토리지 크기 또는 요청 처리량으로 증가할 수 있습니다. Azure Cosmos DB는 데이터 증가 또는 프로비전된 처리량 증가를 처리하기 위해 커버 아래의 실제 파티션에서 데이터를 원활하게 확장합니다. REST API 또는 지원되는 SQL SDK 중 하나를 사용하여 모든 컨테이너에 대해 SQL 쿼리를 실행할 수 있습니다.

분할에 대한 간략한 개요: 데이터가 실제 파티션 간에 분할되는 방식을 결정하는 "city"와 같은 파티션 키를 정의합니다. 단일 파티션 키에 속하는 데이터(예: "city" == "Seattle")는 실제 파티션 내에 저장되며, 단일 실제 파티션은 여러 파티션 키의 데이터를 저장할 수 있습니다. 파티션이 스토리지 한도에 도달하면 서비스는 파티션을 두 개의 새 파티션으로 원활하게 분할합니다. 데이터는 새 파티션에 균등하게 분산되어 단일 파티션 키에 대한 모든 데이터를 함께 유지합니다. 파티션은 일시적이므로 API는 파티션 키 해시의 범위를 나타내는 파티션 키 범위를 추상화합니다.

Azure Cosmos DB에 쿼리를 실행하면 SDK는 다음 논리적 단계를 수행합니다.

  • SQL 쿼리를 구문 분석하여 쿼리 실행 계획을 확인합니다.
  • 쿼리에 파티션 키에 대한 필터가 포함된 경우(예: SELECT * FROM c WHERE c.city = "Seattle"단일 파티션으로 라우팅됨) 쿼리에 파티션 키에 대한 필터가 없는 경우 모든 파티션에서 실행되고 각 파티션의 결과가 병합된 클라이언트 쪽입니다.
  • 쿼리는 클라이언트 구성에 따라 계열 또는 병렬로 각 파티션 내에서 실행됩니다. 각 파티션 내에서 쿼리는 쿼리 복잡성, 구성된 페이지 크기 및 컬렉션의 프로비전된 처리량에 따라 하나 이상의 왕복을 만들 수 있습니다. 각 실행은 쿼리 실행 및 쿼리 실행 통계에서 사용되는 요청 단위 수를 반환합니다.
  • SDK는 파티션 간에 쿼리 결과를 요약합니다. 예를 들어 쿼리에 파티션 간 ORDER BY가 포함된 경우 개별 파티션의 결과가 병합 정렬되어 결과를 전역적으로 정렬된 순서로 반환합니다. 쿼리가 같은 COUNT집계인 경우 개별 파티션의 수를 합산하여 전체 개수를 생성합니다.

SDK는 쿼리 실행을 위한 다양한 옵션을 제공합니다. 예를 들어 .NET에서 이러한 옵션은 클래스에서 QueryRequestOptions 사용할 수 있습니다. 다음 표에서는 이러한 옵션과 이러한 옵션이 쿼리 실행 시간에 미치는 영향에 대해 설명합니다.

옵션 설명
EnableScanInQuery 요청된 필터 경로에 대한 인덱싱을 사용하지 않도록 설정한 경우에만 적용됩니다. 인덱싱을 옵트아웃하고 전체 검사를 사용하여 쿼리를 실행하려는 경우 true로 설정해야 합니다.
MaxItemCount 서버로의 왕복당 반환할 최대 항목 수입니다. 서버에서 반환할 항목 수를 관리할 수 있도록 -1로 설정할 수 있습니다.
MaxBufferedItemCount 병렬 쿼리를 실행하는 동안 클라이언트 쪽에서 버퍼링할 수 있는 최대 항목 수입니다. 양수 속성 값은 버퍼링된 항목 수를 집합 값으로 제한합니다. 시스템에서 버퍼링할 항목 수를 자동으로 결정할 수 있도록 0보다 작은 값으로 설정할 수 있습니다.
MaxConcurrency 병렬 쿼리를 실행하는 동안 클라이언트 쪽에서 실행되는 동시 작업 수를 가져오거나 설정합니다. 양수 속성 값은 동시 작업 수를 집합 값으로 제한합니다. 시스템에서 실행할 동시 작업 수를 자동으로 결정할 수 있도록 0보다 작은 값으로 설정할 수 있습니다.
PopulateIndexMetrics 인덱스 메트릭을 수집하여 쿼리 엔진에서 기존 인덱스를 사용하는 방법과 잠재적인 새 인덱스를 사용하는 방법을 이해할 수 있습니다. 이 옵션은 오버헤드가 발생하므로 느린 쿼리를 디버깅할 때만 사용하도록 설정해야 합니다.
ResponseContinuationTokenLimitInKb 서버에서 반환한 연속 토큰의 최대 크기를 제한할 수 있습니다. 애플리케이션 호스트에 응답 헤더 크기에 제한이 있지만 쿼리에 사용되는 전체 기간 및 RU가 증가할 수 있는 경우 이를 설정해야 할 수 있습니다.

예를 들어 .NET SDK를 사용하여 분할된 /city 컨테이너에 대한 쿼리는 다음과 같습니다.

QueryDefinition query = new QueryDefinition("SELECT * FROM c WHERE c.city = 'Seattle'");
QueryRequestOptions options = new QueryRequestOptions()
{
    MaxItemCount = -1,
    MaxBufferedItemCount = -1,
    MaxConcurrency = -1,
    PopulateIndexMetrics = true
};
FeedIterator<dynamic> feedIterator = container.GetItemQueryIterator<dynamic>(query);

FeedResponse<dynamic> feedResponse = await feedIterator.ReadNextAsync();

각 쿼리 실행은 쿼리 요청 옵션 및 본문의 SQL 쿼리에 대해 헤더가 설정된 REST API POST 에 해당합니다. REST API 요청 헤더 및 옵션에 대한 자세한 내용은 REST API를 사용하여 리소스 쿼리를 참조하세요.

쿼리 성능 모범 사례

다음 요소는 일반적으로 Azure Cosmos DB 쿼리 성능에 가장 큰 영향을 줍니다. 이 문서에서는 이러한 각 요인을 자세히 살펴봅니다.

요소
프로비전된 처리량 쿼리당 RU를 측정하고 쿼리에 필요한 프로비전된 처리량이 있는지 확인합니다.
분할 및 파티션 키 짧은 대기 시간을 위해 필터 절에 파티션 키가 있는 쿼리를 선호합니다.
SDK 및 쿼리 옵션 직접 연결과 같은 SDK 모범 사례를 따르고 클라이언트 쪽 쿼리 실행 옵션을 조정합니다.
네트워크 대기 시간 대기 시간을 줄이기 위해 가능한 경우 Azure Cosmos DB 계정과 동일한 지역에서 애플리케이션을 실행합니다.
인덱싱 정책 쿼리에 필요한 인덱싱 경로/정책이 있는지 확인합니다.
쿼리 실행 메트릭 쿼리 실행 메트릭을 분석하여 쿼리 및 데이터 셰이프의 잠재적 재작성을 식별합니다.

프로비전된 처리량

Azure Cosmos DB에서 각각 초당 RU(요청 단위)로 표현된 예약된 처리량을 포함하는 데이터의 컨테이너를 만듭니다. 1KB 문서의 읽기는 하나의 RU이며 모든 작업(쿼리 포함)은 복잡성에 따라 고정된 RU 수로 정규화됩니다. 예를 들어 컨테이너에 대해 1000RU/s가 프로비전되어 있고 5RU를 사용하는 쿼리 SELECT * FROM c WHERE c.city = 'Seattle' 가 있는 경우(1000RU/s) /(5RU/쿼리) = 초당 200개의 쿼리를 실행할 수 있습니다.

초당 200개 이상의 쿼리(또는 프로비전된 모든 RU를 포화시키는 다른 작업)를 제출하는 경우 서비스는 들어오는 요청을 속도 제한하기 시작합니다. SDK는 백오프/재시도를 수행하여 속도 제한을 자동으로 처리하므로 이러한 쿼리에 대한 대기 시간이 더 높을 수 있습니다. 필요한 값에 대해 프로비전된 처리량을 높이면 쿼리 대기 시간 및 처리량이 향상됩니다.

요청 단위에 대해 알아보려면 요청 단위를 참조하세요.

분할 및 파티션 키

Azure Cosmos DB를 사용하면 데이터를 읽는 다음 시나리오는 일반적으로 가장 빠르거나 가장 효율적인 시나리오에서 가장 느리거나 가장 효율적이지 않은 시나리오로 정렬됩니다.

  • 단일 파티션 키 및 항목 ID에 대한 GET(지점 읽기라고도 함)
  • 단일 파티션 키에 필터 절을 사용하여 쿼리
  • 모든 속성에 대해 같음 또는 범위 필터 절을 사용하여 쿼리
  • 필터 없이 쿼리

모든 파티션에서 실행해야 하는 쿼리는 대기 시간이 더 높고 RU를 더 많이 사용할 수 있습니다. 각 파티션에는 모든 속성에 대한 자동 인덱싱이 있으므로 이 경우 인덱스에서 쿼리를 효율적으로 처리할 수 있습니다. 병렬 처리 옵션을 사용하여 파티션에 걸쳐 있는 쿼리를 더 빠르게 만들 수 있습니다.

분할 및 파티션 키에 대한 자세한 내용은 Azure Cosmos DB의 분할을 참조하세요.

SDK 및 쿼리 옵션

SDK를 사용하여 Azure Cosmos DB에서 최상의 클라이언트 쪽 성능을 얻는 방법에 대한 쿼리 성능 팁성능 테스트를 참조하세요.

네트워크 대기 시간

글로벌 배포를 설정하고 가장 가까운 지역에 연결하는 방법은 Azure Cosmos DB 글로벌 배포를 참조하세요. 네트워크 대기 시간은 여러 왕복을 하거나 쿼리에서 큰 결과 집합을 검색해야 하는 경우 쿼리 성능에 큰 영향을 줍니다.

쿼리 실행 메트릭을 사용하여 쿼리의 서버 실행 시간을 검색하여 쿼리 실행에 소요된 시간을 네트워크 전송에 소요된 시간과 구분할 수 있습니다.

인덱싱 정책

인덱싱 경로, 종류 및 모드에 대한 인덱싱 정책 구성 및 쿼리 실행에 미치는 영향을 참조하세요. 기본적으로 Azure Cosmos DB는 모든 데이터에 자동 인덱싱을 적용하고 문자열 및 숫자에 범위 인덱스를 사용하며 이는 같음 쿼리에 효과적입니다. 고성능 삽입 시나리오의 경우 각 삽입 작업에 대한 RU 비용을 줄이기 위해 경로를 제외하는 것이 좋습니다.

인덱스 메트릭을 사용하여 각 쿼리에 사용되는 인덱스와 쿼리 성능을 향상시키는 누락된 인덱스가 있는지 식별할 수 있습니다.

쿼리 실행 메트릭

요청에 대한 진단에서 각 쿼리 실행에 대한 자세한 메트릭이 반환됩니다 . 이러한 메트릭은 쿼리 실행 중에 소요되는 시간을 설명하고 고급 문제 해결을 사용하도록 설정합니다.

쿼리 메트릭을 가져오는 방법에 대해 자세히 알아봅니다.

메트릭 단위 설명
TotalTime 밀리초 총 쿼리 실행 시간
DocumentLoadTime 밀리초 문서를 로드하는 데 소요된 시간
DocumentWriteTime 밀리초 출력 문서를 작성하고 직렬화하는 데 소요된 시간
IndexLookupTime 밀리초 물리적 인덱스 계층에서 소요된 시간
QueryPreparationTime 밀리초 쿼리 준비에 소요된 시간
RuntimeExecutionTime 밀리초 총 쿼리 런타임 실행 시간
VMExecutionTime 밀리초 쿼리를 실행하는 쿼리 런타임에 소요된 시간
OutputDocumentCount count 결과 집합의 출력 문서 수
OutputDocumentSize count 출력된 문서의 총 크기(바이트)
RetrievedDocumentCount count 검색된 총 문서 수
RetrievedDocumentSize bytes 검색된 총 문서 크기(바이트)
IndexHitRatio 비율 [0,1] 로드된 문서 수에 대한 필터에 의해 일치하는 문서 수의 비율

클라이언트 SDK는 내부적으로 여러 쿼리 요청을 만들어 각 파티션 내에서 쿼리를 제공할 수 있습니다. 총 결과가 최대 항목 수 요청 옵션을 초과하는 경우, 쿼리가 파티션에 대해 프로비전된 처리량을 초과하거나, 쿼리 페이로드가 페이지당 최대 크기에 도달하거나, 쿼리가 시스템 할당 시간 제한에 도달하는 경우 클라이언트는 파티션당 두 개 이상의 호출을 만듭니다. 각 부분 쿼리 실행은 해당 페이지에 대한 쿼리 메트릭을 반환합니다.

몇 가지 샘플 쿼리 및 쿼리 실행에서 반환된 일부 메트릭을 해석하는 방법은 다음과 같습니다.

쿼리 샘플 메트릭 설명
SELECT TOP 100 * FROM c "RetrievedDocumentCount": 101 검색된 문서 수는 TOP 절과 일치하는 100+1입니다. 쿼리 시간은 주로 검색에 WriteOutputTime 소요되기 DocumentLoadTime 때문에 소요됩니다.
SELECT TOP 500 * FROM c "RetrievedDocumentCount": 501 이제 RetrievedDocumentCount가 더 높습니다(TOP 절과 일치하려면 500+1).
SELECT * FROM c WHERE c.N = 55 "IndexLookupTime": "00:00:00.0009500" 인덱스 조회이므로 키 조회를 위해 IndexLookupTime에 /N/?약 0.9ms가 소요됩니다.
SELECT * FROM c WHERE c.N > 55 "IndexLookupTime": "00:00:00.0017700" 인덱스 조회 /N/?이므로 범위 검색을 통해 IndexLookupTime에서 소요된 시간(1.7ms)이 약간 더 깁니다.
SELECT TOP 500 c.N FROM c "IndexLookupTime": "00:00:00.0017700" DocumentLoadTime에는 이전 쿼리와 같은 시간이 소요되지만 하나의 속성만 프로젝션하므로 DocumentWriteTime은 더 짧습니다.
SELECT TOP 500 udf.toPercent(c.N) FROM c "RuntimeExecutionTime": "00:00:00.2136500" 각 값에 RuntimeExecutionTime 대해 UDF를 실행하는 데 약 213ms가 c.N소요됩니다.
SELECT TOP 500 c.Name FROM c WHERE STARTSWITH(c.Name, 'Den') "IndexLookupTime": "00:00:00.0006400", "RuntimeExecutionTime": "00:00:00.0074100" 약 0.6ms가 소요/Name/?됩니다IndexLookupTime. 대부분의 쿼리 실행 시간(~7ms)입니다 RuntimeExecutionTime.
SELECT TOP 500 c.Name FROM c WHERE STARTSWITH(LOWER(c.Name), 'den') "IndexLookupTime": "00:00:00", "RetrievedDocumentCount": 2491, "OutputDocumentCount": 500 쿼리는 LOWER를 사용하므로 검색으로 수행되고 검색된 문서 2491개 중 500개가 반환됩니다.

다음 단계

  • 지원되는 SQL 쿼리 연산자 및 키워드(keyword) 대한 자세한 내용은 SQL 쿼리를 참조하세요.
  • 요청 단위에 대해 알아보려면 요청 단위를 참조 하세요.
  • 인덱싱 정책에 대한 자세한 내용은 인덱싱 정책을 참조 하세요.