Azure Cosmos DB Java SDK 的效能秘訣

適用於:NoSQL

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

減少查詢計劃呼叫

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

使用開放式直接執行來最佳化單一分割區查詢

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

注意

開放式直接執行 (ODE)對於不需要散發的查詢提供改善的效能,不應與 直接模式混淆,這是將應用程式連線到後端復本的路徑。

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

具有 GROUP BY、ORDER BY、DISTINCT 和彙總函數的單一分割區查詢 (例如 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 所產生權杖的情況,則建議使用 2 步驟方式進行升級:

  • 升級至新 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 屬性來以分割區索引鍵為目標而且未包含任何彙總 (包括 Distinct、DCount、Group 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")}))
{
    // ...
}

注意

跨分割區查詢需要 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 設為 -1,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 屬性可限制預先擷取的結果數目。 將 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 版和更新版本預設會啟用查詢方案快取。

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

針對以 CosmosQueryRequestOptions 中的 setPartitionKey 將範圍設定為分割區索引鍵,且不包含彙總 (包括 Distinct、DCount、Group 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();

調整緩衝區大小

平行查詢的設計是可在用戶端處理目前的結果批次時,先預先擷取結果。 預先擷取有助於改善查詢的整體延遲。 CosmosQueryRequestOptions 中的 setMaxBufferedItemCount 可限制預先擷取的結果數目。 若要最大化預先擷取,請將 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}`);
}

下一步

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