一般的なMongoDBコマンドエラーを効果的にトラブルシューティングする

mongoshにおける一般的なMongoDBコマンドエラー(構文、認証、接続、書き込み、レプリカセットの問題)をトラブルシューティングします。

一般的なMongoDBコマンドエラーを効果的にトラブルシューティングする

MongoDBコマンドエラーは、落ち着いて障害が発生している場所を特定すれば、修正は簡単です。mongoshが入力内容の解析に失敗したのでしょうか?ユーザーにロールがないためサーバーがコマンドを拒否したのでしょうか?クライアントが間違ったデータベースに接続したのでしょうか?プライマリが利用不可なのでしょうか?これらはすべてシェルに赤いテキストで表示されるとしても、異なる問題です。

私はまず3つのチェックから始めるのが好きです:どのサーバーに接続しているか、どのデータベースを使用しているか、どのユーザーとして認証されているか。

db.getName()
db.hello()
db.runCommand({ connectionStatus: 1 })

これらのコマンドは、多くの無駄なデバッグを防ぎます。多くの「MongoDBコマンドエラー」は実際にはコンテキストの誤りです:間違ったデータベースから管理コマンドを実行する、間違ったauthSourceに対して認証する、プライマリに書き込むつもりがセカンダリに対してテストするなど。

MongoDBコマンドエラーのカテゴリを理解する

MongoDBコマンドエラーは一般的にいくつかの主要なタイプに分類できます:

  • 構文エラー:MongoDBシェルまたはドライバーが解析できない不正な形式のコマンド。
  • 権限エラー:必要なユーザー権限なしで操作を実行しようとする試み。
  • 運用エラー:コマンドの実行中に発生する問題。ネットワーク問題、リソース制限、データの不整合など。
  • 接続エラー:MongoDBサーバーへの接続確立に関する問題。

一般的な構文エラーとその解決策

構文エラーは多くの場合、最も簡単に修正できます。通常、タイプミス、文字の欠落、またはパラメータの誤った使用に起因します。MongoDBシェル(mongosh)は、これらの問題に対して有益なエラーメッセージを提供するのが一般的です。

1. 無効なフィールド名またはドキュメント構造

ドキュメントを挿入または更新する際に、誤ったフィールド名や無効なドキュメント構造を使用すると、エラーが発生する可能性があります。

エラー例:

db.users.insertOne({ "bad\u0000field": "Alice" })
// MongoServerError: The dotted field 'bad\u0000field' ... is not valid for storage

説明: MongoDBのフィールド名にはnull文字を含めることはできません。"email-address"のようなハイフンを含むフィールド名は許可されていますが、クエリ時に引用符で囲む必要があるため扱いにくい場合があります。本当の問題は、MongoDBが保存できない無効な文字または構造です。

解決策:

インポート、ユーザー入力、またはシリアル化コードによって生成されたフィールド名を注意深く確認してください。emailAddressemail_addressのような一貫した命名規則を選び、データがMongoDBに到達する前に検証してください。

> db.users.insertOne({ name: "Alice", age: 30, emailAddress: "[email protected]" })
{ acknowledged: true, insertedId: ObjectId('...') }

2. カンマの欠落または余分なカンマ

JavaScriptと同様に、MongoDBシェルコマンドはオブジェクトと配列内のカンマの正しい配置に影響を受けます。

エラー例:

db.products.insertOne({ name: "Laptop", price: 1200,, })
// SyntaxError: Unexpected token

解決策:

余分なカンマを削除してください。読みやすさのために一貫したフォーマットを確保してください。

> db.products.insertOne({ name: "Laptop", price: 1200 })
{ acknowledged: true, insertedId: ObjectId('...') }

3. 誤ったコマンド構文(例:findfindOne

誤ったコマンドを使用したり、引数を誤った順序で指定したりすることもエラーの原因となります。

例:

db.inventory.find({ item: "notebook" }, { qty: 1, size: 1, _id: 0 })
// このコマンドはfindとしては構文的に正しいですが、1つのドキュメントのみを取得するつもりだった場合:

解決策:

1つのドキュメントのみを取得する場合は、findOneを使用してください。findはカーソルを返しますが、findOneはドキュメント自体を返します。

> db.inventory.findOne({ item: "notebook" }, { qty: 1, size: 1, _id: 0 })
{
  qty: 20,
  size: { h: 14, w: 21, uom: "cm" },
  ... // プロジェクションで除外されず、特にプロジェクションで除外されていない場合の他のフィールド
}

一般的な権限エラー

権限エラーは通常、ユーザーが必要なロールや権限を持たない操作を実行しようとしたときに発生します。

1. コマンドを実行するための不十分な権限

このエラーメッセージは、権限の不足を明確に示しています。

エラー例:

> db.adminCommand({ listDatabases: 1 })
Error: listDatabases requires authentication

説明: listDatabasesは管理コマンドであり、認証と適切な権限が必要です。ユーザーは1つのアプリケーションデータベースに対して有効であっても、デプロイメント全体を検査することはできません。

解決策:

  • 適切な資格情報で認証する: 必要なロールを持つユーザーを使用してMongoDBに接続します。listDatabasesの場合、管理者として接続する必要があるかもしれません。
    mongosh "mongodb://<adminUser>:<adminPassword>@<host>:<port>/admin?authSource=admin"
    
    その後、コマンドを再試行します:
    db.adminCommand({ listDatabases: 1 })
    
  • ロールを付与する: データベース管理者である場合は、問題が発生しているユーザーに必要なロールを付与します。
    // 例:'admin'データベース上のユーザー'myUser'にreadAnyDatabaseロールを付与
    use admin
    db.grantRolesToUser("myUser", [ { role: "readAnyDatabase", db: "admin" } ])
    

2. 書き込み操作が拒否されました

コレクションまたはデータベースにドキュメントを挿入、更新、または削除する権限なしで試みる。

エラー例:

> db.myCollection.insertOne({ name: "Test" })
WriteError: Not enough privileges to execute on "myCollection" with operation "insert"

解決策:

  • ターゲットのデータベース/コレクションに対する書き込み権限を持つユーザーとして認証します。
  • ユーザーに書き込みロール(例:readWritedbOwner)を付与します。
  • プロンプトのデータベースを確認します。 appdbに対してreadWriteが付与されたユーザーは、コレクション名が同じでも、testに対して自動的に書き込みアクセス権を持つわけではありません。

認証とauthSourceの誤り

認証エラーは、MongoDBユーザーが特定の認証データベースに属しているため、混乱を招くように見えることがよくあります。多くのアプリケーションユーザーはアプリケーションデータベースに作成されます。管理ユーザーは多くの場合adminに作成されます。

次のようなメッセージが表示された場合:

MongoServerError: Authentication failed.

パスワードを変更する前に接続文字列を確認してください:

mongosh "mongodb://appUser:secret@db01:27017/appdb?authSource=appdb"

ユーザーがadminで作成された場合は、次を使用します:

mongosh "mongodb://adminUser:secret@db01:27017/appdb?authSource=admin"

ホストの後のデータベースパス(/appdb)は、シェルが開くデフォルトのデータベースです。authSourceは、MongoDBがユーザーアカウントを探す場所です。これらを混同することは、ローカルの開発データベースからセキュリティ保護されたサーバーに移行した後のログイン失敗の一般的な原因です。

現在認証されているユーザーは、次の方法で確認できます:

db.runCommand({ connectionStatus: 1 })

一般的な運用エラーとその解決策

運用エラーはより複雑で、多くの場合、MongoDBデプロイメントの状態、ネットワークの問題、またはリソースの制約に関連しています。

1. ネットワークタイムアウトまたは接続拒否

これらのエラーは、クライアントがMongoDBサーバーとの接続を確立または維持できなかったことを示します。

エラー例(クライアント側):

Error: connect ECONNREFUSED 127.0.0.1:27017

説明: クライアントが指定されたホストとポートに接続しようとしましたが、接続が拒否されました。これは、MongoDBサーバーが実行されていない、別のポートで実行されている、またはファイアウォールが接続をブロックしている可能性があります。

解決策:

  • MongoDBサーバーのステータスを確認する: mongodプロセスがサーバー上で実行されていることを確認します。
    • Linuxの場合:sudo systemctl status mongod または sudo service mongod status
    • macOS(Homebrew使用時):brew services list
    • Windowsの場合:サービスアプリケーションを確認します。
  • MongoDBの設定を確認する: mongodが正しいIPアドレスとポート(デフォルトは27017)でリッスンするように設定されていることを確認します。mongod.confファイルを確認します。
  • ファイアウォールルール: サーバーレベルまたはネットワークのファイアウォールがMongoDBポートへのトラフィックをブロックしていないことを確認します。
  • 正しい接続文字列: ホストとポートのタイプミスについて接続文字列を再確認します。

レプリカセットの場合、ドライバーが期待するときにレプリカセット名を含めます:

mongosh "mongodb://db01:27017,db02:27017,db03:27017/appdb?replicaSet=rs0"

DNSが関係している場合は、クライアントマシンから正確なホスト名をテストします:

nc -vz db01.example.com 27017

データベースサーバー自体からの接続が成功しても、アプリサーバー、CIランナー、または踏み台ホストが到達できることを証明するわけではありません。

2. ドキュメントサイズ制限を超過

MongoDBドキュメントには最大BSONサイズ制限(現在16MB)があります。

エラー例:

> db.largeDocs.insertOne({ data: "... very large string ..." })
Error: BSONObj size: 17000000 bytes is too large, max 16777216 bytes

解決策:

  • 大きなドキュメントを分割する: 大きなドキュメントをより小さな関連ドキュメントに分割します。参照(例:ObjectId)を使用してリンクします。
  • GridFSを使用する: ドキュメントサイズ制限を超える大きなバイナリファイル(画像や動画など)を保存するには、MongoDBのGridFS仕様を使用します。
  • 無制限の配列を避ける: すべてのイベントを埋め込んだ1つのユーザードキュメントのように、際限なく成長するドキュメントは、最終的に遅くなったり制限に達したりします。大量の子レコードは独自のコレクションに保存します。

3. 書き込み保証(write concern)エラー

書き込み保証は、MongoDBが書き込み操作に要求する確認応答の保証を指定します。これらの保証がタイムアウト内に満たされない場合、書き込み保証エラーが発生します。

例:

// 特定の書き込み保証を使用した書き込み操作の例
db.myCollection.insertOne({ name: "Item" }, { writeConcern: { w: "majority", wtimeout: 1000 } });

潜在的なエラー:

WriteConcernError: waiting for replication timed out

説明: 指定されたwtimeout(1000ms)内に必要なノード数(この場合はmajority)が書き込みを確認応答しなかったため、書き込み操作が失敗しました。

解決策:

  • レプリカセットの健全性を調査する: MongoDBレプリカセットの健全性とステータスを確認します。ノードが遅延していますか?ノード間にネットワークの問題がありますか?
  • wtimeoutを増やす: 一時的なネットワーク遅延やレプリケーションの遅延が原因である場合は、wtimeout値を増やすことを検討してもよいですが、これは根本的な問題を隠す可能性があるため、慎重に行う必要があります。
  • 書き込み保証を確認する: 書き込み保証レベル(w)がアプリケーションのニーズに適切であることを確認します。w: 1(デフォルト)はプライマリからの確認応答のみを必要とし、タイムアウトの問題が発生しにくいですが、耐久性の保証は低くなります。

4. プライマリではない、または読み取り設定(read preference)エラー

書き込みはレプリカセットのプライマリに送信する必要があります。シェルまたはアプリケーションがセカンダリに接続されており、書き込みを試みると、次のようなエラーが表示される場合があります:

MongoServerError: not primary

接続先を確認します:

db.hello()

isWritablePrimaryがfalseの場合、適切なレプリカセットURIを介して接続するか、プライマリに書き込みをルーティングします。読み取りの場合、アプリケーションにとってセカンダリ読み取りが許容されるかどうかを判断します。セカンダリ読み取りは古い可能性があるため、そのユースケースで古い読み取りが安全でない限り、エラーを黙らせるためだけに有効にしないでください。

5. 重複キーエラー

重複キーエラーは通常、一意インデックスが機能していることを意味します。

E11000 duplicate key error collection: app.users index: email_1 dup key

一意性ルールが実際に間違っていない限り、一意インデックスを削除してこれを「修正」しないでください。最初にインデックスを特定します:

db.users.getIndexes()

次に、アプリケーションが既存のドキュメントを更新するか、重複を拒否するか、アップサートを使用するかを決定します:

db.users.updateOne(
  { email: "[email protected]" },
  { $set: { lastLoginAt: new Date() } },
  { upsert: true }
)

アップサートでは、フィルターが目的の一意キーと一致していることを確認してください。緩いフィルターでも、別の一意フィールドに重複が作成され、失敗する可能性があります。

6. クエリ演算子の誤り

一部のエラーは、更新またはクエリ演算子を間違った場所で使用することから発生します。

これは置換スタイルの更新では間違っています:

db.users.updateOne(
  { email: "[email protected]" },
  { lastLoginAt: new Date() }
)

MongoDBはその2番目のドキュメントを置換ドキュメントとして扱います。1つのフィールドを変更するつもりだった場合は、$setを使用します:

db.users.updateOne(
  { email: "[email protected]" },
  { $set: { lastLoginAt: new Date() } }
)

この区別は重要です。置換更新では、置換ドキュメントに含まれていないフィールドが削除される可能性があるためです。

実用的なデバッグルーチン

コマンドが失敗した場合、正確なエラーを取得し、次の質問に順番に答えてください:

  1. mongoshは期待されるホストに接続されていますか?

    db.hello().me
    
  2. 期待されるデータベースを使用していますか?

    db.getName()
    
  3. 認証されていますか?また、どのロールで?

    db.runCommand({ connectionStatus: 1 })
    
  4. これは解析エラー、サーバーエラー、それとも書き込み保証エラーですか?

    解析エラーは通常、コマンドがサーバーに到達する前に表示されます。権限エラー、重複キーエラー、プライマリではないエラー、検証エラー、書き込み保証エラーは、MongoDBがコマンドを評価した後に発生します。

  5. 最小限のドキュメントで同じコマンドは機能しますか?

    小さな挿入は機能するが実際の挿入は失敗する場合、ドキュメントの形状、サイズ、フィールド名、スキーマ検証、および一意インデックスを調査します。

  6. 障害はノードに依存しますか?

    レプリカセットおよびシャーディングされたクラスターの問題は、特定のノードまたは特定のルーターを介してのみ表示される場合があります。シャーディングされたクラスターの場合、アプリケーショントラフィックは通常、シャードメンバーに直接ではなく、mongosを介して送信する必要があります。

推測せずにログを読む

MongoDBログは、シェルの出力よりもサーバー側の理由をより明確に説明することがよくあります。Linuxパッケージインストールでは、サービスのログを確認します:

journalctl -u mongod --since "30 minutes ago"

または、mongod.confから設定されたMongoDBログパスを確認します。

失敗したコマンドと同じタイムスタンプの周辺のメッセージを探します。有用な手がかりには、認証失敗、接続リセット、低速操作、レプリケーション状態の変更、ディスクフルエラー、スキーマ検証失敗などがあります。

低速または失敗するクエリの場合、explain()は推測よりも有用です:

db.orders.find({ customerId: 123, status: "open" }).explain("executionStats")

クエリが少数の結果を返すために大量のドキュメントをスキャンする場合、コマンドは構文的には正しいが、運用コストが高い可能性があります。これはシェルの問題ではなく、インデックスまたはクエリ形状の問題です。

コマンドエラーを防ぐためのベストプラクティス

  • mongoshとその機能を使用する: 最新のMongoDBシェルが提供するタブ補完、コマンド履歴、明確なエラーメッセージを活用します。
  • データモデルを理解する: 特大のドキュメントや非効率的なクエリなどの問題を回避するために、スキーマとドキュメント構造を慎重に設計します。
  • 適切な認証と認可を実装する: ロールに必要な最小限の権限を持つユーザーを定義します。
  • デプロイメントを監視する: MongoDBログ、パフォーマンスメトリクス、レプリカセットのステータスを定期的にチェックして、潜在的な問題を積極的に特定します。
  • コマンドをテストする: 複雑なコマンドや変更を本番環境にデプロイする前に、開発環境またはステージング環境で徹底的にテストします。
  • 計画されたスケジュールでMongoDBを更新する: 新しいバージョンにはバグ修正や動作変更が含まれる場合があります。本番環境にロールアウトする前に、リリースノートを読み、アップグレードをテストします。

ほとんどのMongoDBコマンドエラーは、シェルの構文、認証コンテキスト、認可、トポロジー、データ形状の問題を分離すれば管理可能になります。まず、自分がどこに接続していて誰であるかを確認することから始めます。次に、正確なエラーメッセージが次のレイヤーを指し示すようにし、ランダムにコマンドを書き換えないようにします。