共用方式為


Azure Cosmos DB Java SDK 的效能秘訣

適用於:NoSQL

Azure Cosmos DB 是一個既快速又彈性的分散式資料庫,可在獲得延遲程度與輸送量保證的情況下順暢地調整。 使用 Azure Cosmos DB 時,您不必進行主要的架構變更,或是撰寫複雜的程式碼來調整您的資料庫。 相應增加和減少就像進行單一 API 呼叫一樣簡單。 若要深入了解,請參閱佈建容器輸送量佈建資料庫輸送量

減少查詢計劃呼叫

若要執行查詢,您必須建置查詢計劃。 對 Azure Cosmos DB 閘道的網路要求會增加查詢作業的延遲。 有兩種方式可移除此要求,並減少查詢作業的延遲:

使用 Optimistic Direct Execution 最佳化單一分割區查詢

Azure Cosmos DB NoSQL 具有稱為開放式直接執行 (ODE) 的最佳化,可提升特定 NoSQL 查詢的效率。 具體而言,不需要散發的查詢包括可以在單一實體分割區上執行的查詢,或具有不需要分頁的回應的查詢。 不需要分散的查詢可以放心地略過某些過程,例如用戶端查詢計畫生成和查詢重寫,以減少查詢延遲和請求單位(RU)成本。 如果您在要求或查詢本身中指定分割區索引鍵 (或只有一個實體分割區),而且查詢的結果不需要分頁,則 ODE 可以改善查詢。

附註

ODE 可為不需要散發的查詢提供改善的效能,不應與 直接模式混淆,直接模式是將應用程式連線到後端複本的路徑。

ODE 現在可在 .NET SDK 3.38.0 版和更新版本中取得。 當您執行查詢並在請求或查詢本身中指定分割區索引鍵時,或者您的資料庫只有一個實體分割區,您的查詢執行可以使用 ODE 的優點。 若要啟用 ODE,請在 QueryRequestOptions 中設定 EnableOptimisticDirectExecution 為 true。

具有 GROUP BYORDER BYDISTINCT 和彙總函數 (如 sum、mean、min 和 max) 的單一分割區查詢可以從使用 ODE 中獲益匪淺。 不過,在查詢以多個分割區為目標或仍需要分頁的情況下,查詢回應和 RU 成本的延遲可能會高於不使用 ODE。 因此,在使用ODE時,您應該:

  • 在呼叫或查詢本身中指定分割區索引鍵路徑。
  • 請確定您的資料大小尚未成長,以及尚未導致對分割區進行分割。
  • 請確定您的查詢結果不需要分頁,即可取得完整的 ODE 優點。

以下是一些可以從 ODE 中受益的簡單單分割區查詢範例:

- SELECT * FROM r
- SELECT * FROM r WHERE r.pk == "value"
- SELECT * FROM r WHERE r.id > 5
- SELECT r.id FROM r JOIN id IN r.id
- SELECT TOP 5 r.id FROM r ORDER BY r.id
- SELECT * FROM r WHERE r.id > 5 OFFSET 5 LIMIT 3 

在某些情況下,如果資料項目數隨著時間增加,而且您的 Azure Cosmos DB 資料庫分割分割區,則單一分割區查詢可能仍然需要散發。 可能發生此狀況的查詢範例包括:

- SELECT Count(r.id) AS count_a FROM r
- SELECT DISTINCT r.id FROM r
- SELECT Max(r.a) as min_a FROM r
- SELECT Avg(r.a) as min_a FROM r
- SELECT Sum(r.a) as sum_a FROM r WHERE r.a > 0 

即使以單一分割區為目標,某些複雜查詢一律還是需要散發。 這類查詢的範例包括:

- SELECT Sum(id) as sum_id FROM r JOIN id IN r.id
- SELECT DISTINCT r.id FROM r GROUP BY r.id
- SELECT DISTINCT r.id, Sum(r.id) as sum_a FROM r GROUP BY r.id
- SELECT Count(1) FROM (SELECT DISTINCT r.id FROM root r)
- SELECT Avg(1) AS avg FROM root r 

請務必注意,ODE 可能不一定會擷取查詢計劃,因此無法禁止或關閉不支援的查詢。 例如,分割區分割之後,這類查詢不再符合 ODE 的資格,因此不會執行,因為用戶端查詢計劃評估會封鎖這些查詢。 為了確保相容性/服務持續性,請務必確保只有沒有 ODE 的情況下完全支援的查詢 (即,其會在一般多分割區案例中執行並產生正確結果) 與 ODE 搭配使用。

附註

使用 ODE 可能會導致產生新類型的接續權杖。 根據設計,較舊的 SDK 無法辨識這類權杖,這可能會導致格式不正確的接續權杖例外狀況。 如果您的使用情境是較舊的 SDK 使用從較新的 SDK 生成的令牌,建議您採用兩步驟方法進行升級:

  • 升級至新 SDK 並停用 ODE,這兩者都是單一部署的一部分。 等候所有節點升級。
  • 若要停用 ODE,請在 QueryRequestOptions 中設定 EnableOptimisticDirectExecution 為 false。
  • 針對所有節點,啟用 ODE 作為第二個部署的一部分。

使用本機查詢計劃產生

SQL SDK 包含原生 ServiceInterop.dll,可在本機電腦上解析和最佳化查詢。 ServiceInterop.dll 僅在 Windows x64 平台上受支援。 根據預設,下列類型的應用程式會使用 32 位元主機處理序。 若要將主機處理序變更為 64 位元處理序,請根據應用程式的類型,依照下列步驟操作:

  • 若是可執行的應用程式,您可以在 [組建] 索引標籤的 [專案屬性] 視窗中,將 [平台目標] 設定為 x64,以變更主機處理序。

  • 若是以 VSTest 為基礎的測試專案,您可以從 Visual Studio [測試] 功能表選取 [測試]>[測試設定]>[以 X64 做為預設處理器架構],以變更主機處理序。

  • 若是本機部署的 ASP.NET Web 應用程式,您可以在 [工具]>[選項]>[專案和解決方案]>[Web 專案] 之下,選取 [將 64 位元版本的 IIS Express 用於網站和專案],以變更主機處理序。

  • 若是部署在 Azure 上的 ASP.NET Web 應用程式,您可以在 Azure 入口網站的 [應用程式設定] 中選取 [64 位元] 平台,以變更主機處理序。

附註

根據預設,新的 Visual Studio 專案會設為任何 CPU。 建議您將專案設為 x64,以免其切換至 x86。 如果加入僅限 x86 的相依性,則設為任何 CPU 的專案都可以輕鬆切換至 x86

ServiceInterop.dll 必須位於執行 SDK DLL 的資料夾中。 只有當您手動複製 DLL 或擁有自訂組建/部署系統時,才需要考量這一點。

使用單一資料分割查詢

對於透過在 QueryRequestOptions 中設定 PartitionKey 屬性來以分割區索引鍵為目標,且不包含彙總 (包括 DistinctDCountGroup By) 的查詢。 在此範例中,是根據值 Washington 來篩選 /state 的分割區索引鍵欄位。

using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
    "SELECT * FROM c WHERE c.city = 'Seattle' AND c.state = 'Washington'"
{
    // ...
}

您可以選擇性地提供分割區索引鍵作為要求選項物件的一部分。

using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
    "SELECT * FROM c WHERE c.city = 'Seattle'",
    requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Washington")}))
{
    // ...
}

重要事項

在執行非 Windows 作業系統的用戶端上,例如 Linux 和 macOS,應該 一律 在要求選項物件中指定分割區索引鍵。

附註

跨分割區查詢需要 SDK 來瀏覽所有現有的分割區,以檢查結果。 容器擁有的實體分割區越多,速度可能就會越慢。

避免不必要地重新建立迭代器

當目前元件取用所有查詢結果時,您不需要為每個頁面重新建立具有接續的迭代器。 除非分頁是由另一個呼叫元件所控制,否則一律以完全清空查詢為主:

using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
    "SELECT * FROM c WHERE c.city = 'Seattle'",
    requestOptions: new QueryRequestOptions() { PartitionKey = new PartitionKey("Washington")}))
{
    while (feedIterator.HasMoreResults) 
    {
        foreach(MyItem document in await feedIterator.ReadNextAsync())
        {
            // Iterate through documents
        }
    }
}

調整平行處理原則的程度

針對查詢,請調整 QueryRequestOptions 中的 MaxConcurrency 屬性來識別應用程式的最佳設定,特別是當您執行跨分割區查詢時 (不含分割區索引鍵值上的篩選)。 MaxConcurrency 控制平行工作數目上限,亦即要平行造訪的分割區數目上限。 將值設定為 -1 可讓 SDK 決定最佳並行。

using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
    "SELECT * FROM c WHERE c.city = 'Seattle'",
    requestOptions: new QueryRequestOptions() { 
        PartitionKey = new PartitionKey("Washington"),
        MaxConcurrency = -1 }))
{
    // ...
}

讓我們假設:

  • D = 預設平行工作數目上限 (= 用戶端電腦中的處理器總數)
  • P = 使用者指定的平行工作最大數目
  • N = 為了回答查詢而需要造訪的分割區數目

以下是平行查詢對不同 P 值的行為方式的影響。

  • (P == 0) => 串行模式
  • (P == 1) => 最多一個工作
  • (P > 1) => 至少 (P, N) 個平行工作
  • (P < 1) => 至少 (N, D) 個平行工作

調整頁面大小

當您發出 SQL 查詢時,如果結果集太大,系統會以分段方式傳回結果。

附註

MaxItemCount 屬性不應只用於分頁。 其主要用途是減少在單一頁面中傳回的項目數量上限,以改善查詢的效能。

您也可以使用可用的 Azure Cosmos DB SDK 來設定頁面大小。 QueryRequestOptions 中的 MaxItemCount 屬性可讓您設定列舉作業中要傳回的項目數量上限。 當設定為 MaxItemCount,SDK 會自動尋找最佳值,具體取決於文件大小。 例如:

using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
    "SELECT * FROM c WHERE c.city = 'Seattle'",
    requestOptions: new QueryRequestOptions() { 
        PartitionKey = new PartitionKey("Washington"),
        MaxItemCount = 1000}))
{
    // ...
}

執行查詢時,產生的資料會在 TCP 封包內傳送。 如果您指定的 MaxItemCount 值太低,在 TCP 封包中傳送資料所需的行程次數就會偏高,因而影響到效能。 因此,如果您不確定要為屬性設定 MaxItemCount 什麼值,最好將其設定為 -1 ,並讓 SDK 選擇預設值。

調整緩衝區大小

平行查詢的設計目的是在用戶端處理目前批次的結果時預先擷取結果。 此預先擷取有助於改善查詢的整體延遲。 中的 QueryRequestOptions 屬性會限制預先擷取結果的數目。 設定 MaxBufferedItemCount 為預期傳回的結果數目 (或更高的數目),以允許查詢從預先擷取中獲得最大效益。 如果您將此值設定為 -1,系統會自動決定要緩衝的項目數目。

using (FeedIterator<MyItem> feedIterator = container.GetItemQueryIterator<MyItem>(
    "SELECT * FROM c WHERE c.city = 'Seattle'",
    requestOptions: new QueryRequestOptions() { 
        PartitionKey = new PartitionKey("Washington"),
        MaxBufferedItemCount = -1}))
{
    // ...
}

無論平行處理度為何,預先擷取的運作方式都相同,而且所有分割區的資料都有一個緩衝區。

後續步驟

若要深入了解使用 .NET SDK 的效能:

減少查詢計劃呼叫

若要執行查詢,您必須建置查詢計劃。 對 Azure Cosmos DB 閘道的網路要求會增加查詢作業的延遲。

使用查詢計劃快取

對於範圍在單一分割區上的查詢,其查詢計劃會在用戶端上快取。 這樣就不需要呼叫閘道,才能在第一次呼叫之後擷取查詢計劃。 快取的查詢計劃索引鍵是 SQL 查詢字串。 您必須 確定查詢已參數化。 如果沒有,查詢計劃快取查閱通常是快取遺漏,因為查詢字串不太可能在呼叫之間相同。 Java SDK 4.20.0 版和更新版本以及 Spring Data Azure Cosmos DB SDK 3.13.0 版和更新版本預設會啟用查詢方案快取。

使用參數化單一分割區查詢

對於範圍限定為具有 setPartitionKeyCosmosQueryRequestOptions 分割區索引鍵且不包含彙總 (包括 DistinctDCountGroup By) 的參數化查詢,可以避免查詢計劃:

CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
options.setPartitionKey(new PartitionKey("Washington"));

ArrayList<SqlParameter> paramList = new ArrayList<SqlParameter>();
paramList.add(new SqlParameter("@city", "Seattle"));
SqlQuerySpec querySpec = new SqlQuerySpec(
        "SELECT * FROM c WHERE c.city = @city",
        paramList);

//  Sync API
CosmosPagedIterable<MyItem> filteredItems = 
    container.queryItems(querySpec, options, MyItem.class);

//  Async API
CosmosPagedFlux<MyItem> filteredItems = 
    asyncContainer.queryItems(querySpec, options, MyItem.class);

附註

跨分割區查詢需要 SDK 來瀏覽所有現有的分割區,以檢查結果。 容器擁有的實體分割區越多,速度可能就會越慢。

調整平行處理原則的程度

平行查詢的運作方式是以平行方式查詢多個分割。 不過,查詢會以循序方式擷取來自個別分割容器的資料。 因此,請使用 CosmosQueryRequestOptions 上的 setMaxDegreeOfParallelism 將值設定為您擁有的分割區數目。 如果您不知道分割數目,您可以使用 setMaxDegreeOfParallelism 設定為較高的數字,然後系統會選擇最小值 (分割數目、使用者提供的輸入) 作為平行處理原則的最大刻度。 將值設定為 -1 可讓 SDK 決定最佳並行。

請務必注意,如果資料相對於查詢平均分佈在所有分割區中,則平行查詢會產生最佳優勢。 如果分割儲存器的分割方式是查詢所傳回的所有或大部分資料集中在幾個分割區 (最壞情況下是一個分割區) ,則查詢的效能將會降低。

CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
options.setPartitionKey(new PartitionKey("Washington"));
options.setMaxDegreeOfParallelism(-1);

// Define the query

//  Sync API
CosmosPagedIterable<MyItem> filteredItems = 
    container.queryItems(querySpec, options, MyItem.class);

//  Async API
CosmosPagedFlux<MyItem> filteredItems = 
    asyncContainer.queryItems(querySpec, options, MyItem.class);

讓我們假設:

  • D = 預設平行工作數目上限 (= 用戶端電腦中的處理器總數)
  • P = 使用者指定的平行工作最大數目
  • N = 為了回答查詢而需要造訪的分割區數目

以下是平行查詢對不同 P 值的行為方式的影響:

  • (P == 0) => 串行模式
  • (P == 1) => 最多一個工作
  • (P > 1) => 至少 (P, N) 個平行工作
  • (P == -1) => 至少 (N, D) 個平行工作

調整頁面大小

當您發出 SQL 查詢時,如果結果集太大,系統會以分段方式傳回結果。 根據預設,會以 100 個項目或 4 MB 的區塊傳回結果 (以先達到的限制為準)。 增加頁面大小可減少所需的往返次數,並提高傳回超過 100 個項目的查詢效能。 如果您不確定要設定的值,則 1000 通常是不錯的選擇。 記憶體耗用量會隨著頁面大小的增加而增加,因此如果您的工作負載對記憶體敏感,請考慮較低的值。

您可以針對同步 API使用 iterableByPage() 中的 pageSize 參數,並針對非同步 API 使用 byPage(),以定義頁面大小:

//  Sync API
Iterable<FeedResponse<MyItem>> filteredItemsAsPages =
    container.queryItems(querySpec, options, MyItem.class).iterableByPage(continuationToken,pageSize);

for (FeedResponse<MyItem> page : filteredItemsAsPages) {
    for (MyItem item : page.getResults()) {
        //...
    }
}

//  Async API
Flux<FeedResponse<MyItem>> filteredItemsAsPages =
    asyncContainer.queryItems(querySpec, options, MyItem.class).byPage(continuationToken,pageSize);

filteredItemsAsPages.map(page -> {
    for (MyItem item : page.getResults()) {
        //...
    }
}).subscribe();

調整緩衝區大小

平行查詢的設計目的是在用戶端處理目前批次的結果時預先擷取結果。 預先擷取有助於改善查詢的整體延遲。 setMaxBufferedItemCountCosmosQueryRequestOptions 限制預先擷取結果的數目。 若要最大化預先擷取效益,請將maxBufferedItemCount設定為高於pageSize的值(注意:這也可能導致高記憶體使用量)。 若要將預先擷取降到最低,請將 maxBufferedItemCount 設定為與 pageSize 相等。 如果您將此值設定為 0,系統會自動決定要緩衝的項目數。

CosmosQueryRequestOptions options = new CosmosQueryRequestOptions();
options.setPartitionKey(new PartitionKey("Washington"));
options.setMaxBufferedItemCount(-1);

// Define the query

//  Sync API
CosmosPagedIterable<MyItem> filteredItems = 
    container.queryItems(querySpec, options, MyItem.class);

//  Async API
CosmosPagedFlux<MyItem> filteredItems = 
    asyncContainer.queryItems(querySpec, options, MyItem.class);

無論平行處理度為何,預先擷取的運作方式都相同,而且所有分割區的資料都有一個緩衝區。

後續步驟

若要深入了解使用 Java SDK 的效能:

減少查詢計劃呼叫

若要執行查詢,您必須建置查詢計劃。 對 Azure Cosmos DB 閘道的網路要求會增加查詢作業的延遲。 有一種方法可以移除此要求,並減少單一分割區查詢作業的延遲。 針對單一分割區查詢,請指定項目的分割區索引鍵值,並將其作為引數傳遞 partition_key

items = container.query_items(
        query="SELECT * FROM r where r.city = 'Seattle'",
        partition_key="Washington"
    )

調整頁面大小

當您發出 SQL 查詢時,如果結果集太大,系統會以分段方式傳回結果。 max_item_count 可讓您設定列舉作業中要傳回的項目數上限。

items = container.query_items(
        query="SELECT * FROM r where r.city = 'Seattle'",
        partition_key="Washington",
        max_item_count=1000
    )

後續步驟

若要深入了解使用適用於 NoSQL 的 Python SDK for API:

減少查詢計劃呼叫

若要執行查詢,您必須建置查詢計劃。 對 Azure Cosmos DB 閘道的網路要求會增加查詢作業的延遲。 有一種方法可以移除此要求,並減少單一分割區查詢作業的延遲。 針對單一分割區查詢,可以透過兩種方式將查詢範圍限定為單一分割區。

使用參數化查詢運算式,並在查詢陳述式中指定分割區索引鍵。 查詢會以程式設計方式撰寫為 SELECT * FROM todo t WHERE t.partitionKey = 'Bikes, Touring Bikes'

// find all items with same categoryId (partitionKey)
const querySpec = {
    query: "select * from products p where p.categoryId=@categoryId",
    parameters: [
        {
            name: "@categoryId",
            value: "Bikes, Touring Bikes"
        }
    ]
};

// Get items 
const { resources } = await container.items.query(querySpec).fetchAll();

for (const item of resources) {
    console.log(`${item.id}: ${item.name}, ${item.sku}`);
}

或者,在 FeedOptions 中指定 partitionKey,並將其傳遞為引數:

const querySpec = {
    query: "select * from products p"
};

const { resources } = await container.items.query(querySpec, { partitionKey: "Bikes, Touring Bikes" }).fetchAll();

for (const item of resources) {
    console.log(`${item.id}: ${item.name}, ${item.sku}`);
}

調整頁面大小

當您發出 SQL 查詢時,如果結果集太大,系統會以分段方式傳回結果。 maxItemCount 可讓您設定列舉作業中要傳回的項目數上限。

const querySpec = {
    query: "select * from products p where p.categoryId=@categoryId",
    parameters: [
        {
            name: "@categoryId",
            value: items[2].categoryId
        }
    ]
};

const { resources } = await container.items.query(querySpec, { maxItemCount: 1000 }).fetchAll();

for (const item of resources) {
    console.log(`${item.id}: ${item.name}, ${item.sku}`);
}

增強的查詢控制

針對 Cosmos DB JS SDK 4.3.0 版和更新版本, enableQueryControl 引進了旗標,可進一步控制查詢執行,在管理要求單位 (RU) 耗用量方面提供更大的彈性。

根據預設, enableQueryControl 設定為 false ,且 SDK 保證每次呼叫都會 fetchNext 傳回 maxItemCount 結果數目,前提是後端存在這些結果。 但為了符合保證的結果數量,SDK 可能會在單一 fetchNext 迴圈中多次查詢後端分區。 這有時會導致無法預測的延遲和 RU 耗盡,而沒有使用者控制,尤其是當結果分散在分割區中時。

enableQueryControl 設定為 true 時,現在每次 fetchNext 呼叫都會查詢最多 maxDegreeOfParallelism 個實體分割區。 如果找不到結果或尚未準備好提供結果,SDK 會傳回空白頁面,而不是繼續在背景搜尋所有分割區。 如此一來,使用者就能透過可預測的延遲和精細的 RU 耗用量資料,更精細地控制其查詢執行。

const options = {
  enableQueryControl: true, // Flag to enable new query pipeline.
  maxItemCount: 100,
  maxDegreeOfParallelism: 6
};

const querySpec = {
    query: "select * from products p where p.categoryId=@categoryId",
    parameters: [
        {
            name: "@categoryId",
            value: items[2].categoryId
        }
    ]
};
const queryIterator = container.items.query(querySpec, options);
// use this iterator to fetch results.

後續步驟

若要深入了解使用適用於 NoSQL 的 Node.js SDK for API: