Бази даних: порада «просто використайте NoSQL», що шкодить командам (і реальна альтернатива)

Було корисно?

Продажна репліка зазвичай звучить на нараді, коли ніхто не відповідає на пейджі о 3:00 ночі останнім часом. «Просто використайте NoSQL», — каже хтось, ніби вибір бази даних — це вибір шрифту. Через кілька місяців ви дивитесь на дубльовані замовлення, відсутні записи в книгах і «тимчасове» завдання з перерахунку, яке стало вашим найнадійнішим компонентом.

Проблема не в тому, що NoSQL — поганий. Проблема в тому, що «просто використайте NoSQL» — це лінива відповідь на важке питання: як зберігати правду при реальних продакшн-обмеженнях — затримка, вартість, конкурентність, збої, людська помилка та бізнес, який змінює думку кожні два спрінти?

Чому «просто використайте NoSQL» шкодить командам

«Просто використайте NoSQL» зазвичай є проксі для однієї з таких переконань:

  • SQL не масштабується (переклад: колись у нас був повільний запит і ми звинуватили базу даних).
  • Схеми гальмують команди (переклад: сьогодні ми не хочемо сперечатися про модель даних).
  • Потрібна гнучкість (переклад: вимоги продукту нестабільні, тож ми закодуємо цю нестабільність у сховищі).
  • Джойни дорогі (переклад: одного разу ми бачили join у повільному плані виконання).
  • Хочемо «веб-масштаб» (переклад: хочемо відчувати себе в безпеці без планування потужностей).

Ці переконання не завжди неправильні. Але порада неповна — небезпечно неповна — бо пропускає частину, де ви визначаєте інваріанти і вирішуєте, де їх забезпечувати.

Інваріанти — це продукт

Якщо ви зберігаєте гроші, запаси, права доступу, платежі, кількість місць або «лише одна активна сесія на користувача», ви зберігаєте інваріанти. Це не деталь реалізації; це бізнес. База даних — місце, де інваріанти або спокійно живуть, або голосно вмирають.

Реляційні бази даних не чарівні через SQL. Вони потужні, бо пропонують зрілий інтегрований набір інструментів для забезпечення істини:

  • Транзакції з відомими рівнями ізоляції
  • Обмеження (foreign keys, unique constraints, check constraints)
  • Декларативні індекси і планування запитів
  • Стійке write-ahead logging і передбачуване відновлення
  • Операційна спостережуваність, загострена десятиліттями болю

NoSQL-системи можуть бути правильними, надійними й швидкими. Але багато з них піднімають забезпечення коректності в стек: до додатка, до фонового завдання, до компенсаційних транзакцій, до «ми потім це почистимо». Це «потім» стає постійною платою, яку платять через виснаження на викликах.

Практичне правило: якщо дані мають бути коректні сьогодні — забезпечуйте це в момент запису. Якщо ви можете терпіти «врешті-решт коректно», все одно визначте, що означає «врешті-решт» і як ви виявите, що цього не сталося.

Короткий жарт №1: eventual consistency — як вибачення в груповому чаті: приходить пізніше і не завжди виправляє те, що зламалося.

NoSQL — це не одне й те саме

Коли хтось каже «NoSQL», запитайте, про яку категорію йдеться, бо операційний профіль і історія коректності відрізняються:

  • Документні магазини (наприклад, типу Mongo): чудові для вкладених документів, можуть бути пасткою для інваріантів, що перетинають документи.
  • Key-value (наприклад, типу Redis): фантастичні для кешування і ефермерного стану, погані як джерело правди без ретельних обмежень.
  • Wide-column (наприклад, типу Cassandra): відмінні для великої пропускної здатності записів з відомими шаблонами доступу; гарячі партиції зіпсують ваш вікенд.
  • Пошукові індекси (наприклад, типу Elasticsearch): це не база даних; це індекс із власною логікою.
  • Графові бази: нішеві, але корисні, коли саме зв’язки — це запит.

«Просто використайте NoSQL» ігнорує частину, де ви зіставляєте форму даних, шаблони доступу й терпимість до відмов із реальними гарантіями системи.

Вісім коротких фактів і трохи історії

Трохи контексту корисно, бо індустрія постійно переосмислює одні й ті ж уроки в іншій обгортці.

  1. Реляційні бази отримали свою теорію в 1970 році, коли E. F. Codd опублікував реляційну модель. Це було не про «SQL»; це була модель коректності.
  2. Стандартизація SQL розпочалася в 1980-х. Нудні частини — транзакції, обмеження, оптимізація запитів — саме там мештає більшість цінності.
  3. Термін «NoSQL» став популярним близько 2009 року, здебільшого як банер для систем, створених для вирішення проблем масштабування й розподілу того часу.
  4. Теорема CAP була формалізована на початку 2000-х. Люди й досі її неправильно використовують, здебільшого щоб обґрунтувати зламану поведінку як «компроміс».
  5. Стаття Amazon Dynamo (середина 2000-х) вплинула на покоління key-value і wide-column дизайнів, орієнтованих на доступність і стійкість до розділень.
  6. Стаття Google Bigtable (середина 2000-х) сформувала wide-column сховища і схеми з LSM-деревами, оптимізовані для пропускної здатності записів.
  7. Two-phase commit існує довше за більшість хмарних платформ. Це не новинка; це просто дорога опція в розподіленому середовищі і болюча в експлуатації.
  8. «NewSQL» не був чарівною заміною; це була спроба принести семантику SQL у розподілені системи — іноді успішна, іноді з новими операційними обмеженнями.

Історія не обирає вашу базу даних. Але вона пояснює, чому певні компроміси існують і чому ваша «гнучка» схема постійно обертається низкою індивідуальних бекфілів.

Реальна альтернатива: проектування від інваріантів, потім вибір технології

Реальна альтернатива до «просто використайте NoSQL» — не «просто використайте PostgreSQL». Це процес прийняття рішення:

  1. Запишіть інваріанти. Не функції. Інваріанти. «Загальна сума рахунка має дорівнювати сумі позицій». «Користувач не може мати дві активні підписки». «Запаси не можуть бути від’ємними».
  2. Визначте контракт консистентності. Для кожного інваріанту відповідайте: має бути істинним під час запису, чи його можна відновити пізніше?
  3. Спроєктуйте шаблони доступу. Шляхи читання, шляхи запису, кардинальність, піки, розгалуження.
  4. Визначте, де живе правда. Одна система — це system of record. Все інше похідне: кеш, індекс, денормалізація.
  5. Виберіть мінімальний набір баз даних. Кожне додаткове сховище — додатковий режим відмов, runbook на виклики, історія бекапів і план міграцій даних.

«Використовувати одну базу» — це не догма; це модель витрат

Команди люблять поліглотне збереження, поки не доводиться його відновлювати. «У нас є Postgres для транзакцій, Redis для кешу, Elasticsearch для пошуку, Kafka для подій і документне сховище для гнучких даних». Це може бути правильно. Це також може стати кошмаром надійності.

Практичний дефолт для багатьох компаній:

  • PostgreSQL (або інша реляційна БД) як system of record.
  • Redis для кешування і лімітування запитів, а не для канонічного стану, якщо ви не приймаєте ризики.
  • Пошуковий індекс для текстового пошуку, заповнюваний з system of record.
  • Лог/шина подій якщо потрібна асинхронна інтеграція і можливість повторного програвання.

Почніть з найпростішої архітектури, яка може бути коректною. Масштабуйте її нудними техніками — індекси, оптимізація запитів, партиціювання, репліки для читання — перш ніж «ми потребуємо розподіленої бази». Критерії «випуску» мають бути навантаженням, яке ви можете виміряти, а не страхом, який ви уявляєте.

Одна цитата, бо вона досі влучна

Перефразована ідея (Werner Vogels): «Ви будуєте — ви експлуатуєте» означає, що команди відповідають за операційні результати, а не лише за мерджі коду.

Вибір бази даних — це операційний результат. Якщо ваша база даних — «чуже питання», черга інцидентів навчить вас інакше.

Режими відмов NoSQL, з якими ви справді зіткнетеся

1) «Гнучка схема» перетворюється на «тиху корупцію»

Документні сховища роблять легким запис даних з відсутніми полями, неправильними типами або тонко невідповідними формами. Без обмежень додаток стає виконавцем схеми. А додатки змінюються. Інженери змінюються. Міграції відкладаються.

Схема відмови: один сервіс записує price_cents як число, інший — як рядок, аналітика «допоміжним» кастом приводить до того, що доходи — це помилка округлення під час квартального звіту.

2) Інваріанти між сутностями перетворюються на фоні джоби

Потрібно «лише одна активна підписка на акаунт»? У реляційній БД: унікальне обмеження плюс транзакція. У багатьох NoSQL-сценаріях: перевірка в додатку, гонка під час конкурентності, виправлення періодичним завданням «дедуп», і ручний інструмент для сапорту.

3) Гарячі партиції та нерівномірні ключі

Розподілені NoSQL-системи люблять рівномірний розподіл ключів. Реальні бізнеси люблять послідовні ID, клієнтів з великим трафіком та префікси з датою. Ви можете побудувати кластер wide-column, що обробляє мільйони записів… поки один ключ партиції не стане чорної дірою.

4) Вторинні індекси не безкоштовні (і іноді не справжні)

Деякі системи трактують вторинні індекси як опціональні, eventual-consistent або дорогі в підтримці. Ваш запит працює в стейджингу, а в продакшені розвалюється через кардинальність.

5) Операційна складність стає продуктом

Реплікація, компактинг, ремонт, hinted handoff, чтення кворумом, tombstones — це не «просунуті можливості». Це ціна входу. Якщо у вашої команди немає апетиту до цієї роботи, не купуйте цю систему.

6) Бекапи/відновлення та відновлення до точки в часі стають дивними

Реляційна БД з архівацією WAL дає просту історію: повний бекап + програвання WAL. У деяких NoSQL-системах бекапи — це «знімки на вузол і надія, що вони зійдуться». Це можна зробити, але треба відпрацьовувати. Інакше перша спроба відновлення відбудеться під час аварії. Це не тренування; це момент кар’єри.

Коли NoSQL — правильний вибір (а коли ні)

Використовуйте NoSQL, якщо виконуються ці умови

  • Ваші шаблони доступу відомі і стабільні. Ви можете описати запити заздалегідь і вони не змінюватимуться щотижня.
  • Вам потрібна масивна пропускна здатність записів по партиціях і ви можете спроєктувати ключі, щоб уникнути хотспотів.
  • Ви можете терпіти слабші транзакційні семантики для основного робочого процесу, або у вас є випробувана компенсуюча дизайн-стратегія.
  • Ви будуєте похідні подання: кеші, матеріалізовані read-моделі, індекси пошуку, шари ingestion для time-series.
  • Ваша команда може його експлуатувати. Не «хтось може це загуглити». Хтось відповідатиме.

Уникайте NoSQL як system of record, якщо виконуються ці умови

  • Потрібні інваріанти між сутностями під час запису. Білінг, книга рахунків, права, інвентар, entitlements.
  • Ви ще не знаєте шаблони доступу. «Гнучка схема» не врятує вас від невідомих запитів; вона лише відкладає дискусію.
  • Ви не можете собі дозволити ручну очистку даних. Фонове зіставлення — це прихований план найму.
  • Потрібний прозорий аудит. Потрібні обмеження, логи й чітка історія «хто що змінив».

Короткий жарт №2: «Схемабез» зазвичай означає «схему написали в Slack і загубили в каналі, який ніхто не перевіряє».

Три корпоративні міні-історії з практики

Міні-історія №1: Інцидент через хибне припущення (eventual consistency за замовчуванням)

Середня SaaS-компанія перебудувала білінговий робочий процес, щоб «рухатись швидше». Старий моноліт використовував реляційну базу з транзакціями. Нова архітектура розділила «підписки», «рахунки» і «платежі» на окремі сервіси, кожен зі своїм документним сховищем. Сервіси спілкувалися через шину подій.

Хибне припущення було тонким: вони вважали, що «eventual consistency» означає «кілька секунд». Насправді це означало «коли споживачі здорові, наздогналися і не репроцессать». Під час деплою сервіс рахунків відстав від подій платежів настільки, що UI показував «платіж отримано», але «рахунок не оплачено». Кількість звернень у сапорт різко зросла, а фінанси ескалували, бо автоматичне стягнення запускалось на оплачувані акаунти.

On-call намагався латати це ретраями і масштабуванням споживачів. Стало гірше. Реплеї міняли порядок подій. Деякі події обробилися двічі. Невелика кількість була втрачена через баг у idempotency keys. Ніхто не мав одного місця, де можна було запитати «яка правда для акаунта X зараз?»

Виправлення не було героїчним. Вони зробили один сервіс — бухгалтерський журнал — авторитетним у реляційній базі. Інші сервіси стали проекціями. Додали забезпечення ідемпотентності на шляху запису до журналу і зробили UI читання з журналу для відображення статусу клієнта. Шина подій залишилась, але її роль — інтеграція, а не правда.

Міні-історія №2: Оптимізація, що відгукнулась боком (денормалізація, щоб уникнути джойнів)

Платформа e-commerce мала повільний endpoint «деталі замовлення». Хтось запрофілював його, побачив кілька джойнів і впевнено проголосив: «Потрібно денормалізувати. Джойни не масштабуются». Вони перемістили позиції замовлення і статус доставки в один документ на замовлення в документне сховище. Читання стало швидшим одразу. Усі пораділи.

Потім відкат почався за розкладом. Обробка повернень вимагала оновлювати окремі позиції з дотриманням аудиту. Підтримка клієнтів потребувала часткових оновлень без перезапису конкурентних змін. Фолс-фрод вимагав додавати теги і нотатки. Три команди почали писати в один документ з різних сервісів, з різними припущеннями щодо конкурентності.

Конфлікти перетворились на втрату даних через last-write-wins. Інженери додали логіку злиття по полях. Потім додали поле «версія документа» і повторювали оновлення. Потім додали фонове «ремонтне» завдання для зіставлення пропущених полів. Документ замовлення став полем битви. Інциденти стали політичними: «ваш сервіс перезаписав наші поля».

Зрештою стабільний дизайн поновив нормалізацію там, де важлива конкурентність. Заголовок замовлення залишився в одному рядку, позиції — в іншій таблиці, а змінні анотації — у власному сховищі з явним власництвом. «Швидке читання» отримали через матеріалізовані подання та кешування — не перетворивши проблему коректності на JSON-блоб.

Міні-історія №3: Нудна, але правильна практика, що врятувала день (тестовані відновлення і PITR)

B2B-платформа використовувала Postgres як основне сховище. Нічого гламурного. Але SRE наполягав на двох нудних дисциплінах: архівація WAL для відновлення до точки в часі і щомісячна перевірка відновлення. Не «у нас є бекапи», а «ми відновили на новому кластері й запустили перевірки додатка».

Одного дня інженер запустив міграцію, яка видалила стовпець і створила його знову з тим самим ім’ям, але іншим типом. Міграція пройшла в стейджингу. У продакшині додаток почав писати нісенітниці в новий стовпець. Баг швидко виявили, але дані вже були неправильною і не могли «виправитися вперед» без надійної точки відліку.

Вони виконали відновлення до точки в часі на кілька хвилин перед міграцією, витягли постраждалі таблиці і відтворили легітимні записи з логів додатка і ідемпотентних job-рунерів. Аварія була болючою, але обмеженою. Не було листа «ми втратили день даних». Фінанси не втрутилися. Нікому не довелося пояснювати «ми думаємо, що це правильно».

Цей інцидент не приніс доповіді на конференцію. Але він запобіг кварталу повільного хаосу. Нудне перемагає, бо повторюване.

План швидкої діагностики: що перевірити першим/другим/третім

Це послідовність дій на виклики, що знаходить горло пляшки без годин сварок над дашбордами.

Перше: вирішіть, чи це насичення, конкуренція або коректність

  • Насичення: CPU, IO, пам’ять або мережа вичерпані. Затримка зростає з навантаженням.
  • Конкуренція: блокування, гарячі ключі, обмежена конкурентність. Пропускна здатність упирається; черги ростуть.
  • Коректність: відставання реплікації, непослідовні читання, відсутні індекси, що викликають таймаути, які здаються аврійними.

Друге: знайдіть точку горла (додаток vs БД vs сховище)

  1. Перевірте p95 латентність додатка і рівень помилок навколо викликів до БД.
  2. Перевірте здоров’я БД: підключення, очікування блокувань, повільні запити.
  3. Перевірте сховище: iowait, затримки диска, насичення, помилки файлової системи.

Третє: виберіть найшвидшу безпечну міру

  • Зменшити навантаження: обмежити швидкість, скидати некритичний трафік, відключити дорогі ендпоїнти.
  • Покращити план запиту: додати/скорегувати індекс, виправити N+1, обмежити нестрогі запити.
  • Виправити конкуренцію: уникати гарячих рядків/партицій, скоротити транзакції, налаштувати ізоляцію, додати черги.
  • Масштабувати безпечно: додати репліки для читання, додати кеш, збільшити розмір інстансу тільки коли ви розумієте межу.

Практичні завдання: команди, виводи та рішення

Це реальні завдання, які ви можете виконати сьогодні. Кожне включає команду, що її вивід говорить і яке рішення приймати. Припускайте Linux-сервери і мікс PostgreSQL та поширених NoSQL-компонентів. Налаштуйте імена хостів та облікові дані відповідно.

Завдання 1: Перевірити, чи хост обмежений по IO (Linux iostat)

cr0x@server:~$ iostat -x 1 3
Linux 6.1.0 (db01)     02/04/2026  _x86_64_  (16 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          12.31    0.00    4.22   31.88    0.00   51.59

Device            r/s     w/s   rkB/s   wkB/s  await  svctm  %util
nvme0n1         85.0   220.0  5200.0 14800.0   18.2   0.9   92.4

Що це означає: Високий %iowait і велика зайнятість диска %util свідчать про насичення сховища. await показує чергу затримок.

Рішення: Спочатку не «оптимізуйте код». Зменшіть write amplification (індекси, autovacuum, compacting), перемістіть WAL на швидший диск або масштабуйтесь по сховищу/IOPS.

Завдання 2: Побачити, хто «з’їдає» пам’ять і чи відбувається свопінг

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            64Gi        52Gi       1.2Gi       1.1Gi        11Gi       8.5Gi
Swap:            8Gi       2.9Gi       5.1Gi

Що це означає: Використання swap на хості БД часто корелює з випадковими спайками латентності.

Рішення: Зменшіть тиск на пам’ять (налаштуйте shared buffers / cache sizes, виправте витоки, правильно підійміть розміри). Якщо свопінг триває — ваша «проблема з БД» фактично проблема ядра.

Завдання 3: Підтвердити вільне місце на файловій системі та інодині

cr0x@server:~$ df -h /var/lib/postgresql
Filesystem      Size  Used Avail Use% Mounted on
/dev/nvme0n1p2  900G  812G   42G  96% /var/lib/postgresql

Що це означає: 96% заповнення — це повільна аварія (vacuum, compacting та тимчасові файли потребують запасу).

Рішення: Розширте сховище або безпечно очистіть. Додайте алерти на 80/85/90%. Не чекайте 100%.

Завдання 4: Перевірити латентність диска безпосередньо

cr0x@server:~$ nvme smart-log /dev/nvme0n1 | sed -n '1,12p'
Smart Log for NVME device:nvme0n1 namespace-id:ffffffff
critical_warning                    : 0x00
temperature                         : 41 C
available_spare                     : 100%
available_spare_threshold           : 10%
percentage_used                     : 4%
data_units_read                     : 1,210,331
data_units_written                  : 2,980,552
host_read_commands                  : 18,220,114
host_write_commands                 : 55,991,202

Що це означає: Не метрика латентності, але каже, чи пристрій нездоровий. Критичні попередження мають значення.

Рішення: Якщо з’являються попередження/помилки SMART — припиніть дебати про плани запитів і почніть план заміни диска або міграції.

Завдання 5: Знайти найповільніші запити PostgreSQL за загальним часом

cr0x@server:~$ psql -d appdb -c "SELECT query, calls, total_time, mean_time FROM pg_stat_statements ORDER BY total_time DESC LIMIT 5;"
                                query                                 | calls | total_time | mean_time
----------------------------------------------------------------------+-------+------------+----------
SELECT * FROM orders WHERE account_id = $1 ORDER BY created_at DESC... |  9842 |  812345.12 |    82.54
UPDATE inventory SET available = available - $1 WHERE sku = $2        | 62111 |  420998.77 |     6.78
SELECT * FROM events WHERE tenant_id = $1 AND created_at > $2         |  2109 |  318120.33 |   150.86

Що це означає: total_time виявляє «смерть від тисячі порізів». mean_time виявляє різкі ножі у хвості.

Рішення: Виправляйте високий total_time спочатку, якщо він домінує у навантаженні; виправляйте високий mean_time, якщо він домінує у хвості латентності.

Завдання 6: Пояснити конкретний повільний запит (Postgres EXPLAIN ANALYZE)

cr0x@server:~$ psql -d appdb -c "EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE account_id = 42 ORDER BY created_at DESC LIMIT 50;"
Limit  (cost=0.56..220.14 rows=50 width=312) (actual time=120.331..120.410 rows=50 loops=1)
  Buffers: shared hit=120 read=845
  ->  Index Scan Backward using orders_created_at_idx on orders  (cost=0.56..44120.12 rows=10024 width=312) (actual time=120.329..120.401 rows=50 loops=1)
        Filter: (account_id = 42)
        Rows Removed by Filter: 580000
        Buffers: shared hit=120 read=845
Planning Time: 0.219 ms
Execution Time: 120.450 ms

Що це означає: Індекс по created_at, але ви фільтруєте по account_id. Він багато сканує, потім фільтрує.

Рішення: Додайте композитний індекс, наприклад (account_id, created_at DESC), або перебудуйте запит. Це «SQL не масштабується» лише якщо ви відмовляєтесь правильно індексувати.

Завдання 7: Перевірити контенцію блокувань у PostgreSQL

cr0x@server:~$ psql -d appdb -c "SELECT blocked.pid AS blocked_pid, blocking.pid AS blocking_pid, blocked.query AS blocked_query FROM pg_stat_activity blocked JOIN pg_locks bl ON bl.pid = blocked.pid JOIN pg_locks kl ON kl.locktype = bl.locktype AND kl.database IS NOT DISTINCT FROM bl.database AND kl.relation IS NOT DISTINCT FROM bl.relation AND kl.page IS NOT DISTINCT FROM bl.page AND kl.tuple IS NOT DISTINCT FROM bl.tuple AND kl.virtualxid IS NOT DISTINCT FROM bl.virtualxid AND kl.transactionid IS NOT DISTINCT FROM bl.transactionid AND kl.classid IS NOT DISTINCT FROM bl.classid AND kl.objid IS NOT DISTINCT FROM bl.objid AND kl.objsubid IS NOT DISTINCT FROM bl.objsubid AND kl.pid != bl.pid JOIN pg_stat_activity blocking ON blocking.pid = kl.pid WHERE NOT bl.granted;"
 blocked_pid | blocking_pid |          blocked_query
------------+--------------+--------------------------------
      21934 |        21811 | UPDATE inventory SET available =

Що це означає: Транзакція блокує інші. БД у межах норми; ваша модель конкурентності — ні.

Рішення: Скоротіть транзакції, додайте правильні індекси, щоб уникнути ескалації блокувань, або перебудуйте патерн гарячих рядків (лічильники на SKU — класичний приклад).

Завдання 8: Перевірити відставання реплікації (Postgres streaming replica)

cr0x@server:~$ psql -d appdb -c "SELECT application_name, state, sync_state, write_lag, flush_lag, replay_lag FROM pg_stat_replication;"
 application_name |  state  | sync_state | write_lag | flush_lag | replay_lag
------------------+---------+------------+-----------+-----------+------------
 replica01        | streaming | async     | 00:00:01  | 00:00:02  | 00:00:15

Що це означає: Replay lag означає, що читання з репліки може бути застарілим на цей проміжок.

Рішення: Якщо ваш додаток читає після записів з реплік, потрібні правила маршрутизації читань, сесійна «липкість» або синхронна реплікація для критичних робочих процесів.

Завдання 9: Перевірити, чи autovacuum справляється (ризик bloat)

cr0x@server:~$ psql -d appdb -c "SELECT relname, n_dead_tup, last_autovacuum FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 5;"
  relname   | n_dead_tup |     last_autovacuum
------------+------------+--------------------------
 events     |   81234567 | 2026-02-03 01:12:02+00
 sessions   |   21003444 | 2026-01-29 12:40:10+00
 orders     |    8120032 | 2026-02-04 00:02:11+00

Що це означає: Велика кількість мертвих кортежів означає bloat таблиці, гірші показники кеш-хітів і повільніші запити.

Рішення: Налаштуйте autovacuum, відрегулюйте fillfactor або партиціонізуйте великі таблиці з інтенсивними змінами. Bloat — це не «містична повільність»; це фізика.

Завдання 10: Перевірити тиск підключень (Postgres)

cr0x@server:~$ psql -d appdb -c "SELECT count(*) AS connections, state FROM pg_stat_activity GROUP BY state ORDER BY connections DESC;"
 connections |        state
------------+---------------------
        220 | active
        180 | idle
         40 | idle in transaction

Що це означає: Забагато активних підключень може сильно нагружа тиж. «Idle in transaction» — мовчазний вбивця (тримають блокування і перешкоджають vacuum).

Рішення: Додайте пулер (наприклад, pgbouncer), виправте роботу з транзакціями і обмежте кількість підключень від сервісів.

Завдання 11: Виявити патерн гарячих ключів на рівні додатка (приклад nginx / access logs)

cr0x@server:~$ awk '{print $7}' /var/log/nginx/access.log | grep -E '^/api/orders\?account_id=' | sort | uniq -c | sort -nr | head
  18421 /api/orders?account_id=42
   9211 /api/orders?account_id=17
   4420 /api/orders?account_id=5

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

Рішення: Додайте кешування, обмеження пагінації або попередньо обчислені подання для гарячих акаунтів/тенантів. Також розгляньте обмеження по кожному тентанту.

Завдання 12: Перевірити відставання споживача Kafka (якщо ви використовуєте події як «врешті-решт правду»)

cr0x@server:~$ kafka-consumer-groups.sh --bootstrap-server kafka01:9092 --describe --group invoice-service
GROUP           TOPIC      PARTITION  CURRENT-OFFSET  LOG-END-OFFSET  LAG  CONSUMER-ID
invoice-service billing    0          1288812         1299910         11098 consumer-1
invoice-service billing    1          1290011         1299988          9977 consumer-2

Що це означає: Lag означає, що ваші проекції застарівають. Якщо UI або робочі процеси залежать від цих проекцій, користувачі побачать непослідовний стан.

Рішення: Або масштабувати споживачів/виправити обробку, або припинити використання проекцій як system of record для видимої користувачу правди.

Завдання 13: Перевірити використання індексів у MongoDB (приклад через mongosh)

cr0x@server:~$ mongosh --quiet --eval 'db.orders.find({account_id: 42}).sort({created_at:-1}).limit(50).explain("executionStats").executionStats'
{
  "nReturned" : 50,
  "executionTimeMillis" : 187,
  "totalKeysExamined" : 0,
  "totalDocsExamined" : 580050
}

Що це означає: Перевірка великої кількості документів при нульовому огляді ключів вказує на відсутність корисного індексу для цього шаблону запиту.

Рішення: Додайте композитний індекс {account_id:1, created_at:-1} або перегляньте модель даних. Ваш кластер не повільний; ваш запит дорогий.

Завдання 14: Перевірити пам’ять Redis і політику виведення (коли кеш перетворюється на аврію)

cr0x@server:~$ redis-cli INFO memory | egrep 'used_memory_human|maxmemory_human|mem_fragmentation_ratio'
used_memory_human:14.92G
maxmemory_human:16.00G
mem_fragmentation_ratio:1.62

Що це означає: Майже до max memory з фрагментацією означає, що ви наближаєтеся до виведення ключів або OOM-поведінки залежно від конфігурації.

Рішення: Якщо Redis — кеш, перевірте, що політика виведення встановлена й безпечна. Якщо Redis зберігає канонічний стан — перегляньте свої життєві рішення і додайте дисципліну збереження/бекупів.

Поширені помилки: симптом → корінь → виправлення

1) Симптом: дубльовані «унікальні» записи (користувачі, підписки, замовлення)

Корінь: унікальність забезпечується кодом додатка, а не сховищем; гонки конкурентності перемагають.

Виправлення: Забезпечте унікальність під час запису за допомогою обмеження БД або атомарного умовного запису. Якщо не можете — перебачте переробку: оберіть канонічний ключ, використайте idempotency keys і введіть процес дедупа з аудитом.

2) Симптом: «випадкові» застарілі читання після деплою

Корінь: читання з реплік або проекцій без контракту на часову свіжість; відставання реплікації або споживачів під час деплоїв.

Виправлення: маршрутизація «читати-після-запису» (читати з primary для сесії), обмежені перевірки свіжості або синхронна реплікація для критичних робочих процесів.

3) Симптом: спайки хвостової латентності під помірним навантаженням

Корінь: насичення сховища (iowait), тиск від compacting/vacuum або кеш-треш через bloat.

Виправлення: зменшити write amplification, налаштувати autovacuum/compaction, додати відповідні індекси, партиціонувати таблиці з високою змінністю і переконатися, що диск розмірний для навантаження.

4) Симптом: БД «повільна», але CPU низький

Корінь: блокування або черги на єдиному гарячому ключі/партиції/рядку. Або ви IO-bound.

Виправлення: знайти блокувальників (lock views), перебудувати гарячі лічильники, шардувати за кращим ключем або пакетувати оновлення. Для IO-bound — попрацювати з диском і патернами запису.

5) Симптом: зміни схеми займають тижні і лякають

Корінь: відсутня дисципліна міграцій; великі перезаписи таблиць; відсутні feature flags; недостатня стратегія бекафілу.

Виправлення: застосуйте розширення/звуження міграцій, бекафіли контрольованими батчами і підтримуйте сумісність коду між версіями до завершення розгортання.

6) Симптом: «Ми не можемо швидко відновитися»

Корінь: бекапи є, але відновлення не тестували; немає визначених RTO/RPO; немає runbook.

Виправлення: проводьте відновні тренування, визначте RPO/RTO, реалізуйте PITR там, де можливо, і автоматизуйте підготовку середовища для відновлення.

7) Симптом: результати пошуку не відповідають джерелу правди

Корінь: пошуковий індекс використовують як базу даних; пайплайн індексування має відставання, пропуски або проблеми з порядком.

Виправлення: трактуйте пошук як похідний стан; додайте можливість реіндексації; використайте версіювання і ідемпотентність; побудуйте перевірку цілісності порівнюючи індекс і джерело.

8) Симптом: кластер NoSQL «здоровий», але помилки в додатку зростають

Корінь: таймаути і ретраї підсилюють навантаження; клієнти неправильно налаштовані; p95 латентність зростає, але середнє виглядає нормально.

Виправлення: інструментуйте латентність на боці клієнта, обмежуйте ретраї, додавайте circuit breakers і налаштовуйте таймаути відповідно до SLA і поведінки при відмовах.

Чеклисти / покроковий план

Покроково: як вибрати сховище даних без самообману

  1. Перелічіть інваріанти (правда під час запису проти істини «врешті-решт»).
  2. Визначте терпимість до відмов: що станеться, якщо записи частково пройдуть? Який видимий для користувача вплив?
  3. Визначте шаблони читання: випишіть топ-10 запитів/ендпойнтів, а не «ми будемо шукати пізніше».
  4. Оцініть зростання: обсяг даних, QPS записів, QPS читань і розподіл по тентантах/ключах.
  5. Виберіть одну систему запису. Все інше — похідне.
  6. Оберіть найпростішу БД, що задовольнить інваріанти. Якщо це реляційна — прийміть її.
  7. Сплануйте міграції і відкат. Якщо ви не можете безпечно відкотитися — у вас немає плану.
  8. Оперціалізуйте: моніторинг, бекапи, відновчі тренування, політика зміни схеми, алерти за потужністю.

Чеклист: готовність до продакшену для будь-якої бази даних

  • RPO і RTO визначені, зафіксовані і погоджені з бізнесом
  • Бекапи автоматизовані, зашифровані і перевірені
  • Проведено відновчий тренінг (не «ми перевірили логи»)
  • Алерти по потужності: CPU, пам’ять, диск і IOPS
  • Видимість повільних запитів із вибіркою і збереженням
  • Чітка відповідальність і шлях ескалації
  • Таймаути клієнтів і ретраї налаштовані свідомо
  • Процес зміни схеми з expand/contract
  • План збереження та архівації даних
  • Навантажувальне тестування, що включає режими відмов (втрата вузла, мережеві розділення, відставання споживача)

Чеклист: якщо ви наполягаєте на «eventual consistency»

  • Визначте «врешті-решт»: секунди? хвилини? години? Що прийнятно?
  • Ідемпотентні ключі для кожного споживача, що модифікує стан
  • Стратегія повторного відтворення протестована (включно з out-of-order і дублями)
  • Dead-letter queue з операційним власництвом
  • Завдання з примирення, що має аудит, а не чорну скриньку
  • Одне місце для запиту поточної правди (system of record)

Питання і відповіді

1) Чи «SQL проти NoSQL» взагалі правильне питання?

Не зовсім. Справжнє питання: де ви забезпечуєте інваріанти і які режими відмов може оперувати ваша команда? SQL часто є найпростішою правильною відповіддю для систем запису.

2) Але хіба NoSQL не масштабується краще?

Деякі NoSQL-системи дуже добре масштабується за записами і горизонтальним розподілом — коли ви правильно спроєктували ключі і шаблони доступу. Реляційні системи також масштабуються далеко за межі того, чого досягають більшість команд, особливо з правильними індексами, партиціюванням і масштабуванням читань.

3) Чи джойни за своєю природою повільні?

Ні. Погані джойни — повільні. Відсутні індекси — повільні. Джойни великих нефільтрованих наборів — повільні. Добре індексований join за селективними предикатами часто швидкий і передбачуваний.

4) Яка найбільша прихована вартість «безсхемних» баз?

Операційний та організаційний відкіл: ви втрачаєте центральну точку забезпечення. Кожен сервіс стає виконавцем схеми, міграції стають «кращими практиками», а чистка даних — постійним робочим процесом.

5) Чи можна робити транзакції в NoSQL базах?

Іноді, так — в обмежених межах. Питання в тому, наскільки зріла модель транзакцій, яка її вартість і наскільки вона передбачувана в експлуатації під навантаженням і розділеннями. Тестуйте режими відмов, а не демо.

6) Яка «реальна альтернатива», якщо команда хоче швидкість і гнучкість?

Використовуйте реляційну БД як реєстр істини, потім будуйте гнучкі похідні моделі: JSON-колонки де доречно, матеріалізовані подання, кеші і пошукові індекси. Ви отримуєте гнучкість без відмови від цілісності.

7) Коли документне сховище — правильне system of record?

Коли документи справді незалежні і ваші інваріанти здебільшого в межах кордонів документа. Приклад: контент-менеджмент блої, налаштування користувача або сесійні дані — за умови, що ви все одно проєктуєте міграції і валідацію даних.

8) Як уникнути гарячих партицій у розподіленому NoSQL?

Проєктуйте ключі партицій для рівномірного розподілу, уникайте монотонно зростаючих префіксів і симулюйте реальний трафік. Слідкуйте за топ-ключами і скосом. Якщо один тенант домінує — створіть ізоляцію і лімітування на рівні тенанта.

9) Що робити, якщо ми вже обрали NoSQL і тепер шкодуємо?

Не переписуйте все в паніці. Визначте інваріанти, що завдають болю, введіть авторитетне сховище для них (часто реляційне) і мігруйте робочі процеси поступово з подвійними записами тільки якщо ви можете перевірити коректність.

10) Який перший «апгрейд» бази даних більшість команд має виконати?

Не шардинг. Зазвичай: виправити індексацію, застосувати connection pooling, реалізувати бекап+PITR і побудувати дисципліну міграцій. Шардінг — це спосіб життя в експлуатації, а не feature flag.

Наступні кроки, які ви можете зробити цього тижня

  • Запишіть ваші топ-10 інваріантів і відзначте, які мають бути істинними під час запису.
  • Виберіть system of record для кожного інваріанту (ідеально — одну). Оголосіть все інше похідним.
  • Програйте план швидкої діагностики для вашого нинішнього болю: це насичення, конкуренція чи коректність?
  • Зробіть одне відновлення до свіжого середовища. Поміряйте час. Запишіть кроки. Це ваш реальний RTO.
  • Виключіть одне «тимчасове» завдання з перерахунку, перемістивши інваріант у обмеження під час запису або атомарну операцію.
  • Зробіть один запит нудним: додайте правильний індекс, перевірте за допомогою EXPLAIN (ANALYZE) і закріпіть продуктивність регресійним тестом.

Технологія баз даних не врятує вас від невизначеної правди. Але чітка модель правди врятує вас від більшості дискусій про технології баз даних. Обирайте системи так само, як ви обираєте процес реагування на інциденти: за тим, що відбувається, коли щось ламається, а не за тим, що показують демо.

← Попередня
Встановіть Windows без надмірностей: важливі налаштування при першому завантаженні
Наступна →
Зміцнення Windows для домашніх лабораторних серверів: мінімальні зміни, максимальний ефект

Залишити коментар