エラーの内容
db.myCollection.drop() または db.myCollection.dropIndex('some_index') を呼び出すと、MongoDBが次のエラーを返します:
MongoError: ns not found
新しいドライバーやmongoshでは、表示が若干異なります:
MongoServerError: ns not found
MongoError: ns not found (codeName: 'NamespaceNotFound', code: 26)
操作は即座に停止し、部分的なクリーンアップもフォールバックも行われません。
原因
削除しようとした名前空間が存在しないことが原因です。それだけです。SQLの DROP TABLE IF EXISTS のように、明示的に指定しない限り、MongoDBは存在しない名前空間をサイレントにスキップしません。
このエラーが発生しやすい状況をいくつか挙げます:
- マイグレーションやシードスクリプトを2回実行した場合 — 2回目の実行で、1回目がすでに削除したものを削除しようとする。
- セットアップが途中で失敗したため作成されなかったコレクションを、テストのティアダウンで削除しようとした場合。
- コレクション名のタイポ — 例えば
usersの代わりにuserと指定した場合。 - 前のスクリプトがすでに削除したインデックスに対して
dropIndex()を呼び出した場合。 - CIで初期化直後のデータベースからコレクションを削除しようとした場合。
修正方法
1. MongoDBシェル — 削除前に確認する
最も安全なmongoshのアプローチ:操作前にコレクションの存在を確認します。
if (db.getCollectionNames().includes('myCollection')) {
db.myCollection.drop();
print('Dropped.');
} else {
print('Collection does not exist, skipping.');
}
インデックスも同様に、事前に現在のインデックスリストを取得します:
const indexes = db.myCollection.getIndexes().map(i => i.name);
if (indexes.includes('email_1')) {
db.myCollection.dropIndex('email_1');
}
2. Node.js — MongoDBネイティブドライバー
事前チェックではなく、エラーコード 26(NamespaceNotFound)をキャッチして無視します。それ以外のエラーは引き続きスローされます。
const { MongoClient } = require('mongodb');
async function safeDrop(db, collectionName) {
try {
await db.collection(collectionName).drop();
console.log(`Dropped ${collectionName}`);
} catch (err) {
if (err.code === 26) {
console.log(`${collectionName} doesn't exist, skipping drop.`);
} else {
throw err;
}
}
}
const client = new MongoClient(process.env.MONGO_URI);
await client.connect();
const db = client.db('mydb');
await safeDrop(db, 'oldCollection');
dropIndex の場合は、2つのコードを処理する必要があります:コレクション自体が存在しない場合の 26 と、コレクションは存在するがインデックスがすでに削除されている場合の 27(IndexNotFound)です:
async function safeDropIndex(collection, indexName) {
try {
await collection.dropIndex(indexName);
} catch (err) {
if (err.code === 26 || err.code === 27) {
return; // すでに削除済み
}
throw err;
}
}
3. Mongoose
Mongooseはネイティブドライバーをラップしているため、同じエラーコードが適用されます。catchブロックでは、エラーをメッセージ文字列として返す古いドライバーバージョンにも対応しています:
async function safeDrop(modelOrCollectionName) {
try {
await mongoose.connection.dropCollection(modelOrCollectionName);
} catch (err) {
if (err.code === 26 || err.message === 'ns not found') {
return;
}
throw err;
}
}
// テストのティアダウン時
afterEach(async () => {
await safeDrop('users');
});
4. PyMongo(Python)
PyMongoの drop_collection() は、ほとんどのバージョンで ns not found エラーを内部的に握りつぶします。drop_index() はそうではないため、自分でキャッチする必要があります。
from pymongo import MongoClient
from pymongo.errors import OperationFailure
client = MongoClient("mongodb://localhost:27017")
db = client["mydb"]
# drop_collectionは通常安全だが、明示的に処理することを推奨
try:
db.drop_collection("old_collection")
except OperationFailure as e:
if e.code == 26:
print("Collection doesn't exist, skipping.")
else:
raise
# drop_indexは手動でガードが必要
try:
db.my_collection.drop_index("email_1")
except OperationFailure as e:
if e.code in (26, 27): # 26 = コレクション不在, 27 = インデックス不在
pass
else:
raise
5. 冪等性のあるマイグレーション
CIパイプラインではロールバックと再試行でマイグレーションが再実行されることがよくあります。深夜2時に再実行が失敗しないよう、すべての破壊的な処理をラップしましょう:
// migration_001_drop_old_index.js
module.exports = {
async up(db) {
const col = db.collection('orders');
try {
await col.dropIndex('status_1_createdAt_1');
} catch (err) {
if (err.code !== 26 && err.code !== 27) throw err;
}
},
async down(db) {
await db.collection('orders').createIndex(
{ status: 1, createdAt: 1 },
{ name: 'status_1_createdAt_1' }
);
}
};
修正の確認
スクリプトを2回連続で実行してください。2回目にエラーが発生しなければ、ラッパーが正しく機能しています。
mongoshでコレクションとインデックスの状態を直接確認します:
// 現在のデータベースのコレクションを一覧表示
db.getCollectionNames()
// 対象が削除されたことを確認
db.getCollectionNames().includes('oldCollection') // → false
// コレクションの残存インデックスを確認
db.myCollection.getIndexes()
予防策
- メッセージ文字列ではなくエラーコードで判定する。 MongoDBはバージョンによってエラーテキストの表現を変更することがありますが、数値コード(NamespaceNotFoundの26、IndexNotFoundの27)は変わりません。
- 最初からすべてのマイグレーションを冪等にする。 dropのラップはわずか2行のコストです。CIの再実行失敗をデバッグするコストははるかに大きくなります。
- テストでティアダウンが正常に完了したと思い込まない。 前のテストのクリーンアップが成功したと期待するのではなく、
beforeEachを使ってデータを新たにセットアップしましょう。 - コレクション名のスペルを確認する。
usersとuserは全く同じエラーを引き起こし、意外と見落としやすいミスです。

