mongo

docker-compose.yml
sub-db:
container_name: mongodb
image: mongo:6.0
restart: always
ports:
- "27017:27017"
volumes:
- ./mongo_data:/data/db
- ./mongodb/mongo_init.js:/docker-entrypoint-initdb.d/mongo_init.js
environment:
MONGO_INITDB_ROOT_USERNAME: ${MONGO_INITDB_ROOT_USERNAME}
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_INITDB_ROOT_PASSWORD}
env_file:
- .env
command: mongod --auth

mongo_init.js
db = db.getSiblingDB("sample"); //dbを実際に作成するわけではないが、ユーザー追加の前提としてDBが存在する必要があるため
db.createUser(
{
user: 'user',
pwd: 'password',
roles: [{role: 'readWrite', db: 'sample'}],
},
);

上記の例でsampleに一つもコレクションがない場合(データが存在しない場合)mongosh -u root -p
とかでログインし、show dbs
でもsampleは表示されない。
また、データが存在しないということはDBも存在しないものとして扱われるようで、DBが存在しない場合DBに紐づけたユーザーも存在しない扱いになるのでDatagripなどからこの状態では接続できない(少なくとも1件データが必要)

mongo_init
初期化処理はDokcerのログで確認しにくい。
手動でコマンド実行もできるのでmongoコンテナに入って以下のように実行できる
mongosh YOUR_MONGO_DB_NAME /docker-entrypoint-initdb.d/mongo_init.js

ODM - Beanie

startup
アプリケーションの初期化の段階で、利用するデータベースとモデルを一気に読み込んでしまう。
@retry(stop=stop_after_delay(30), wait=wait_fixed(1))
async def connect_to_mongodb():
database = get_mongo_database_client().fun_relations
try:
await init_beanie(database=database, document_models=[Prompt])
except OperationFailure:
print("Connection failed. Retrying...")
raise
@app.on_event('startup')
async def startup_event():
await connect_to_mongodb()
@app.on_event('shutdown')
async def shutdown_event():
await get_mongo_database_client().close()

tree architecture

#mongosh
mongosh -u USER_NAME -p
show dbs

beanie

injection
RDBMSにあるプレースホルダーがないらしいので、慎重にクエリを組む必要がある。基本的にはjsを利用しない方向でビルトインの機能を使った方が良さそう。
キーワード検索
インデックスが貼られてる文字列については、$textで検索できる
$text 検索は path フィールド内のテキストを検索対象とします。このため、 $text 検索を実行する際には、どのフィールドを検索対象とするかを再度指定する必要はありません。
ただし、テキストインデックスは複数のフィールドに対して作成することも可能で、その場合には $text 検索は指定したすべてのフィールドを対象に検索を行います。このとき、各フィールドに対する検索の重み付けを設定することも可能です。
Beanie odmを利用している場合
class Category(TimeStampBaseModel, Document):
name: str
path: Indexed(str, unique=True, index_type=pymongo.TEXT)
query = StimulusCategory.find(Text(keyword))

text index
MongoDBのテキスト検索は、ドキュメント内のフィールドが含む単語に対して動作します。各単語は、白文字(スペース、タブ、改行など)によって区切られるため、'box-music'は 'box'と'music'という2つの別々の単語として認識されます

aggregate
一つのクエリの中で、複数のステージで処理を実行できる。$match
ステージで絞り込んで$facet
で絞り込んだ内容に対して何かしら処理をして、$addFields
で最終の結果を加工するなど。
上手く使えばfindAndCountなども実現できる。

performance
ドキュメント取得とカウント2回クエリする(get)とaggregateを利用してカウントと取得を一つにまとめる(get_with_count)の簡易比較
1回目 get->Total time: 0.002029895782470703 seconds
2回目 get->Total time: 0.0012104511260986328 seconds (キャッシュ利用)
1回目 get_with_count->Total time: 0.0013267993927001953 seconds
2回目 get_with_count->Total time: 0.0009324550628662109 seconds(キャッシュ利用)
100回平均:get -> 0.0010334134101867675
100回平均:get_with_count -> 0.0006110191345214844
aggregateのほうが早そうだし、ネットワークは完全にはコントロールできないのでaggregate使う

$unwind
配列フィールドの展開ステージ

$project

予約語
language フィールドは、MongoDBのテキスト検索の文書レベルでの言語オーバーライドに使用される特別なフィールド名です。MongoDBでは、$text クエリを実行するときに、このフィールドを探して、テキストのトークン化やストップワードの処理に使用する言語をオーバーライドすることができます。そのため、language というフィールド名を使用すると、MongoDBが特定の挙動を期待します。

beanieにurlオブジェクトを渡してしまうとencoderで処理できないのでE ValueError: [TypeError("'pydantic_core._pydantic_core.Url' object is not iterable"), TypeError('vars() argument must have __dict__ attribute')]
が起きる。
Annotated[str, HttpUrl]などにすれば良い

Inclusion and exclusion should be separated
E pymongo.errors.OperationFailure: Invalid $project :: caused by :: Cannot do exclusion on field documents in inclusion projection, full error: {'ok': 0.0, 'errmsg': 'Invalid $project :: caused by :: Cannot do exclusion on field documents in inclusion projection', 'code': 31254, 'codeName': 'Location31254'}
$project stageで含める操作と、除外操作は両立できないのでまず含めてから、除外するようんステップを分割する必要がある。

elemMatch
配列フィールドに指定した条件に合致する要素が存在するかどうかと言う条件