Еволюція схеми в NoSQL: де ламається прод
У документних базах схема живе в самих записах, а не в окремій таблиці: міграція не блокує запис — і це головна причина, чому еволюцію схеми в NoSQL відкладають до першого збою. Код оновили, правила валідації колекції увімкнули, а дані залишилися у двох-трьох формах одночасно.
У MongoDB достатньо додати обов’язкове поле в правило валідації — і частина наявних записів перестане проходити перевірку під час оновлення.
Перше місце відмови — падіння читання документа. Новий код очікує атрибут, якого в старому записі немає: об’єкт не проходить перевірку, обробник віддає помилку.
Поки такі записи рідкісні, збій виглядає випадковим — відмовляє один запит із сотні, і відтворити його на свіжих даних не вдається.
Друге — розсинхронізація структури на обов’язкових полях. Правило перевіряє запис цілком, а точкове оновлення пише частковий набір: частина документів залишається без нового атрибута, і правило починає відбраковувати вже власні записи сервісу.
Третє — пакетна обробка на змішаних версіях записів. Завдання читає вибірку, спотикається на першому ж записі старого формату і або падає цілком, або дописує половину набору в новому вигляді — набір залишається в проміжному стані, і розбирати його доводиться вручну.
Розбираємо, як це закривається: версіонування записів, читання з урахуванням обох форм і зворотне заповнення без зупинки сервісу.
Що дає керована зміна структури записів
Сумісність перевіряється до застосування
Зміна структури запису проходить розбір на зворотну сумісність версій: додавання поля зі значенням за замовчуванням стара версія застосунку просто не помічає, а видалення або перейменування розбивається на дві фази з проміжним станом.
У продуктивний контур потрапляють лише правки, які читаються обома версіями коду, тому частка змін, що відкочуються через несумісність, падає до нуля.
Відкат зміни — хвилини замість годин
Версія структури описана окремо від самих документів, і повернення до попередньої — це перемикання дескриптора, а не вивантаження з повторним завантаженням колекції.
На масивах у десятки мільйонів записів час відкату скорочується з годин до хвилин, і його можна проводити в робоче вікно.
Відмови на читанні переходять у керований клас
Раніше розходження структури давало необроблений виняток на весь запит. Тепер відсутнє в старому записі поле підставляється перетворенням читання, а частка запитів, що падають через незбіг версій, прямує до нуля — залишаються поодинокі випадки, видимі в метриках.
Дані переписуються ліниво, за зверненням
Масова міграція не потрібна: перетворення застосовується до документа в момент читання, а в новій структурі він зберігається під час наступного оновлення.
Обсяг переписуваних даних скорочується до частки реально використовуваних записів, і тривалих блокувань на колекції не виникає.
Ручні правки переходять у регламент, випуск пришвидшується
Зміни проходять рев’ю, фіксуються в історії версій і накочуються тим самим способом, що й код. Частка правок в обхід регламенту знижується до поодиноких випадків, а узгоджений випуск зміни структури займає дні замість тижнів.
Підходи до еволюції схеми: порівняння варіантів
Схема в документній базі не описується заздалегідь, тому питання не в тому, чи потрібна валідація, а в який момент вона спрацьовує.
Перевірка під час читання, жорстка перевірка під час запису й явна версія запису дають різний баланс між доступністю запису та передбачуваністю читання.
| Варіант | Коли підходить | Чим обмежений |
|---|---|---|
| Перевірка під час читання | Різнорідні джерела, де зупинка запису неприпустима: документ записується як є, розбирається на стороні читача | Помилка проявляється пізніше, а логіка сумісності розповзається по коду всіх споживачів |
| Жорстка перевірка під час запису | Один власник даних, висока ціна неправильного документа — відмова видна авторові одразу | Розширення схеми вимагає одночасного оновлення всіх сервісів, що пишуть, старі версії починають відхилятися |
| Явна версія запису | Довгоживучі дані та вивантаження: потрібно читати документи, записані місяцями раніше | Потрібні правила перетворення між версіями та дисципліна їх актуалізації |
| Гібридна валідація | Ядро полів перевіряється жорстко під час запису, необов’язкові розширення — під час читання | Потребує явної межі між обов’язковою та розширюваною частиною документа |
На практиці вибір визначає не зручність розробки, а те, де ми готові платити за помилку. Гібридний режим закриває більшість сценаріїв: обов’язкові поля не дають записати заздалегідь битий документ, а вільна частина дозволяє додавати атрибути без релізу всіх сервісів одразу.
Якщо дані читаються довше, ніж живе код, версія запису стає обов’язковою — інакше відновлення старого вивантаження перетворюється на археологію.
Етапи впровадження: від аудиту до передачі
Структуру даних у NoSQL не можна змінити одним релізом: документи попередніх форм живуть у тих самих колекціях місяцями, і сервіс весь цей час зобов’язаний читати обидві форми. Тому перехід ведемо послідовними кроками, де кожен стан сумісний із попереднім.
- Інвентаризація версій записів. Рахуємо, яка частка документів відповідає якій формі: де тип поля розійшовся, де залишилися поля від відкочених релізів, де відрізняється вкладеність. Отримуємо карту фактичного стану даних, а не припущень за кодом.
- Схема сумісності та план зміни структури. Ділимо правки на зворотно сумісні та ламкі: необов’язкове поле сервіс переживає відразу, перейменування або зміна типу — лише двофазно, через період читання обох варіантів.
- Налаштування правил перевірки. Валідатор вмикаємо в м’якому режимі: він пише невідповідності в журнал і не блокує запис. Через кілька діб видно, що саме порушує нові правила, і правки в код ідуть за фактом.
- Обкатка на копії даних. Знімок робочої бази розгортаємо в ізольованому контурі й проганяємо перетворення разом зі зворотним відкатом: час проходу, частка записів, не перетворених автоматично, поведінка сервісу на змішаних даних.
- Поступове вмикання правил. Переводимо перевірки з м’якого режиму в строгий за колекціями, починаючи з низьконавантажених. Контроль — частка записів, що проходять перевірку, і частота помилок читання на кожному кроці.
- Передача регламенту. Віддаємо документ: як додати поле, що вважається ламкою зміною, як оформлюється двофазний перехід, хто запускає відкат, які перевірки стоять у збірці. Далі команда змінює схему сама.
Комплект постачання: правила, регламенти, документація
Комплект постачання — це не презентація і не папка зі скриншотами, а робочі файли в репозиторії та вікі команди, за якими наступний інженер продовжить роботу без усних пояснень.
Кожен документ відповідає на конкретне питання: що зараз у структурі даних, що можна змінювати безпечно і як діяти, якщо зміна пішла не так.
- Опис версій структури: які поля з’являються, які перестають заповнюватися, що залишається читабельним для записів попередніх версій і до якого моменту їх потрібно підтримувати.
- Правила сумісності версій: що допустимо додавати без зупинки сервісів, а які зміни потребують паралельного запису двох версій і проміжного шару читання.
- Схема обробки записів: шлях документа від запису до читання — як читач розбирає різні версії в одній колекції і куди додається нова гілка розбору.
- Політики доступу до даних: хто і через яку роль змінює структуру, де потрібен другий підтверджувач, як обмежено доступ до чутливих полів і вивантажень.
- Регламент випуску змін: порядок викладки, допустимі вікна, набір автоматичних перевірок на копії даних і критерій, за яким випуск зупиняють.
- Регламент відкату: заздалегідь описані кроки повернення до попередньої версії структури і окремо — що робити із записами, які вже встигли записатися в новому форматі.
- Документація зі структури: актуальна карта колекцій, призначення полів, зв’язки між сутностями і дата останнього перегляду, щоб не доводилося відновлювати її за кодом.
- Набір перевірочних сценаріїв на копії даних: типові та межові випадки, на яких зміну тестують до продакшену.
Усе це передається разом із налаштуванням, а не залишається в нас: команда замовника може сама вносити правки в структуру, не порушуючи сумісність.
Кейс: зміна структури без простою сервісу
Що було
Сервіс замовлень зберігав документи в NoSQL-сховищі MongoDB без поля версії схеми: застосунок читав їх напряму й очікував рівно той набір полів, який колись записав.
Щойно в структуру додали вкладений блок адреси доставки, частина обробників почала падати на розборі — частка відмов читання документів зросла до 4–6% запитів.
Переведення версій записів робили разовим скриптом із зупинкою запису нових: зміна структури займала 2–3 тижні, і на кожній ітерації вручну правили десятки документів.
Що змінили
У кожен документ додали поле версії, а на читання — шар трансформації: запис зберігається у вихідному вигляді, застосунок приводить його до поточного контракту й не залежить від того, коли документ створено.
Зафіксували правила сумісності: нові поля лише необов’язкові, перейменування — через дублювальне поле на період переходу, видалення — лише після того, як записів старої версії не залишилося.
Частка записів старої версії за перший місяць упала зі 100% до менш ніж 1% — фонова задача переписує документи в міру звернення до них, без пауз в обслуговуванні. Відмови читання на нових полях зникли, бо контракт читання перевіряється тестами на обох версіях схеми.
Час випуску зміни структури скоротився з двох-трьох тижнів до двох днів, обсяг ручних правок — до нуля, а відкат застосунку на попередню версію більше не потребує міграції даних.
Що робити, якщо сховище вже в проді з різними версіями
Розкид уже накопичено: в одній колекції лежать документи різних генерацій — з перейменованими полями, без полів, що з’явилися пізніше, зі зміненими типами.
Переписати все відразу не можна: запис у сховищі не зупиняється, і будь-який прогін наткнеться на документ, який ще не приведено до потрібної форми.
Тому першим кроком вводимо маркер версії схеми прямо в документ — за ним ухвалюється рішення, як трактувати запис.
Читання змішаних записів закриває шар приведення даних: відсутні поля отримують значення за замовчуванням, перейменовані розгортаються в актуальне ім’я, типи приводяться до очікуваних. Прикладна логіка залишається в одному варіанті й про розкид версій не знає.
Далі — поступове вирівнювання: фоновий прохід іде колекцією курсором невеликими пачками, приводить документи до поточної форми й позначає новою версією. Він відновлюваний після обриву, не тримає довгих блокувань і не конкурує з робочим навантаженням. Частку документів старих версій тримаємо метрикою — за нею видно, коли прохід справді завершено.
Головне — не зупиняти запис. Нове поле додається адитивно, старе ім’я на перехідний період пишеться дублювально, і код, який ще не оновлено, продовжує формувати валідні документи. Зняття старої гілки — останній крок, коли лічильник записів минулих версій дійшов до нуля.
Валідація, відкати та сумісність: часті питання
Строга валідація записів б’є не по нових, а по старих документах: правило з обов’язковим полем відсікає все, що записано до нього.
Тому поля додають необов’язковими з явним значенням за замовчуванням, а обов’язковість вмикають, коли лічильник записів без поля — узятий із самого сховища — падає до нуля.
Відкат зміни структури — не бінарна операція. Поки застосунок пише у двох форматах, надійніше повернути попередню версію правил: вона приймає і старі, і нові записи, а частково оновлені дані не блокують читання.
Межа відповідальності зазвичай така: сховище перевіряє структуру й типи, застосунок — бізнес-правила та узгодженість полів.
Що буде зі старими документами в базі?
Нічого не переписуємо в один прохід: міграція йде порціями, а читання підставляє значення за замовчуванням, поки документ не оновлено. Відставання міграції при цьому не впливає на роботу сервісів.
Чи можна відкотити зміну структури, якщо дані вже частково записані в новому форматі?
Так, якщо схема розширювана: старі правила ігнорують невідомі поля, а нові не вимагають їх обов’язкової наявності. Повний відкат не потрібен — достатньо повернути попередній набір правил.
Де закінчується відповідальність сховища і починається відповідальність застосунку?
Сховище тримає структуру, типи й допустимі діапазони значень. Зв’язки між сутностями, права на зміну та обчислювані поля — завдання застосунку: правила такого рівня всередині бази не виражаються і не повинні.
Як переконатися в сумісності версій до викатки?
Проганяємо читання й запис новим кодом на копії робочого набору даних і звіряємо лічильники відмов валідації. Якщо приріст відмов нульовий, вмикаємо правила, залишивши попередній набір у готовності до повернення.
Обговоримо задачу і повернемося з оцінкою та планом
Щоб план робіт із даними спирався на факти, опишіть поточний стан: скільки колекцій і які варіанти документів у них сусідять, чи є вже поле версії, як читачі реагують на відсутнє поле, які релізи застосунку працюють одночасно під час викатки.
Додайте обмеження — вікно на обслуговування, вимоги до узгодженості, обсяг даних і те, що змінювати не можна.
Натомість надсилаємо предметний документ, а не набір загальних практик:
- план робіт із даними: інвентаризація варіантів схеми за колекціями, частка документів кожного виду й місця, де розходження вже ламає читання;
- склад етапів: поле версії в документі, правила читання для кількох версій, викат за схемою «читачі раніше за письменників», фонове доповнення старих записів;
- точки контролю: частка документів старої версії, лічильник відмов розбору, алерт на невідому версію, тести на читання кожного підтримуваного виду;
- політика зворотної сумісності: зміна типу або імені поля — лише через період подвійного запису, видалення — після того як лічильник старої версії дійшов до нуля;
- оцінка трудомісткості за етапами: що закриваємо своїми силами, а де потрібні рішення з вашого боку;
- план відкату: які кроки оборотні та на якому етапі точка повернення.
Опишіть задачу — поточні колекції, обмеження, що вже пробували. Повернемося з оцінкою, складом етапів і точками контролю, за якими ви будете приймати роботу.







