Новая история

Сократите стоимость MongoDB на 79% с помощью оптимизации Shape-First

к Hayk Ghukasyan8m2025/04/18
Read on Terminal Reader

Слишком долго; Читать

Счет MongoDB фиктивного стартапа поднялся на 20%, когда Atlas автоматически масштабировался до M60.Профилируя медленные операции, фиксируя запросы N + 1 с помощью $lookup, ограничивая/TTL'ing неограниченные запросы, переопределяя jumbo-документы и перегруппируя индексы в течение 48-часового спринта, они сократили свою ежемесячную стоимость с $ 15,284 до $ 3,210 (-79%) и улучшили задержку p95 с 1,9 s до 140 msno-sharding требуется.
featured image - Сократите стоимость MongoDB на 79% с помощью оптимизации Shape-First
Hayk Ghukasyan HackerNoon profile picture
0-item


Защитите свою базу данных от будущих пожаров, чтобы избежать масштабных потерь капитала на этапе Серии А

Защитите свою базу данных от будущих пожаров, чтобы избежать масштабных потерь капитала на этапе Серии А


Ответственность: Ниже приведенное является вымышленным исследованием случаев, используемым для коммуникации лучших практик для проектирования схем MongoDB, настройки производительности и оптимизации затрат.

Disclaimer: The following is a fictional case study used to communicate best practices for MongoDB schema design, performance tuning, and cost optimization


День, когда Билл стал ядерным

Призыв прошел через2:17. .


Atlas caused another unsolicited production cluster scale-up that resulted in an M60Машины с ежемесячной стоимостью$15kСовет хотел узнать, почему ожог увеличился на 20% в то время как M60 служит дорогостоящим$15 k/monthсистемы .


I opened the profiler:

db.system.profile.aggregate([
  { $match: { millis: { $gt: 100 } } },
  { $group: {
      _id: { op: "$op", ns: "$ns", query: "$command.filter" },
      n: { $sum: 1 },
      avgMs: { $avg: "$millis" }
  }},
  { $sort: { n: -1 } }, { $limit: 10 }
]).pretty();


Каждый виджет Dashboard требовал, чтобы нарушитель вытащил в общей сложности1.7 GBЧистое количество использования памяти создало горные вершины в графике, которые напоминают Эверест.


The M30 servers now operate with one of these clusters. The solution did not entail an increase in shards. Three common flaws known as Формы преступленийсуществовало в кодовой базе до ликвидации.


Расследование криминальной сцены

2.1 N + 1 Запрос Цунами

Это признается анти-образцом — при заказе одного набора заказов требуется выполнение N отдельных запросов для получения строк заказов.

// Incorrect:  Orders   +   1 000 extra queries
const orders = await db.orders.find({ userId }).toArray();
for (const o of orders) {
  o.lines = await db.orderLines.find({ orderId: o._id }).toArray();
}


Hidden taxes

→Метр Почему он шипает→→→→→→→→

Compute

1 000 cursors = 1 000 context switches

Хранилище I/O

1 000 index walks + 1 000 doc deserialisations

Сеть

Each round‑trip eats ~1 ms RTT + TLS handshake overhead

Метр Почему он шипает→→

Compute

1000 курсоров = 1000 контекстных переключателей→→I/O 1 000 индексных ходов + 1 000 дезериализаций doc→Сеть Каждая круговая поездка съедает ~1 мс RTT + TLS рукопожатие→Метр Почему он шипаетМетр

Метр

Почему она шипает

Почему она шипает

→вычислить→1000 курсоров = 1000 контекстных переключателей→→

вычислить

Compute

1000 курсоров = 1000 контекстных переключателей

1000 курсоров = 1000 контекстных переключателей

→Хранилище I/O→1 000 индексных ходов + 1 000 дезериализаций doc→Хранилище I/O

Storage I/O

1 000 индексных ходов + 1 000 дезериализаций doc

1 000 индексных ходов + 1 000 дезериализаций doc

Сеть Каждая круговая поездка съедает ~1 мс RTT + TLS рукопожатиеСеть

Network

Каждый круг съедает ~1 мс RTT + TLS рукопожатие над головой

Каждый круг съедает ~1 мс RTT + TLS рукопожатие над головой


Refactor (4 lines):

// Success: Single round‑trip, 1 read unit per order
db.orders.aggregate([
  { $match: { userId } },
  { $lookup: {
      from: "orderLines",
      localField: "_id",
      foreignField: "orderId",
      as: "lines"
  }},
  { $project: { lines: 1, total: 1, ts: 1 } }
]);


Задержка p95 снизилась с 2300 мс до 160 мс.

2 300 мс160 мс

Atlas read‑ops: 101 → 1.Это 99% скидка — никакого кода купона не требуется.


2.2 Неограниченный запрос Firehose

«Но мы должны показать полную историю кликов!»

«Но мы должны показать полную историю кликов!»


Конечно — просто не в одном курсоре.

// Failure: Streams 30 months of data through the API gateway
db.events.find({ userId }).toArray();


Fix: hard-cap партию и проект только поля, которые вы делаете.

db.events.find(
  { userId, ts: { $gte: ISODate(new Date() - 1000*60*60*24*30) } },
  { _id: 0, ts: 1, page: 1, ref: 1 }     // projection
).sort({ ts: -1 }).limit(1_000);


Тогда пусть монго очистится позади вас:

// 90‑day sliding window
db.events.createIndex({ ts: 1 }, { expireAfterSeconds: 60*60*24*90 });


Один клиент финтех сократил свой счет за хранение на 72% за ночь, просто добавив TTL.

Один клиент финтех сократил свой счет за хранение на 72% за ночь, просто добавив TTL.


2.2 Jumbo-Document Money Pit

Mongo покрывает документы на 16 МБ, но все, что превышает 256 КБ, уже является красным флагом.

{
  "_id": "...",
  "type": "invoice",
  "customer": { /* 700 kB */ },
  "pdf": BinData(0,"..."),        // 4 MB binary
  "history": [ /* 1 200 delta rows */ ],
  "ts": ISODate()
}


Why it hurts

  1. Весь документ настраивается, даже если вы прочтете одно поле.
  2. WiredTiger не может хранить столько документов на страницу → более низкое соотношение ударов кэша.
  3. Записи в индексе огромны → фильтр пропускает → больше дисков ищет.

Solution: →schema‑by‑access‑pattern: →

graph TD
  Invoice[(invoices<br/>&lt;2 kB)] -->|ref| Hist[history<br/>&lt;1 kB * N]
  Invoice -->|ref| Bin[pdf‑store (S3/GridFS)]


Мета-фактуры для небольших счетов остаются горячими; BLOBS в S3 стоит $0.023/GB-месяц вместо NAND-класса SSD Atlas.

Мета-фактуры для небольших счетов остаются горячими; BLOBS в S3 стоит $0.023/GB-месяц вместо NAND-класса SSD Atlas.


Четыре еще формы преступлений, в которых вы, вероятно, виноваты

  1. Индекс низкой кардинальности ({тип: 1, тс: -1 }) — перенаправить его на { userId: 1, тс: -1 }.
  2. $regex начинается с неиндексированного поля — сканирования строк из ада.
  3. findOneAndUpdate queue—document‐level locking bottleneck; используйте Redis/Kafka.
  4. скачивание + большая страничка с отклонением — Mongo должен считать каждый пропущенный doc; переключитесь на курсоры диапазона (ts, _id).

4 · Cost Anatomy 101

«Но Атлас говорит, что чтение дешево!»

«Но Атлас говорит, что чтение дешево!»


Давайте сделаем математику.

→Стоимость единицы метрической стоимости Стоимость чтения в месяц (3 k/s) 7.8 B $0.09 / M $702 Writes (150 / s) 380 M $0.225 / M $86 Передача данных 1.5 ТБ $0.25 / GB $375 Хранение (2 ТБ) $0.24 / GB $480
Стоимость единицы метрической стоимости Стоимость чтения в месяц (3 k/s) 7.8 B $0.09 / M $702 Writes (150 / s) 380 M $0.225 / M $86 Передача данных 1.5 ТБ $0.25 / GB $375 Хранение (2 ТБ) $0.24 / GB $480Метрическая стоимость Единая стоимость Месячная стоимость→

Метрический

Метрический

ценность

ценность

Единая стоимость

Единая стоимость

Месячные затраты

Месячные затраты

→Читать (3 к/с)→7.8 Б→$0.09 / М→$702→

Читать (3 к/с)

Читать (3 к/с)

7.8 Б

7.8 Б

$0.09 / М

$0.09 / М

$702

$702

Письма (150/с) 380 М $0.225 / М $86Письменность (150 с)

Письменность (150 с)

380 м

380 м

$ 0,225 / м

$ 0,225 / м

86 долларов

$86

Передача данных→1.5 ТБ$ 0,25 / ГБ→$375→Передача данных

Передача данных

1.5 ТБ

1.5 ТБ

$ 0,25 / ГБ

$ 0,25 / ГБ

$375

$375

Хранение (2 ТБ) $0.24 / GB $480→

Объем памяти (2 Тб)

Объем памяти (2 Тб)



$ 0,24 / ГБ

$ 0,24 / ГБ

480 долларов

$480


Всего в целом:$1,643.

Apply the fixes:

  • Читает упал на 70% → $210
  • Перевод падает на 80% → $75
  • Storage falls 60 % → $192


Новый счет: $ 564. Это один инженер среднего уровня или взлетно-посадочная полоса до четвертого квартала — вы выбираете.

New bill: $564. That’s one mid‑level engineer илиПуть до Q4 — вы выбираете.


48-часовой спасательный спринт (Battle-tested Timeline)

Hour Action Tool Wins 0‐2 Turn on profiler (slowms = 50). Mongo shell Surface top 10 slow ops. 2‐6 Rewrite N + 1 into $lookup. VS Code + Jest тестирует 90% меньше прочтений. 6‐10 Добавьте проекции и ограничивайте неограниченные находки. API layer RAM steady; API 4× faster. 10‐16 Break jumbo docs → metas + GridFS/S3. Scripted ETL Working set fits in RAM. 16‐22 Drop/place low-cardinality indexes. Compass Disk shrinks; cache hits ↑. 22‐30 Create TTLs, month‐partition cold data, enable Online Archive. Atlas UI 60 % storage saved. 30‐36 Добавьте графические панели: hit cache %, scan
→Час→Акцияинструмент→выиграл→→0‐2 Включите профиль (медленно = 50). Mongo shell Surface top 10 slow ops.→→2‐6→Перепишите N + 1 в $lookup.→VS Code + Jest тесты→На 90 процентов меньше читают.→→6‐10 Добавить проекции и ограничить неограниченные находки. API-слой RAM стабилен; API 4x быстрее.10‐16 Break jumbo docs → metas + GridFS/S3. Scripted ETL Working set вписывается в оперативную память.→16 - 22→→

Снижение/замена индексов низкой кардинальности.

→компас→

Disk shrinks; cache hits ↑.

→→22‐30 Создание TTL, холодные данные месячного раздела, включение онлайн-архива.→30‐36 Добавьте панели Grafana: кеш-хит %, сканирование:ix ratio, скорость отключения.→36‐48 Тест нагрузки с k6 k6 + метрики Atlas Подтвердить p95 < 150 мс @ 2× нагрузка.→→Час→Акцияинструмент→выиграл→Час

Час

Акция

Акция

инструмент

инструмент

выиграл

выиграл

→0 — 2→Нажмите на профиль ( slow = 50 )→Шелл Mongo→Название: Surface Top 10 Slow Ops.→0 — 2

0 — 2

Нажмите на профиль ( slow = 50 )

Обратите внимание на профиль (slowms = 50)

Mongo shell

Шелл Mongo

Название: Surface Top 10 Slow Ops.

Surface top 10 slow ops.

→2‐6→Перепишите N + 1 в $lookup.→VS Code + Jest тесты→На 90 процентов меньше читают.→→

2‐6

2‐6

Перепишите N + 1 в $lookup.

Перепишите N + 1 в $lookup.

VS Code + Jest tests

VS Code + Jest тесты

На 90 процентов меньше читают.

На 90 процентов меньше читают.

6‐10 Добавить проекции и ограничить неограниченные находки. API-слой RAM стабилен; API 4x быстрее.6 — 10

6‑10

Добавить прогнозы и ограничить неограниченные находки.

Добавить прогнозы &limitНеограниченные находки.

Огненный лазер

API layer

RAM стабильный; API 4x быстрее.

RAM steady; API 4× faster.

10‐16 Break jumbo docs → metas + GridFS/S3. Scripted ETL Working set вписывается в оперативную память.→

10 — 16

10 — 16

Break jumbo docs → metas + GridFS/S3.

Разбивайте Jumbo Docs → Metas + GridFS/S3.

Сценарий ETL

Сценарий ETL

Рабочий набор устанавливается в RAM.

Working set fits in RAM.

→16 - 22→→

Drop/replace low‑cardinality indexes.

→компас

Disk shrinks; cache hits ↑.

→16 - 22

16 - 22

Снижение/замена индексов низкой кардинальности.

Снижение/замена индексов низкой кардинальности.

компас

компас

Disk shrinks; cache hits ↑.

Disk shrinks; cache hits ↑.

22‑30

→Создайте TTL, холодные данные месячного раздела, включите онлайн-архив.→Атлас УИ→Сохранено 60 % хранилища.

22‑30

22‑30

Create TTLs, month‑partition cold data, enable Online Archive.

Create TTLs, month‑partition cold data, enable Online Archive.

Атлас УИ

Атлас УИ

60 % storage saved.

Сохранено 60 % хранилища.

30 — 36→→

Add Grafana panels: cache hit %, scan:ix ratio, eviction rate.

→ПрометейВизуальные ранние предупреждения.30 — 36

30 — 36

Add Grafana panels: cache hit %, scan:ix ratio, eviction rate.

Add Grafana panels: cache hit %, scan:ix ratio, eviction rate.

Прометей

Prometheus

Visual early warnings.

Visual early warnings.

→→

36‑48

Тест на загрузку с K6k6 + атлас метрики→Подтвердить p95 < 150 мс @ 2× нагрузка.→→

36‑48

36‑48

Тест на загрузку с K6

Тест на загрузку с K6

k6 + Atlas metrics

k6 + атлас метрики

Confirm p95 < 150 ms @ 2× load.

Confirm p95 < 150 ms @ 2× load.


Self‑Audit Checklist—Pin It Above Your Desk

  • Largest doc ÷ median > 10? → Refactor.

  • Cursor returns > 1 000 docs? → Paginate.

  • TTL on every event/store table? (Yes/No)

  • Любой индекс, где кардинальность < 10 %? → Drop/re‐order.
  • Profiler slowops > 1 % total ops? → Optimize or cache.


If primary cache hits remain under 90% it is wise to separate collections or add additional RAM memory post fixes.

Place the checklist on your laptop with adhesive glue after laminating it for printing.


Почему Shape Beats индексы

MongoDB’s query planner does a cost‑based search across candidate plans. The cost vector includes:

workUnits = ixScans + fetches + sorts + #docs returned


Индексы только снижаютixScansПлохая форма инфляцииfetchesиsorts, which often dominate. Example:

db.logs.find(
  { ts: { $gte: start, $lt: end }, level: "error" }
).sort({ level: 1, ts: -1 });


Индекс{ level: 1, ts: -1 }Планер не помогает избегать поимки каждого документа, когда он добавляет предикат к не упомянутому полю массива в ваших проекциях. Нетто-результат: 20 k поимки на 200 ударов.


Live Metrics You Should Be Watching (Графана PromQL)

# WiredTiger cache hit ratio
(rate(wiredtiger_blockmanager_blocks_read[1m]) /
 (rate(wiredtiger_blockmanager_blocks_read[1m]) +
  rate(wiredtiger_blockmanager_blocks_read_from_cache[1m]))
) < 0.10


Alert if > 10 % misses for 5 m.

# Docs scanned vs returned
rate(mongodb_ssm_metrics_documents[1m]{state="scanned"}) /
rate(mongodb_ssm_metrics_documents[1m]{state="returned"}) > 100


If you scan 100× more docs than you return, you’re burning money.

Если вы сканируете в 100 раз больше документов, чем возвращаете, вы сжигаете деньги.


Hands‑On: Thin‑Slice Migration Script

Нужно взломать 1‐TBevents collection into clicks, views, loginsбез задержки времени? используйтеdouble‑write / backfill pattern.

// 1. Add trigger
const changeStream = db.events.watch([], { fullDocument: 'updateLookup' });
changeStream.on('change', ev => {
  const dest = db[`${ev.fullDocument.type}s`];
  dest.insertOne({ ...ev.fullDocument });
});

// 2. Backfill historical in chunks
let lastId = ObjectId("000000...");
while (true) {
  const batch = db.events.find({_id: {$gt: lastId}}).sort({_id: 1}).limit(10_000);
  if (!batch.hasNext()) break;
  const docs = batch.toArray();
  docs.forEach(d => db[`${d.type}s`].insertOne(d));
  lastId = docs[docs.length - 1]._id;
}


Zero downtime, minimal extra storage (thanks to TTL), everyone sleeps.


Когда ShardingIs the Answer

Согласно правилу большого пальца вы должны разорвать только в том случае, еслиoneиз этих условий происходит после оптимизации вашей базы данных:

  1. Система работает с рабочим набором, представляющим более 80 процентов оперативной памяти независимо от скорости производительности кэша.
  2. Система генерирует более 15 тысяч операций в секунду в своей пиковой производительности при использовании одного основного сервера.
  3. Your main priorities should be maintaining sub-70-millisecond multi-region latency because high AWS billing costs do not represent your critical concern.


The decision should be simple when the conditions do not match these rules.


Case Study Wrap‑Up

→Метрический до после Δ отпечаток оперативной памяти 120 ГБ 36 ГБ −70 % Читание/сек 6 700 900 −86 % Хранение (горячее) 2.1 ТБ 600 ГБ −71 % задержка p95 1.9 с 140 мс −92 % Атлас стоимость/месяц $15 284 $3 210 −79 %
→Метрический

До этого

After

Δ

→→→

Отпечатки рук

→→

120 Гб

→36 Гб−70 %→→

Читать / Sec

→6 700900→−86 %→Теплое хранение (Hot Storage)2.1 ТБ→→

600 GB

−71 %

p95 задержка 1,9 с 140 мс −92 %→Стоимость атласа / mo.→$15 284→$3 210→−79 %→→Метрический

До этого

After

Δ

Метрический

Metric

До этого

До этого

После

After

Δ

Δ

→→

Отпечатки рук

→→

120 Гб

→36 Гб−70 %→→

Отпечатки рук

Отпечатки рук

120 Гб

120 Гб

36 Гб

36 Гб

−70 %

−70 %

Читать / Sec

→6 700900→−86 %→

Читать / Sec

Читать / Sec

6 700

6 700

900

900

−86 %

−86 %

→Теплое хранение (Hot Storage)2.1 ТБ→600 Гб→

−71 %

Теплое хранение (Hot Storage)

Теплое хранение (Hot Storage)

2.1 ТБ

2.1 ТБ

600 GB

600 GB

−71 %

−71 %

p95 latency

→→

1.9 С

140 мс→

−92 %

p95 latency

P95 Латентность

1.9 s

1.9 С

140 мс

140 мс

−92 %

−92 %

→Стоимость атласа / mo.→$15 284→$3 210→−79 %→Стоимость атласа / mo.

Стоимость атласа / mo.

$15 284

$15 284

$3 210

$3 210

−79 %

−79 %


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

No shards, no major code freeze, just ruthless shape surgery.


Оригинальное название: Debt vs. Death Spiral

Требование быстрого доставки является обязательным, но поддержание некачественной работы относится к добровольному накоплению долгов. Поставщик облачных услуг взимает с вас сложные проценты, которые накапливаются с годовой процентной ставкой 1000 % на ваш неоплаченный долг. Мы изучили кредитные карты MongoDB с высоким процентом, поскольку они представляют собой пять видов преступлений, которые мы изучали.


You need to open the profiler to work with the $lookupпистоны при добавлении пыли TTL, а затем разверните свой проект в строгом режиме. Ваша доска и ваша команда разработчиков и ваш pager в 02:17 получат качественный отдых.


Продолжайте переработка вашего кода, пока не произойдет следующий инцидент автоскалирования.

Trending Topics

blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks