Open23

mongo

nicopinnicopin

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
nicopinnicopin

mongo_init.js

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

上記の例でsampleに一つもコレクションがない場合(データが存在しない場合)mongosh -u root -pとかでログインし、show dbsでもsampleは表示されない。

また、データが存在しないということはDBも存在しないものとして扱われるようで、DBが存在しない場合DBに紐づけたユーザーも存在しない扱いになるのでDatagripなどからこの状態では接続できない(少なくとも1件データが必要)

nicopinnicopin

mongo_init

初期化処理はDokcerのログで確認しにくい。
手動でコマンド実行もできるのでmongoコンテナに入って以下のように実行できる

 mongosh YOUR_MONGO_DB_NAME /docker-entrypoint-initdb.d/mongo_init.js 
nicopinnicopin
nicopinnicopin

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()

nicopinnicopin

#mongosh

login
mongosh -u USER_NAME -p
show_databases
show dbs
nicopinnicopin

injection

RDBMSにあるプレースホルダーがないらしいので、慎重にクエリを組む必要がある。基本的にはjsを利用しない方向でビルトインの機能を使った方が良さそう。
キーワード検索
インデックスが貼られてる文字列については、$textで検索できる

$text 検索は path フィールド内のテキストを検索対象とします。このため、 $text 検索を実行する際には、どのフィールドを検索対象とするかを再度指定する必要はありません。
ただし、テキストインデックスは複数のフィールドに対して作成することも可能で、その場合には $text 検索は指定したすべてのフィールドを対象に検索を行います。このとき、各フィールドに対する検索の重み付けを設定することも可能です。
Beanie odmを利用している場合

Document.py
class Category(TimeStampBaseModel, Document):
	name: str
	path: Indexed(str, unique=True, index_type=pymongo.TEXT)
Repository.py
query = StimulusCategory.find(Text(keyword))
nicopinnicopin

text index

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

nicopinnicopin

aggregate

一つのクエリの中で、複数のステージで処理を実行できる。
https://www.mongodb.com/docs/manual/reference/operator/aggregation-pipeline/#stages
$matchステージで絞り込んで$facetで絞り込んだ内容に対して何かしら処理をして、$addFieldsで最終の結果を加工するなど。
上手く使えばfindAndCountなども実現できる。

nicopinnicopin

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使う

nicopinnicopin

予約語

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

nicopinnicopin

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]などにすれば良い

nicopinnicopin

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で含める操作と、除外操作は両立できないのでまず含めてから、除外するようんステップを分割する必要がある。