ApsaraDB for MongoDBを使ってみる【事前準備編】

## overview

Alibaba CloudとMongoDBを触る機会ができたので、試しにいろいろいじってみる。

まずは事前準備としてローカルでMongoDBを触る。

### Mongo DBとは

> MongoDBはRDBMSではなく、いわゆるNoSQLと呼ばれるデータベースに分類されるものである。RDBMSのようにレコードをテーブルに格納するのではなく、「ドキュメント」と呼ばれる構造的データをJSONライクな形式で表現し、そのドキュメントの集合を「コレクション」として管理する（このデータの物理的な格納はBSONと呼ばれるJSONのバイナリ版といえる形式で行われる）。
> 

引用：[Wikipedia](https://ja.wikipedia.org/wiki/MongoDB)

データ構造は以下。

![https://storage.googleapis.com/zenn-user-upload/592d71f0a368-20240314.png](https://storage.googleapis.com/zenn-user-upload/592d71f0a368-20240314.png)

引用：[公式](https://www.mongodb.com/docs/manual/introduction/)

> MongoDB のストレージ構造は、従来のリレーショナル データベースとは異なります。 MongoDB のデータは次のレベルで編成されます。
ドキュメント: ドキュメントは、MongoDB のデータの基本単位です。ドキュメントは BSON キーと値のペアで構成され、リレーショナル データベースの行に相当します。
コレクション: コレクションには複数のドキュメントが含まれます。リレーショナル データベースのテーブルに相当します。
データベース: データベースには複数のコレクションが含まれます。リレーショナル データベースに相当します。 ApsaraDB for MongoDB インスタンスに複数のデータベースを作成できます。
> 

引用：[MongoDBのストレージ構造](https://www.alibabacloud.com/help/ja/mongodb/product-overview/what-is-apsaradb-for-mongodb)

- 多くのプログラミング言語のネイティブデータ型に対応
- RDBのような多重JOINに陥りにくい
- 動的スキーマにより振る舞いを変えやすい
などの利点がある

スケールアウトしやすく、ログ蓄積や軽い読み取りを大量に捌くケースに使う。
ちなみにRedisよりも使われてるらしい。意外。

Redis vs Mongoに関してはAWSが示してくれているので参考になる。
[https://aws.amazon.com/jp/compare/the-difference-between-redis-and-mongodb/](https://aws.amazon.com/jp/compare/the-difference-between-redis-and-mongodb/)

> 水平スケーリングにより、MongoDB は大量のデータを効率的に処理できます。シャーディングを使用して、複数のリージョンとノードにデータを分散します。クロスシャーディング操作では、複数のシャードにわたってクエリと更新を行うことができます。
Redis は MongoDB ほどのスケーラビリティを提供していません。Redis は、プライマリオペレーションにデフォルトでシャードを 1 つだけ使用します。シャーディングはハッシュベースで手動で管理する必要があるため、管理が複雑になります。Redis にはクロスシャード機能もありません。
> 

> MongoDB と Redis はどちらも、レプリケーションによる可用性をサポートしています。ただし、MongoDB はレプリカセットを使用することでより高い水準の可用性をサポートします。自動フェイルオーバーメカニズムをサポートしています。
これとは対照的に、Redis はデフォルトでは自動フェイルオーバーを提供しません。特にレプリカが別のデータセンターにある場合は、管理者ユーザーが手動フェイルオーバーを開始します。自動フェイルオーバーが必要な場合は、Redis Sentinel という別のコンポーネントをセットアップして構成する必要があります。
> 

> MongoDB は、マルチドキュメントの、原子性、一貫性、独立性、耐久性のある (ACID) トランザクションをサポートしています。そのため、複数の操作でデータの一貫性を保つことができます。MongoDB は、セッション内ですべての変更をコミットまたはロールバックします。これにより、ACID への準拠が確保されます。
逆に、Redis には ACID サポートが組み込まれていません。ただし、MULTI コマンドを使用すると、複数のコマンドを 1 つのアトミック操作にグループ化できます。しかし、これだけでは解決策にはなりません。また、Redis はトランザクション内でロールバック機能をネイティブにサポートしていないため、アプリケーションコード内にロールバック機能を実装する必要があります
> 

> MongoDB は、複雑な空間計算やデータ分析機能を実行する場合でも、高い柔軟性でクエリを実行できます。MongoDB は JSON に似た構文をサポートする MongoDB クエリー言語 (MQL) を使用しており、高度なクエリを簡略化します。
これとは対照的に、Redis は複雑なクエリや検索機能ではなく、key-value の高速アクセス操作に最適化されています。
> 

どうやら同じNoSQLでもより可用性があって、データ保持もある程度担保しつつ、リッチな操作ができるのがMongo
アプリ側でデータ抽出を頑張れる＆データが揮発性高いのであればRedis
というイメージ。

ただ、AlibabaCloudのApsaraDB for Redisでは以下の点が違うみたい。

> Redis は、プライマリオペレーションにデフォルトでシャードを 1 つだけ使用します。シャーディングはハッシュベースで手動で管理する必要があるため、管理が複雑になります。Redis にはクロスシャード機能もありません。
> 

クラスターバージョンでは、自動的にハッシュベースのシャーディングが行われ、各キーはハッシュスロットに割り当てられる。なので、ユーザーは手動でシャーディングを管理する手間を省ける。

> Redis はデフォルトでは自動フェイルオーバーを提供しません。特にレプリカが別のデータセンターにある場合は、管理者ユーザーが手動フェイルオーバーを開始します。
> 

master-replicaの構成になり、masterが障害発生する場合は、自動フェールオーバーする。

だから大きな懸念点はなさそう！

## やってみる

まずはローカルでPythonからMongo DBに接続するAPIをつくる。

### アプリケーション

使用技術スタック

- Docker
- Python
- FastAPI
- MongoDB
- pip

ファイル構成

![https://storage.googleapis.com/zenn-user-upload/8c7c8932c120-20240317.png](https://storage.googleapis.com/zenn-user-upload/8c7c8932c120-20240317.png)

DockerでFastAPIを利用。
[公式サンプル](https://fastapi.tiangolo.com/ja/deployment/docker/#fastapi-docker)をベースに用意。

```docker
FROM python:3.10.13

WORKDIR /code

COPY ./app/requirements.txt /code/requirements.txt

RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt

COPY ./app /code/app

```

docker-composeを利用し、webコンテナとMongoDBコンテナを立てる

```docker
services:
  web:
    build: .
    ports:
      - '8000:8000'
    volumes:
      - ./app:/code/app
    command: uvicorn main:app --host 0.0.0.0 --reload
    working_dir: /code/app
  mongo:
    image: mongo
    container_name: mongo
    restart: always
    ports:
      - 27017:27017

```

requirements.txtを用意

```
fastapi>=0.68.0,<0.69.0
pydantic>=1.8.0,<2.0.0
uvicorn>=0.15.0,<0.16.0
pymongo

```

main.pyを用意

```python
from fastapi import FastAPI, Body
from mongo import MongoManager

app = FastAPI()
mongo_manager = MongoManager()

@app.get("/")
def get_all():
    all = mongo_manager.get_all()
    return all

@app.get("/name/{name}")
def get_item(name: str):
    return mongo_manager.get_by_name(name)

@app.post("/name")
def post_name(name: str = Body(...)):
    mongo_manager.add_one(name)
    return "ok"

@app.delete("/")
def delete_all():
    mongo_manager.delete_all()
    return "ok"

```

mongo.pyを用意

```python
from pymongo import MongoClient

class MongoManager(object):

    def __init__(self):
        self.client = MongoClient('mongodb://mongo')
        self.db = self.client['test']

    def get_all(self):
        """全データ取得"""
        return list(self.db.test.find({}, {'_id': 0}))

    def get_by_name(self, name: str):
        """データ取得"""
        return list(self.db.test.find({"dir": {
            "name": name
        }}, {'_id': 0}))

    def add_one(self, name: str):
        """データ挿入"""
        d = {"name": name}
        data = {
            'title': 'names',
            'dir': d
        }
        return self.db.test.insert_one(data)

    def delete_all(self):
        """全データ削除"""
        self.db.test.delete_many({})

```

### ローカルで起動する

`docker-compose up`でコンテナを立ちあげると下記ログが出る。

```
[+] Running 2/0
 ✔ Container python-mongo-web-1    Created                                                           0.0s
 ✔ Container python-mongo-mongo-1  Recreated                                                         0.0s
Attaching to mongo, python-mongo-web-1
mongo               | {"t":{"$date":"2024-03-17T07:53:14.978+00:00"},"s":"I",  "c":"CONTROL",  "id":23285,   "ctx":"main","msg":"Automatically disabling TLS 1.0, to force-enable TLS 1.0 specify --sslDisabledProtocols 'none'"}
mongo               | {"t":{"$date":"2024-03-17T07:53:14.978+00:00"},"s":"I",  "c":"NETWORK",  "id":4915701, "ctx":"main","msg":"Initialized wire specification","attr":{"spec":{"incomingExternalClient":{"minWireVersion":0,"maxWireVersion":21},"incomingInternalClient":{"minWireVersion":0,"maxWireVersion":21},"outgoing":{"minWireVersion":6,"maxWireVersion":21},"isInternalClient":true}}}
mongo               | {"t":{"$date":"2024-03-17T07:53:14.979+00:00"},"s":"I",  "c":"NETWORK",  "id":4648601, "ctx":"main","msg":"Implicit TCP FastOpen unavailable. If TCP FastOpen is required, set tcpFastOpenServer, tcpFastOpenClient, and tcpFastOpenQueueSize."}
mongo               | {"t":{"$date":"2024-03-17T07:53:14.981+00:00"},"s":"I",  "c":"REPL",     "id":5123008, "ctx":"main","msg":"Successfully registered PrimaryOnlyService","attr":{"service":"TenantMigrationDonorService","namespace":"config.tenantMigrationDonors"}}
mongo               | {"t":{"$date":"2024-03-17T07:53:14.981+00:00"},"s":"I",  "c":"REPL",     "id":5123008, "ctx":"main","msg":"Successfully registered PrimaryOnlyService","attr":{"service":"TenantMigrationRecipientService","namespace":"config.tenantMigrationRecipients"}}
mongo               | {"t":{"$date":"2024-03-17T07:53:14.981+00:00"},"s":"I",  "c":"CONTROL",  "id":5945603, "ctx":"main","msg":"Multi threading initialized"}
mongo               | {"t":{"$date":"2024-03-17T07:53:14.981+00:00"},"s":"I",  "c":"TENANT_M", "id":7091600, "ctx":"main","msg":"Starting TenantMigrationAccessBlockerRegistry"}
mongo               | {"t":{"$date":"2024-03-17T07:53:14.982+00:00"},"s":"I",  "c":"CONTROL",  "id":4615611, "ctx":"initandlisten","msg":"MongoDB starting","attr":{"pid":1,"port":27017,"dbPath":"/data/db","architecture":"64-bit","host":"9e7f96999d20"}}
mongo               | {"t":{"$date":"2024-03-17T07:53:14.982+00:00"},"s":"I",  "c":"CONTROL",  "id":23403,   "ctx":"initandlisten","msg":"Build Info","attr":{"buildInfo":{"version":"7.0.6","gitVersion":"66cdc1f28172cb33ff68263050d73d4ade73b9a4","openSSLVersion":"OpenSSL 3.0.2 15 Mar 2022","modules":[],"allocator":"tcmalloc","environment":{"distmod":"ubuntu2204","distarch":"aarch64","target_arch":"aarch64"}}}}
mongo               | {"t":{"$date":"2024-03-17T07:53:14.982+00:00"},"s":"I",  "c":"CONTROL",  "id":51765,   "ctx":"initandlisten","msg":"Operating System","attr":{"os":{"name":"Ubuntu","version":"22.04"}}}
mongo               | {"t":{"$date":"2024-03-17T07:53:14.982+00:00"},"s":"I",  "c":"CONTROL",  "id":21951,   "ctx":"initandlisten","msg":"Options set by command line","attr":{"options":{"net":{"bindIp":"*"}}}}
mongo               | {"t":{"$date":"2024-03-17T07:53:14.985+00:00"},"s":"I",  "c":"STORAGE",  "id":22270,   "ctx":"initandlisten","msg":"Storage engine to use detected by data files","attr":{"dbpath":"/data/db","storageEngine":"wiredTiger"}}
mongo               | {"t":{"$date":"2024-03-17T07:53:14.985+00:00"},"s":"I",  "c":"STORAGE",  "id":22297,   "ctx":"initandlisten","msg":"Using the XFS filesystem is strongly recommended with the WiredTiger storage engine. See <http://dochub.mongodb.org/core/prodnotes-filesystem","tags":["startupWarnings>"]}
mongo               | {"t":{"$date":"2024-03-17T07:53:14.985+00:00"},"s":"I",  "c":"STORAGE",  "id":22315,   "ctx":"initandlisten","msg":"Opening WiredTiger","attr":{"config":"create,cache_size=3413M,session_max=33000,eviction=(threads_min=4,threads_max=4),config_base=false,statistics=(fast),log=(enabled=true,remove=true,path=journal,compressor=snappy),builtin_extension_config=(zstd=(compression_level=6)),file_manager=(close_idle_time=600,close_scan_interval=10,close_handle_minimum=2000),statistics_log=(wait=0),json_output=(error,message),verbose=[recovery_progress:1,checkpoint_progress:1,compact_progress:1,backup:0,checkpoint:0,compact:0,evict:0,history_store:0,recovery:0,rts:0,salvage:0,tiered:0,timestamp:0,transaction:0,verify:0,log:0],"}}
python-mongo-web-1  | INFO:     Will watch for changes in these directories: ['/code/app']
python-mongo-web-1  | INFO:     Uvicorn running on <http://0.0.0.0:8000> (Press CTRL+C to quit)
python-mongo-web-1  | INFO:     Started reloader process [1] using statreload
python-mongo-web-1  | INFO:     Started server process [8]
python-mongo-web-1  | INFO:     Waiting for application startup.
python-mongo-web-1  | INFO:     Application startup complete.
mongo               | {"t":{"$date":"2024-03-17T07:53:15.936+00:00"},"s":"I",  "c":"STORAGE",  "id":4795906, "ctx":"initandlisten","msg":"WiredTiger opened","attr":{"durationMillis":951}}
mongo               | {"t":{"$date":"2024-03-17T07:53:15.936+00:00"},"s":"I",  "c":"RECOVERY", "id":23987,   "ctx":"initandlisten","msg":"WiredTiger recoveryTimestamp","attr":{"recoveryTimestamp":{"$timestamp":{"t":0,"i":0}}}}
mongo               | {"t":{"$date":"2024-03-17T07:53:15.942+00:00"},"s":"W",  "c":"CONTROL",  "id":22120,   "ctx":"initandlisten","msg":"Access control is not enabled for the database. Read and write access to data and configuration is unrestricted","tags":["startupWarnings"]}
mongo               | {"t":{"$date":"2024-03-17T07:53:15.943+00:00"},"s":"W",  "c":"CONTROL",  "id":5123300, "ctx":"initandlisten","msg":"vm.max_map_count is too low","attr":{"currentValue":262144,"recommendedMinimum":1677720,"maxConns":838860},"tags":["startupWarnings"]}
mongo               | {"t":{"$date":"2024-03-17T07:53:15.945+00:00"},"s":"I",  "c":"NETWORK",  "id":4915702, "ctx":"initandlisten","msg":"Updated wire specification","attr":{"oldSpec":{"incomingExternalClient":{"minWireVersion":0,"maxWireVersion":21},"incomingInternalClient":{"minWireVersion":0,"maxWireVersion":21},"outgoing":{"minWireVersion":6,"maxWireVersion":21},"isInternalClient":true},"newSpec":{"incomingExternalClient":{"minWireVersion":0,"maxWireVersion":21},"incomingInternalClient":{"minWireVersion":21,"maxWireVersion":21},"outgoing":{"minWireVersion":21,"maxWireVersion":21},"isInternalClient":true}}}
mongo               | {"t":{"$date":"2024-03-17T07:53:15.945+00:00"},"s":"I",  "c":"REPL",     "id":5853300, "ctx":"initandlisten","msg":"current featureCompatibilityVersion value","attr":{"featureCompatibilityVersion":"7.0","context":"startup"}}
mongo               | {"t":{"$date":"2024-03-17T07:53:15.945+00:00"},"s":"I",  "c":"STORAGE",  "id":5071100, "ctx":"initandlisten","msg":"Clearing temp directory"}
mongo               | {"t":{"$date":"2024-03-17T07:53:15.946+00:00"},"s":"I",  "c":"CONTROL",  "id":6608200, "ctx":"initandlisten","msg":"Initializing cluster server parameters from disk"}
mongo               | {"t":{"$date":"2024-03-17T07:53:15.946+00:00"},"s":"I",  "c":"CONTROL",  "id":20536,   "ctx":"initandlisten","msg":"Flow Control is enabled on this deployment"}
mongo               | {"t":{"$date":"2024-03-17T07:53:15.947+00:00"},"s":"I",  "c":"FTDC",     "id":20625,   "ctx":"initandlisten","msg":"Initializing full-time diagnostic data capture","attr":{"dataDirectory":"/data/db/diagnostic.data"}}
mongo               | {"t":{"$date":"2024-03-17T07:53:15.949+00:00"},"s":"I",  "c":"REPL",     "id":6015317, "ctx":"initandlisten","msg":"Setting new configuration state","attr":{"newState":"ConfigReplicationDisabled","oldState":"ConfigPreStart"}}
mongo               | {"t":{"$date":"2024-03-17T07:53:15.949+00:00"},"s":"I",  "c":"STORAGE",  "id":22262,   "ctx":"initandlisten","msg":"Timestamp monitor starting"}
mongo               | {"t":{"$date":"2024-03-17T07:53:15.951+00:00"},"s":"I",  "c":"NETWORK",  "id":23015,   "ctx":"listener","msg":"Listening on","attr":{"address":"/tmp/mongodb-27017.sock"}}
mongo               | {"t":{"$date":"2024-03-17T07:53:15.951+00:00"},"s":"I",  "c":"NETWORK",  "id":23015,   "ctx":"listener","msg":"Listening on","attr":{"address":"0.0.0.0"}}
mongo               | {"t":{"$date":"2024-03-17T07:53:15.951+00:00"},"s":"I",  "c":"NETWORK",  "id":23016,   "ctx":"listener","msg":"Waiting for connections","attr":{"port":27017,"ssl":"off"}}
mongo               | {"t":{"$date":"2024-03-17T07:53:15.951+00:00"},"s":"I",  "c":"CONTROL",  "id":8423403, "ctx":"initandlisten","msg":"mongod startup complete","attr":{"Summary of time elapsed":{"Startup from clean shutdown?":true,"Statistics":{"Transport layer setup":"0 ms","Run initial syncer crash recovery":"0 ms","Create storage engine lock file in the data directory":"0 ms","Get metadata describing storage engine":"0 ms","Validate options in metadata against current startup options":"0 ms","Create storage engine":"951 ms","Write current PID to file":"0 ms","Initialize FCV before rebuilding indexes":"2 ms","Drop abandoned idents and get back indexes that need to be rebuilt or builds that need to be restarted":"0 ms","Rebuild indexes for collections":"0 ms","Load cluster parameters from disk for a standalone":"0 ms","Build user and roles graph":"0 ms","Set up the background thread pool responsible for waiting for opTimes to be majority committed":"0 ms","Initialize information needed to make a mongod instance shard aware":"0 ms","Start up the replication coordinator":"1 ms","Start transport layer":"0 ms","_initAndListen total elapsed time":"969 ms"}}}}
mongo               | {"t":{"$date":"2024-03-17T07:53:25.248+00:00"},"s":"I",  "c":"NETWORK",  "id":22943,   "ctx":"listener","msg":"Connection accepted","attr":{"remote":"172.18.0.3:43728","uuid":{"uuid":{"$uuid":"d9da7bf8-5fd7-4663-9834-4e7e51ea0fce"}},"connectionId":1,"connectionCount":1}}
mongo               | {"t":{"$date":"2024-03-17T07:53:25.251+00:00"},"s":"I",  "c":"NETWORK",  "id":51800,   "ctx":"conn1","msg":"client metadata","attr":{"remote":"172.18.0.3:43728","client":"conn1","negotiatedCompressors":[],"doc":{"driver":{"name":"PyMongo","version":"4.6.2"},"os":{"type":"Linux","name":"Linux","architecture":"aarch64","version":"5.15.49-linuxkit-pr"},"platform":"CPython 3.9.18.final.0"}}}
mongo               | {"t":{"$date":"2024-03-17T07:53:25.264+00:00"},"s":"I",  "c":"NETWORK",  "id":22943,   "ctx":"listener","msg":"Connection accepted","attr":{"remote":"172.18.0.3:43742","uuid":{"uuid":{"$uuid":"812c9429-30d5-4e56-9c7c-cee9790633eb"}},"connectionId":2,"connectionCount":2}}
mongo               | {"t":{"$date":"2024-03-17T07:53:25.265+00:00"},"s":"I",  "c":"NETWORK",  "id":51800,   "ctx":"conn2","msg":"client metadata","attr":{"remote":"172.18.0.3:43742","client":"conn2","negotiatedCompressors":[],"doc":{"driver":{"name":"PyMongo","version":"4.6.2"},"os":{"type":"Linux","name":"Linux","architecture":"aarch64","version":"5.15.49-linuxkit-pr"},"platform":"CPython 3.9.18.final.0"}}}

```

無事立ち上げ成功！だけどMongoのログ長い。。。
実運用ではログレベル変更する設定を入れた方がよさそう。

[https://www.mongodb.com/docs/manual/reference/log-messages/#configure-log-verbosity-levels](https://www.mongodb.com/docs/manual/reference/log-messages/#configure-log-verbosity-levels)

![https://storage.googleapis.com/zenn-user-upload/8b7f878482b8-20240317.png](https://storage.googleapis.com/zenn-user-upload/8b7f878482b8-20240317.png)

### APIを叩く

postmanで各エンドポイントを叩いてみる。

- データ投入
    
    ![https://storage.googleapis.com/zenn-user-upload/2fef3e876489-20240317.png](https://storage.googleapis.com/zenn-user-upload/2fef3e876489-20240317.png)
    
- 全データ取得
    
    ![https://storage.googleapis.com/zenn-user-upload/f9a67fe76c07-20240317.png](https://storage.googleapis.com/zenn-user-upload/f9a67fe76c07-20240317.png)
    
- name指定でデータ取得
    
    ![https://storage.googleapis.com/zenn-user-upload/6d72183302b1-20240317.png](https://storage.googleapis.com/zenn-user-upload/6d72183302b1-20240317.png)
    
- 全データ消去のち全データ取得

↓
    
    ![https://storage.googleapis.com/zenn-user-upload/68dccd979a97-20240317.png](https://storage.googleapis.com/zenn-user-upload/68dccd979a97-20240317.png)
    
    ![https://storage.googleapis.com/zenn-user-upload/f802b6f158da-20240317.png](https://storage.googleapis.com/zenn-user-upload/f802b6f158da-20240317.png)
    

できた〜
とりあえず手元で動かせたので、次はMongo DBをAlibabaのをつかってみる。

つづきはこちら！　[ApsaraDB for MongoDBを使ってみる【AlibabaCloud 接続編】](https://qiita.com/maipippi/items/df62822fa10becde2145)