Как делать11 октября 2026 г.8 мин чтения
Базы данныхШаг 11 из 12

NoSQL: документы, ключ-значение и когда они лучше SQL

Коротко

NoSQL — общее имя для баз, которые хранят данные не в связанных таблицах: документами (MongoDB), парами «ключ — значение» (Redis), широкими строками (Cassandra) или графом (Neo4j). Документная модель удобна, когда данные читают и меняют вместе — например, заказ с позициями; она мешает, когда между сущностями много связей, общие данные дублируются, а одна операция должна атомарно изменить несколько записей. Если почти все данные реляционные и гибкой должна быть только часть, обычно хватает колонки jsonb в PostgreSQL.

СравнениеУровень: СреднийКонцепции NoSQL

После материала вы сможете

  • различить документную, key-value, колоночную и графовую модели и назвать типичную задачу для каждой
  • описать заказ с позициями документом в MongoDB и строкой с jsonb в PostgreSQL и сравнить запросы к ним
  • объяснить, где встраивание вредит: дубли данных, отсутствие проверки схемы, изменения в нескольких документах сразу
  • выбрать хранилище по таблице и обосновать выбор

Проверено на

MongoDB 8.3 (mongo:8), mongosh 2.12PostgreSQL 17.11Redis 7.4

Сверено с документацией: 11 октября 2026 г.

Содержание
  1. 01Таблицы и четыре семейства NoSQL
  2. 02Документная модель: заказ одним документом
  3. 03Где документ мешает: дубли, схема, транзакции
  4. 04Ключ — значение, широкие строки и графы
  5. 05jsonb в PostgreSQL: документ внутри таблицы
  6. 06Таблица выбора

Таблицы и четыре семейства NoSQL

Реляционная база хранит данные в таблицах с заранее объявленными колонками и связывает их ключами, а запрос собирает нужное через JOIN. Словом NoSQL называют хранилища, устроенные иначе. Общего у них немного: каждое оптимизировано под свой способ доступа к данным и жертвует частью того, что реляционные СУБД дают по умолчанию, — строгой схемой, произвольными JOIN или транзакциями, которые затрагивают много записей.

Реляционная

Как хранит данные
Таблицы и строки, связи по ключам
Пример
PostgreSQL, MySQL
Сильная сторона
Любые запросы и JOIN, ограничения, транзакции
Слабое место
Изменение структуры — через миграции

Документная

Как хранит данные
JSON-подобные документы (в MongoDB — BSON) в коллекциях
Пример
MongoDB
Сильная сторона
Объект целиком читается одним запросом, поля у документов могут различаться
Слабое место
Связи между документами и дубли данных

Ключ — значение

Как хранит данные
Значение по уникальному ключу, в памяти
Пример
Redis
Сильная сторона
Простые и быстрые операции по ключу, срок жизни ключа (TTL)
Слабое место
Поиск по содержимому требует отдельных структур

Широкие строки (wide-column)

Как хранит данные
Таблицы, разложенные по узлам кластера по ключу раздела
Пример
Apache Cassandra
Сильная сторона
Запись и чтение по ключу на многих серверах
Слабое место
Нет JOIN, таблицу проектируют под конкретный запрос

Графовая

Как хранит данные
Узлы, связи между ними и свойства
Пример
Neo4j
Сильная сторона
Обход связей любой глубины
Слабое место
Узкий профиль: остальные данные обычно живут в другой базе

Документная модель: заказ одним документом

Возьмём заказ кофейни: клиент и позиции. В реляционной базе это минимум три таблицы — заказы, позиции и клиенты. В MongoDB заказ — один документ: клиент лежит в нём вложенным объектом, позиции — массивом. Основное правило моделирования в документации MongoDB звучит так: данные, которые читают вместе, хранят вместе. Для повторения достаточно Docker: docker run -d --rm --name kb-mongo-nosql mongo:8 поднимает временный сервер, который удалится после docker stop.

orders.mongo.js
// orders.mongo.js — run: mongosh shop --quiet orders.mongo.js
const show = (x) => print(JSON.stringify(x));
db.orders.drop();

db.orders.insertMany([
  {
    _id: 1001,
    status: "paid",
    customer: { id: 7, name: "Aigerim", email: "aigerim@example.com" },
    items: [
      { sku: "LATTE", title: "Latte", price: 1600, qty: 2 },
      { sku: "CROISSANT", title: "Croissant", price: 1200, qty: 1 }
    ]
  },
  {
    _id: 1002,
    status: "new",
    customer: { id: 8, name: "Timur", email: "timur@example.com" },
    items: [{ sku: "LATTE", title: "Latte", price: 1600, qty: 1 }]
  },
  {
    _id: 1003,
    status: "paid",
    customer: { id: 7, name: "Aigerim", email: "aigerim@example.com" },
    items: [{ sku: "ESPRESSO", title: "Espresso", price: 1100, qty: 3 }]
  }
]);

// 1. The whole order in one read, no JOIN
show(db.orders.findOne({ _id: 1001 }, { _id: 0, "customer.name": 1, "items.sku": 1, "items.qty": 1 }));

// 2. Orders that contain a latte (search inside an array)
show(db.orders.find({ "items.sku": "LATTE" }, { _id: 1 }).toArray());

// 3. Revenue per product in paid orders
show(db.orders.aggregate([
  { $match: { status: "paid" } },
  { $unwind: "$items" },
  { $group: { _id: "$items.sku", revenue: { $sum: { $multiply: ["$items.price", "$items.qty"] } } } },
  { $sort: { revenue: -1 } }
]).toArray());

// 4. Add an item and change the status: one atomic write to one document
show(db.orders.updateOne(
  { _id: 1002 },
  { $push: { items: { sku: "CROISSANT", title: "Croissant", price: 1200, qty: 1 } }, $set: { status: "paid" } }
));

// 5. Customer data is copied into every order: the email changed, so fix every copy
show(db.orders.updateMany({ "customer.id": 7 }, { $set: { "customer.email": "aigerim.new@example.com" } }));
Три заказа и пять операций: чтение, поиск в массиве, агрегация, атомарное изменение, обновление копий
terminal
$ docker cp orders.mongo.js kb-mongo-nosql:/tmp/
$ docker exec kb-mongo-nosql mongosh shop --quiet /tmp/orders.mongo.js
{"customer":{"name":"Aigerim"},"items":[{"sku":"LATTE","qty":2},{"sku":"CROISSANT","qty":1}]}
[{"_id":1001},{"_id":1002}]
[{"_id":"ESPRESSO","revenue":3300},{"_id":"LATTE","revenue":3200},{"_id":"CROISSANT","revenue":1200}]
{"acknowledged":true,"insertedId":null,"matchedCount":1,"modifiedCount":1,"upsertedCount":0}
{"acknowledged":true,"insertedId":null,"matchedCount":2,"modifiedCount":2,"upsertedCount":0}
Реальный вывод mongosh 2.12 на MongoDB 8.3 (образ mongo:8)

Первый запрос возвращает заказ вместе с позициями без всяких JOIN. Второй ищет внутри массива: условие "items.sku": "LATTE" выполняется, если подходит хотя бы один элемент. Третий — конвейер агрегации: $unwind разворачивает массив в отдельные записи, $group суммирует. Четвёртый добавляет позицию и меняет статус одной операцией: запись в один документ в MongoDB атомарна, даже если затрагивает несколько вложенных полей, — либо применятся все изменения, либо ни одно.

Обратите внимание на цену в позиции. Это не дубль, а снимок: цена на момент заказа не должна меняться вслед за прайсом. Именно такие данные — принадлежащие родителю, ограниченные по объёму и читаемые вместе с ним — документация MongoDB советует встраивать.

Встраивать в документ

  • отношение «содержит»: заказ содержит позиции
  • данные читают и обновляют вместе
  • вложенный массив ограничен по размеру
  • данные архивируют вместе с родителем

Хранить отдельно и ссылаться

  • у родителя много дочерних записей, и их число растёт без предела
  • дубли сложно поддерживать актуальными
  • вложенные данные часто пишут отдельно от родителя
  • сущность существует сама по себе: клиент, товар

Где документ мешает: дубли, схема, транзакции

В примере клиент встроен в каждый заказ. Пока это снимок «кому доставили», всё в порядке. Но если по тем же данным работает учёт клиентов и клиент меняет email, пятой операции пришлось обновить два документа — matchedCount: 2. На реальной базе это тысячи копий, и обновлять их приходится коду приложения. В реляционной схеме email хранится в одной строке таблицы customers.

Вторая цена гибкости — схема. Коллекция по умолчанию принимает документы любой формы. Ниже цена записана строкой: вставка проходит, а ошибка всплывает позже, в отчёте.

schema.mongo.js
// schema.mongo.js — run after orders.mongo.js
const show = (x) => print(JSON.stringify(x));

// Nobody checks the schema: a price stored as a string is accepted
show(db.orders.insertOne({ _id: 1004, status: "paid", items: [{ sku: "LATTE", price: "1600", qty: 1 }] }));

// ...and breaks the report later, at read time
try {
  db.orders.aggregate([
    { $match: { status: "paid" } },
    { $unwind: "$items" },
    { $group: { _id: "$items.sku", revenue: { $sum: { $multiply: ["$items.price", "$items.qty"] } } } }
  ]).toArray();
} catch (e) {
  print(e.message);
}
db.orders.deleteOne({ _id: 1004 });

// A $jsonSchema validator: you switch the checks on yourself
db.runCommand({
  collMod: "orders",
  validator: {
    $jsonSchema: {
      bsonType: "object",
      required: ["status", "items"],
      properties: {
        status: { enum: ["new", "paid", "cancelled"] },
        items: {
          bsonType: "array",
          minItems: 1,
          items: {
            bsonType: "object",
            required: ["sku", "price", "qty"],
            properties: {
              price: { bsonType: ["int", "long", "double", "decimal"], minimum: 0 },
              qty: { bsonType: "int", minimum: 1 }
            }
          }
        }
      }
    }
  }
});

try {
  db.orders.insertOne({ _id: 1005, status: "paid", items: [{ sku: "LATTE", price: "1600", qty: 1 }] });
} catch (e) {
  print(e.errmsg);
}
Без валидатора неверный документ записывается, с валидатором $jsonSchema — отклоняется
terminal
$ docker exec kb-mongo-nosql mongosh shop --quiet /tmp/schema.mongo.js
{"acknowledged":true,"insertedId":1004}
Executor error during aggregate command on namespace: shop.orders :: caused by :: $multiply only supports numeric types, not string
Document failed validation
Реальный вывод: вставка без ошибок, падение агрегации при чтении и отказ после включения валидатора

Валидатор $jsonSchema решает проблему, но его нужно спроектировать и поддерживать так же, как схему таблиц. В подробностях ошибки (errInfo) MongoDB указывает поле price и причину type did not match. Уже записанные документы при включении правил по умолчанию не перепроверяются: уровень strict применяет их ко всем новым вставкам и обновлениям.

Третье — транзакции. В MongoDB атомарна операция над одним документом, поэтому встраивание часто избавляет от транзакций. Транзакции через несколько документов есть — на наборах реплик и шардированных кластерах, — но документация предупреждает: обычно они дороже записи в один документ и не должны заменять продуманную схему. Если бизнес-операция по своей природе меняет несколько независимых сущностей — оплату, складской остаток, бонусный счёт, — реляционная база с транзакциями и внешними ключами решает её прямее.

Ключ — значение, широкие строки и графы

Redis хранит данные в оперативной памяти и описывает себя как сервер структур данных: кроме строк есть хеши, списки, множества, сортированные множества, потоки и другие типы. Типичные задачи — кэш, сессии, счётчики, очереди, ограничение частоты запросов. Значение достают по точному ключу, поэтому ключи проектируют под способ чтения: найти «все сессии пользователя 7» без отдельной структуры не получится. Временный сервер для примера: docker run -d --rm --name kb-redis-nosql redis:7-alpine.

terminal
$ docker exec -it kb-redis-nosql redis-cli
127.0.0.1:6379> SET session:9f2c '{"userId":7,"cart":["LATTE"]}' EX 1800
OK
127.0.0.1:6379> GET session:9f2c
"{\"userId\":7,\"cart\":[\"LATTE\"]}"
127.0.0.1:6379> TTL session:9f2c
(integer) 1800
127.0.0.1:6379> INCR views:product:LATTE
(integer) 1
127.0.0.1:6379> INCR views:product:LATTE
(integer) 2
127.0.0.1:6379> DEL session:9f2c
(integer) 1
127.0.0.1:6379> GET session:9f2c
(nil)
Реальные ответы Redis 7.4 на команды по порядку

EX 1800 задаёт срок жизни: через 30 минут ключ исчезнет сам. INCR атомарно увеличивает счётчик, даже если его одновременно вызывают сотни запросов. Как использовать Redis как кэш перед основной базой, разбираем в уроке о кэшировании.

Базы с широкими строками, например Apache Cassandra, распределяют данные по узлам кластера по ключу раздела. Документация Cassandra прямо говорит, что моделирование в ней идёт от запросов, а JOIN не поддерживаются, поэтому данные денормализуют: одну и ту же информацию кладут в несколько таблиц, каждую — под свой запрос. Это выбор для очень больших объёмов записи на многих серверах, а не для первой версии продукта.

Графовые базы, например Neo4j, хранят узлы и связи как равноправные данные: у связи есть тип, направление и свойства. Они удобны, когда главный вопрос — путь и связи: друзья друзей, рекомендации, маршруты, зависимости между объектами. Реляционная база отвечает на такие вопросы цепочкой JOIN или рекурсивным запросом, и чем глубже обход, тем сложнее запрос.

jsonb в PostgreSQL: документ внутри таблицы

В PostgreSQL для JSON есть два типа. json сохраняет текст как есть и разбирает его при каждом обращении. jsonb хранит разобранное двоичное представление: вставка чуть медленнее, зато обработка значительно быстрее, а для поиска есть индексы GIN. Документация рекомендует большинству приложений jsonb. Ниже — те же заказы: статус в обычной колонке с проверкой, остальное в документе. Сервер: docker run -d --rm --name kb-pg-nosql -e POSTGRES_PASSWORD=test postgres:17-alpine.

orders_jsonb.sql
-- orders_jsonb.sql — run: psql -U postgres -f orders_jsonb.sql
DROP TABLE IF EXISTS orders_doc;

CREATE TABLE orders_doc (
  id     integer PRIMARY KEY,
  status text NOT NULL CHECK (status IN ('new', 'paid', 'cancelled')),
  doc    jsonb NOT NULL
);

INSERT INTO orders_doc VALUES
  (1001, 'paid', '{"customer": {"id": 7, "name": "Aigerim", "email": "aigerim@example.com"},
                   "items": [{"sku": "LATTE", "title": "Latte", "price": 1600, "qty": 2},
                             {"sku": "CROISSANT", "title": "Croissant", "price": 1200, "qty": 1}]}'),
  (1002, 'new',  '{"customer": {"id": 8, "name": "Timur", "email": "timur@example.com"},
                   "items": [{"sku": "LATTE", "title": "Latte", "price": 1600, "qty": 1}]}'),
  (1003, 'paid', '{"customer": {"id": 7, "name": "Aigerim", "email": "aigerim@example.com"},
                   "items": [{"sku": "ESPRESSO", "title": "Espresso", "price": 1100, "qty": 3}]}');

-- GIN index for the containment operator @>
CREATE INDEX orders_doc_gin ON orders_doc USING GIN (doc jsonb_path_ops);

-- 1. The whole order is one row, no JOIN
SELECT doc -> 'customer' ->> 'name' AS customer,
       jsonb_path_query_array(doc, '$.items[*].sku') AS skus
FROM orders_doc WHERE id = 1001;

-- 2. Orders that contain a latte
SELECT id FROM orders_doc
WHERE doc @> '{"items": [{"sku": "LATTE"}]}'
ORDER BY id;

-- 3. Revenue per product in paid orders
SELECT item ->> 'sku' AS sku,
       sum((item ->> 'price')::int * (item ->> 'qty')::int) AS revenue
FROM orders_doc, jsonb_array_elements(doc -> 'items') AS item
WHERE status = 'paid'
GROUP BY 1
ORDER BY revenue DESC;

-- 4. Add an item and change the status in one UPDATE
UPDATE orders_doc
SET doc = jsonb_set(doc, '{items}',
          (doc -> 'items') || '{"sku": "CROISSANT", "title": "Croissant", "price": 1200, "qty": 1}'),
    status = 'paid'
WHERE id = 1002;

-- 5. Customer data is copied into every order: fix every copy
UPDATE orders_doc
SET doc = jsonb_set(doc, '{customer,email}', '"aigerim.new@example.com"')
WHERE doc @> '{"customer": {"id": 7}}';
Те же заказы и те же операции в PostgreSQL
terminal
$ docker cp orders_jsonb.sql kb-pg-nosql:/tmp/
$ docker exec kb-pg-nosql psql -U postgres -f /tmp/orders_jsonb.sql
DROP TABLE
CREATE TABLE
INSERT 0 3
CREATE INDEX
 customer |          skus          
----------+------------------------
 Aigerim  | ["LATTE", "CROISSANT"]
(1 row)

  id  
------
 1001
 1002
(2 rows)

    sku    | revenue 
-----------+---------
 ESPRESSO  |    3300
 LATTE     |    3200
 CROISSANT |    1200
(3 rows)

UPDATE 1
UPDATE 2
Реальный вывод psql на PostgreSQL 17.11 (строка NOTICE от DROP TABLE IF EXISTS опущена)

Результаты совпадают с MongoDB. @> — оператор вхождения: строка подходит, если документ содержит указанный фрагмент. Класс индекса jsonb_path_ops поддерживает именно такие запросы и, по документации, обычно меньше индекса по умолчанию. Поля, по которым часто фильтруют и которые должны быть строгими, удобнее держать обычными колонками, а изменчивую часть — в jsonb.

jsonb_extra.sql
-- json keeps the text as is, jsonb keeps the parsed value
SELECT '{"b": 1, "a": 2, "a": 3}'::json  AS as_json,
       '{"b": 1, "a": 2, "a": 3}'::jsonb AS as_jsonb;

-- With three rows the planner prefers a full scan; switch it off to see that the index fits
SET enable_seqscan = off;
EXPLAIN (COSTS OFF)
SELECT id FROM orders_doc WHERE doc @> '{"items": [{"sku": "LATTE"}]}';
RESET enable_seqscan;

-- jsonb does not check the structure either: a string price gets in...
INSERT INTO orders_doc VALUES (1004, 'paid', '{"items": [{"sku": "LATTE", "price": "1600", "qty": 1}]}');
DELETE FROM orders_doc WHERE id = 1004;

-- ...until you add a constraint
ALTER TABLE orders_doc ADD CONSTRAINT items_is_array
  CHECK (jsonb_typeof(doc -> 'items') = 'array');
INSERT INTO orders_doc VALUES (1005, 'new', '{"items": {"sku": "LATTE"}}');
json против jsonb, индекс и проверка структуры
terminal
$ docker exec kb-pg-nosql psql -U postgres -f /tmp/jsonb_extra.sql
         as_json          |     as_jsonb     
--------------------------+------------------
 {"b": 1, "a": 2, "a": 3} | {"a": 3, "b": 1}
(1 row)

SET
                             QUERY PLAN                              
---------------------------------------------------------------------
 Bitmap Heap Scan on orders_doc
   Recheck Cond: (doc @> '{"items": [{"sku": "LATTE"}]}'::jsonb)
   ->  Bitmap Index Scan on orders_doc_gin
         Index Cond: (doc @> '{"items": [{"sku": "LATTE"}]}'::jsonb)
(4 rows)

RESET
INSERT 0 1
DELETE 1
ALTER TABLE
psql:/tmp/jsonb_extra.sql:18: ERROR:  new row for relation "orders_doc" violates check constraint "items_is_array"
DETAIL:  Failing row contains (1005, new, {"items": {"sku": "LATTE"}}).
Реальный вывод psql: план с отключённым полным перебором и отказ ограничения CHECK
  • jsonb убирает повторяющиеся ключи (остаётся последнее значение) и не хранит порядок ключей; json сохраняет текст без изменений.
  • Индекс подходит для запроса с @>. На трёх строках планировщику выгоднее полный перебор, поэтому для демонстрации он отключён.
  • jsonb, как и MongoDB без валидатора, примет цену строкой: структуру проверяют ограничения CHECK или код приложения.

Документация PostgreSQL даёт два практичных совета. Держите у JSON-документов предсказуемую структуру — по ним проще писать запросы. И не раздувайте их: любое обновление блокирует строку целиком, поэтому крупные часто изменяемые документы мешают параллельным транзакциям.

orders_relational.sql
-- orders_relational.sql — the same orders in tables
DROP TABLE IF EXISTS order_items, orders, products, customers;

CREATE TABLE customers (
  id    integer PRIMARY KEY,
  name  text NOT NULL,
  email text NOT NULL UNIQUE
);

CREATE TABLE products (
  sku   text PRIMARY KEY,
  title text NOT NULL
);

CREATE TABLE orders (
  id          integer PRIMARY KEY,
  customer_id integer NOT NULL REFERENCES customers (id),
  status      text NOT NULL CHECK (status IN ('new', 'paid', 'cancelled'))
);

CREATE TABLE order_items (
  order_id integer NOT NULL REFERENCES orders (id),
  sku      text    NOT NULL REFERENCES products (sku),
  price    integer NOT NULL CHECK (price >= 0),  -- price at the time of the order
  qty      integer NOT NULL CHECK (qty > 0),
  PRIMARY KEY (order_id, sku)
);

INSERT INTO customers VALUES (7, 'Aigerim', 'aigerim@example.com'), (8, 'Timur', 'timur@example.com');
INSERT INTO products VALUES ('LATTE', 'Latte'), ('CROISSANT', 'Croissant'), ('ESPRESSO', 'Espresso');
INSERT INTO orders VALUES (1001, 7, 'paid'), (1002, 8, 'new'), (1003, 7, 'paid');
INSERT INTO order_items VALUES
  (1001, 'LATTE', 1600, 2), (1001, 'CROISSANT', 1200, 1),
  (1002, 'LATTE', 1600, 1),
  (1003, 'ESPRESSO', 1100, 3);

-- Revenue per product: the same result, via JOIN
SELECT i.sku, sum(i.price * i.qty) AS revenue
FROM orders o JOIN order_items i ON i.order_id = o.id
WHERE o.status = 'paid'
GROUP BY i.sku
ORDER BY revenue DESC;

-- The email lives in one place: one row
UPDATE customers SET email = 'aigerim.new@example.com' WHERE id = 7;

-- The schema rejects a string price and an item of a missing order
INSERT INTO order_items VALUES (1002, 'ESPRESSO', 'one thousand', 1);
INSERT INTO order_items VALUES (9999, 'LATTE', 1600, 1);
Для сравнения — те же заказы в таблицах со связями
terminal
$ docker exec kb-pg-nosql psql -U postgres -f /tmp/orders_relational.sql
DROP TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
INSERT 0 2
INSERT 0 3
INSERT 0 3
INSERT 0 4
    sku    | revenue 
-----------+---------
 ESPRESSO  |    3300
 LATTE     |    3200
 CROISSANT |    1200
(3 rows)

UPDATE 1
psql:/tmp/orders_relational.sql:48: ERROR:  invalid input syntax for type integer: "one thousand"
LINE 1: INSERT INTO order_items VALUES (1002, 'ESPRESSO', 'one thous...
                                                          ^
psql:/tmp/orders_relational.sql:49: ERROR:  insert or update on table "order_items" violates foreign key constraint "order_items_order_id_fkey"
DETAIL:  Key (order_id)=(9999) is not present in table "orders".
Реальный вывод psql: отчёт совпадает, email меняется в одной строке, неверные данные отклоняются (строки NOTICE от DROP TABLE IF EXISTS опущены)

В таблицах отчёт требует JOIN, зато email меняется в одной строке, а неверные данные не попадают в базу: тип колонки и внешний ключ отклоняют их сразу, при записи.

Таблица выбора

Заказы, оплаты, складские остатки

Что выбрать
PostgreSQL или MySQL
Почему
Связи, ограничения и транзакции через несколько таблиц

Каталог, где у товаров разные характеристики

Что выбрать
PostgreSQL с jsonb для характеристик или MongoDB
Почему
Общие поля — колонками, изменчивые — документом

Контент, анкеты, настройки: объект читают целиком

Что выбрать
MongoDB или jsonb
Почему
Данные читают и меняют вместе, связей мало

Кэш, сессии, счётчики, лимиты запросов

Что выбрать
Redis рядом с основной базой
Почему
Доступ по ключу, TTL, атомарный INCR

Поток событий или телеметрии на многих серверах

Что выбрать
База с широкими строками (Cassandra)
Почему
Распределение по ключу раздела, таблица под запрос

Рекомендации, социальные связи, маршруты

Что выбрать
Графовая база (Neo4j)
Почему
Обход связей произвольной глубины
  • Какие запросы будут самыми частыми: один объект целиком или срез по многим объектам?
  • Есть ли операции, которые должны атомарно изменить несколько сущностей?
  • Что продублируется при встраивании и как часто эти данные меняются?
  • Кто проверяет структуру данных — база или только код?
  • Растут ли вложенные массивы без предела?
Как проверено5 · пройденосверено 11 октября 2026 г.
  • запущено, работает

    orders.mongo.js (5 операций) и его вывод

    docker run --rm mongo:8 (MongoDB 8.3.11, mongosh 2.12.0), mongosh shop --quiet; вывод в статье — дословно из out/orders.mongo.txt, вставлен скриптом build.mjs

  • запущено, работает

    schema.mongo.js: строковая цена, ошибка $multiply, валидатор $jsonSchema

    тот же контейнер: insertOne проходит, aggregate падает «$multiply only supports numeric types, not string», после collMod вставка → «Document failed validation»; errInfo проверен отдельно: propertyName price, reason «type did not match», consideredType string

  • запущено, работает

    orders_jsonb.sql, jsonb_extra.sql, orders_relational.sql и вывод psql

    docker run --rm postgres:17-alpine (17.11), psql -f; результаты совпадают с MongoDB; EXPLAIN с enable_seqscan=off — Bitmap Index Scan on orders_doc_gin; CHECK и FK отклоняют строки; строки NOTICE от DROP TABLE IF EXISTS удалены из показанного вывода (оговорено в подписи)

  • запущено, работает

    Redis-команды (redis.txt) и вывод

    docker run --rm redis:7-alpine (7.4.10), redis-cli --no-raw < redis.txt; в статье команды и реальные ответы сведены в вид интерактивной сессии redis-cli (--no-raw даёт тот же формат ответов, что и интерактивный режим)

  • запущено, работает

    решения упражнений (exercise.mongo.js, exercise.sql)

    после основных скриптов: 7 → 7700, 8 → 2800 в обеих базах

«Запущено» — код выполнен во временном проекте или контейнере, вывод в статье взят из прогона. «Частично» — проверено с заглушками вместо внешних сервисов. «Не запускалось» — нужны устройство, аккаунт или ключ; шаги сверены с официальной документацией. Файлы проверочного проекта и логи запусков сохранены в архиве InterLAB.

Расскажите о задаче — мы изучим запрос и свяжемся с вами

Есть задача?
Обсудим и оценим — бесплатно.

Расскажите о продукте или проблеме. Предложим архитектуру, срок и стоимость первой версии после изучения задачи.

  • Оценка объёма и рисков до старта
  • NDA — по вашему или нашему шаблону
  • Старт работ в течение недели

Не присылайте через форму пароли, ключи доступа и исходный код — безопасную передачу доступа обсудим отдельно.

Нажимая кнопку, вы соглашаетесь с обработкой персональных данных.