Эффективное устранение распространенных ошибок команд MongoDB
Устранение распространенных ошибок команд MongoDB в mongosh, включая синтаксические, аутентификационные, соединения, записи и проблемы с реплика-сетом.
Эффективное устранение распространенных ошибок команд MongoDB
Ошибки команд MongoDB легче исправить, если замедлиться и определить, где произошел сбой. Не удалось ли mongosh разобрать введенные данные? Отклонил ли сервер команду из-за отсутствия роли у пользователя? Подключился ли клиент к неправильной базе данных? Недоступен ли первичный узел? Это разные проблемы, даже если все они отображаются красным текстом в оболочке.
Я предпочитаю начинать с трех проверок: к какому серверу я подключен, какую базу данных использую и под каким пользователем аутентифицирован?
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 не могут содержать нулевой символ. Имена полей с дефисами, такие как "email-address", разрешены, хотя их может быть неудобно запрашивать, поскольку их нужно заключать в кавычки. Настоящая проблема — недопустимый символ или структура, которую MongoDB не может сохранить.
Решение:
Внимательно проверяйте имена полей, сгенерированные импортами, пользовательским вводом или кодом сериализации. Выберите согласованное соглашение об именах, например emailAddress или email_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. Неправильный синтаксис команды (например, find vs findOne)
Использование неправильной команды или предоставление аргументов в неправильном порядке также может привести к ошибкам.
Пример:
db.inventory.find({ item: "notebook" }, { qty: 1, size: 1, _id: 0 })
// Эта команда синтаксически верна для find, но если вы намеревались найти только один документ:
Решение:
Если вы намереваетесь получить только один документ, используйте 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 — это административная команда, требующая аутентификации и соответствующих привилегий. Пользователь может быть действителен для одной базы данных приложения, но все равно не может просматривать весь развернутый экземпляр.
Решение:
- Аутентифицируйтесь с соответствующими учетными данными: Подключитесь к MongoDB, используя пользователя с необходимыми ролями. Для
listDatabasesможет потребоваться подключиться как администратор.
Затем повторите команду:mongosh "mongodb://<adminUser>:<adminPassword>@<host>:<port>/admin?authSource=admin"db.adminCommand({ listDatabases: 1 }) - Предоставьте роли: Если вы администратор базы данных, предоставьте необходимые роли пользователю, испытывающему проблему.
// Пример: Предоставление роли readAnyDatabase пользователю 'myUser' в базе данных 'admin' 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"
Решение:
- Аутентифицируйтесь как пользователь с правами на запись для целевой базы данных/коллекции.
- Предоставьте роли на запись (например,
readWrite,dbOwner) пользователю. - Проверьте базу данных в приглашении. Пользователь, которому предоставлен
readWriteнаappdb, не имеет автоматического доступа на запись к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: Проверьте приложение "Службы".
- На Linux:
- Проверьте конфигурацию 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 (в настоящее время 16 МБ).
Пример ошибки:
> db.largeDocs.insertOne({ data: "... очень большая строка ..." })
Error: BSONObj size: 17000000 bytes is too large, max 16777216 bytes
Решение:
- Разделите большие документы: Разбейте большой документ на несколько меньших связанных документов. Используйте ссылки (например,
ObjectId) для их связи. - Используйте GridFS: Для хранения больших двоичных файлов (например, изображений или видео), превышающих лимит размера документа, используйте спецификацию GridFS MongoDB.
- Избегайте неограниченных массивов: Документ, который постоянно растет, например, один документ пользователя со всеми событиями, встроенными навсегда, со временем станет медленным или достигнет лимитов. Храните дочерние записи с большим объемом в собственной коллекции.
3. Ошибки гарантии записи (Write Concern)
Гарантии записи определяют требования к подтверждению, необходимые MongoDB для операций записи. Если эти гарантии не выполняются в течение тайм-аута, возникает ошибка гарантии записи.
Пример:
// Пример операции записи с определенной гарантией записи
db.myCollection.insertOne({ name: "Item" }, { writeConcern: { w: "majority", wtimeout: 1000 } });
Потенциальная ошибка:
WriteConcernError: waiting for replication timed out
Объяснение: Операция записи не удалась, потому что требуемое количество узлов (в данном случае majority) не подтвердило запись в течение указанного wtimeout (1000 мс).
Решение:
- Исследуйте состояние набора реплик: Проверьте работоспособность и статус вашего набора реплик MongoDB. Отстают ли узлы? Есть ли сетевые проблемы между узлами?
- Увеличьте
wtimeout: Если причиной являются временные сетевые задержки или задержки репликации, вы можете рассмотреть возможность увеличения значенияwtimeout, но это следует делать с осторожностью, так как это может маскировать основные проблемы. - Пересмотрите гарантию записи: Убедитесь, что уровень гарантии записи (
w) соответствует потребностям вашего приложения.w: 1(по умолчанию) требует подтверждения только от первичного узла, что менее подвержено проблемам с тайм-аутом, но обеспечивает меньшую гарантию долговечности.
4. Ошибки "Не первичный узел" или предпочтения чтения
Записи должны направляться на первичный узел в наборе реплик. Если ваша оболочка или приложение подключены к вторичному узлу и вы пытаетесь выполнить запись, вы можете увидеть ошибку, подобную:
MongoServerError: not primary
Проверьте, куда вы подключены:
db.hello()
Если isWritablePrimary равно false, подключитесь через правильный URI набора реплик или направляйте записи на первичный узел. Для чтения решите, приемлемо ли чтение со вторичных узлов для вашего приложения. Чтение со вторичных узлов может быть устаревшим, поэтому не включайте их только для того, чтобы заглушить ошибку, если устаревшие чтения небезопасны для этого случая использования.
5. Ошибки дублирования ключа
Ошибки дублирования ключа обычно означают, что уникальный индекс выполняет свою работу.
E11000 duplicate key error collection: app.users index: email_1 dup key
Не "исправляйте" это удалением уникального индекса, если только правило уникальности на самом деле не является неверным. Сначала определите индекс:
db.users.getIndexes()
Затем решите, должно ли приложение обновить существующий документ, отклонить дубликат или использовать upsert:
db.users.updateOne(
{ email: "[email protected]" },
{ $set: { lastLoginAt: new Date() } },
{ upsert: true }
)
При использовании upsert убедитесь, что фильтр соответствует уникальному ключу, который вас волнует. Нечеткий фильтр все равно может создать дубликаты по другому уникальному полю и привести к сбою.
6. Ошибки операторов запросов
Некоторые ошибки возникают из-за использования операторов обновления или запроса в неправильном месте.
Это неправильно для обновлений в стиле замены:
db.users.updateOne(
{ email: "[email protected]" },
{ lastLoginAt: new Date() }
)
MongoDB обрабатывает этот второй документ как документ замены. Если вы хотели изменить одно поле, используйте $set:
db.users.updateOne(
{ email: "[email protected]" },
{ $set: { lastLoginAt: new Date() } }
)
Это различие важно, потому что обновления замены могут удалять поля, которые не включены в документ замены.
Практическая процедура отладки
Когда команда не выполняется, зафиксируйте точную ошибку, а затем ответьте на эти вопросы по порядку:
Подключен ли
mongoshк ожидаемому хосту?db.hello().meИспользую ли я ожидаемую базу данных?
db.getName()Аутентифицирован ли я и с какими ролями?
db.runCommand({ connectionStatus: 1 })Это ошибка разбора, ошибка сервера или ошибка гарантии записи?
Ошибки разбора обычно появляются до того, как команда достигает сервера. Ошибки разрешений, дублирования ключа, "не первичный узел", проверки и гарантии записи поступают от MongoDB после того, как он оценивает команду.
Работает ли та же команда с минимальным документом?
Если небольшая вставка работает, а реальная вставка не удается, проверьте форму документа, размер, имена полей, проверку схемы и уникальные индексы.
Зависит ли сбой от узла?
Проблемы с набором реплик и шардированным кластером могут проявляться только на определенных узлах или через определенные маршрутизаторы. Для шардированных кластеров трафик приложения обычно должен проходить через
mongos, а не напрямую к членам шарда.
Чтение журналов без угадывания
Журналы MongoDB часто объясняют причину на стороне сервера более четко, чем вывод оболочки. В установках Linux из пакетов проверьте журналы службы:
journalctl -u mongod --since "30 minutes ago"
или настроенный путь к журналу MongoDB из mongod.conf.
Ищите сообщения с той же временной меткой, что и неудачная команда. Полезные подсказки включают сбои аутентификации, сбросы соединений, медленные операции, изменения состояния репликации, ошибки заполнения диска и сбои проверки схемы.
Для медленных или неудачных запросов explain() полезнее, чем угадывание:
db.orders.find({ customerId: 123, status: "open" }).explain("executionStats")
Если запрос сканирует большое количество документов, чтобы вернуть несколько результатов, команда может быть синтаксически правильной, но операционно дорогой. Это не проблема оболочки; это проблема индексирования или формы запроса.
Лучшие практики предотвращения ошибок команд
- Используйте
mongoshи его функции: Используйте автодополнение по табуляции, историю команд и четкие сообщения об ошибках, предоставляемые современной оболочкой MongoDB. - Понимайте свою модель данных: Тщательно проектируйте схему и структуру документов, чтобы избежать таких проблем, как документы большого размера или неэффективные запросы.
- Внедрите правильную аутентификацию и авторизацию: Определите пользователей с минимально необходимыми привилегиями для их ролей.
- Мониторьте свое развертывание: Регулярно проверяйте журналы MongoDB, показатели производительности и статус набора реплик, чтобы активно выявлять потенциальные проблемы.
- Тестируйте команды: Перед развертыванием сложных команд или изменений в производственной среде тщательно тестируйте их в среде разработки или промежуточной среде.
- Обновляйте MongoDB по плановому графику: Новые версии могут включать исправления ошибок и изменения поведения. Читайте примечания к выпуску и тестируйте обновления перед развертыванием в производственной среде.
Большинство ошибок команд MongoDB становятся управляемыми, как только вы разделяете проблемы синтаксиса оболочки, контекста аутентификации, авторизации, топологии и формы данных. Начните с подтверждения того, куда вы подключены и кто вы. Затем позвольте точному сообщению об ошибке указать вам на следующий уровень, вместо того чтобы переписывать команду наугад.