Symbolのデータベースをクラウドデータベースに置き換え

# はじめに

こんにちは。晴れ時々Symbolの（時期的には雪時々Symbolか）だい（@ishidad2）です。

本記事は、[nem / symbol Advent Calendar 2024](https://qiita.com/advent-calendar/2024/nem) の16日目の記事となります。

普段は[株式会社 OpeningLine](https://www.opening-line.co.jp/)でSymbolブロックチェーンを用いたWebアプリケーションの開発、ブロックチェーンノードの保守などを行っています。
個人でもスマホアプリの作成やIoT機器の個人開発などを行っています。

# 対象読者

この記事の対象読者としては、最低限以下の要件を満たす人を対象としています。

- サーバー（Linux）をいじったことのある人
- コマンドラインで作業できる人（ファイルの編集などはVSCodeでもOK）
- Symbolノードを自力で構築できる人（shoestringを使います）

# 本記事のゴール

本記事では、タイトルの通り「Symbolのデータベースをクラウドデータベースに置き換え」に挑戦します。

ブロックチェーンのデータベース（以下、DB）の肥大化はローカルサーバーにおいて大きな課題です。

そこで、DB本体をローカルからクラウドに切り替えられないかと思い調査しました。

結論から言うと、「Symbolのデータベースをクラウドデータベースに置き換え」は成功しました。

（置き換えは成功しましたが、証明書の問題やクラッシュした際の復帰方法、アクセス速度の問題など色々と課題はあります）

:::note warn
この記事では「Symbolのデータベースをクラウドデータベースに置き換え」ることを推奨しているわけではありません。
あくまでもクラウドサービスを使うことができるかどうかの検証結果を示すものとなります。
検証する際には自己責任でお願いします。
:::

# MongoDB Atlas

MongoDB Atlasは、MongoDB向けのクラウドデータベースソリューションです。

今回はこのMongoDB Atlasを使います。

https://www.mongodb.com/

今回はお試しなので無料枠を使います。

無料枠ではストレージが512MBまでしか使えないようです。
とりあえず、試すだけならなんとかなりそうなのでFreeプランを使います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/934471/795f68fa-6a57-1114-e054-ffc64d70fe2f.png)

:::note warn
MongoDB Atlasのはじめ方や使い方はこの記事では説明しません。
:::

## Clusterを作成

無料プランでDBを作成します。

必ずM0(Free)を選択してください。

デプロイ先のプロバイダーはお好きな場所を選択してください。

私は使い慣れたAzureを選択してみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/934471/22cd3171-6f5d-5055-8297-0d141cfcd189.png)

Clusterを作成する画面でデータベースへの接続パスワードが表示されるのでコピーして他者にバレないように保存してきます。

![スクリーンショット 2024-12-10 8.31.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/934471/ff93de5e-b449-2eb2-f439-87d7f51283cb.png)

次に「Create Database User」をクリックしてユーザーを作成します。

![スクリーンショット 2024-12-10 8.31.23 (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/934471/cb870b36-be7e-0a85-ad93-21294a0b1c8e.png)

![image (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/934471/17702d11-8d6c-473b-60e9-5ff7cb082656.png)

## 作成したClusterの確認

デプロイが完了すると、「sample_mflix」というDBができていました。

正確な用途は不明ですが恐らくサンプルDBでしょう。（フリープランということもあり、容量がもったいないので消しておきましょう）

![https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_934471_e585dbe8-4410-7fca-71bb-2412a3fe49f3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/934471/eac7e220-fa1d-0012-b3f7-fa4edad74e1f.png)

:::note warn
IPアドレス制限について

Atlasの初期の設定ではIP接続制限がかけられています。
接続するサーバーのIPアドレスを許可するか、一時的に0.0.0.0/0を許可するようにしておいてください。
:::

## DB接続文字列の取得

外部からDBに接続するための接続文字列を取得します。

「Get connection string」をクリックします。
（初回以降は左側メニューの「DATABASE」→「Clusters」から確認できます）

![スクリーンショット 2024-12-10 9.42.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/934471/21dd43ef-b9be-24ee-d81d-5561de03e957.png)


後ほどノード側で利用するのでDB接続文字列をコピーしておきます。

![スクリーンショット 2024-12-10 9.44.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/934471/277015c4-be98-f6fe-54ae-78c4893a39e1.png)

Mongo DB Atlas側の設定は以上です。

続いて、Symbolノードの構築を行います。

# Symbolノード構築

## 環境

作業環境は以下のようになります。

```bash
$ cat /etc/os-release 
PRETTY_NAME="Ubuntu 22.04 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy
```

```bash
$ docker -v
Docker version 27.4.0, build bde2b89
```

```bash
$ python --version
Python 3.9.20
```

ノードの構築には`shoestring`を使います。

https://pypi.org/project/symbol-shoestring/

## ノード構築

:::note warn
ノード構築について

基本的にノード構築のノウハウは説明しませんので、その辺りはQiita内に記事がたくさんあるのでそちらを参考にしてください。
:::

```bash
python3 -m shoestring.wizard
```

上記のコマンドを実行して、ノードの初期設定ファイル群を生成します。

今回は実験ですのでテストネットを指定して作成で問題ありません。

諸々の設定ファイルができたらいくつかファイルの書き換えを行っていきます。

## 接続文字列の修正

まず、ノードの初期設定ファイルのデータベース接続文字列を書き換えていきます。

対象となるファイルは以下の２つです。

- userconfig/resources/config-database.properties
- userconfig/rest.json

### config-database.propertiesの書き換え

`databaseUri`を以下のように書き換えます。

`<username>`と`<password>`を自身のものに書き換えてください。

また`@cluster0.pkkzx`は環境によって違うものになる可能性があるので、DB接続文字列の取得でコピーした値に書き換えてください。

mongodb+srv://形式はTLS/SSLを使用します。接続時に証明書エラーが発生する可能性があったので、自己署名証明書を許可するためのオプション（`tlsAllowInvalidCertificates`）を追加しています。

```diff
[database]
- databaseUri = mongodb://db:27017
+ databaseUri = mongodb+srv://<username>:<password>@cluster0.pkkzx.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0&tlsAllowInvalidCertificates=true

databaseName = catapult
maxWriterThreads = 8
maxDropBatchSize = 10
writeTimeout = 10m

[plugins]

catapult.mongo.plugins.accountlink = true
catapult.mongo.plugins.aggregate = true
catapult.mongo.plugins.lockhash = true
catapult.mongo.plugins.locksecret = true
catapult.mongo.plugins.metadata = true
catapult.mongo.plugins.mosaic = true
catapult.mongo.plugins.multisig = true
catapult.mongo.plugins.namespace = true
catapult.mongo.plugins.restrictionaccount = true
catapult.mongo.plugins.restrictionmosaic = true
catapult.mongo.plugins.transfer = true
```

### rest.jsonの書き換え

`url`と`name`を以下のように書き換えます。

こちらも先ほど同じように`<username>`と`<password>`を自身のものに書き換えてください。

`name`には`?tlsAllowInvalidCertificates=true`を追加します。

```diff
...
    "db": {
-		"url":"mongodb://db:27017/",
+		"url": "mongodb+srv://<username>:<password>@cluster0.pkkzx.mongodb.net/",
-		"name": "catapult",
+		"name": "catapult?tlsAllowInvalidCertificates=true",
		"pageSizeMin": 10,
		"pageSizeMax": 100,
		"pageSizeDefault": 20,
		"maxConnectionAttempts": 15,
		"baseRetryDelay": 750,
		"connectionPoolSize": 10,
		"connectionTimeout": 500
	},
 ...
```

## 各種スクリプトの修正

次に以下の各種スクリプトを修正します。

- startup/mongors.sh
- mongo/mongoDbPrepare.js

### mongors.sh

このスクリプトはデータベースの起動をチェックしているものです。
今のままではローカルデータベースを参照してしまうので、クラウドデータベースを参照するように修正します。

こちらも先ほど同じように`<username>`と`<password>`を自身のものに書き換えてください。

```diff
#!/bin/bash

sleep 1
set -e
state_filename=$1
shift

pwd

while true;
do
-   if mongosh --eval "db.runCommand( { serverStatus: 1 } )" db/local > /dev/null 2>&1; then
+   if mongosh "mongodb+srv://<username>:<password>@cluster0.pkkzx.mongodb.net/catapult" --eval "db.runCommand( { serverStatus: 1 } )" > /dev/null 2>&1; then
		break;
	fi
	echo "waiting for mongod start..."
	sleep 1
done

echo " [+] Preparing db5"
cd /mongo
-mongosh db/catapult < mongoDbPrepare.js
+mongosh "mongodb+srv://<username>:<password>@cluster0.pkkzx.mongodb.net/catapult" < mongoDbPrepare.js
echo " [.] (exit code: $?)"
cd -

echo " [+] db prepared, checking account indexes"
-mongosh --eval 'db.accounts.getIndexes()' db/catapult
+mongosh "mongodb+srv://<username>:<password>@cluster0.pkkzx.mongodb.net/catapult" --eval 'db.accounts.getIndexes()'


trap 'echo "exiting"; exit 0' SIGTERM
mkdir -p "$(dirname "${state_filename}")"
touch "${state_filename}"

sleep infinity & wait

```

### mongoDbPrepare.jsの修正

Mongo DB Atlasでは２箇所ほどエラーが出るので、コメントアウトをして回避します。

```diff
(function prepareDbConfiguration() {
-	db.setProfilingLevel(1, 100); // log slow queries
+	// db.setProfilingLevel(1, 100); // log slow queries
})();

(function prepareDbCollections() {
	function makeSparse(propertyName) {
		return { partialFilterExpression: { [propertyName]: { $exists: true } } };
	}

	function addCommonTransactionIndexes(collection) {
		collection.createIndex({ 'transaction.signerPublicKey': 1, _id: -1 });
		collection.createIndex({ 'transaction.recipientAddress': 1, _id: -1 });
		collection.createIndex({ 'meta.hash': 1 }, Object.assign({ unique: true }, makeSparse('meta.hash')));
		collection.createIndex({ 'meta.addresses': 1 }, makeSparse('meta.addresses'));
		collection.createIndex({ 'meta.aggregateId': 1 }, makeSparse('meta.aggregateId'));
	}

	function addTransactionCollection(collectionName) {
		db.createCollection(collectionName);
		addCommonTransactionIndexes(db[collectionName]);
		db[collectionName].createIndex({ 'meta.aggregateHash': 1 }, makeSparse('meta.aggregateHash'));
	}

	db.createCollection('blocks');
	db.blocks.createIndex({ 'block.signerPublicKey': 1 });
	db.blocks.createIndex({ 'block.timestamp': -1 }, { unique: true });
	db.blocks.createIndex({ 'block.height': -1 }, { unique: true });
	db.blocks.createIndex({ 'block.type': 1, 'block.height': -1 }, { unique: true });
	db.blocks.createIndex({ 'block.signerPublicKey': 1, 'block.height': -1 }, { unique: true });
	db.blocks.createIndex({ 'block.beneficiaryAddress': 1, 'block.height': -1 }, { unique: true });

	db.createCollection('finalizedBlocks');
	db.finalizedBlocks.createIndex({ 'block.finalizationEpoch': -1 });
	db.finalizedBlocks.createIndex({ 'block.height': -1 });
	db.finalizedBlocks.createIndex({ 'block.finalizationEpoch': -1, 'block.finalizationPoint': -1 }, { unique: true });

	db.createCollection('transactions');
	addCommonTransactionIndexes(db.transactions);
	db.transactions.createIndex({ 'meta.height': -1 });
	db.transactions.createIndex({ 'transaction.deadline': -1 });
	db.transactions.createIndex({ 'transaction.cosignatures.signerPublicKey': 1 }, makeSparse('transaction.cosignatures.signerPublicKey'));
	db.transactions.createIndex({ 'transaction.id': 1, 'transaction.type': 1 }, makeSparse('transaction.id'));

	db.createCollection('transactionStatements');
	db.transactionStatements.createIndex(
		{ 'statement.height': 1, 'statement.source.primaryId': 1, 'statement.source.secondaryId': 1 },
		{ unique: true });

	['addressResolutionStatements', 'mosaicResolutionStatements'].forEach(collectionName => {
		db.createCollection(collectionName);
		db[collectionName].createIndex({ 'statement.height': 1, 'statement.unresolved': 1 }, { unique: true });
	});

	db.createCollection('accounts');
	db.accounts.createIndex({ 'account.publicKey': 1 }); // cannot be unique because zeroed public keys are stored
	db.accounts.createIndex({ 'account.address': 1 }, { unique: true });

	['unconfirmedTransactions', 'partialTransactions'].forEach(addTransactionCollection);

	db.createCollection('transactionStatuses', { capped: true, size: 53000000, max: 500000 });
	db.transactionStatuses.createIndex({ 'status.hash': 1 }, { unique: true });
	db.transactionStatuses.createIndex({ 'status.deadline': -1 });
 
-	db.adminCommand( { setFeatureCompatibilityVersion: '6.0' } )
+	// db.adminCommand( { setFeatureCompatibilityVersion: '6.0' } )
})();

(function preparePluginDbCollections() {
	const pluginNames = [
		'LockHash', 'LockSecret', 'Metadata', 'Mosaic', 'Multisig', 'Namespace', 'RestrictionAccount', 'RestrictionMosaic'
	];
	pluginNames.forEach(pluginName => {
		print(`Loading ${pluginName}`);
		load(`mongo${pluginName}DbPrepare.js`);
	});
})();

(function printCollectionIndexes() {
	db.getCollectionNames().forEach(collectionName => {
		print(`===== ${collectionName} INDEXES =====`);
		db[collectionName].getIndexes().forEach(index => {
			printjson(index.key);
		});
	});
})();

```

## docker-compose.ymlの修正

最後に`docker-compose.yml`を修正します。

不要になったdbコンテナ部分をコメントアウトしておきます。

```yaml
version: '2'
services:
  # db:
  #   image: mongo:6.0.14
  #   user: '1000:1000'
  #   command: mongod --dbpath=/dbdata
  #   stop_signal: SIGINT
  #   ports:
  #     - 127.0.0.1:27017:27017
  #   volumes:
  #     - ./dbdata:/dbdata:rw
  #     - ./dbdata:/data:rw

  initiate:
    image: mongo:6.0.14
    user: '1000:1000'
    command: /bin/bash /startup/mongors.sh /real_data/startup/mongo-initialized
    volumes:
      - ./startup:/startup:ro
      - ./mongo:/mongo:ro
      - ./dbdata:/dbdata:rw
      - ./dbdata:/data:rw
      - ./data:/real_data
    # depends_on:
    #   - db

  client:
    image: 'symbolplatform/symbol-server:gcc-1.0.3.7'
    user: '1000:1000'
    logging:
      driver: json-file
      options:
        max-size: 25M
        max-file: '10'
    command: /bin/bash /startup/wait.sh /data/startup/mongo-initialized /startup/startServer.sh
    environment:
      - LD_LIBRARY_PATH=/usr/catapult/lib:/usr/catapult/deps
    stop_signal: SIGINT
    stop_grace_period: 300s
    ports:
      - 7900:7900
    working_dir: /
    volumes:
      - ./startup:/startup:ro
      - ./userconfig:/userconfig:ro
      - ./seed:/seed:ro
      - ./keys/cert:/certificates:ro
      - ./data:/data
      - ./logs:/logs
      - ./keys/voting:/votingkeys

  broker:
    image: 'symbolplatform/symbol-server:gcc-1.0.3.7'
    user: '1000:1000'
    cap_add:
      - SYS_PTRACE
    command: /bin/bash /startup/wait.sh /data/startup/datadir-initialized /startup/startBroker.sh
    environment:
      - LD_LIBRARY_PATH=/usr/catapult/lib:/usr/catapult/deps
    stop_signal: SIGINT
    stop_grace_period: 60s
    working_dir: /
    volumes:
      - ./startup:/startup:ro
      - ./userconfig:/userconfig:ro
      - ./keys/cert:/certificates:ro
      - ./data:/data
      - ./logs:/logs

  rest-api:
    image: 'symbolplatform/symbol-rest:2.4.4'
    user: '1000:1000'
    command: /bin/bash /startup/delayrestapi.sh
    environment:
      - HOME=/symbol-workdir
      - NODE_ENV=production
    working_dir: /symbol-workdir
    ports:
      - 3000:3000
    volumes:
      - ./startup:/startup:ro
      - ./userconfig:/userconfig:ro
      - ./keys/cert:/certificates:ro
      - ./data:/data
      - ./logs:/logs
      - ./rest-cache:/symbol-workdir
    networks:
      default:
        ipv4_address: 172.20.0.25

networks:
  default:
    name: catapult-node-network
    ipam:
      config:
        - subnet: 172.20.0.0/24
```

# 変更したファイル一覧

- userconfig/resources/config-database.properties
- userconfig/rest.json
- startup/mongors.sh
- mongo/mongoDbPrepare.js
- docker-compose.yml

# 起動と確認

それでは、ノードを起動してクラウドデータベースにデータが保存されるか確認してみましょう。

Mongo DB AtlasのDATABASEにはまだ何もない状態です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/934471/49d8ae3c-f515-9ff5-037d-1b449e360504.png)


## ノードの起動

以下のコマンドを実行してノードを起動します。

ログを確認したいのでデタッチオプション（-d）は付けずに起動します。

```bash
docker compose up
```

起動するとログが表示されます（長いので折りたたんであります）

<details><summary>ログ</summary>

```bash
Attaching to broker-1, client-1, initiate-1, rest-api-1
initiate-1  | /
initiate-1  |  [+] Preparing db5
initiate-1  | Warning: Could not access file: EACCES: permission denied, mkdir '/data/db/.mongodb'
initiate-1  | Current Mongosh Log ID:	6758de35db87ca1ebbc934dc
initiate-1  | Connecting to:		mongodb+srv://<credentials>@cluster0.pkkzx.mongodb.net/catapult?appName=mongosh+2.2.4
initiate-1  | Using MongoDB:		8.0.3
initiate-1  | Using Mongosh:		2.2.4
initiate-1  | 
initiate-1  | For mongosh info see: https://docs.mongodb.com/mongodb-shell/
initiate-1  | 
initiate-1  | 
initiate-1  | To help improve our products, anonymous usage data is collected and sent to MongoDB periodically (https://www.mongodb.com/legal/privacy-policy).
initiate-1  | You can opt-out by running the disableTelemetry() command.
initiate-1  | 
initiate-1  | 
initiate-1  | Error: Could not open history file.
initiate-1  | REPL session history will not be persisted.
initiate-1  | [1G[0J [1GAtlas atlas-13wiab-shard-0 [primary] catapult> ... ... 
initiate-1  | Atlas atlas-13wiab-shard-0 [primary] catapult> 
initiate-1  | Atlas atlas-13wiab-shard-0 [primary] catapult> ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... 
initiate-1  | Atlas atlas-13wiab-shard-0 [primary] catapult> 
initiate-1  | Atlas atlas-13wiab-shard-0 [primary] catapult> ... ... ... ... ... ... ... ... Loading LockHash
initiate-1  | Loading LockSecret
initiate-1  | Loading Metadata
initiate-1  | Loading Mosaic
initiate-1  | Loading Multisig
initiate-1  | Loading Namespace
initiate-1  | Loading RestrictionAccount
initiate-1  | Loading RestrictionMosaic
initiate-1  | 
initiate-1  | Atlas atlas-13wiab-shard-0 [primary] catapult> 
initiate-1  | Atlas atlas-13wiab-shard-0 [primary] catapult> ... ... ... ... ... ... ... ===== metadata INDEXES =====
initiate-1  | {
initiate-1  |   _id: 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'metadataEntry.compositeHash': 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'metadataEntry.sourceAddress': 1,
initiate-1  |   'metadataEntry.metadataType': 1,
initiate-1  |   'metadataEntry.scopedMetadataKey': 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'metadataEntry.targetAddress': 1,
initiate-1  |   'metadataEntry.metadataType': 1,
initiate-1  |   'metadataEntry.scopedMetadataKey': 1
initiate-1  | }
initiate-1  | ===== hashLocks INDEXES =====
initiate-1  | {
initiate-1  |   _id: 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'lock.hash': 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'lock.ownerAddress': 1
initiate-1  | }
initiate-1  | ===== partialTransactions INDEXES =====
initiate-1  | {
initiate-1  |   _id: 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'transaction.signerPublicKey': 1,
initiate-1  |   _id: -1
initiate-1  | }
initiate-1  | {
initiate-1  |   'transaction.recipientAddress': 1,
initiate-1  |   _id: -1
initiate-1  | }
initiate-1  | {
initiate-1  |   'meta.hash': 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'meta.addresses': 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'meta.aggregateId': 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'meta.aggregateHash': 1
initiate-1  | }
initiate-1  | ===== addressResolutionStatements INDEXES =====
initiate-1  | {
initiate-1  |   _id: 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'statement.height': 1,
initiate-1  |   'statement.unresolved': 1
initiate-1  | }
initiate-1  | ===== mosaicRestrictions INDEXES =====
initiate-1  | {
initiate-1  |   _id: 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'mosaicRestrictionEntry.compositeHash': 1
initiate-1  | }
initiate-1  | ===== secretLocks INDEXES =====
initiate-1  | {
initiate-1  |   _id: 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'lock.compositeHash': 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'lock.ownerAddress': 1
initiate-1  | }
initiate-1  | ===== mosaics INDEXES =====
initiate-1  | {
initiate-1  |   _id: 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'mosaic.id': 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'mosaic.ownerAddress': 1
initiate-1  | }
initiate-1  | ===== unconfirmedTransactions INDEXES =====
initiate-1  | {
initiate-1  |   _id: 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'transaction.signerPublicKey': 1,
initiate-1  |   _id: -1
initiate-1  | }
initiate-1  | {
initiate-1  |   'transaction.recipientAddress': 1,
initiate-1  |   _id: -1
initiate-1  | }
initiate-1  | {
initiate-1  |   'meta.hash': 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'meta.addresses': 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'meta.aggregateId': 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'meta.aggregateHash': 1
initiate-1  | }
initiate-1  | ===== transactionStatuses INDEXES =====
initiate-1  | {
initiate-1  |   _id: 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'status.hash': 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'status.deadline': -1
initiate-1  | }
initiate-1  | ===== accountRestrictions INDEXES =====
initiate-1  | {
initiate-1  |   _id: 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'accountRestrictions.address': 1
initiate-1  | }
initiate-1  | ===== transactionStatements INDEXES =====
initiate-1  | {
initiate-1  |   _id: 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'statement.height': 1,
initiate-1  |   'statement.source.primaryId': 1,
initiate-1  |   'statement.source.secondaryId': 1
initiate-1  | }
initiate-1  | ===== namespaces INDEXES =====
initiate-1  | {
initiate-1  |   _id: 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'namespace.level0': 1,
initiate-1  |   'meta.index': 1,
initiate-1  |   'namespace.depth': 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'meta.latest': -1,
initiate-1  |   'meta.index': 1,
initiate-1  |   'namespace.level0': 1,
initiate-1  |   'namespace.depth': 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'meta.latest': -1,
initiate-1  |   'namespace.level1': 1,
initiate-1  |   'namespace.depth': 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'meta.latest': -1,
initiate-1  |   'namespace.level2': 1,
initiate-1  |   'namespace.depth': 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'meta.latest': -1,
initiate-1  |   'namespace.ownerAddress': 1
initiate-1  | }
initiate-1  | ===== blocks INDEXES =====
initiate-1  | {
initiate-1  |   _id: 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'block.signerPublicKey': 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'block.timestamp': -1
initiate-1  | }
initiate-1  | {
initiate-1  |   'block.height': -1
initiate-1  | }
initiate-1  | {
initiate-1  |   'block.type': 1,
initiate-1  |   'block.height': -1
initiate-1  | }
initiate-1  | {
initiate-1  |   'block.signerPublicKey': 1,
initiate-1  |   'block.height': -1
initiate-1  | }
initiate-1  | {
initiate-1  |   'block.beneficiaryAddress': 1,
initiate-1  |   'block.height': -1
initiate-1  | }
initiate-1  | ===== mosaicResolutionStatements INDEXES =====
initiate-1  | {
initiate-1  |   _id: 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'statement.height': 1,
initiate-1  |   'statement.unresolved': 1
initiate-1  | }
initiate-1  | ===== transactions INDEXES =====
initiate-1  | {
initiate-1  |   _id: 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'transaction.signerPublicKey': 1,
initiate-1  |   _id: -1
initiate-1  | }
initiate-1  | {
initiate-1  |   'transaction.recipientAddress': 1,
initiate-1  |   _id: -1
initiate-1  | }
initiate-1  | {
initiate-1  |   'meta.hash': 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'meta.addresses': 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'meta.aggregateId': 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'meta.height': -1
initiate-1  | }
initiate-1  | {
initiate-1  |   'transaction.deadline': -1
initiate-1  | }
initiate-1  | {
initiate-1  |   'transaction.cosignatures.signerPublicKey': 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'transaction.id': 1,
initiate-1  |   'transaction.type': 1
initiate-1  | }
initiate-1  | ===== finalizedBlocks INDEXES =====
initiate-1  | {
initiate-1  |   _id: 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'block.finalizationEpoch': -1
initiate-1  | }
initiate-1  | {
initiate-1  |   'block.height': -1
initiate-1  | }
initiate-1  | {
initiate-1  |   'block.finalizationEpoch': -1,
initiate-1  |   'block.finalizationPoint': -1
initiate-1  | }
initiate-1  | ===== multisigs INDEXES =====
initiate-1  | {
initiate-1  |   _id: 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'multisig.accountAddress': 1
initiate-1  | }
initiate-1  | ===== accounts INDEXES =====
initiate-1  | {
initiate-1  |   _id: 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'account.publicKey': 1
initiate-1  | }
initiate-1  | {
initiate-1  |   'account.address': 1
initiate-1  | }
initiate-1  | 
initiate-1  | Atlas atlas-13wiab-shard-0 [primary] catapult>  [.] (exit code: 0)
initiate-1  | /
initiate-1  |  [+] db prepared, checking account indexes
initiate-1  | Warning: Could not access file: EACCES: permission denied, mkdir '/data/db/.mongodb'
initiate-1  | [
initiate-1  |   { v: 2, key: { _id: 1 }, name: '_id_' },
initiate-1  |   {
initiate-1  |     v: 2,
initiate-1  |     key: { 'account.publicKey': 1 },
initiate-1  |     name: 'account.publicKey_1'
initiate-1  |   },
initiate-1  |   {
initiate-1  |     v: 2,
initiate-1  |     key: { 'account.address': 1 },
initiate-1  |     name: 'account.address_1',
initiate-1  |     unique: true
initiate-1  |   }
initiate-1  | ]
client-1    | uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu)
client-1    | total 20K
client-1    | drwx------ 3 ubuntu ubuntu 4.0K Dec 10 05:58 .
client-1    | drwxr-xr-x 1 root   root   4.0K Dec 10 09:57 ..
client-1    | drwx------ 2 ubuntu ubuntu 4.0K Dec 10 05:58 00000
client-1    | -r-------- 1 ubuntu ubuntu    8 Dec 10 05:58 index.dat
client-1    | -r-------- 1 ubuntu ubuntu   48 Dec 10 05:58 proof.index.dat
client-1    | Copyright (c) Jaguar0625, gimre, BloodyRookie, Tech Bureau, Corp.
client-1    | catapult version: 1.0.3.7 (public)
client-1    | loading resources from "/userconfig/resources"
client-1    | loading configuration from "/userconfig/resources/config-inflation.properties"
client-1    | loading configuration from "/userconfig/resources/config-extensions-server.properties"
client-1    | loading configuration from "/userconfig/resources/config-user.properties"
client-1    | loading configuration from "/userconfig/resources/config-logging-server.properties"
client-1    | loading configuration from "/userconfig/resources/config-node.properties"
client-1    | loading configuration from "/userconfig/resources/config-network.properties"
client-1    | loading configuration from "/userconfig/resources/config-pt.properties"
client-1    | loading configuration from "/userconfig/resources/config-finalization.properties"
client-1    | loading configuration from "/userconfig/resources/config-task.properties"
client-1    | loading configuration from "/userconfig/resources/config-timesync.properties"
broker-1    | uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu)
broker-1    | total 36K
broker-1    | drwx------ 7 ubuntu ubuntu 4.0K Dec 11 00:35 .
broker-1    | drwxr-xr-x 1 root   root   4.0K Dec 10 09:57 ..
broker-1    | drwx------ 2 ubuntu ubuntu 4.0K Dec 11 00:35 00000
broker-1    | drwx------ 3 ubuntu ubuntu 4.0K Dec 11 00:35 importance
broker-1    | -rw------- 1 ubuntu ubuntu    8 Dec 11 00:35 index.dat
broker-1    | -rw------- 1 ubuntu ubuntu   48 Dec 11 00:35 proof.index.dat
broker-1    | ---------- 1 ubuntu ubuntu    0 Dec 11 00:35 server.lock
broker-1    | drwx------ 9 ubuntu ubuntu 4.0K Dec 11 00:35 spool
broker-1    | drwxr-xr-x 2 ubuntu ubuntu 4.0K Dec 11 00:35 startup
broker-1    | drwx------ 5 ubuntu ubuntu 4.0K Dec 11 00:35 statedb
broker-1    | Copyright (c) Jaguar0625, gimre, BloodyRookie, Tech Bureau, Corp.
broker-1    | catapult version: 1.0.3.7 (public)
broker-1    | loading resources from "/userconfig/resources"
broker-1    | loading configuration from "/userconfig/resources/config-inflation.properties"
broker-1    | loading configuration from "/userconfig/resources/config-extensions-broker.properties"
broker-1    | loading configuration from "/userconfig/resources/config-user.properties"
broker-1    | loading configuration from "/userconfig/resources/config-logging-broker.properties"
broker-1    | loading configuration from "/userconfig/resources/config-node.properties"
broker-1    | loading configuration from "/userconfig/resources/config-network.properties"
broker-1    | loading configuration from "/userconfig/resources/config-database.properties"
rest-api-1  |  [+] delaying rest API startup
broker-1    | loading configuration from "/userconfig/resources/config-messaging.properties"
rest-api-1  | 
rest-api-1  | > symbol-api-rest@2.4.4 start
rest-api-1  | > node src/index.js /userconfig/rest.json
rest-api-1  | 

```

</details>

## 解説

```bash
Attaching to broker-1, client-1, initiate-1, rest-api-1
...
initiate-1  | Connecting to:		mongodb+srv://<credentials>@cluster0.pkkzx.mongodb.net/catapult?appName=mongosh+2.2.4
...
```

initiateコンテナが先ほど設定したMongo DB Atlasへ接続しに行っているのがわかります。

```bash
initiate-1  | Atlas atlas-13wiab-shard-0 [primary] catapult> 
initiate-1  | Atlas atlas-13wiab-shard-0 [primary] catapult> ... ... ... ... ... ... ... ... Loading LockHash
...
initiate-1  | 
initiate-1  | Atlas atlas-13wiab-shard-0 [primary] catapult> 
initiate-1  | Atlas atlas-13wiab-shard-0 [primary] catapult> ... ... ... ... ... ... ... ===== metadata INDEXES =====
initiate-1  | {
initiate-1  |   _id: 1
initiate-1  | }
...
initiate-1  |   'metadataEntry.scopedMetadataKey': 1
initiate-1  | }
initiate-1  | ===== hashLocks INDEXES =====
initiate-1  | {
initiate-1  |   _id: 1
initiate-1  | }
...
initiate-1  | {
initiate-1  |   'multisig.accountAddress': 1
initiate-1  | }
initiate-1  | ===== accounts INDEXES =====
initiate-1  | {
...
initiate-1  | }
initiate-1  | 
initiate-1  | Atlas atlas-13wiab-shard-0 [primary] catapult>  [.] (exit code: 0)
initiate-1  | /
initiate-1  |  [+] db prepared, checking account indexes
initiate-1  | Warning: Could not access file: EACCES: permission denied, mkdir '/data/db/.mongodb'
initiate-1  | [
initiate-1  |   { v: 2, key: { _id: 1 }, name: '_id_' },
initiate-1  |   {
initiate-1  |     v: 2,
initiate-1  |     key: { 'account.publicKey': 1 },
initiate-1  |     name: 'account.publicKey_1'
initiate-1  |   },
initiate-1  |   {
initiate-1  |     v: 2,
initiate-1  |     key: { 'account.address': 1 },
initiate-1  |     name: 'account.address_1',
initiate-1  |     unique: true
initiate-1  |   }
initiate-1  | ]
client-1    | uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu)
client-1    | total 20K
client-1    | drwx------ 3 ubuntu ubuntu 4.0K Dec 10 05:58 .
client-1    | drwxr-xr-x 1 root   root   4.0K Dec 10 09:57 ..
client-1    | drwx------ 2 ubuntu ubuntu 4.0K Dec 10 05:58 00000
client-1    | -r-------- 1 ubuntu ubuntu    8 Dec 10 05:58 index.dat
client-1    | -r-------- 1 ubuntu ubuntu   48 Dec 10 05:58 proof.index.dat
client-1    | Copyright (c) Jaguar0625, gimre, BloodyRookie, Tech Bureau, Corp.
client-1    | catapult version: 1.0.3.7 (public)
client-1    | loading resources from "/userconfig/resources"
client-1    | loading configuration from "/userconfig/resources/config-inflation.properties"
...
broker-1    | uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu)
broker-1    | total 36K
broker-1    | drwx------ 7 ubuntu ubuntu 4.0K Dec 11 00:35 .
broker-1    | drwxr-xr-x 1 root   root   4.0K Dec 10 09:57 ..
broker-1    | drwx------ 2 ubuntu ubuntu 4.0K Dec 11 00:35 00000
broker-1    | drwx------ 3 ubuntu ubuntu 4.0K Dec 11 00:35 importance
broker-1    | -rw------- 1 ubuntu ubuntu    8 Dec 11 00:35 index.dat
broker-1    | -rw------- 1 ubuntu ubuntu   48 Dec 11 00:35 proof.index.dat
broker-1    | ---------- 1 ubuntu ubuntu    0 Dec 11 00:35 server.lock
broker-1    | drwx------ 9 ubuntu ubuntu 4.0K Dec 11 00:35 spool
broker-1    | drwxr-xr-x 2 ubuntu ubuntu 4.0K Dec 11 00:35 startup
broker-1    | drwx------ 5 ubuntu ubuntu 4.0K Dec 11 00:35 statedb
broker-1    | Copyright (c) Jaguar0625, gimre, BloodyRookie, Tech Bureau, Corp.
broker-1    | catapult version: 1.0.3.7 (public)
broker-1    | loading resources from "/userconfig/resources"
broker-1    | loading configuration from "/userconfig/resources/config-inflation.properties"
broker-1    | loading configuration from "/userconfig/resources/config-extensions-broker.properties"
broker-1    | loading configuration from "/userconfig/resources/config-user.properties"
broker-1    | loading configuration from "/userconfig/resources/config-logging-broker.properties"
broker-1    | loading configuration from "/userconfig/resources/config-node.properties"
broker-1    | loading configuration from "/userconfig/resources/config-network.properties"
broker-1    | loading configuration from "/userconfig/resources/config-database.properties"
rest-api-1  |  [+] delaying rest API startup
broker-1    | loading configuration from "/userconfig/resources/config-messaging.properties"
rest-api-1  | 
rest-api-1  | > symbol-api-rest@2.4.4 start
rest-api-1  | > node src/index.js /userconfig/rest.json
rest-api-1  | 

```

 その後、いろいろなログが吐かれますがエラーなどがなければBrokerコンテナやRestAPIコンテナが立ち上がります。

 また、Mongo DB AtlasのDATABASEを確認すると`catapult`データベースが作成されており、データが保存されていることがわかります。

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/934471/0c2c3871-9ddd-be59-4845-e42d316bc565.png)

:::note warn
無料枠を使っているのでノードの起動が確認できたら`Ctrl + C`でノードを停止しておいてください。
:::

:::note info
ノードを停止するとそのままでは再起動できない可能性があります。（起動時Brokerが落ちる）
その場合は、Mongo DB Atlas側のDBを削除した上で、python3 -m shoestring.wizardを使用してresetDataを実行してください。
全てのデータをクリアすれば起動できるはずです。
:::


# 最後に

一応、当初の目的である「Symbolのデータベースをクラウドデータベースに置き換え」は成功しました。

とはいえ、これはあくまで検証です。

今回は無料枠で512MBしかデータベースにデータを保存できないので、フルでデータを保存した場合どのような挙動になるのかは正直不明です（通信速度とか検索性能とかセキュリティとか...etc）
あと、ノートの再起動がうまくできない場合もあります（理由は不明だがBrokerが落ちる...）

データの肥大化に伴うクラウド利用に関してはまだまだ問題点も多いですが、ローカルで物理サーバー運用している人などにはちょっとだけ嬉しい情報になるのではないでしょうか？（そうでもない？）

興味のある方はぜひ挑戦してみてください。