Содержание урока · 10 разделов
- Что такое MongoDB: документы и коллекции
- Зачем mongodb go-сервису: пять сценариев
- Сценарии одним списком
- Когда Mongo — неправильный выбор
- Репликация и шардирование: что сервер делает сам
- Три хранилища в одном бэкенде
- mongo-driver v2: что важно знать про версии
- Песочница урока
- Коротко: что запомнить
- Вопросы и ответы
Начнём с ситуации, в которой MongoDB обычно и появляется. Команда пишет бэкенд каталога: товары приходят от сотни поставщиков, у одного есть размер и цвет, у второго — voltage и certifications, у третьего — что-то ещё, чего вчера не существовало. В Postgres это либо гигантская таблица с колонками-призраками (почти все NULL), либо EAV-схема со связкой атрибут-значение, либо JSONB — три обходных пути вокруг модели строк. Каждое новое поле поставщика превращается в миграцию, а выгрузка для аналитики разрастается в десяток JOIN. Документоориентированная СУБД предлагает перевернуть задачу: товар целиком — один документ, со всеми своими полями, включая вложенные характеристики и массив картинок. Схема при этом не теряется — она переезжает в код: структура на Go с bson-тегами описывает документ, а компилятор и тесты следят за её консистентностью. Именно поэтому драйвер для golang читается как продолжение языка: вставил структуру — получил документ. Второй источник сценариев — события: сервисы пишут в коллекции аудит действий, заказы дублируются в историю для аналитики, а TTL-индексы потом подчищают устаревшее. Оба вида данных объединяет одно: они читаются целиком по известному ключу, и JOIN им не нужен. Полный курс занимает двадцать пять уроков и живёт на странице курса; здесь — карта местности, чтобы вы понимали, куда ехать.
Что такое MongoDB: документы и коллекции#
MongoDB — документоориентированная СУБД с открытыми исходниками: её разработка началась в 2007 году в компании 10gen, а первый публичный релиз вышел в 2009-м. Логическая иерархия простая: инстанс содержит базы данных, база — коллекции, коллекция — документы. Документ — это набор пар поле-значение с поддержкой вложенности: значениями могут быть строки, числа, даты, массивы и другие документы. Физически документ хранится в BSON — бинарном формате, родственном JSON, но с явными типами: int32 и int64 различаются, дата — это число миллисекунд с эпохи, есть десятичный Decimal128 для денег и отдельный тип ObjectID для идентификаторов. Коллекция — контейнер документов без обязательной схемы: два товара в одной коллекции могут иметь разный набор полей, и база не возразит. Для человека, пришедшего из SQL, быстрая таблица соответствий: база — это база, коллекция — примерно таблица, документ — примерно строка, но с вложенностью и без JOIN на каждое чихание. Схема при этом никуда не исчезает — она живёт в коде: Go-структуры с bson-тегами, которые мы разберём в уроке про структуры и bson-теги, становятся контрактом, который драйвер обязан соблюдать при записи и чтении. Инструменты вокруг базы это подчёркивают: mongosh — оболочка с JavaScript-синтаксисом, Compass — графический клиент; оба показывают те же документы, которые видит драйвер, и пригодятся на каждом уроке курса.
BSON добавляет к JSON типы, которых в нём нет: int32 и int64, дату с миллисекундами, Decimal128, бинарные данные и ObjectID. За это платим размером — длина поля указывается явно — и тем, что читает BSON человек только через инструменты вроде mongosh и Compass, а не блокнотом.
Зачем mongodb go-сервису: пять сценариев#
Отвечать на вопрос «зачем mongodb go» честнее задачами, а не слоганами про web scale. Первый сценарий — каталоги и контент: товары, статьи, анкеты с разным набором полей; документ повторяет форму предметной области один в один, без EAV-компромиссов. Второй — события и логи: вставки идут потоком, старые данные читаются редко, а TTL-индексы вычищают устаревшее без cron-скриптов — до них доберёмся во второй половине курса. Третий — профили и настройки пользователей: набор атрибутов меняется от версии к версии, и миграция на сотни миллионов строк никому не нужна. Четвёртый — агрегаты над документами: конвейер $match, затем $group, затем $sort закрывает отчёты, для которых раньше писали три JOIN и подзапрос. Пятый — прототипирование и MVP: без схемы первый рабочий релиз собирается быстрее, а требования ещё меняются каждую неделю. Общий знаменатель у всех пяти: данные самодостаточны, читаются целиком и меняются по форме. Как только главный вопрос превращается в «как связать пять таблиц с целостностью» — это уже не про Mongo, и честнее взять SQL.
Сценарии одним списком#
- Каталоги и контент: товары, статьи, анкеты с разными наборами полей.
- События и логи: потоковые вставки, TTL-индексы вместо cron-чисток.
- Профили и настройки: гибкие атрибуты без миграций на миллионы строк.
- Отчёты по документам: агрегации $match, $group, $sort без JOIN.
- MVP и прототипы: схема ещё нестабильна, скорость релиза важнее.
- Материализованные представления: готовый документ вместо пересчёта на лету.
Когда Mongo — неправильный выбор#
Спорное мнение, которое я защищаю на проектировках: если данные — это сеть сильных связей, Mongo сделает жизнь хуже, и никакой драйвер не поможет. Деньги и заказы, где списание со счёта и создание записи обязаны произойти атомарно в трёх коллекциях, — территория классических SQL-СУБД. Транзакции в MongoDB существуют, и мы дойдём до них в отдельном уроке, но они моложе, требовательнее к конфигурации (нужен репликасет) и не дают привычных гарантий многотабличных внешних ключей. То же с тяжёлой отчётностью по произвольным срезам: соединения коллекций эмулируются оператором $lookup, и на больших объёмах это дороже честного SQL. Практическое правило: если половину модели вы мысленно проектируете через нормальные формы, берите Postgres и работайте с ним напрямую через pgx, а Mongo добавляйте точечно — под каталог, события или профиль. Обратная ошибка выглядит зеркально: если проект целиком состоит из документов разной формы, реляционная база будет сопротивляться каждому новому полю, и переезд на документы всё упростит. Я видел оба сценария в одной компании за год: команду, которая переехала с Postgres на Mongo целиком ради «гибкости», и команду, которая вернула деньги в SQL после квартала ручных проверок целостности. Обе потратили месяцы; оба переезда предотвратило бы полдня честного разговора о модели данных.
Репликация и шардирование: что сервер делает сам#
Пара слов о том, что MongoDB делает сама — чтобы вы не искали в драйвере то, чего там нет. Репликация в комплекте: разворачиваете репликасет из трёх узлов, первичный принимает записи, вторичные догоняют, и при падении первичного кластер сам выбирает нового — приложение с правильной строкой подключения переподключится без вашего участия, ретраи записей задаёт параметр retryWrites. Шардирование — горизонтальное разбиение коллекции по ключу: данные размазываются по шардам, роутер mongos развозит запросы, и растущая коллекция не упирается в диск одной машины. Драйвер умеет работать с обоими из коробки: топология читается из строки подключения, клиент сам узнаёт о смене узлов и перекладывает операции на выжившие. Для курса хватит одиночного инстанса в Docker, но знать про эти два механизма нужно уже сейчас: именно они — второй аргумент в пользу Mongo, когда данные обгоняют вертикальное масштабирование.
Три хранилища в одном бэкенде#
| Хранилище | Модель данных | Сильная сторона | Типовая роль |
|---|---|---|---|
| MongoDB | BSON-документы в коллекциях | гибкая схема, агрегации, шардирование | каталог, события, профили |
| Postgres | строки и колонки, SQL | транзакции, внешние ключи, JOIN | источник правды, деньги |
| Redis | ключ-значение в памяти | микросекунды, структуры данных | кэш, сессии, очереди |
К таблице добавлю нюанс, который обычно всплывает на втором году жизни сервиса. Базы в архитектуре не конкурируют — они делят задачи. Заказ с оплатой живёт в Postgres, потому что потерять его нельзя; товар с сотней разнородных характеристик — в Mongo, потому что описать его колонками больно; корзина неавторизованного гостя — в Redis, потому что её потеря не стоит ничего, а скорость нужна каждая. Смешивать модели внутри одной задачи — отдельное решение, которое принимается осознанно: например, идентификатор заказа из Postgres кладётся в mongo-документ счётчика просмотров, чтобы не плодить истину в двух местах. Записывайте для себя, какое хранилище в вашем проекте считается источником правды для каждой сущности: через полгода при разборе инцидента это сэкономит часы. И не забывайте про идентификаторы между базами: идентификатор заказа из Postgres в mongo-документе — это строка или ObjectID по договорённости, и лучше один раз зафиксировать формат в коде моделей, чем обнаружить два формата в одной коллекции после года эксплуатации.
Первый взгляд на драйвер v2
GOpackage main
import (
"context"
"fmt"
"time"
"go.mongodb.org/mongo-driver/v2/mongo"
"go.mongodb.org/mongo-driver/v2/mongo/options"
"go.mongodb.org/mongo-driver/v2/mongo/readpref"
)
func main() {
client, err := mongo.Connect(options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
panic(err)
}
defer client.Disconnect(context.Background())
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := client.Ping(ctx, readpref.Primary()); err != nil {
panic(err)
}
fmt.Println("подключено")
}Не вчитывайтесь в каждую строку — это телепорт вперёд. mongo.Connect принимает только опции: в v2 из сигнатуры убрали контекст, он остался у конкретных операций вроде Ping. options.Client().ApplyURI разбирает строку подключения, Ping с readpref.Primary() проверяет связь, Disconnect закрывает пул соединений. В песочнице сайта такой код не запустится — драйверу нужна настоящая MongoDB; следующий урок поднимет её в Docker одной командой.
mongo-driver v2: что важно знать про версии#
Официальный клиент MongoDB для Go называется mongo-driver, его путь импорта — go.mongodb.org/mongo-driver/v2. Первая мажорная версия вышла в 2019 году и собрала вокруг себя почти весь Go-стек, но к 2024-му накопила legacy: пакет primitive дублировал часть типов bson, конструкторы требовали лишние аргументы, опции жили в указателях. Вторая мажорная версия, вышедшая в 2024-м, почистила API: mongo.Connect потерял контекст (он нужен только операциям), primitive влит в bson — идентификатор теперь bson.ObjectID, а опции операций передаются вариадиками. Для новичка это хорошая новость: читать документацию v2 проще, чем гадать, какой пример из интернета относится к какой версии. Наш курс целиком на v2, и там, где старый код из туториалов не соберётся, я показываю разницу. Вторая честная оговорка — про запуск: драйверу нужна живая MongoDB по сети, поэтому песочница сайта показывает тематическую программу на чистом Go, а примеры уроков гоняйте локально — Docker одной командой и Atlas поднимаются за минуты, всё покажет следующий урок.
Три глагола CRUD одной строкой
GO// запись: структура с bson-тегами превращается в BSON-документ
res, err := orders.InsertOne(ctx, order)
// чтение одного документа: BSON декодируется обратно в структуру
err = orders.FindOne(ctx, bson.M{"sku": "kb-87-tkl"}).Decode(&order)
// конвейер агрегации: этапы идут строго по порядку — bson.D
cur, err := orders.Aggregate(ctx, mongo.Pipeline{
bson.D{{Key: "$match", Value: bson.M{"status": "paid"}}},
bson.D{{Key: "$group", Value: bson.D{
{Key: "_id", Value: "$sku"},
{Key: "total", Value: bson.D{{Key: "$sum", Value: "$price"}}},
}}},
})Это меню курса, а не программа. InsertOne пишет документ и возвращает идентификатор — четвёртый урок. FindOne читает один документ и декодирует его в структуру; пустой результат — ErrNoDocuments, герой шестого урока. Aggregate прогоняет документы по конвейеру: $match фильтрует, $group суммирует; для агрегаций отведены пятнадцатый и шестнадцатый уроки. bson.M и bson.D — два способа собрать документ; их различия разбирает пятый.
Типичная ошибка стартапа: завести Mongo сразу для всего — и деньги, и заказы, и отчёты. Через полгода конвейер из пяти $lookup изображает JOIN, транзакции на три коллекции окружены обходными путями, а команда втихую мигрирует деньги обратно в Postgres. Сначала решите, где источник правды: Mongo — не синоним «современной архитектуры».
Проверка: что лежит в коллекции
Из чего состоит коллекция в MongoDB?
Проверка: границы применимости
Какая задача — плохой кандидат для MongoDB как единственной базы?
Что напечатает программа, если MongoDB не запущена?
Прочитайте код и выберите, что он выведет:
client, err := mongo.Connect(options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
fmt.Println("connect err:", err)
return
}
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
err = client.Ping(ctx, readpref.Primary())
fmt.Println("ping:", err)Соберите подключение
Восстановите каркас: конструктор опций, порт по умолчанию, конструктор клиента и проверку связи.
Соберите каркас mongo-программы
Расставьте строки минимальной программы: пакет, импорты, main, опции, Connect, контекст, Ping.
Строки
Ваша программа
Нажимайте на строки слева, чтобы собрать программу
Карточки: словарь MongoDB
Карта собрана: MongoDB хранит документы в коллекциях, BSON добавляет к JSON нормальные типы, а mongo-driver v2 — способ добраться до всего этого из Go со словарём из трёх слов: Connect, Ping, Disconnect. Дальше по порядку: в уроке про установку и подключение соберём main.go с Ping и строкой Atlas, в уроке про базы, коллекции и документы разберём иерархию Client — Database — Collection, а bson-теги научат структуры превращаться в документы. Ещё один совет перед стартом: поставьте MongoDB Compass — визуальный клиент от разработчиков базы; по ходу курса вы будете постоянно сверять, что именно упало в коллекцию после каждой операции, и Compass экономит время лучше любых принтов. Если Go пока на уровне «читал, но не писал», сначала пройдите базовый курс: структуры, теги и контекст из него понадобятся уже во втором уроке.
Песочница урока
Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.
Запустите код, чтобы увидеть вывод.
Коротко: что запомнить
- MongoDB — документоориентированная СУБД: базы, коллекции, BSON-документы с необязательной общей схемой.
- BSON богаче JSON: int32/int64, даты, Decimal128, бинарные данные, ObjectID.
- Сильные стороны Mongo: каталоги, события, гибкие профили, агрегации, быстрый MVP.
- Деньги и жёсткая целостность — территория Postgres; транзакции Mongo — инструмент с условиями, а не замена.
- mongo-driver v2 (2024): Connect без контекста, primitive влит в bson, опции операций вариадиками.
- Connect не ходит в сеть: связь проверяет Ping, недоступность всплывает как server selection error.
- Песочница не запускает драйвер: для практики нужен локальный Go и MongoDB в Docker или Atlas.
Домашнее задание
Подготовьте площадку: поставьте Go с go.dev и запустите MongoDB одной командой Docker — docker run --name go-mongo-course -p 27017:27017 -d mongo:7. Проверьте связь через docker exec -it go-mongo-course mongosh --eval "db.runCommand({ping: 1})" — в ответе должно прийти ok: 1. Песочница сайта для этого не подойдёт — нужен локальный запуск.
Личные заметкиПусто
Вопросы и ответы по уроку
Можно ли использовать MongoDB как основную базу вместо Postgres?
Можно — для документов это нормальный выбор: репликация, персистентность и транзакции на месте. Но если в модели много связанных сущностей с жёсткой целостностью, Postgres дешевле в поддержке. Типичная архитектура держит обе базы под разные задачи.
Чем mongo-driver v2 отличается от v1?
Путь импорта получил суффикс /v2, mongo.Connect потерял обязательный контекст, пакет primitive влит в bson (ObjectID стал bson.ObjectID), опции операций передаются вариадиками. Старые туториалы на v1 не всегда компилируются — сверяйте версию в примерах.
MongoDB бесплатна?
СУБД с открытыми исходниками под лицензией SSPL — бесплатно для использования, включая локальную разработку и самохостинг. У облачного Atlas есть навсегда бесплатный кластер M0 на 512 МБ: его хватает на весь курс.
Термины урока
Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.
Изучайте дальше
Следующая ступень учебного пути
Zerolog в Go: быстрое JSON-логирование
Курс zerolog для Go: JSON-события, уровни, поля, ConsoleWriter, контекстные поля, ротация, sampling, HTTP-логи и тесты. 25 уроков с примерами.
- Писать структурированные JSON-логи с нуля аллокаций
- Выбирать уровни и поля события осознанно
- Настраивать ConsoleWriter для dev и JSON для прода
- Строить сублоггеры и контекстные поля на запрос