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 요청 헤더 및 옵션에 대한 자세한 내용은 Querying resources using the REST API(REST API를 사용하여 리소스 쿼리)를 참조하세요.
쿼리 성능에 대한 모범 사례
다음 요소는 일반적으로 Azure Cosmos DB 쿼리 성능에 가장 큰 영향을 미칩니다. 이 문서에서는 이러한 각 요소에 대해 심층적으로 분석해보겠습니다.
요소 | 팁 |
---|---|
프로비전된 처리량 | 쿼리당 RU를 측정하고 사용자 쿼리에 필요한 프로비전된 처리량이 있는지 확인합니다. |
분할 및 파티션 키 | 짧은 대기 시간을 위해 필터 절에 파티션 키가 있는 쿼리를 선호합니다. |
SDK 및 쿼리 옵션 | 직접 연결 같은 SDK 모범 사례를 따르고 클라이언트 쪽 쿼리 실행 옵션을 조정합니다. |
네트워크 대기 시간 | 대기 시간을 줄이기 위해 가능하면 Azure Cosmos DB 계정과 동일한 지역에서 애플리케이션을 실행합니다. |
인덱싱 정책 | 쿼리에 필요한 인덱싱 경로/정책이 있는지 확인합니다. |
쿼리 실행 메트릭 | 쿼리 실행 메트릭을 분석하여 쿼리 및 데이터 셰이프의 재작성 가능성을 파악합니다. |
프로비전된 처리량
Azure Cosmos DB에서 각각 초당 RU(요청 단위)로 표현된 예약된 처리량을 포함하는 데이터의 컨테이너를 만듭니다. 1KB 문서의 읽기는 1RU이고 쿼리를 포함한 모든 작업은 해당 복잡성에 따라 고정된 RU로 정규화됩니다. 예를 들어 사용자 컨테이너에 대해 프로비전된 1000 RU/s가 있고 5RU를 사용하는 SELECT * FROM c WHERE c.city = 'Seattle'
과 같은 쿼리가 있는 경우 이러한 쿼리는 초당 (1000 RU/s) / (5 RU/query) = 200 query/s를 실행할 수 있습니다.
초당 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" |
범위 검색을 통한 IndexLookupTime에는 /N/? 에 대한 인덱스 조회이므로 약간 더 많은 시간(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" |
c.N 의 각 값에서 UDF를 실행하는 RuntimeExecutionTime 에는 약 213ms가 소요됩니다. |
SELECT TOP 500 c.Name FROM c WHERE STARTSWITH(c.Name, 'Den') |
"IndexLookupTime": "00:00:00.0006400", "RuntimeExecutionTime": "00:00:00.0074100" |
/Name/? 의 IndexLookupTime 에는 약 0.6ms가 소요됩니다. RuntimeExecutionTime 에서 대부분의 쿼리 실행 시간은 약 7ms입니다. |
SELECT TOP 500 c.Name FROM c WHERE STARTSWITH(LOWER(c.Name), 'den') |
"IndexLookupTime": "00:00:00", "RetrievedDocumentCount": 2491, "OutputDocumentCount": 500 |
쿼리는 LOWER 를 사용하므로 검색으로 수행되고 검색된 문서 2491개 중 500개가 반환됩니다. |