何が起きたか
マルチドキュメントトランザクションのサポートを実装してアプリをローカルで実行したところ、以下のエラーが発生しました:
MongoServerError: Transaction numbers are only allowed on a replica set member or mongos
9割のケースはローカル開発環境で発生します。MongoDBがスタンドアロンのmongodとして実行されている場合です——パッケージマネージャーでインストールしたり、設定ファイルなしで起動した場合のデフォルト設定です。トランザクションにはレプリカセットまたはシャードクラスター(mongos)が必要です。スタンドアロンモードでは単純にサポートされていません。
根本原因の確認
何かを触る前に、インスタンスが実際にどのモードで動作しているか確認しましょう:
mongosh --eval "db.adminCommand({ isMaster: 1 })"
出力に"setName"フィールドがない?スタンドアロンノードで動作しています。それが原因です。
{
"ismaster": true,
"maxBsonObjectSize": 16777216,
...
// "setName"フィールドなし = スタンドアロンモード
}
レプリカセットメンバーは見た目が異なります——常にセット名を持ちます:
{
"setName": "rs0",
"ismaster": true,
...
}
注意: MongoDB 5.0以降では、isMasterは非推奨です。代わりにdb.adminCommand({ hello: 1 })を使用してください——出力構造は同じで、今後はこちらが推奨コマンドです。
修正方法:スタンドアロンをシングルノードレプリカセットに変換する
ローカル開発に3ノードは不要です。シングルノードレプリカセットで、ほぼオーバーヘッドなしに完全なトランザクションサポートが使えます。セットアップに応じて3つの方法があります。
方法1 — mongod.confを編集する(推奨)
設定ファイルを見つけます——Linuxでは通常/etc/mongod.conf、HomebrewのmacOSでは/usr/local/etc/mongod.confです。レプリケーションブロックを追加します:
# /etc/mongod.conf
replication:
replSetName: "rs0"
サービスを再起動します:
# Linux (systemd)
sudo systemctl restart mongod
# macOS (Homebrew)
brew services restart mongodb-community
次にレプリカセットの初期化を実行します——これは一度だけ行う手順です:
mongosh --eval "rs.initiate()"
以下のように表示されるはずです:
{ "ok": 1 }
方法2 — コマンドラインフラグ(簡易動作確認)
設定ファイルなしでmongodを手動起動する場合は、フラグを直接渡します:
mongod --replSet rs0 --dbpath /data/db
別のターミナルを開いて、一度だけセットを初期化します:
mongosh --eval "rs.initiate({ _id: 'rs0', members: [{ _id: 0, host: 'localhost:27017' }] })"
方法3 — Docker Compose
コンテナの注意点は、rs.initiate()をMongoDBが正常起動した後に実行する必要があることです。initコンテナを使えばこれをきれいに処理できます:
services:
mongo:
image: mongo:7
command: ["--replSet", "rs0", "--bind_ip_all"]
ports:
- "27017:27017"
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 5
mongo-init:
image: mongo:7
depends_on:
mongo:
condition: service_healthy
restart: "no"
entrypoint: >
mongosh --host mongo:27017 --eval
"rs.initiate({ _id: 'rs0', members: [{ _id: 0, host: 'mongo:27017' }] })"
docker compose up -d
修正の確認
レプリカセットのステータスを確認します:
mongosh --eval "rs.status()"
membersの配列に"stateStr" : "PRIMARY"が表示されることを確認します。次にトランザクションを直接実行して、エンドツーエンドで動作することを確認します:
// mongoshでの簡易動作確認
const session = db.getMongo().startSession();
session.startTransaction();
try {
session.getDatabase('test').orders.insertOne({ item: 'test' }, { session });
session.commitTransaction();
print('Transaction committed OK');
} catch (e) {
session.abortTransaction();
print('Transaction failed:', e.message);
} finally {
session.endSession();
}
Transaction committed OKと表示されれば完了です。
接続文字列の更新
レプリカセットモードに切り替えたら、接続文字列にレプリカセット名を追加してください。これを省略すると、レプリカセットに接続していても一部のドライバーがトランザクションサポートを有効にしない場合があります——見落としがちな落とし穴です。
# 変更前
mongodb://localhost:27017/mydb
# 変更後
mongodb://localhost:27017/mydb?replicaSet=rs0
MongooseのNode.js:
await mongoose.connect('mongodb://localhost:27017/mydb?replicaSet=rs0');
PyMongoのPython:
from pymongo import MongoClient
client = MongoClient('mongodb://localhost:27017/mydb?replicaSet=rs0')
本番環境での対応
本番環境でこのエラーが発生した場合、デプロイがスタンドアロンMongoDBで動作していることを意味します——自動フェイルオーバーなし、データ冗長性なし。トランザクションに関係なく、これは修正が必要です。適切な3ノードレプリカセットは、あらゆる本番ワークロードのベースラインです。ほとんどのマネージドサービス(Atlas、DigitalOceanのMongoDB等)はデフォルトでレプリカセットをプロビジョニングするため、これは実質的にセルフホスト環境の問題です。MongoDBレプリカセットデプロイガイドに完全なセットアップ手順が記載されています。
得られた教訓
- MongoDBのトランザクションはバージョン4.0からレプリカセットを必須としています——これはハードなアーキテクチャ上の制約であり、設定で切り替えられるものではありません。
- 開発環境でシングルノードレプリカセットを実行してもコストはゼロで、コードがステージングに上がった時のこのような予期せぬ問題を防ぐことができます。
- この変更後はローカルの接続文字列に必ず
?replicaSet=rs0を追加してください——トランザクション検出に関するドライバーの紛らわしい動作を回避できます。 - 素の
mongodを使ったCIでは、Docker Compose initコンテナパターンが、レースコンディションなしに一度だけrs.initiate()を呼び出す最も信頼性の高い方法です。

