CLOVER🍀

That was when it all began.

Apache GeodeでのBucketとキーの配置について(クラスタ動作確認編)

先日、このようなエントリを書きました。

Apache GeodeでのBucketとキーの配置について(概要/API編) - CLOVER

その後編を書きたいと思います。

前回のエントリのテーマは

  • Partitioned RegionのBucketとデータの分散状況を確認するためのAPIについて(ただし、単一メンバー
  • クラスタを構成して、複数のメンバー間でデータが分散して配置されている様子を確認する

のうち前者でしたが、今回は後者について書いていきます。

Partitioned RegionやBucket、Primaryやバックアップなどの単語の説明はほぼ端折りますので、そのあたりは前回のエントリをご確認ください。

お題

Apache GeodeでPartitioned Regionを使ったクラスタを構成し、Bucketおよびキーの分散配置状況を確認してみます。

簡単なCLIツールと、単純に浮いてもらっているだけのサーバー×2の3メンバー構成とし、プロセスをダウンさせたりしてデータの移動などについても見てみたいと思います。

プログラムと設定ファイル

それでは、プログラムを作成します。

Maven依存関係については、以下があれば十分です。

        <dependency>
            <groupId>org.apache.geode</groupId>
            <artifactId>gemfire-core</artifactId>
            <version>1.0.0-incubating.M1</version>
        </dependency>

利用するApache Geodeバージョンは、1.0.0-incubating.M1とします。

gemfire.propertiesは用意せず、gemfire.propertiesで指定するパラメーターは、gemfire.〜システムプロパティで起動時に指定することにしますが、Cache XMLは用意します。
src/main/resources/cache.xml

<?xml version="1.0" encoding="UTF-8"?>
<cache
        xmlns="http://schema.pivotal.io/gemfire/cache"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://schema.pivotal.io/gemfire/cache http://schema.pivotal.io/gemfire/cache/cache-8.1.xsd"
        version="8.1">
    <region name="partitionRegion" refid="PARTITION">
        <region-attributes>
            <partition-attributes redundant-copies="1"/>
        </region-attributes>
    </region>
</cache>

バックアップ数1で、Partitioned Regionをひとつだけ定義しました。

続いて、プログラム。

単純な浮いていてもらうだけのサーバー
src/main/java/org/littlewings/geode/bucketkey/Server.java

package org.littlewings.geode.bucketkey;

import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.CacheFactory;

public class Server {
    public static void main(String... args) {
        Cache cache =
                new CacheFactory()
                        .create();

        System.console().readLine("> Enter, stop server.");

        cache.close();
    }
}

Enterを打つと終了する、ものすごく単純なサーバーです。

そして、動作確認のためのCLIツール。
src/main/java/org/littlewings/geode/bucketkey/Cli.java

package org.littlewings.geode.bucketkey;

import java.io.Console;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.CacheFactory;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.cache.control.RebalanceOperation;
import com.gemstone.gemfire.cache.control.RebalanceResults;
import com.gemstone.gemfire.cache.control.ResourceManager;
import com.gemstone.gemfire.cache.partition.PartitionRegionHelper;
import com.gemstone.gemfire.distributed.DistributedMember;
import com.gemstone.gemfire.distributed.DistributedSystem;
import com.gemstone.gemfire.internal.cache.PartitionedRegion;
import com.gemstone.gemfire.internal.cache.PartitionedRegionDataStore;
import com.gemstone.gemfire.internal.cache.PartitionedRegionHelper;

public class Cli {
    public static void main(String... args) {
        try (Cache cache = new CacheFactory().create()) {
            Console console = System.console();

            boolean cliContinue = true;

            while (cliContinue) {
                String line = console.readLine("> ");

                if (line == null || line.trim().isEmpty()) {
                    continue;
                }

                try {
                    Region<String, String> region;
                    String[] tokens = line.split("\\s+");

                    String command = tokens[0];
                    switch (command) {

                        // コマンドに応じた処理を実装

                    }
                } catch (Exception e) {
                    System.out.printf("Unknown Command, or Parse Error, [%s].%n", line);
                    e.printStackTrace();
                }
            }
        }
    }
}

コメント部は、個別に記載していきます。

クラスタに参加しているメンバー全体を見れる、「members」コマンド。

                        case "members":
                            if (tokens.length == 1) {
                                DistributedSystem ds = cache.getDistributedSystem();
                                System.out.printf("Self = %s.%n", ds.getDistributedMember().getName());
                                System.out.printf("Other Members = %s.%n",
                                        ds.getAllOtherMembers().stream().map(m -> m.getName()).collect(Collectors.joining(", ", "[", "]")));
                                System.out.printf("Other Members, alias = %s.%n",
                                        cache.getMembers().stream().map(m -> m.getName()).collect(Collectors.joining(", ", "[", "]")));
                            } else {
                                region = cache.getRegion(tokens[1]);
                                System.out.printf("Region[%s] Other Members = %s.%n",
                                        region.getName(),
                                        cache.getMembers(region).stream().map(m -> m.getName()).collect(Collectors.joining(", ", "[", "]")));
                            }
                            break;

指定した範囲で機械的にキーと値を生成してRegionに登録する、「range-put」コマンド。

                        case "range-put":
                            region = cache.getRegion(tokens[1]);
                            int start = Integer.parseInt(tokens[2]);
                            int end = Integer.parseInt(tokens[3]);
                            IntStream.rangeClosed(start, end).forEach(i -> {
                                String key = "key" + i;
                                String value = "value" + i;
                                region.put(key, value);
                                System.out.printf("Region[%s] putted, key = %s, value = %s.%n", region.getName(), key, value);
                            });
                            break;

現在Regionに登録されているすべてのキーと値、Bucket ID、そしてデータのPrimaryとバックアップの保持メンバーを表示する「get-all」コマンド。

                        case "get-all":
                            region = cache.getRegion(tokens[1]);

                            System.out.printf("Region[%s]%n",
                                    region.getName());

                            region
                                    .entrySet()
                                    .stream()
                                    .forEach(entry -> {
                                        System.out.printf("    key = %s, value = %s%n",
                                                entry.getKey(),
                                                entry.getValue());

                                        if (region instanceof PartitionedRegion) {
                                            DistributedMember primary = PartitionRegionHelper.getPrimaryMemberForKey(region, entry.getKey());
                                            Set<DistributedMember> allMembers = PartitionRegionHelper.getAllMembersForKey(region, entry.getKey());
                                            Set<DistributedMember> backups = new LinkedHashSet<>(allMembers);
                                            backups.remove(primary);

                                            System.out.printf("       bucket id = %d%n",
                                                    PartitionedRegionHelper.getHashKey((PartitionedRegion) region, null, entry.getKey(), null, null));
                                            /* もしくは
                                            System.out.printf("       bucket id = %d%n",
                                                    PartitionedRegionHelper.getHashKey((PartitionedRegion) region, entry.getKey()));
                                                    */
                                            System.out.printf("       primary = %s%n", primary.getName());
                                            System.out.printf("       backup = %s%n",
                                                    backups.stream().map(m -> m.getName()).collect(Collectors.joining(", ", "[", "]")));
                                        }
                                    });
                            break;

現在のローカルメンバーCLIツール)に割り当てられている、キーと値、全BucketのID、Primaryとして割り当てられているBucketのID、バックアップを含めたBucketのIDを出力する「get-all-local」コマンド。

                        case "get-all-local":
                            region = cache.getRegion(tokens[1]);

                            if (region instanceof PartitionedRegion) {
                                System.out.printf("Region[%s]%n",
                                        region.getName());

                                PartitionedRegion partitionedRegion = (PartitionedRegion) region;
                                PartitionedRegionDataStore dataStore = partitionedRegion.getDataStore();

                                Set<Integer> bucketIds = dataStore.getAllLocalBucketIds();
                                System.out.printf("    All Buckets%n       => %s%n", bucketIds);

                                System.out.println();

                                System.out.printf("    [%s]'s Primary Local Buckets%n       => %s%n",
                                        cache.getName(),
                                        partitionedRegion.getDataStore().getAllLocalPrimaryBucketIds());
                                dataStore
                                        .getAllLocalPrimaryBucketRegions()
                                        .forEach(bucketRegion -> {
                                            int bucketId = bucketRegion.getId();
                                            bucketRegion
                                                    .entrySet()
                                                    .forEach(e -> {
                                                        Map.Entry<String, String> entry = (Map.Entry<String, String>) e;
                                                        System.out.printf("    BucketId[%d]: key = %s, value = %s%n",
                                                                bucketId,
                                                                entry.getKey(),
                                                                entry.getValue());
                                                    });
                                        });
                                System.out.println();

                                System.out.printf("    [%s]'s All Local Buckets%n       => %s%n",
                                        cache.getName(),
                                        partitionedRegion.getDataStore().getAllLocalBucketIds());
                                dataStore
                                        .getAllLocalBucketRegions()
                                        .forEach(bucketRegion -> {
                                            int bucketId = bucketRegion.getId();
                                            bucketRegion
                                                    .entrySet()
                                                    .forEach(e -> {
                                                        Map.Entry<String, String> entry = (Map.Entry<String, String>) e;
                                                        System.out.printf("    BucketId[%d]: key = %s, value = %s%n",
                                                                bucketId,
                                                                entry.getKey(),
                                                                entry.getValue());
                                                    });
                                        });
                            } else {
                                System.out.printf("Region[%s] is not Partitioned Region.%n", tokens[1]);
                            }

                            break;

データのリバランスを行う、「rebalance」コマンド。

                        case "rebalance":
                            region = cache.getRegion(tokens[1]);
                            ResourceManager resourceManager = cache.getResourceManager();
                            RebalanceOperation operation = resourceManager.createRebalanceFactory().start();
                            RebalanceResults results = operation.getResults();
                            System.out.printf("Region[%s] Rebalance Results: totalBucketTransferBytes = %d, totalBucketCreatesCompleted = %d%n",
                                    region.getName(),
                                    results.getTotalBucketTransferBytes(),
                                    results.getTotalBucketCreatesCompleted());
                            break;

「exit」で終了、また上記以外までのコマンドを入力すると無視されます。

                        case "exit":
                            cliContinue = false;
                            break;
                        default:
                            System.out.printf("Unknown Command[%s].%n", line);
                            break;

では、これらのプログラムを使って動作確認してみたいと思います。

動作確認

CLIツール、その他のメンバー2つを順次起動します。

## CLI
$ mvn compile exec:java \
 -Dexec.mainClass=org.littlewings.geode.bucketkey.Cli \
 -Dgemfire.name=MEMBER-CLI \
 -Dgemfire.start-locator=localhost[10334] \
 -Dgemfire.locators=localhost[10334],localhost[10335],localhost[10336]


## NODE-A
$ mvn compile exec:java \
 -Dexec.mainClass=org.littlewings.geode.bucketkey.Server \
 -Dgemfire.name=MEMBER-NODE-A \
 -Dgemfire.start-locator=localhost[10335] \
 -Dgemfire.locators=localhost[10334],localhost[10335],localhost[10336]

## NODE-B
$ mvn compile exec:java \
 -Dexec.mainClass=org.littlewings.geode.bucketkey.Server \
 -Dgemfire.name=MEMBER-NODE-B \
 -Dgemfire.start-locator=localhost[10336] \
 -Dgemfire.locators=localhost[10334],localhost[10335],localhost[10336]

見やすいように、gemfire.nameシステムプロパティで名前を付けました。

起動したら、まずはクラスタに参加しているメンバー確認。

> members
Self = MEMBER-CLI.
Other Members = [MEMBER-NODE-A, MEMBER-NODE-B].
Other Members, alias = [MEMBER-NODE-A, MEMBER-NODE-B].

自分自身は、「MEMBER-CLI」です。

データを10個登録してみます。

> range-put partitionRegion 1 10

全データを確認してみます。

> get-all partitionRegion
Region[partitionRegion]
    key = key10, value = value10
       bucket id = 84
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]
    key = key1, value = value1
       bucket id = 85
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-B]
    key = key2, value = value2
       bucket id = 86
       primary = MEMBER-NODE-A
       backup = [MEMBER-CLI]
    key = key3, value = value3
       bucket id = 87
       primary = MEMBER-NODE-B
       backup = [MEMBER-NODE-A]
    key = key4, value = value4
       bucket id = 88
       primary = MEMBER-NODE-A
       backup = [MEMBER-CLI]
    key = key5, value = value5
       bucket id = 89
       primary = MEMBER-NODE-B
       backup = [MEMBER-NODE-A]
    key = key6, value = value6
       bucket id = 90
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-B]
    key = key7, value = value7
       bucket id = 91
       primary = MEMBER-NODE-B
       backup = [MEMBER-NODE-A]
    key = key8, value = value8
       bucket id = 92
       primary = MEMBER-NODE-A
       backup = [MEMBER-CLI]
    key = key9, value = value9
       bucket id = 93
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-B]

※初回はログがたくさん出るので、2回目の実行結果を表示しています

それぞれ、Primaryとバックアップがわかるようになっていますね。

このうち、CLIツールに割り当てられているデータを確認してみましょう。

> get-all-local partitionRegion
Region[partitionRegion]
    All Buckets
       => [1, 2, 4, 5, 6, 8, 10, 11, 12, 14, 15, 16, 17, 20, 21, 24, 25, 27, 28, 30, 31, 32, 35, 36, 37, 39, 41, 42, 43, 44, 46, 48, 49, 50, 51, 53, 55, 56, 57, 59, 61, 62, 65, 66, 67, 68, 69, 70, 73, 74, 75, 77, 79, 80, 81, 83, 84, 85, 86, 88, 90, 92, 93, 95, 96, 97, 100, 101, 103, 104, 106, 107, 108, 109, 111]

    [MEMBER-CLI]'s Primary Local Buckets
       => [67, 4, 6, 70, 8, 73, 11, 75, 77, 16, 17, 81, 20, 84, 85, 25, 90, 28, 93, 30, 95, 32, 96, 35, 101, 39, 103, 106, 43, 44, 109, 48, 51, 55, 57, 59, 62]
    BucketId[84]: key = key10, value = value10
    BucketId[90]: key = key6, value = value6
    BucketId[93]: key = key9, value = value9
    BucketId[85]: key = key1, value = value1

    [MEMBER-CLI]'s All Local Buckets
       => [1, 2, 4, 5, 6, 8, 10, 11, 12, 14, 15, 16, 17, 20, 21, 24, 25, 27, 28, 30, 31, 32, 35, 36, 37, 39, 41, 42, 43, 44, 46, 48, 49, 50, 51, 53, 55, 56, 57, 59, 61, 62, 65, 66, 67, 68, 69, 70, 73, 74, 75, 77, 79, 80, 81, 83, 84, 85, 86, 88, 90, 92, 93, 95, 96, 97, 100, 101, 103, 104, 106, 107, 108, 109, 111]
    BucketId[84]: key = key10, value = value10
    BucketId[88]: key = key4, value = value4
    BucketId[86]: key = key2, value = value2
    BucketId[90]: key = key6, value = value6
    BucketId[92]: key = key8, value = value8
    BucketId[93]: key = key9, value = value9
    BucketId[85]: key = key1, value = value1

先ほどの「get-all」で、

       primary = MEMBER-CLI

となっているものや、

       backup = [MEMBER-CLI]

に絞られてBucketの情報が表示されましたね。
※参考までに、このPartition Regionが保持している全BucketのIDも出力しています


では、ここで「MEMBER-NODE-A」を停止してみます。「MEMBER-NODE-A」側のコンソールで、Enterキーを打つと停止します。

[info 2016/04/03 14:24:31.758 JST <org.littlewings.geode.bucketkey.Server.main()> tid=0xb] GemFireCache[id = 194268085; isClosing = true; isShutDownAll = false; created = Sun Apr 03 14:22:46 JST 2016; server = false; copyOnRead = false; lockLease = 120; lockTimeout = 60]: Now closing.

[info 2016/04/03 14:24:31.880 JST <org.littlewings.geode.bucketkey.Server.main()> tid=0xb] Shutting down DistributionManager 192.168.254.129(MEMBER-NODE-A:124841)<ec><v0>:1024. 

[info 2016/04/03 14:24:32.077 JST <org.littlewings.geode.bucketkey.Server.main()> tid=0xb] Now closing distribution for 192.168.254.129(MEMBER-NODE-A:124841)<ec><v0>:1024

[info 2016/04/03 14:24:32.078 JST <org.littlewings.geode.bucketkey.Server.main()> tid=0xb] Stopping membership services

[info 2016/04/03 14:24:33.082 JST <org.littlewings.geode.bucketkey.Server.main()> tid=0xb] GMSHealthMonitor server socket is closed in stopServices().

[info 2016/04/03 14:24:33.083 JST <Geode Failure Detection Server thread 0> tid=0x1f] GMSHealthMonitor server thread exiting

[info 2016/04/03 14:24:33.089 JST <org.littlewings.geode.bucketkey.Server.main()> tid=0xb] GMSHealthMonitor serverSocketExecutor is terminated

[info 2016/04/03 14:24:33.121 JST <org.littlewings.geode.bucketkey.Server.main()> tid=0xb] DistributionManager stopped in 1,239ms.

[info 2016/04/03 14:24:33.123 JST <org.littlewings.geode.bucketkey.Server.main()> tid=0xb] Marking DistributionManager 192.168.254.129(MEMBER-NODE-A:124841)<ec><v0>:1024 as closed.

[info 2016/04/03 14:24:33.124 JST <org.littlewings.geode.bucketkey.Server.main()> tid=0xb] Stopping Distribution Locator on localhost/127.0.0.1[10335]

[info 2016/04/03 14:24:33.128 JST <Distribution Locator on localhost/127.0.0.1[10335]> tid=0x10] locator shutting down

[info 2016/04/03 14:24:33.129 JST <org.littlewings.geode.bucketkey.Server.main()> tid=0xb] Distribution Locator on localhost/127.0.0.1[10335]  is stopped

確認。

> get-all partitionRegion
Region[partitionRegion]
    key = key10, value = value10
       bucket id = 84
       primary = MEMBER-CLI
       backup = []
    key = key1, value = value1
       bucket id = 85
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-B]
    key = key2, value = value2
       bucket id = 86
       primary = MEMBER-CLI
       backup = []
    key = key3, value = value3
       bucket id = 87
       primary = MEMBER-NODE-B
       backup = []
    key = key4, value = value4
       bucket id = 88
       primary = MEMBER-CLI
       backup = []
    key = key5, value = value5
       bucket id = 89
       primary = MEMBER-NODE-B
       backup = []
    key = key6, value = value6
       bucket id = 90
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-B]
    key = key7, value = value7
       bucket id = 91
       primary = MEMBER-NODE-B
       backup = []
    key = key8, value = value8
       bucket id = 92
       primary = MEMBER-CLI
       backup = []
    key = key9, value = value9
       bucket id = 93
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-B]

なんか薄くなりましたね??

リバランスしてみましょう。

> rebalance partitionRegion

再配置されました。

> get-all partitionRegion
Region[partitionRegion]
    key = key10, value = value10
       bucket id = 84
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-B]
    key = key1, value = value1
       bucket id = 85
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-B]
    key = key2, value = value2
       bucket id = 86
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-B]
    key = key3, value = value3
       bucket id = 87
       primary = MEMBER-NODE-B
       backup = [MEMBER-CLI]
    key = key4, value = value4
       bucket id = 88
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-B]
    key = key5, value = value5
       bucket id = 89
       primary = MEMBER-NODE-B
       backup = [MEMBER-CLI]
    key = key6, value = value6
       bucket id = 90
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-B]
    key = key7, value = value7
       bucket id = 91
       primary = MEMBER-NODE-B
       backup = [MEMBER-CLI]
    key = key8, value = value8
       bucket id = 92
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-B]
    key = key9, value = value9
       bucket id = 93
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-B]

「MEMBER-NODE-B」も落としてみます。

すると、バックアップがなくなってしまいます。

> get-all partitionRegion
Region[partitionRegion]
    key = key10, value = value10
       bucket id = 84
       primary = MEMBER-CLI
       backup = []
    key = key1, value = value1
       bucket id = 85
       primary = MEMBER-CLI
       backup = []
    key = key2, value = value2
       bucket id = 86
       primary = MEMBER-CLI
       backup = []
    key = key3, value = value3
       bucket id = 87
       primary = MEMBER-CLI
       backup = []
    key = key4, value = value4
       bucket id = 88
       primary = MEMBER-CLI
       backup = []
    key = key5, value = value5
       bucket id = 89
       primary = MEMBER-CLI
       backup = []
    key = key6, value = value6
       bucket id = 90
       primary = MEMBER-CLI
       backup = []
    key = key7, value = value7
       bucket id = 91
       primary = MEMBER-CLI
       backup = []
    key = key8, value = value8
       bucket id = 92
       primary = MEMBER-CLI
       backup = []
    key = key9, value = value9
       bucket id = 93
       primary = MEMBER-CLI
       backup = []
> 

ここで再度、「MEMBER-NODE-A」を起動してみましょう。

$ mvn compile exec:java \
 -Dexec.mainClass=org.littlewings.geode.bucketkey.Server \
 -Dgemfire.name=MEMBER-NODE-A \
 -Dgemfire.start-locator=localhost[10335] \
 -Dgemfire.locators=localhost[10334],localhost[10335],localhost[10336]

クラスタメンバー確認。

> members
Self = MEMBER-CLI.
Other Members = [MEMBER-NODE-A].
Other Members, alias = [MEMBER-NODE-A].

バックアップにも割り当てられるようになります。

> get-all partitionRegion
Region[partitionRegion]
    key = key10, value = value10
       bucket id = 84
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]
    key = key1, value = value1
       bucket id = 85
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]
    key = key2, value = value2
       bucket id = 86
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]
    key = key3, value = value3
       bucket id = 87
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]
    key = key4, value = value4
       bucket id = 88
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]
    key = key5, value = value5
       bucket id = 89
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]
    key = key6, value = value6
       bucket id = 90
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]
    key = key7, value = value7
       bucket id = 91
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]
    key = key8, value = value8
       bucket id = 92
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]
    key = key9, value = value9
       bucket id = 93
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]

「MEMBER-NODE-B」も起こしてみましょう。

$ mvn compile exec:java \
 -Dexec.mainClass=org.littlewings.geode.bucketkey.Server \
 -Dgemfire.name=MEMBER-NODE-B \
 -Dgemfire.start-locator=localhost[10336] \
 -Dgemfire.locators=localhost[10334],localhost[10335],localhost[10336]

…割り当てが変わりません。

> get-all partitionRegion
Region[partitionRegion]
    key = key10, value = value10
       bucket id = 84
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]
    key = key1, value = value1
       bucket id = 85
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]
    key = key2, value = value2
       bucket id = 86
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]
    key = key3, value = value3
       bucket id = 87
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]
    key = key4, value = value4
       bucket id = 88
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]
    key = key5, value = value5
       bucket id = 89
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]
    key = key6, value = value6
       bucket id = 90
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]
    key = key7, value = value7
       bucket id = 91
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]
    key = key8, value = value8
       bucket id = 92
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]
    key = key9, value = value9
       bucket id = 93
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]

リバランスしてみます。

> rebalance partitionRegion

ちょっと「MEMBER-CLI」に偏っていますが、「MEMBER-NODE-B」にも割り当てられるようになりましたね。

> get-all partitionRegion
Region[partitionRegion]
    key = key10, value = value10
       bucket id = 84
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]
    key = key1, value = value1
       bucket id = 85
       primary = MEMBER-NODE-B
       backup = [MEMBER-NODE-A]
    key = key2, value = value2
       bucket id = 86
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-B]
    key = key3, value = value3
       bucket id = 87
       primary = MEMBER-NODE-B
       backup = [MEMBER-NODE-A]
    key = key4, value = value4
       bucket id = 88
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-B]
    key = key5, value = value5
       bucket id = 89
       primary = MEMBER-NODE-B
       backup = [MEMBER-NODE-A]
    key = key6, value = value6
       bucket id = 90
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-B]
    key = key7, value = value7
       bucket id = 91
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]
    key = key8, value = value8
       bucket id = 92
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]
    key = key9, value = value9
       bucket id = 93
       primary = MEMBER-CLI
       backup = [MEMBER-NODE-A]

なんですが、「MEMBER-NODE-A」はPrimaryにならないんでしょうかねぇ…。

リバランスについて

先ほどから、メンバーが離脱する度にリバランスしていますが、これはデフォルトではメンバーダウン時にリバランスを自動的にはしてくれないからです。

Configure Member Crash Redundancy Recovery for a Partitioned Region

自動的にリバランスしてもらう場合は、以下の様に「recovery-delay」を指定します(単位はmilliseconds)。

    <region name="partitionRegion" refid="PARTITION">
        <region-attributes>
            <partition-attributes redundant-copies="1" recovery-delay="1000"/>
        </region-attributes>
    </region>

デフォルトでは、この「recovery-delay」が-1になっているため、自動でリバランスされません。

結果は載せませんが、今回は1秒設定で試すとリバランスしてくれました。

また、メンバー追加時もデフォルトではリバランスしてくれない…と書かれています…(そう?)。一応、こちらも設定で可能なようですが。

http://geode.docs.pivotal.io/docs/developing/partitioned_regions/set_join_redundancy_recovery.html

    <region name="partitionRegion" refid="PARTITION">
        <region-attributes>
            <partition-attributes redundant-copies="1" startup-recovery-delay="1000"/>
        </region-attributes>
    </region>

単独のメンバー時から、さらに2つメンバー追加しても、2つめのメンバーには結局自分でリバランスさせないといけないような挙動に…。あくまで、バックアップの補完という意味合いということでしょうかね。

多重障害のケース

バックアップがあるとはいえ、Primary+バックアップ数のメンバーが同時にダウンしてしまうとデータのロストが発生します。

今回の構成ですと、Primary+Backupがひとつなので、メンバーが2つ同時にダウンするとデータがロストします。このあたりは、リソースと耐障害性の兼ね合いというところですね。

ちょっと気になること

あとからクラスタに参加したメンバーは、Primaryにはなりにくいように見えるのですが、そういうもの?

例えば、CLIだけ起動してデータを入れて、次にメンバーを追加した場合はその追加メンバーはPrimaryにはなれていないような…。そこからさらに追加してリバランスすると、2つめに追加したメンバーはPrimaryになれるっぽいんですけどね。
追加されたメンバーは、最初はバックアップに全部割り当てられてしまうからでしょうか?

まとめ

2回に渡って、Apache GeodeのPartitioned RegionのBucketについての話や、実際にクラスタ構成でデータがメンバー間を移動していく様子を確認してみました。

これを調べる過程で、データの持ち方、構造的なところも知れて良かったかなと思います。