MongoDB URI выглядит так: mongodb://[user:password@]host1[:port1][,host2[:port2],...][/database][?options]. Если ничего не указано, все официальные инструменты исходят из mongodb://localhost:27017. Форма mongodb+srv:// принимает ровно одно имя хоста без порта, запрашивает у DNS настоящий список хостов и по умолчанию включает TLS. Несколько хостов через запятую - это начальный список (seed list) для replica set или набора маршрутизаторов mongos, а не список запасных серверов: драйвер подключится к тому участнику, который сочтёт нужным, и именно поэтому существует directConnection.
Это весь формат. Дальше разбирается, что каждая часть делает на практике: значения по умолчанию, DNS-записи за +srv, как драйвер обращается с несколькими хостами, опции, меняющие поведение, и куда вставлять строку в PyMongo, драйвере Node, mongosh, конфигурационных файлах .NET и Compass. Если нужен более мягкий разбор частей URI и кодирования паролей, начните со статьи MongoDB connection strings. Если у вас есть URI, а сервер отвечает Authentication failed, переходите к руководству по authSource: здесь ни то, ни другое не повторяется.
Формат по частям#
mongodb:// schemeshopapp:pw@ credentials (optional)db1.example.net:27017,db2.example.net:27017 host list/shop default database (optional)?replicaSet=rs0&appName=api options (optional)Если соединить части без пробелов, получится mongodb://shopapp:pw@db1.example.net:27017,db2.example.net:27017/shop?replicaSet=rs0&appName=api.
Правила, которые легко забыть:
- Порт необязателен и по умолчанию равен
27017для каждого хоста, у которого он не указан.mongodb://db.example.netиmongodb://db.example.net:27017- одно и то же. - Литерал IPv6 пишется в квадратных скобках:
mongodb://[2001:db8::10]:27017. Без скобок двоеточия в адресе принимаются за разделитель порта, и URI не разбирается. - Unix domain socket записывается как путь в процентном кодировании на месте хоста:
mongodb://%2Ftmp%2Fmongodb-27017.sock.mongodсоздаёт этот сокет по умолчанию в Linux и macOS, и это удобный способ подключиться с той же машины, вообще не открывая TCP-порт. - Слэш перед
?обязателен, когда есть опции.mongodb://host:27017/?tls=trueкорректен; драйверы по-разному относятся к отсутствующему слэшу, так что не полагайтесь на это. - Имена опций нечувствительны к регистру (
authsourceиauthSource- одно и то же), значения чувствительны, а булевы значения - строчные строкиtrueиfalse. Некоторые драйверы принимают также1,yesи подобное, но это непереносимо. - Опции соединяются знаком
&. Некоторые опции можно повторять, напримерreadPreferenceTags; у остальных побеждает последняя либо драйвер выдаёт ошибку, в зависимости от драйвера.
Значение по умолчанию: localhost:27017
mongodb://localhost:27017 - строка из каждого руководства, значение по умолчанию на экране нового подключения в Compass и то, к чему подключается MongoClient() без аргументов в PyMongo. У неё есть известная ловушка. В Node.js 17 и новее localhost может сначала разрешаться в IPv6-адрес ::1, тогда как mongod по умолчанию слушает только 127.0.0.1. В итоге получается connect ECONNREFUSED ::1:27017 при совершенно исправно работающем сервере. В новых версиях Node пробуются оба семейства адресов, и это встречается реже, но надёжное решение - писать в локальных URI 127.0.0.1 вместо localhost или добавить ::1 в net.bindIp в mongod.conf.
mongosh без аргументов тоже использует не голое значение по умолчанию. Он подключается к mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000 и печатает эту строку первой, что неплохо подсказывает, какие опции стоит задавать, когда вы всегда работаете только с одним локальным сервером.
Какую базу данных называет путь
Часть /database - это база данных клиента по умолчанию и значение по умолчанию для authSource, если эта опция не указана. Она ничего не ограничивает: клиент, подключённый с /shop, всё равно может открыть client["reports"], если у пользователя есть права на неё. Если путь пуст, база по умолчанию зависит от драйвера (часто test или никакой), а authSource принимает значение admin. Именно второе значение по умолчанию вызывает большую часть путаницы с аутентификацией, а руководство по пользователям и ролям объясняет, почему пользователи живут там, где живут.
mongodb+srv:// и DNS-записи за ним#
URI со списком хостов из DNS (mongodb+srv://) заменяет перечень хостов одним DNS-именем. Увидев такую строку, драйвер делает два запроса ещё до открытия первого соединения.
SRV-запись лежит по имени _mongodb._tcp.<hostname> и перечисляет по одному целевому хосту и порту на каждого участника. TXT-запись лежит по самому имени хоста и содержит опции URI в виде строки запроса, например authSource=admin&replicaSet=rs0. Там разрешены только три опции - authSource, replicaSet и loadBalanced, - а TXT-запись может быть только одна; несколько записей считаются ошибкой. Всё, что написано в самом URI, переопределяет TXT-запись.
Увидеть в точности то, что увидит драйвер, можно обычными DNS-инструментами:
$ dig +short SRV _mongodb._tcp.cluster0.example.net0 0 27017 shard-00-00.example.net.0 0 27017 shard-00-01.example.net.0 0 27017 shard-00-02.example.net.$ dig +short TXT cluster0.example.net"authSource=admin&replicaSet=rs0"В Windows то же делает nslookup -type=SRV _mongodb._tcp.cluster0.example.net. Если SRV-запрос вернулся пустым, в приложении вы увидите ошибку querySrv ENOTFOUND (Node) или ConfigurationError с упоминанием DNS-имени (PyMongo), и никакие изменения опций не помогут.
Правила, которые следуют из устройства механизма:
- Ровно одно имя хоста и никакого порта. Порты берутся из SRV-записей.
- Имена хостов, которые возвращает SRV-запись, должны находиться под тем же родительским доменом, что и запрошенное имя.
cluster0.example.netможет указывать наshard-00-00.example.net, но не наdb.othercompany.com; драйверы отклоняют такие цели из соображений безопасности. Старые драйверы также требовали минимум три части в имени, разделённые точками; в новых версиях это ослабили - если двухчастное имя не работает, загляните в changelog вашего драйвера. - TLS включён по умолчанию. Если вам действительно нужно незашифрованное соединение, явно добавьте
tls=false, но с+srvэто почти никогда не требуется. directConnection=trueс+srvне допускается, потому что весь смысл в том, что драйвер находит несколько серверов.- Две новые опции настраивают поиск:
srvServiceName(по умолчаниюmongodb) меняет часть_mongodbв имени записи, аsrvMaxHostsограничивает, сколько из возвращённых хостов используется; она рассчитана на большие наборы маршрутизаторовmongos.
При подключении к шардированным кластерам через mongos драйверы периодически повторяют запрос SRV-записи и добавляют или убирают маршрутизаторы по мере изменения записей. Для replica set в этом нет необходимости, потому что после первого соединения сам набор сообщает свой состав.
mongodb+srv:// стоит использовать, когда эти записи кто-то ведёт за вас - управляемый кластер, состав которого меняется, или ваш собственный replica set с записями, опубликованными в вашей DNS-зоне. Для одного сервера это не нужно. Одна база с A-записью и mongodb://db.example.net:27017 проще, не требует дополнительных запросов и ломается реже и менее причудливо. О типах A, TXT и SRV, если вы хотите публиковать их сами, рассказывает статья DNS records explained.
Несколько хостов и replicaSet#
Список хостов через запятую - это seed list. Драйвер подключается к указанным хостам, спрашивает у каждого, как replica set видит себя в данный момент (команда hello), и дальше работает по тому, что сообщает сервер, а не по URI.
mongodb://db1.example.net:27017,db2.example.net:27017,db3.example.net:27017/shop?replicaSet=rs0Что это значит на практике:
- Перечислять всех участников не обязательно. Для обнаружения достаточно одного доступного участника, хотя если перечислить всех, запуск сработает и тогда, когда первый из них недоступен.
- Порядок хостов не определяет, куда идут записи. Записи всегда идут на текущий primary, а чтения - туда, куда их направляет
readPreference. replicaSetдолжен совпадать с настоящим именем набора (значением_id, переданным вrs.initiate()). Участник, сообщающий другое имя набора, исключается из топологии, и если исключены все, вы получите тайм-аут выбора сервера, а не понятное сообщение. Настоящее имя показываетdb.hello().setNameвmongosh.- Имена хостов, которыми драйвер в итоге пользуется, берутся из конфигурации replica set, а не из вашего URI. Если набор был инициализирован с
localhost:27017или с внутренними именами контейнеров Docker, клиент вне этой сети узнаёт имена, которые не может разрешить, и после удачного первого контакта упирается в тайм-аут. Исправьте имена участников в конфигурации replica set черезrs.reconfig(); не заметайте проблему под ковёр на стороне клиента.
В шардированном кластере список хостов содержит маршрутизаторы mongos, опции replicaSet нет, а драйвер распределяет операции между доступными маршрутизаторами, предпочитая те, у которых меньше задержка.
directConnection
directConnection=true велит драйверу общаться только с одним хостом из URI и пропустить обнаружение. Разрешено только с одним хостом и никогда с +srv. Используйте её, когда:
- Нужно подключиться к конкретному участнику, обычно secondary, для обслуживания или для выполнения запроса без обращения к primary.
- Настроенные в replica set имена хостов недоступны оттуда, где вы находитесь, например при SSH-туннеле к одному участнику. Такая схема описана в статье SSH tunnels and port forwarding.
Значение по умолчанию важно, и оно менялось. В PyMongo 4 и в драйвере Node начиная с 4.0 directConnection по умолчанию равна false, поэтому URI с одним хостом, который оказывается участником replica set, запускает обнаружение: драйвер находит primary и отправляет записи туда. PyMongo 3 вёл себя так, будто для одного хоста она равна true. Код, который после обновления рассчитывал на "подключаться только к этой машине", внезапно начинает работать с другим сервером, поэтому задавайте опцию явно, когда различие имеет значение. На отдельном сервере (standalone), который вообще не сообщает о replica set, оба значения приводят к одному результату.
loadBalanced=true - третья опция топологии, для кластеров за балансировщиком нагрузки, понимающим протокол MongoDB (её используют некоторые управляемые serverless-предложения). Её нельзя сочетать с replicaSet, directConnection=true или более чем одним хостом, и встретите вы её только в URI, который выдал провайдер.
Опции, меняющие поведение#
Полный список опций URI длинный. Здесь собраны те, что встречаются в реальных строках подключения и меняют работу клиента.
| Option | Default | Effect |
|---|---|---|
authSource | path db, else admin | База, где хранится пользователь |
tls | false; true for +srv | Шифрует соединение |
retryWrites | true | Повторяет неудавшуюся запись один раз |
w | server default | Подтверждение записи |
readPreference | primary | Какие участники обслуживают чтения |
maxPoolSize | 100 | Соединений на сервер |
connectTimeoutMS | driver-specific | Тайм-аут TCP-соединения |
appName | none | Метка в логах сервера |
directConnection | false | Пропустить обнаружение топологии |
authSource
Указывает базу данных, в которой создан пользователь. Правила значений по умолчанию описаны выше, а разбор проблем - в руководстве по аутентификации. Добавить стоит только эквивалент для командной строки: mongosh принимает --authenticationDatabase, это та же настройка под другим именем.
tls
tls=true шифрует соединение и проверяет сертификат сервера по системному хранилищу доверенных сертификатов или по центру сертификации, указанному в tlsCAFile. ssl=true - более старый псевдоним, он по-прежнему работает. Сервер сначала должен быть настроен на TLS; если включить её в URI для сервера без TLS, соединение рвётся на рукопожатии, и обычно это проявляется как тайм-аут. tlsAllowInvalidCertificates=true и tlsInsecure=true существуют для тестов и никогда не должны попадать в production.
retryWrites и w
retryWrites=true стала значением по умолчанию в драйверах со времён MongoDB 4.2. Запись, завершившаяся сетевой ошибкой или выборами primary, повторяется один раз, и это безопасно, потому что сервер отслеживает идентификатор операции. Повторяемые записи требуют replica set или шардированного кластера; на отдельном сервере драйвер просто не повторяет запись. Иногда вы увидите retryWrites=false в строках, написанных для старых серверов или необычных движков хранения, - оставляйте значение по умолчанию, если только ошибка сервера не говорит иного.
w задаёт write concern: w=1 означает, что запись подтвердил primary, w=majority - что её подтвердило большинство голосующих участников, а w=0 - что не подтвердил никто и о сбоях вы никогда не узнаете. Начиная с MongoDB 5.0 неявное значение сервера по умолчанию для большинства replica set - majority, поэтому чаще всего правильно не указывать w в URI. journal=true добавляет условие "и запись попала на диск в журнал", а wtimeoutMS не даёт записи с majority ждать вечно, когда в сети слишком мало участников. На одиночном сервере majority выполняется самим этим сервером.
readPreference
Принимает primary, primaryPreferred, secondary, secondaryPreferred и nearest. Всё, кроме primary, может вернуть данные с небольшим отставанием, потому что secondary реплицируются асинхронно. maxStalenessSeconds (минимум 90) исключает secondary, отставшие слишком сильно, а readPreferenceTags=dc:east направляет чтения на участников с подходящими тегами. С одним хостом выбирать не из чего.
maxPoolSize и connectTimeoutMS
maxPoolSize действует на каждый сервер и на каждый клиент. Один клиент, работающий с replica set из трёх участников, при значении по умолчанию может открыть до трёхсот соединений, а у каждого процесса вашего приложения свой клиент. Задавайте значение осознанно; арифметика разобрана в статье connection pools and limits. minPoolSize держит соединения наготове, а maxIdleTimeMS закрывает простаивающие.
connectTimeoutMS - это тайм-аут TCP-соединения для каждого соединения, и его значение по умолчанию - одно из немногих, которые различаются между драйверами: общая спецификация говорит о 10 секундах, PyMongo использует 20, а драйвер Node - 30. Но не эта настройка заставляет зависать неверный URI - за это отвечает serverSelectionTimeoutMS, 30 секунд везде. Новые драйверы также поддерживают timeoutMS - единый клиентский лимит на всю операцию, который призван заменить несколько старых тайм-аутов отдельных этапов.
appName
Произвольная текстовая метка до 128 байт, отправляемая при рукопожатии соединения. Она видна в логе сервера для каждого соединения и в db.currentOp(), так что можно понять, какой сервис открыл какие соединения. Это ничего не стоит; задавайте её у каждого клиента.
Строки URI для типичных конфигураций#
Большинство строк укладывается в несколько шаблонов. Сравнить их рядом быстрее, чем читать спецификацию.
# Локальная разработкаmongodb://127.0.0.1:27017/shop# Один удалённый сервер, пользователь в adminmongodb://shopapp:pw@db.example.net:27017/shop?authSource=admin&appName=api# Replica set из трёх участников, чтение со secondary разрешеноmongodb://shopapp:pw@db1.example.net,db2.example.net,db3.example.net/shop ?replicaSet=rs0&authSource=admin&readPreference=secondaryPreferred# Только один участник, через SSH-туннель на локальном порту 27018mongodb://shopapp:pw@127.0.0.1:27018/shop?directConnection=true&authSource=admin# Управляемый кластер, опубликованный в DNSmongodb+srv://shopapp:pw@cluster0.example.net/shop?appName=api# Тот же кластер, записанный вручную (то, что за вас делал +srv)mongodb://shopapp:pw@shard-00-00.example.net:27017,shard-00-01.example.net:27017 ,shard-00-02.example.net:27017/shop?replicaSet=rs0&authSource=admin&tls=trueПереносы строк в примерах с replica set сделаны только для удобства чтения; настоящий URI - одна строка без пробелов. Последний пример стоит запомнить на случай, когда проблема в DNS: чтобы превратить +srv в обычный список, нужно скопировать хосты из SRV-записи, опции из TXT-записи и самостоятельно добавить tls=true. Пропустите любой из трёх пунктов - и соединение сломается по-новому.
Куда вставлять строку в каждом клиенте#
Все официальные драйверы принимают один и тот же URI - в этом и смысл его существования. Различается то, откуда строка читается и что что переопределяет.
PyMongo
import osfrom pymongo import MongoClientclient = MongoClient(os.environ["MONGODB_URI"], appName="reports", maxPoolSize=10)db = client.get_default_database() # the /database from the URIИменованные аргументы и опции URI взаимозаменяемы, а если одна и та же опция есть в обоих местах, побеждает именованный аргумент. get_default_database() возвращает базу данных из пути и выбрасывает исключение, если её там нет. Чтобы проверить строку без подключения, pymongo.uri_parser.parse_uri(uri) возвращает разобранные хосты, учётные данные и опции в виде словаря и выбрасывает исключение при любой некорректности, в том числе для имени +srv без записей.
Драйвер Node.js
import { MongoClient } from "mongodb";const client = new MongoClient(process.env.MONGODB_URI, { appName: "api" });const db = client.db(); // the /database from the URIВторой аргумент принимает те же опции в camelCase. client.db() без имени возвращает базу данных из URI по умолчанию. Mongoose передаёт строку напрямую через mongoose.connect(uri). Разбор выполняет пакет mongodb-connection-string-url, от которого зависит драйвер и которым вы можете пользоваться сами, чтобы изучить URI или скрыть пароль перед записью в лог.
mongosh
$ mongosh 'mongodb://db1.example.net,db2.example.net/shop?replicaSet=rs0' \ -u shopapp -p --authenticationDatabase adminЗаключайте URI в одинарные кавычки, чтобы оболочка не трогала & и $. mongosh также принимает replica set в более старой форме --host rs0/db1.example.net:27017,db2.example.net:27017. После подключения db.hello() показывает setName, список участников и то, кто из них primary, - это снимает любые сомнения насчёт того, что обнаружит драйвер.
.NET: appsettings.json и web.config
Драйвер C# принимает URI в виде строки, а в ASP.NET Core она должна лежать в разделе ConnectionStrings:
{ "ConnectionStrings": { "Mongo": "mongodb://shopapp:pw@db.example.net:27017/shop?authSource=admin" }}var cs = builder.Configuration.GetConnectionString("Mongo");var url = new MongoUrl(cs);builder.Services.AddSingleton<IMongoClient>(new MongoClient(url));builder.Services.AddSingleton(sp => sp.GetRequiredService<IMongoClient>().GetDatabase(url.DatabaseName));MongoUrl разбирает строку, а его DatabaseName даёт базу данных из пути. Регистрируйте клиент как singleton по той же причине, что и в любом другом драйвере: это пул. В production задавайте значение как переменную окружения ConnectionStrings__Mongo, а не записывайте пароль в файл; этот приём описан в статье environment variables and secrets.
В .NET Framework строка лежит в web.config или app.config и читается через ConfigurationManager.ConnectionStrings["Mongo"].ConnectionString. Этот файл - XML, поэтому каждый & между опциями нужно писать как &:
<connectionStrings> <add name="Mongo" connectionString="mongodb://db.example.net:27017/shop?authSource=admin&appName=legacy" /></connectionStrings>Голый & там приводит к тому, что весь конфигурационный файл не загружается, и это обычно проявляется как ошибка запуска приложения без единого упоминания MongoDB. Та же идея для строк SQL Server разобрана в статье SQL Server connection strings.
Compass
Вставьте URI в поле на экране нового подключения. "Advanced Connection Options" разбивает его по вкладкам: на вкладке General есть переключатель схемы между mongodb и mongodb+srv, список хостов и флажок Direct Connection; Authentication содержит имя пользователя, пароль и базу аутентификации; у TLS/SSL и Proxy/SSH свои вкладки. Правки во вкладках переписывают строку и наоборот, поэтому Compass - разумный способ увидеть, как интерпретируется присланный вам URI, в том числе разобрал ли он список хостов так, как вы задумали.
Ошибки, связанные с самим форматом#
Это ошибки, вызванные формой строки, а не учётными данными или сетью.
- Порт в URI с `+srv`.
mongodb+srv://cluster0.example.net:27017отклоняется сразу. Уберите порт. - Несколько хостов в URI с `+srv`. Тоже отклоняется;
+srvпринимает одно имя. - `directConnection=true` с несколькими хостами. Отклоняется, потому что нет единственного сервера, к которому можно подключиться напрямую.
- Несовпадающее имя `replicaSet`. Выбор сервера завершается тайм-аутом через 30 секунд, хотя все хосты были доступны. Сравните с
db.hello().setName. - Мышление в духе fallback. Если указать в одном URI рабочий и резервный серверы, отказоустойчивость между несвязанными машинами не появится. Если они не участники одного replica set, драйвер видит две несовпадающие топологии, и вы получите ошибки, а не резервирование.
- Пробелы или переносы строк. При копировании URI с переносами из документа или сообщения в чате вместе с ним попадает пробел или перевод строки. Драйверы сообщают о неверном хосте или неизвестной опции, а лишний символ в большинстве логов не виден.
- Незакодированные спецсимволы в пароле. Подробно разобрано в статье MongoDB connection strings; коротко: кодируйте один раз или передавайте учётные данные отдельными опциями.
На хостинге MongoDB RE:NODE каждый сервер создаётся со сгенерированными для него учётными данными, и подключаться к нему нужно напрямую по хосту и порту, указанным в тарифе. SRV-записи для него не публикуются, и никакого прокси перед ним нет, поэтому правильная форма - mongodb:// с этим хостом и портом, сгенерированным пользователем и authSource, указывающим на базу, в которой этот пользователь находится. Где панель показывает эти данные, видно в разделе о базах данных в руководствах. Если ваше приложение работает на одной из линеек для приложений, например Python, Node.js или C# / .NET, URI нужно записать в переменные окружения этого сервера на вкладке Startup, а не в код.
FAQ#
Какой MongoDB URI используется по умолчанию?
mongodb://localhost:27017, без учётных данных и без базы данных. Если на вашей машине localhost разрешается в IPv6, а mongod слушает только IPv4, используйте mongodb://127.0.0.1:27017.
Можно ли использовать mongodb+srv с IP-адресом?
Нет. +srv существует для поиска SRV- и TXT-записей по DNS-имени, а у IP-адреса их нет. Используйте mongodb:// с адресом и портом.
Нужно ли перечислять в URI всех участников replica set?
Нет. Достаточно одного доступного участника, потому что остальных драйвер узнаёт от самого набора. Если указать двух-трёх, клиент сможет запуститься, даже когда один из них недоступен, поэтому в большинстве production-строк так и делают.
Почему после обновления драйвера приложение стало писать на другой сервер?
Скорее всего, изменилось значение directConnection по умолчанию. Новые драйверы находят replica set по одному хосту и направляют записи на primary. Добавьте directConnection=true, если вам действительно нужен именно этот сервер, и не добавляйте, если нужно обычное поведение replica set.
Важен ли порядок опций в URI?
Нет. Опции - это набор пар ключ-значение. Важно лишь то, что опция в URI переопределяет такую же опцию из TXT-записи +srv и что аргументы драйвера, переданные в коде, обычно переопределяют URI.
Что делает replicaSet на одиночном сервере?
На отдельном сервере она приводит к сбою соединения, потому что сервер не сообщает имени набора, а драйвер ждёт участника rs0. Добавляйте replicaSet только тогда, когда сервер действительно участник replica set, пусть даже из одного члена.


