Документоориентированные
Документоориентированные базы данных — хранение данных как самодостаточных документов (JSON/BSON) с гибкой схемой (schema-on-read), вложенными документами и массивами. Ключевые принципы: денормализация, встраивание (embedding) или ссылки (referencing). Популярные системы: MongoDB (BSON, агрегационный pipeline, шардирование, транзакции), Couchbase (N1QL, встроенный кеш), Firestore (real-time, облачная), CouchDB (multi-master репликация). Паттерны проектирования схем, примеры запросов и агрегаций, индексы, критерии выбора (каталоги, CMS, профили) и типичные ошибки.
Введение: Данные как документы
Представьте, что вы ведете личную записную книжку. В ней есть страницы про друзей, про рабочие контакты, про любимые рестораны. Каждая страница имеет свою структуру. У друга вы записываете день рождения и телефон, у ресторана — адрес и любимое блюдо, у рабочего контакта — должность и рабочий email. Вы не пытаетесь втиснуть все в одинаковые строки и столбцы. Каждая страница — это документ со своей схемой.
Документоориентированные базы данных работают по тому же принципу. Вместо того чтобы разбивать данные на множество связанных таблиц (как в реляционных базах), они хранят данные как самодостаточные документы — обычно в формате JSON, BSON или XML.
Документоориентированная база данных (Document-Oriented Database) — это база данных, в которой единицей хранения является документ. Документ содержит данные в структурированном или полуструктурированном виде, обычно в формате, близком к JSON. Документы группируются в коллекции (аналог таблиц), но в отличие от реляционных таблиц, документы в одной коллекции могут иметь разную структуру.
Это идеальный выбор, когда ваши данные естественным образом выглядят как документы: заказы, профили пользователей, статьи блога, товары в каталоге. И особенно хорош, когда структура данных меняется со временем или зависит от типа объекта.
Документ как единица хранения
Что такое документ
Документ — это самодостаточная запись, которая содержит все данные о некотором объекте. В отличие от реляционной строки, документ может иметь вложенную структуру и не обязан соответствовать фиксированной схеме.
Пример документа в MongoDB (BSON — бинарный JSON):
{
"_id": ObjectId("507f1f77bcf86cd799439011"),
"name": "Иван Петров",
"email": "ivan@example.com",
"age": 30,
"address": {
"city": "Москва",
"street": "Тверская",
"zip": "101000"
},
"phones": ["+7-999-123-45-67", "+7-495-123-45-67"],
"orders": [
{
"order_id": 1001,
"date": "2024-01-15",
"total": 5000,
"items": [
{"product": "iPhone", "quantity": 1, "price": 5000}
]
}
],
"preferences": {
"notifications": true,
"language": "ru"
}
}Свойства документа
| Свойство | Описание |
|---|---|
| Самодостаточность | Документ содержит все данные об объекте. Для чтения заказа не нужно делать JOIN с таблицей товаров и таблицей клиентов |
| Схема на чтение (schema-on-read) | База данных не проверяет структуру документа при записи. Документы в одной коллекции могут быть разными |
| Вложенность | Документы могут содержать другие документы и массивы. Это естественно отражает иерархические данные |
| Уникальный идентификатор | Каждый документ имеет уникальный ключ (обычно _id), который служит первичным ключом |
Документ vs Реляционная строка
| Характеристика | Реляционная строка | Документ |
|---|---|---|
| Структура | Фиксированная (схема на запись) | Гибкая (схема на чтение) |
| Вложенность | Нет (нужны отдельные таблицы) | Да (объекты и массивы) |
| Связанные данные | Через JOIN | Вложены в документ или ссылки |
| Нормализация | Требуется (3NF) | Денормализация поощряется |
| NULL | Много NULL для отсутствующих полей | Отсутствующие поля просто не включаются |
Ключевые принципы
Гибкая схема (Schema Flexibility)
В реляционных базах вы сначала определяете схему (CREATE TABLE), потом вставляете данные. В документоориентированных базах схема не требуется. Вы просто вставляете документ, и он сам определяет свою структуру.
Одна коллекция — документы разной структуры:
// Коллекция "products" может содержать:
// Телефон
db.products.insertOne({
_id: 1,
name: "iPhone 15",
type: "phone",
specs: {
screen: "6.1 inch",
camera: "48MP",
storage: "256GB"
}
});
// Одежда
db.products.insertOne({
_id: 2,
name: "Футболка",
type: "clothing",
specs: {
size: "L",
color: "red",
material: "cotton"
}
});
// Книга
db.products.insertOne({
_id: 3,
name: "Война и мир",
type: "book",
specs: {
author: "Толстой",
pages: 1225,
isbn: "978-5-17-123456-7"
}
});Вложенные документы и массивы
Вложенность — ключевая особенность документных БД. Вместо того чтобы создавать отдельные таблицы для адресов, телефонов, позиций заказа, вы вкладываете их в родительский документ.
// Заказ с вложенными позициями
{
_id: 1001,
customer: {
id: 123,
name: "Иван",
email: "ivan@example.com"
},
items: [
{ product_id: 1, name: "iPhone", quantity: 1, price: 50000 },
{ product_id: 2, name: "Чехол", quantity: 2, price: 1000 }
],
total: 52000,
status: "shipped"
}Преимущества вложенности:
- Один запрос для получения всего заказа (вместо 3-4 JOIN)
- Атомарность операций над документом
- Естественное отображение иерархических данных
Недостатки вложенности:
- Дублирование данных (customer.name повторяется в каждом заказе)
- Сложность обновления (если Иван сменил email, нужно обновить все его заказы)
- Ограничение на размер документа (обычно 16 МБ в MongoDB)
Денормализация
В отличие от реляционных баз, где нормализация (3NF) — стандарт, в документных БД денормализация поощряется. Данные дублируются, чтобы сделать запросы быстрее и проще.
Нормализованный подход (реляционный):
customers (id, name, email)
orders (id, customer_id, date)
order_items (order_id, product_id, quantity)
products (id, name, price)Денормализованный подход (документный):
// Один документ заказа содержит все нужные данные
{
_id: 1001,
customer: { id: 123, name: "Иван", email: "ivan@example.com" },
items: [
{ product_id: 1, name: "iPhone", price: 50000, quantity: 1 }
],
total: 50000
}Когда денормализация хороша:
- Данные часто читаются вместе
- Данные редко меняются
- Скорость чтения критична
- Размер документа в разумных пределах
Когда денормализация плоха:
- Данные часто обновляются (придется обновлять много мест)
- Связи сложные и глубокие (лучше использовать ссылки)
- Документы становятся огромными
Ссылки между документами
Иногда вложенность не подходит. В этих случаях документные БД позволяют использовать ссылки (аналоги внешних ключей).
// Документ пользователя
db.users.insertOne({
_id: 123,
name: "Иван",
email: "ivan@example.com"
});
// Документ заказа (ссылается на пользователя)
db.orders.insertOne({
_id: 1001,
user_id: 123, // Ссылка
total: 50000,
items: [...]
});
// Чтение заказа с пользователем (два запроса)
const order = db.orders.findOne({ _id: 1001 });
const user = db.users.findOne({ _id: order.user_id });MongoDB поддерживает оператор $lookup для JOIN между коллекциями (но это дороже, чем в реляционных БД).
db.orders.aggregate([
{
$lookup: {
from: "users",
localField: "user_id",
foreignField: "_id",
as: "user"
}
}
]);Популярные документоориентированные БД
MongoDB
Самая популярная документоориентированная БД. Использует BSON (бинарный JSON).
// Базовая работа с MongoDB (через Node.js драйвер)
const { MongoClient } = require('mongodb');
const client = new MongoClient('mongodb://localhost:27017');
await client.connect();
const db = client.db('shop');
const users = db.collection('users');
// Вставка
await users.insertOne({ name: "Иван", email: "ivan@example.com", age: 30 });
// Поиск
const user = await users.findOne({ email: "ivan@example.com" });
// Обновление
await users.updateOne(
{ email: "ivan@example.com" },
{ $set: { age: 31 }, $inc: { version: 1 } }
);
// Агрегация
const stats = await users.aggregate([
{ $match: { age: { $gte: 18 } } },
{ $group: { _id: "$city", count: { $sum: 1 }, avgAge: { $avg: "$age" } } },
{ $sort: { count: -1 } }
]).toArray();Индексы в MongoDB:
// Обычный индекс
db.users.createIndex({ email: 1 });
// Составной индекс
db.users.createIndex({ city: 1, age: -1 });
// Уникальный индекс
db.users.createIndex({ email: 1 }, { unique: true });
// Текстовый индекс (полнотекстовый поиск)
db.articles.createIndex({ title: "text", content: "text" });
// Геопространственный индекс
db.places.createIndex({ location: "2dsphere" });Характеристики MongoDB:
- Документы до 16 МБ
- Поддержка транзакций (с версии 4.0 — на одном документе, с 4.2 — на нескольких)
- Шардирование для горизонтального масштабирования
- Репликация с автоматическим failover
- Агрегационный pipeline (мощный аналог SQL GROUP BY)
- Собственный язык запросов (MQL)
Couchbase
Документная БД с встроенным кешем (Memcached) и поддержкой SQL-подобного языка (N1QL).
-- N1QL (SQL for JSON)
SELECT name, email, address.city
FROM `users`
WHERE age > 18 AND address.city = 'Москва'
ORDER BY name
LIMIT 10;Характеристики Couchbase:
- Встроенный кеш (данные и в RAM, и на диске)
- N1QL — SQL-подобный язык для JSON
- Мобильная синхронизация (Couchbase Lite)
- Высокая производительность
Firestore (Google)
Облачная документная БД от Google, часть Firebase.
// Firestore
const docRef = db.collection('users').doc('123');
await docRef.set({
name: "Иван",
email: "ivan@example.com",
created_at: firebase.firestore.FieldValue.serverTimestamp()
});
const doc = await docRef.get();
const data = doc.data();Характеристики Firestore:
- Реальное время (live queries)
- Автоматическое масштабирование
- Сильная согласованность
- Интеграция с Firebase (аутентификация, функции)
CouchDB
Документная БД с акцентом на репликацию и eventual consistency.
// CouchDB использует HTTP API
PUT /users/123
{
"name": "Иван",
"email": "ivan@example.com"
}
GET /users/123Характеристики CouchDB:
- Multi-master репликация (работает офлайн)
- HTTP API (RESTful)
- MVCC (Multi-Version Concurrency Control)
- Встроенный веб-интерфейс (Fauxton)
Сравнение документоориентированных БД
| Характеристика | MongoDB | Couchbase | Firestore | CouchDB |
|---|---|---|---|---|
| Лицензия | SSPL (ранее AGPL) | Apache 2 | Проприетарная (Google) | Apache 2 |
| Язык запросов | MQL (JSON) | N1QL (SQL-like) | Firestore API | MapReduce / Mango |
| Транзакции | Да (multi-doc с 4.2) | Да (N1QL) | Да (ACID) | Нет |
| Репликация | Master-slave | Master-master | Автоматическая | Multi-master |
| Шардирование | Да (автоматическое) | Да | Автоматическое | Нет |
| Full-text search | Да (через Atlas) | Да (через FTS) | Нет | Да (через плагины) |
| Встроенный кеш | Нет | Да (memcached) | Нет | Нет |
| Офлайн-работа | Нет | Нет | Да (мобильные SDK) | Да (PouchDB) |
Паттерны проектирования схем в документных БД
Паттерн 1: Встраивание (Embedding)
Данные вкладываются в родительский документ.
// Пользователь с вложенными адресами
{
_id: 123,
name: "Иван",
addresses: [
{ type: "home", city: "Москва", street: "Тверская" },
{ type: "work", city: "Москва", street: "Ленинский" }
]
}Когда использовать:
- Данные читаются и обновляются вместе
- Связь “один-ко-многим” с небольшой кардинальностью (до нескольких сотен)
- Нет необходимости запрашивать вложенные данные отдельно
Паттерн 2: Ссылки (Referencing)
Документы ссылаются друг на друга (аналогично внешним ключам).
// Пользователь
{ _id: 123, name: "Иван" }
// Заказы (ссылаются на пользователя)
{ _id: 1001, user_id: 123, total: 5000 }
{ _id: 1002, user_id: 123, total: 3000 }Когда использовать:
- Связь “один-ко-многим” с высокой кардинальностью (тысячи заказов)
- Данные обновляются независимо
- Вложенность привела бы к превышению лимита размера документа
Паттерн 3: Агрегация (Aggregation)
Комбинация встраивания и ссылок. Основные данные встраиваются, остальные — по ссылкам.
// Заказ с встроенными данными клиента (только то, что нужно для заказа)
{
_id: 1001,
customer: {
id: 123,
name: "Иван" // только имя, email не нужен в заказе
},
items: [...]
}Паттерн 4: Деревья и графы
Для иерархических данных (категории, комментарии).
// Категории с parent_id
{ _id: 1, name: "Электроника", parent_id: null }
{ _id: 2, name: "Телефоны", parent_id: 1 }
{ _id: 3, name: "Смартфоны", parent_id: 2 }
// Или вложенные документы (для небольших деревьев)
{
_id: 1,
name: "Электроника",
children: [
{ name: "Телефоны", children: [
{ name: "Смартфоны" },
{ name: "Кнопочные телефоны" }
]},
{ name: "Компьютеры", children: [...] }
]
}Паттерн 5: Атрибуты (Attributes / EAV)
Для данных с динамическими атрибутами.
// Товар с динамическими характеристиками
{
_id: 1,
name: "iPhone 15",
type: "phone",
attributes: [
{ key: "color", value: "black" },
{ key: "storage", value: "256GB" },
{ key: "camera", value: "48MP" }
]
}В MongoDB для таких случаев лучше использовать прямые вложенные поля (схема гибкая), а не EAV.
Запросы и агрегации
Базовые операции MongoDB
// Поиск (фильтрация)
db.users.find({ age: { $gte: 18, $lte: 65 }, city: "Москва" });
// Проекция (выбор полей)
db.users.find({}, { name: 1, email: 1, _id: 0 });
// Сортировка
db.users.find().sort({ age: -1, name: 1 });
// Пагинация
db.users.find().skip(100).limit(10);
// Подсчет
db.users.countDocuments({ city: "Москва" });Агрегационный pipeline (MongoDB)
Агрегационный pipeline — мощный аналог SQL GROUP BY и оконных функций.
db.orders.aggregate([
// Этап 1: Фильтрация
{ $match: { status: "completed", date: { $gte: "2024-01-01" } } },
// Этап 2: Разворачивание массива items
{ $unwind: "$items" },
// Этап 3: Группировка
{ $group: {
_id: "$items.product_id",
total_quantity: { $sum: "$items.quantity" },
total_revenue: { $sum: { $multiply: ["$items.quantity", "$items.price"] } }
} },
// Этап 4: Сортировка
{ $sort: { total_revenue: -1 } },
// Этап 5: Лимит (топ-10)
{ $limit: 10 },
// Этап 6: JOIN с коллекцией products
{ $lookup: {
from: "products",
localField: "_id",
foreignField: "_id",
as: "product"
} },
// Этап 7: Разворачивание результата JOIN
{ $unwind: "$product" },
// Этап 8: Проекция (выбор полей)
{ $project: {
product_name: "$product.name",
total_quantity: 1,
total_revenue: 1
} }
]);Индексы для документных БД
// Обычный индекс (B-Tree)
db.users.createIndex({ email: 1 });
// Составной индекс
db.users.createIndex({ city: 1, age: -1 });
// Текстовый индекс (поиск по словам)
db.articles.createIndex({ title: "text", content: "text" });
// Геопространственный индекс
db.places.createIndex({ location: "2dsphere" });
// Уникальный индекс
db.users.createIndex({ email: 1 }, { unique: true });
// Частичный индекс (индексируются только документы, удовлетворяющие условию)
db.users.createIndex(
{ email: 1 },
{ partialFilterExpression: { is_active: true } }
);
// TTL-индекс (автоматическое удаление)
db.sessions.createIndex(
{ created_at: 1 },
{ expireAfterSeconds: 3600 }
);Когда использовать документоориентированные БД
Идеальные сценарии
| Сценарий | Почему подходит |
|---|---|
| Каталоги товаров | Разные типы товаров имеют разные атрибуты; гибкая схема идеальна |
| Системы управления контентом (CMS) | Статьи, страницы, меню — естественно выглядят как документы |
| Профили пользователей | Разные пользователи могут иметь разный набор полей; легко добавлять новые поля |
| Логи и события | Каждый лог — документ; можно индексировать по времени и другим полям |
| JSON-данные от API | Сохраняем ответы API как есть, без преобразования |
| Быстрое прототипирование | Нужно часто менять схему? Документные БД не требуют миграций |
| Приложения с “горячими” данными | Один документ содержит все, что нужно для экрана |
Сомнительные сценарии
| Сценарий | Почему плохо подходит |
|---|---|
| Сложные транзакции на нескольких документах | Хотя поддержка есть, производительность хуже, чем в реляционных БД |
| Отчеты и аналитика (OLAP) | JOIN и агрегации медленнее, чем в колоночных БД |
| Графовые запросы (друзья друзей) | Нет оптимизации для обхода связей; лучше использовать графовые БД |
| Данные с жесткой схемой и сложными связями | Реляционные БД с нормализацией будут эффективнее |
| Огромные документы (>16 МБ) | MongoDB имеет лимит 16 МБ; большие документы плохо кешируются |
Преимущества и недостатки
Преимущества
| Преимущество | Описание |
|---|---|
| Гибкая схема | Легко добавлять новые поля; нет миграций; подходит для agile-разработки |
| Естественная модель | Данные выглядят так же, как в приложении (JSON) |
| Производительность чтения | Один документ содержит все данные; нет JOIN |
| Масштабирование | Хорошо шардируется (особенно MongoDB) |
| Разработка | Быстрое прототипирование; не нужно проектировать схему заранее |
Недостатки
| Недостаток | Описание |
|---|---|
| Денормализация | Дублирование данных; сложность обновлений |
| Ограниченные JOIN | $lookup есть, но медленнее, чем в реляционных БД |
| Размер документа | Ограничения на размер (16 МБ в MongoDB) |
| Целостность данных | Нет внешних ключей; ссылочная целостность — на совести приложения |
| Сложные транзакции | Хотя поддержка есть, производительность хуже |
Распространенные ошибки
Ошибка 1: Использование документной БД как реляционной
Пытаться нормализовать данные так же, как в SQL — создавать отдельные коллекции для всего и делать JOIN.
Как исправить: В документных БД нормализация не является стандартом. Денормализуйте данные, вкладывайте связанные данные в родительский документ.
Ошибка 2: Бесконечное вложение
Вкладывание тысяч поддокументов в один документ (например, все заказы пользователя в документ пользователя).
Как исправить: Используйте ссылки для “один-ко-многим” с высокой кардинальностью. Лимит MongoDB — 16 МБ на документ.
Ошибка 3: Игнорирование индексов
Запросы без индексов на больших коллекциях выполняют полное сканирование коллекции (collection scan), что очень медленно.
Как исправить: Анализируйте медленные запросы через explain() и создавайте индексы.
Ошибка 4: Неправильный выбор шардирования
Выбор шардированного ключа, который приводит к “горячей” партиции (все запросы идут на один сервер).
Как исправить: Выбирайте шардированный ключ с высокой кардинальностью и равномерным распределением.
Ошибка 5: Хранение бинарных данных в документах
Сохранение изображений, видео, PDF в документах БД.
Как исправить: Для больших бинарных данных используйте Blob Storage (S3, MinIO). В MongoDB храните только ссылки (URL, ключи).
Резюме для системного аналитика
Документоориентированные БД хранят данные как самодостаточные документы (обычно JSON/BSON). Документы группируются в коллекции. Каждый документ имеет уникальный
_id.Главное преимущество — гибкая схема. Документы в одной коллекции могут иметь разную структуру. Это идеально для каталогов товаров, CMS, профилей пользователей.
Вложенность позволяет хранить связанные данные внутри документа. Это ускоряет чтение (один запрос вместо нескольких JOIN), но приводит к денормализации и дублированию.
MongoDB — безусловный лидер среди документных БД. Богатый язык запросов, агрегационный pipeline, транзакции, шардирование, репликация.
Проектирование схемы в документных БД — это искусство компромисса. Встраивать или ссылаться? Денормализовать или нормализовать? Ответ зависит от паттернов доступа.
Индексы критически важны. Без индексов запросы на больших коллекциях будут медленными. MongoDB поддерживает B-Tree, текстовые, геопространственные, составные, частичные, TTL-индексы.