MongoDB і гнучкі схеми даних
(Розділ: Технології та Інфраструктура)
Коротке резюме
MongoDB - документно-орієнтоване сховище з гнучкими схемами (BSON), швидкими вставками, горизонтальним масштабуванням і потужним Aggregation Pipeline. У iGaming воно відмінно підходить для профілів гравців, гнучких CRM-карток, логів подій, телеметрії, матеріалізованих проекцій зі стріму, каталогів ігор і кешованих уявлень для фронтів. Для грошових інваріантів (гаманці/ledger) частіше залишається SQL/CP-контур; MongoDB доречна як read-model і високопродуктивне документне сховище.
Де MongoDB дає максимум в iGaming
Профілі та налаштування гравців: змінні структури (налаштування локалі, уподобання, KYC-метадані).
Каталоги контенту/ігор/провайдерів: швидке читання карток, фільтри, тегування, повнотекст.
Події/телеметрія/журнали: високі TPS, тимчасові вікна, TTL-зберігання.
Матеріалізовані представлення (CQRS): швидкі екрани (лідерборди, останні дії, агрегати).
Персоналізація/фічі онлайн-ML: KV-патерни в колекціях, короткий TTL.
Принципи гнучкої схеми: дисципліна замість хаосу
MongoDB не «без схеми» - схема живе в коді і валідації.
Рекомендується:1. Схема як контракт: JSON Schema Validation в колекціях.
2. Версіонування документів полем'schemaVersion'.
3. Строгі обов'язкові поля (id, ключі пошуку), «хвіст» рідкісних атрибутів - опціонально.
4. Обмеження розмірності масивів і вкладеності (для індексів і RAM).
5. Міграції в тлі: апдейти по'schemaVersion', шедулери, бек-філли.
Приклад: JSON Schema Validation
js db.createCollection("player_profiles", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["playerId", "createdAt", "schemaVersion"],
properties: {
playerId: { bsonType: "string" },
createdAt: { bsonType: "date" },
schemaVersion: { bsonType: "int", minimum: 1 },
locale: { bsonType: "string" },
kyc: {
bsonType: "object",
properties: {
status: { enum: ["pending", "verified", "rejected"] },
doc: { bsonType: "object" }
}
}
}
}
}
});
Модель даних і проектування документів
Проектуйте «під запит»: 1 екран/ендпойнт = 1 документ або невеликий набір документів.
Денормалізація: включайте невеликі вкладені піддокументи (наприклад, міні-картки провайдерів ігор).
- Вбудовування - для тісно пов'язаних і рідко оновлюваних фрагментів.
- Посилання ('ref') - при великому розмірі/частих апдейтах/повторному використанні.
- Обмеження розміру: документ ≤ 16 МБ; великі бінарники - GridFS/об'єктні сховища.
- Аудит/метадані: `createdAt`, `updatedAt`, `traceId`, `tenantId`, `idempotencyKey`.
Індекси: якість читання і стабільність latency
Типи індексів і практики:- B-Tree (основний)
Compound: порядок полів відповідає частим предикатам і сортуванням.
Prefix-правило: для'( tenantId, playerId, createdAt)'працюють префіксні варіанти.
Сортування: враховуйте'sort'в кінці індексу (наприклад,'createdAt: -1`).
js db.bets.createIndex(
{ tenantId: 1, playerId: 1, createdAt: -1 },
{ name: "idx_bets_tenant_player_created_desc" }
);
Partial / Sparse
Прискорюють часті підмножини ('status: «pending»'), зменшують розмір.
js db.withdrawals.createIndex(
{ playerId: 1, createdAt: -1 },
{ partialFilterExpression: { status: "pending" } }
);
TTL
Для телеметрії/логів/часових фічів - автоматичне закінчення.
js db.events.createIndex({ expireAt: 1 }, { expireAfterSeconds: 0 });
Текстовий/autocomplete
'text'для повнотексту (обмеження щодо мов); для автодоповнення -'n-gram '/trigram через поля і regex-підходи або Atlas Search.
Антипатерни індексів
Індекс «на все» → падіння швидкості запису.
Низька кардинальність без partial → низька селективність.
Дублюючі компаунди.
Індексувати поля всередині гігантських масивів без лімітів.
Aggregation Pipeline: швидкі екрани та звіти
Використовуйте " " " " "як ранні стадії; проектуйте індекси під'$match/ $sort'.
«$lookup» для контрольованих джойнів (м'які, в розумних обсягах).
«$facet» для множинних метрик; «$unionWith» - об'єднання колекцій.
«$merge »/« $out» - матеріалізація результатів у колекції (read-models).
js db.bets.aggregate([
{ $match: { tenantId: "eu-1", playerId: "p123" } },
{ $sort: { createdAt: -1 } },
{ $limit: 100 },
{ $group: {
_id: "$playerId",
lastBets: { $push: { amount: "$amount", ts: "$createdAt", game: "$gameId" } },
totalAmount: { $sum: "$amount" }
} }
]);
Транзакції, узгодженість та ідемпотентність
Single-document atomic - безкоштовна атомарність; складні інваріанти - думайте про розбиття по документах.
Multi-document transactions (ACID) - є з репліка-сетами, але дорожче по latency; застосовувати точково.
- `w: «majority»'для критичних записів (вартість latency);
- `readConcern: «majority»'для узгодженого читання.
- Ідемпотентність: унікальні ключі на'idempotencyKey '/' pspTx', UPSERT-операції ('$setOnInsert','$inc').
js db.wallet.updateOne(
{ playerId: "p123" },
{ $inc: { balanceCents: -5000 }, $set: { updatedAt: new Date() } },
{ upsert: true, writeConcern: { w: "majority" } }
);
Шардування і вибір ключів
MongoDB шардує по shard key. Вибір критичний:- Розподіл навантаження: ключ високої кардинальності і рівномірним розподілом (наприклад,'( tenantId, playerId)').
- Уникайте монотонності: 'createdAt'як єдиний ключ → «гарячий» шард.
- Hashed - рівніше розподіляє записи.
- Ranged - краще для діапазонних запитів, але стежте за гарячими хвостами.
- Зона-шардинг (tag ranges) для регуляторики/локалізації (EU/LatAm/TR).
js sh.enableSharding("igaming");
db.bets.createIndex({ tenantId: 1, playerId: 1, _id: "hashed" });
sh.shardCollection("igaming.bets", { tenantId: 1, playerId: 1, _id: "hashed" });
Антипатерни:
- Шард-ключ за низькою кардинальністю («status») - перекіс шардів.
- Часті «$lookup» між шардованими колекціями без співшардування по одному ключу.
- Змінюваний shard key (міняти складно і дорого).
Репліка-сети, читання і політика read-after-write
Репліка-сет = HA і основа транзакцій.
Read Preference:- 'primary'для критичних read-after-write;
- 'primaryPreferred '/' secondary'- для аналітики/не критичних.
- Read/Write concern узгоджуйте з SLO і бюджетом latency.
Change Streams, CDC та інтеграції
Change Streams: підписка на вставки/апдейти/видалення - зручно для:- синхронізації кеш-шарів (Redis),
- тригерів CRM/повідомлень,
- завантаження в OLAP (ClickHouse/Pinot),
- Реактивні екрани.
- Outbox-патерн: для критичних доменів публікуйте події в окрему колекцію, яку потім читає конектор і транслює в шину (Kafka). Це підвищує передбачуваність інтеграцій.
Спостережуваність і SLO
SLO: p99 читання карток ≤ 10-20 мс; вставки подій ≤ 20-40 мс; різниця лейтенсі між шардами в межах X%; доступність ≥ 99. 9%.
Метрики: оп-латентність, queue depth,% юмпсів на вторинні, cache/WT статистики, page faults, lock-waits, кількість відкритих курсорів/з'єднань.
Профілювання: `system. profile','explain («executionStats»)', блокування колекцій/індексів.
Алерти: зростання WT cache pressure, повільні операції, зростання не потрапили в індекс запитів, відставання вторинних, chunk migrations/балансер.
Продуктивність і тюнінг
WiredTiger Cache: за замовчуванням ~ 50% RAM - валідуйте під профіль.
Compression: snappy/zstd для колекцій, zstd для журналів - баланс CPU/IO.
Batch-вставки і bulkWrite для телеметрії.
Projection (`{field:1}') щоб не тягти «товсті» документи.
Limit/Skip: уникайте великих'skip'→ використовуйте пагінацію по курсору/по маркеру ('createdAt/_ id').
Capped колекції для «кільцевих» логів.
Безпека та комплаєнс
Auth/RBAC: ролі на колекції/БД, мінімально необхідні привілеї.
TLS в транзиті, шифрування на диску (FLE/at-rest).
Політики PII: маскування/псевдонімізація, окремі колекції для чутливих полів.
Мульти-тенантність: префікси/окремі БД/колекції, фільтри по'tenantId', можна RLS-подібні шари в додатку.
Аудит: включайте аудит операцій на критичних колекціях.
Бекапи, PITR і DR
Знімки (snapshots) томів + oplog-бекапи для Point-in-Time Recovery.
Репліка-сет в іншому регіоні для DR; регулярні навчання відновлення.
Контроль зростання oplog під піки вставок (PSP вебхуки/турніри).
У шард-кластерах - узгоджені бекапи з config-сервером.
Інтеграція з іншою архітектурою
CQRS: команди б'ють по SQL (гроші), події → Materialized Views в MongoDB.
Event-Streaming: Kafka/Pulsar як шина, Mongo - sink/source через конектори і Change Streams.
Redis: поруч як шар ультра-низької латентності (кеші/лічильники).
OLAP: вивантаження в ClickHouse/Pinot для довгих сканів і BI.
Чек-лист впровадження
1. Зафіксуйте домени: що йде в Mongo (гнучко/високий TPS/проекції), що залишається в SQL.
2. Визначте schema contracts: JSON Schema Validation, `schemaVersion`.
3. Спроектуйте індекси під реальні запити; додайте TTL для «галасливих» даних.
4. Виберіть shard key (висока кардинальність, рівномірність); при необхідності - zone-шардинг.
5. Налаштуйте репліка-сет, Read/Write Concern під SLO; політика read-after-write.
6. Увімкніть спостережуваність і профілювання, алерти на індекси/WT cache/oplog.
7. Організуйте бекапи + PITR, DR-кластер і регулярні навчання.
8. Підключіть Change Streams/Outbox для синхронізації кешів і шин.
9. Обмежте розмір документів і вкладеність; впроваджуйте пагінацію по курсору.
10. Окремі політики для PII/тенантів, шифрування, аудит.
Антипатерни
«Без схеми» в проді: відсутність валідації і версій → хаос.
Шард-ключ за часом/монотонний - гарячий шард і нестабільна p99.
Джойни «$lookup» на величезних наборах без індексів/пагінації.
Використовувати транзакції повсюдно - втрати продуктивності.
Відсутність TTL/ретенції для логів → зростання обсягу і вартості.
Зберігати критично важливі грошові інваріанти тільки в Mongo без суворої ідемпотентності.
Підсумки
MongoDB - потужний інструмент для гнучких доменів iGaming: профілі, каталоги, телеметрія, проекції та персоналізація. Ключ до успіху - схема-контракти і валідація, продумана індексація, грамотно обраний shard key, усвідомлені Read/Write Concern, Change Streams для інтеграцій і жорстка експлуатаційна дисципліна (спостережуваність, бекапи, DR). У поєднанні з SQL-ядром і стрімінговою шиною це дає платформі швидкі інтерфейси і стійкість під турнірні піки.