日別アーカイブ: 2019年5月9日

Cosmos DBの地理冗長化に関するあれこれ (v2)

このエントリは今日(2019/05/09)現在の備忘録です。将来指定方法やそもそもその機能がなくなる、などの変更が発生する可能性があります。また、この内容はSDK v2をベースにしています。


マルチマスタの有効化・無効化

Azure CLIで可能ですが、マルチマスタからシングルマスタへの変更には最大24時間かかります。

az cosmosdb update --name <Azure Cosmos Account Name> --resource-group <Resource Group Name> --enable-multiple-write-locations {true|false}

Cosmos DBのstrongという整合性レベル(Consistency Level)

ドキュメントには以下のような記述があります。

厳密な整合性では、線形化可能性の保証が提供されます。 読み取りでは、アイテムの最後にコミットされたバージョンが返ることが保証されています。 クライアントが、コミットされていない書き込みや部分的な書き込みを見ることは決してありません。 ユーザーが最新のコミット済みの書き込みを読み取ることが常に保証されます。

Azure Cosmos DB の整合性レベル
https://docs.microsoft.com/ja-jp/azure/cosmos-db/consistency-levels#guarantees-associated-with-consistency-levels

これを読むと、複数リージョンにまたがってもこの整合性レベルが特に制約なく使えるように見えますが、実際には、5000マイル(8050km)以上離れたリージョン間でのstrongという整合性の利用はサポートされません。設定すると以下のエラーメッセージが出ます。

Strong consistency account with regions spanning more than 5000 miles is not supported for the target subscription ActivityId: xxxxxxxx, Microsoft.Azure.Documents.Common/2.2.0.0

各Regionに冗長化されたCosmos DBのURIを取得する

Cosmos DBは複数のリージョンに分散配置できます。各リージョンに配置されたCosmos DBにアクセスできれば、レイテンシを小さくできます。そのためには、以下のデータが必要です。

  1. アプリケーションが稼働しているリージョン
  2. 読み取り可能なCosmos DB地理冗長先のリージョン(もちろんマルチマスタ構成であれば書き込み可能なリージョンも取得できます)

1は、アプリケーションが稼働しているリージョンはアプリケーションに対して環境変数を使って渡すことにし、このエントリでは2を取得することを目指します。

.NET APIを使ったサンプルは多数見つかるので、以下の例では非同期Java APIを使っています。

読み取り可能なCosmos DBの地理冗長化先のリージョン

AsyncDocumentClientクラスのインスタンスを作成します(この例ではIteratorとして取得していますが、Spliteratorを使ってStreamに変換してもよいです)。

AsyncDocumentClient tempClient = new AsyncDocumentClient.Builder()
                .withServiceEndpoint(URI)
                .withMasterKeyOrResourceToken(KEY)
                .build();
Iterator<DatabaseAccount> dbAccount = tempClient.getDatabaseAccount().toBlocking().getIterator();

あとは、取得したDatabaseAccountに設定されているリージョンを調べ、ReadableLocationsとアプリケーションが稼働中のリージョンと一致すればURIを取得し、そのURLとKeyを使ってアクセスします。

// currentRegionName is given through environment variable

String targetURI;

while(dbAccount.hasNext()){
    Iterator<DatabaseAccountLocation> it = dbAccount.next()
                                                    .getReadableLocations()
                                                    .iterator();
    while(it.hasNext()) {
        DatabaseAccountLocation loc = it.next();
        if(loc.getName().equals(currentRegionName)) {
            targetURI = loc.getEndpoint();
            break;
        }
    }
    // Exit loop if targetURI is given
    if(Optional.of(targetURI).isPresent()) {
        break;
    }
}

DatabaseAccount.getReadableLocations Method
https://docs.microsoft.com/en-us/java/api/com.microsoft.azure.cosmosdb.databaseaccount.getreadablelocations?view=azure-java-stable

この例では、アプリケーションが稼働するリージョンのCosmos DBのURIを取得していますが、以下のドキュメントにもある通り、通常は当該リージョンのCosmos DBにアクセスできなかった場合を考慮して、複数の読み取りリージョンを追加し、フォールバックさせます。

Azure Cosmos DB での高可用性
https://docs.microsoft.com/ja-jp/azure/cosmos-db/high-availability
High availability with Azure Cosmos DB
https://docs.microsoft.com/en-us/azure/cosmos-db/high-availability

これを実現するには、ConnectionPoilicyで以下を設定しておく必要があります。

  1. フォールバック先を自動的に探索するよう、EnableEndpointDiscoveryを有効化
    • デフォルトでtrue
  2. PreferredLocationsの設定
    • EnableEndpointDiscoveryがfalseの場合、PreferredLocationsは無視される
    • PreferredLocationsにはjava.util.Listのかたちで複数のリージョンを設定できる
  3. PreferredLocationsを参照してフォールバックするよう、enableReadRequestsFallbackを有効化
    • Consistency LevelがBounded Staleness以外であれば、デフォルトでtrue(Bounded Stalenessの場合はデフォルトでfalse)
    • trueで、PreferredLocationsに複数のリージョンが指定されている場合、リストの先頭から順にアクセスを試行していき、すべて失敗する場合には、書き込みリージョンに対して接続を試行

ConnectionPolicy.setEnableEndpointDiscovery(boolean enableEndpointDiscovery) Method
https://docs.microsoft.com/en-us/java/api/com.microsoft.azure.cosmosdb.connectionpolicy.setenableendpointdiscovery?view=azure-java-stable
ConnectionPolicy.setEnableReadRequestsFallback(Boolean enableReadRequestsFallback) Method
https://docs.microsoft.com/en-us/java/api/com.microsoft.azure.cosmosdb.connectionpolicy.setenablereadrequestsfallback?view=azure-java-stable
ConnectionPolicy.setPreferredLocations(List preferredLocations) Method
https://docs.microsoft.com/en-us/java/api/com.microsoft.azure.cosmosdb.connectionpolicy.setpreferredlocations?view=azure-java-stable#com_microsoft_azure_cosmosdb_ConnectionPolicy_setPreferredLocations_List_String__

上記プロパティを構成する例は以下の通りです。

List<String> preferredLocation = new ArrayList<>();
String targetURI;

// preferredLocation and targetURI are configured with ReadableLocations.
...

// ConnectionPolicy
ConnectionPolicy pol = ConnectionPolicy.GetDefault();
pol.setPreferredLocations(preferredLocation);
pol.setEnableEndpointDiscovery(true);
pol.setEnableReadRequestsFallback(true);

// Document Client
AsyncDocumentClient client = new AsyncDocumentClient.Builder()
                                                    .withServiceEndpoint(targetURI)
                                                    .withMasterKeyOrResourceToken(KEY)
                                                    .withConnectionPolicy(pol)
                                                    .build();

書き込み可能なリージョンは、WritableLocationsです。ReadableLocationsと同様、以下のように取得できます。

while(dbAccount.hasNext()){
    Iterator<DatabaseAccountLocation> it = dbAccount.next().getWritableLocations().iterator();
    while(it.hasNext()) {
        DatabaseAccountLocation loc = it.next();
        System.out.println("Writable Location: "+loc.getName() + " /URL "+ loc.getEndpoint());
    }
}

DatabaseAccount.getWritableLocations Method
https://docs.microsoft.com/en-us/java/api/com.microsoft.azure.cosmosdb.databaseaccount.getwritablelocations?view=azure-java-stable