РозробкаБудь-який рівень

Моноліт

Моноліт — це застосунок, який збирають, тестують і деплоять як одне ціле, незалежно від його розміру й того, як організовано код усередині. Більшість скарг на моноліти насправді стосується відсутніх меж усередині — це інша проблема з дешевшим лікуванням, — а моноліт зі справжніми межами модулів є правильним вибором за замовчуванням для більшості систем. Сторінка починається з шістнадцяти поширених скарг і того, де насправді живе кожна; далі — дві осі, які плутає кожна суперечка про архітектуру, чому один артефакт — вибір за замовчуванням і який найкращий аргумент проти, як провести й утримати межі модулів, як експлуатувати великий моноліт, де він справді вичерпується, п’ять задокументованих кейсів і де все це вироджується.

Одиниця деплою
Один артефакт, часто в кількох ролях
У масштабі
GitHub: ~2 млн рядків, 1000+ інженерів, до 20 деплоїв на день
Режим відмови
Розмиті межі модулів, а не розмір

Що насправді не так — шістнадцять скарг на моноліт і де живе кожна

Слово «моноліт» зазвичай звучить тоді, коли щось болить. Зміна йде в продакшн сорок хвилин. Ніхто не наважується торкнутися білінгу. Баґ у звіті поклав разом із собою оформлення замовлення. Кожна з цих скарг називає реальну ціну, і за кожною зазвичай іде та сама пропозиція: розрізати систему на сервіси. Але скарги походять із трьох різних місць. Одні живуть у коді — у межах між його частинами або в їхній відсутності. Інші — в інструментах збірки, тестування й деплою. Лише кілька останніх живуть у тому, що все виходить як одне ціле. Поділ деплою лікує лише цю останню групу. Ось шістнадцять скарг, які команди озвучують щодня, розкладені за тим, де вони насправді живуть. Наступні розділи посилаються на рядки за номерами.

#СкаргаДе вона насправді живеЩо зробити спершуКоли відповідь — окремий сервіс
1«Зміна в рахунках зламала сторінку входу»Межі: модулі викликають нутрощі одне одногоДати кожному модулю публічний інтерфейс і валити збірку на будь-якому іншому імпортіНіколи з цієї причини: сервіс із тією самою зв’язністю ламається вже через мережу
2«Ніхто не знає, що залежить від білінгу»Межі: граф залежностей існує лише в головах людейЗадекларувати, хто від кого може залежати, і перевіряти це в CIНіколи: граф — передумова будь-якого поділу
3«Кожна фіча зачіпає файли в десяти теках»Межі: код згруповано за технічним шаром, а не за тим, що робить бізнесПерегрупувати за модулями — переносити файли, а не переписуватиНіколи
4«Половина кодової бази читає й пише таблицю orders»Межі: спільні таблиці та JOIN між модулямиОдин модуль-власник на таблицю; решта питає цей модульЛише коли власникові справді потрібне інше сховище
5«Модуль common — найбільший»Межі: спільна шухляда, від якої залежить усе і якою не володіє ніхтоРозділити: справжня базова частина без залежностей лишається, доменний код переходить до власникаНіколи
6«Дві команди постійно правлять ті самі файли»Межі й власність: модулі не збігаються з командамиВласник для кожного модуля; посунути межі під командиКоли команди вже володіють модулями й досі блокують одна одну на релізі (рядок 15)
7«Ніхто вже не розуміє цілого»Межі: один глобальний простір іменМодулі, які можна прочитати окремо, і сторінка документації до кожногоНіколи: сорок сервісів теж ніхто не розуміє
8«Тести йдуть сорок хвилин»Інструменти: кожна зміна запускає всі тестиЗапускати лише тести зачеплених модулів — за графом із рядка 2 — паралельно; лагодити найповільнішіРідко: час тестів — проблема CI
9«Збірка триває вічність»Інструменти: немає інкрементної збірки й кешуКеш збірки й компіляція на рівні модулівРідко
10«Злиття в main — це бійка»Процес: довгоживучі гілкиМалі pull request, розробка в trunk, черга злиттяНіколи
11«Деплої страшні, тож робимо їх раз на тиждень»Процес: великі партії й немає швидкого відкатуДеплоїти менше й частіше; canary-релізи; feature flags, щоб відділити деплой від релізуНіколи: частота деплою — не властивість архітектури; GitHub деплоїть свій моноліт до 20 разів на день
12«Новому інженерові треба два дні, щоб запустити це локально»Інструменти: ручне налаштуванняОдин скрипт налаштування або dev-контейнерНіколи: сорок сервісів запустити локально важче
13«Обробці зображень треба вшістнадцятеро більше CPU, ніж решті»Форма: одна збірка — але не обов’язково один пул процесівЗапустити той самий артефакт окремим пулом воркерів на більших машинахКоли ця робота потребує іншого заліза, як-от GPU, чи іншого рантайму
14«Витік пам’яті у звітах кладе оформлення замовлення»Форма: один процес, один радіус ураженняОкремі пули для ролей (рядок 13), ліміти пам’яті, таймаути, окремі чергиКоли ізоляцію треба гарантувати: інша цільова доступність чи межа відповідності, як-от для платежів карткою
15«Дванадцять команд чекають на один релізний потяг»Форма: одна одиниця деплоюСпершу рядки 10–11: більшість болю релізного потяга — розмір партіїКоли команди володіють модулями, деплої вже часті, а координація досі — виміряне вузьке місце
16«Цій частині було б значно краще іншою мовою»Форма: один рантаймЗазвичай нічого: друга мова — це другий набір інструментів, другий ринок найму й другий графік оновленьКоли аргумент технічний і виміряний: машинне навчання на Python, жорстка ціль затримки, бібліотека, яка є лише в одній екосистемі
Моноліт — це одна одиниця деплою: його модулі викликають одне одного всередині процесу, і всі мають спільну базу даних, у якій кожен модуль має володіти власними таблицями. Червона пунктирна стрілка — це зміна, що спричиняє гниття: один модуль читає таблиці іншого напряму, тут через JOIN, замість звернутися до того модуля через його інтерфейс. З цієї миті два модулі вже не можна змінювати, тестувати чи виділяти окремо. Моноліт із дотриманими межами лишається робочим стільки, скільки він потрібен бізнесу; без них він стає системою, у якій жодна зміна не є локальною.

Дві осі, які плутає кожна суперечка про архітектуру

Моноліт — це застосунок, чий код збирають, тестують і деплоять як одне ціле. Це все визначення. Воно нічого не каже про розмір кодової бази чи кількість людей на ній. Не каже воно й того, чи добре організовано код усередині. Продукт на десять людей і GitHub — обидва моноліти саме в цьому сенсі.

Образою це слово стало приблизно 25 березня 2014 року. Того дня Джеймс Льюїс і Мартін Фаулер опублікували статтю, що дала назву мікросервісам, і визначили новий стиль через протиставлення «монолітному застосунку». За кілька років «моноліт» почав означати старе, повільне й заплутане. Але заплутаний стан старший і має власну назву. Браян Фут і Джозеф Йодер назвали його великою грудкою бруду (big ball of mud) 1997 року: система без упізнаваної структури, де все пов’язано з усім. Це властивість коду, а не деплою. Велика грудка бруду, розрізана на двадцять сервісів, лишається великою грудкою бруду — просто тепер її зв’язки перетинають мережу.

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

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

ВидЩо змінюється разомВиклик між частинамиУзгодженість між частинамиПересунути межу
Велика грудка брудуЩо завгодно з чим завгодноВиклик функціїОдна транзакція бази данихВажко: ніхто не знає, що від чого залежить
Модульний монолітОдин модульВиклик функції через інтерфейсОдна транзакція бази даних, де ви це дозволяєтеРефакторинг, який перевіряє компілятор
Розподілений монолітКілька сервісів, які випускають у фіксованому порядкуМережевий виклик, що може відвалитися за таймаутомСага — а часто й нічогоМіграція через кілька репозиторіїв
МікросервісиОдин сервісМережевий виклик через версійований APIСага з компенсаційними крокамиМіграція версійованого API

Чому один артефакт — правильний вибір за замовчуванням і найкращий аргумент проти

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

ПитанняУсередині одного процесуЧерез мережу
ВикликВиклик функції: наносекунди, і він або повернув результат, або кинув винятокБлизько пів мілісекунди в межах одного дата-центру, і він може відвалитися за таймаутом, впасти на півдорозі чи вдатися вже після того, як ви здалися
Узгодженість двох частинОдна транзакція бази данихСага, outbox, компенсаційні дії — і пояснення бізнесу, що таке «з часом»
Пересунути межуРефакторинг, який перевіряють компілятор і IDEЗміна версійованого API через два репозиторії, два конвеєри й два деплої в правильному порядку
Налагодження одного запитуОдин стектрейсРозподілене трасування, яке хтось має збудувати й підтримувати робочим
Локальний запускОдин процес і база данихБагато сервісів, їхні бази й брокер повідомлень — або спільне тестове середовище
ДеплойОдин конвеєрКонвеєр на кожен сервіс і матриця сумісності між їхніми версіями

Глибший аргумент — про знання, і Мартін Фаулер подав його під назвою «MonolithFirst» 3 червня 2015 року. Він навів два спостереження з проєктів, які бачив. Майже всі успішні історії мікросервісів починалися з моноліту, який виріс занадто великим і був розібраний. Майже всі системи, збудовані як мікросервіси з нуля, опинилися в серйозних проблемах. Його пояснення: межі сервісів дорого пересувати й важко провести правильно, а помиляються в них тоді, коли ще не розуміють предметної області, — а на старті продукту ви її не розумієте. Моноліт дозволяє дешево рухати межі, поки ви ще вчитеся, де їм бути. Він відкладає дороге зобов’язання до появи доказів.

Через шість днів Фаулер опублікував на власному сайті найсильнішу відповідь. Стефан Тільков у статті «Don’t start with a monolith» доводив протилежне, спираючись на такий самий довгий досвід. Розділити наявний моноліт, писав він, надзвичайно важко, бо за роки кожна частина починає залежати від спільних бібліотек, спільних доменних об’єктів і спільної бази даних. А старт нової системи — саме той момент, коли варто думати про поділ на частини, бо це останній момент, коли він дешевий. Тільков додав одну умову: предметну область, для якої ви будуєте, треба знати дуже-дуже добре. Ці два есеї досі найкраще коротке формулювання цього компромісу.

Найчастіше цитований захист цього стилю прийшов від невеликої команди. У тексті «The Majestic Monolith» (29 лютого 2016 року) Девід Гайнемаєр Ганссон описав Basecamp: дванадцять програмістів, мільйони користувачів і каталог застосунків, які вони пообіцяли підтримувати. Його теза: щоразу, коли співпрацю між об’єктами перетворюють на співпрацю між системами, ви погоджуєтеся на «цілий світ болю». Аргумент — про пропорцію. Розподілена система — це податок, який сплачують людьми. Дванадцять програмістів не можуть дозволити собі витрачати на нього чотирьох.

Як провести межі модулів і змусити їх триматися

Проводьте модулі навколо того, що робить бізнес, а не навколо того, яким кодом є файл. Замовлення, білінг, каталог і користувачі — це модулі. Контролери, сервіси й репозиторії — ні: це шари, а шар зачіпає кожну фічу. Допомагають два швидкі тести. Чи можна описати роботу модуля одним реченням без слова «і»? І чи лишається типова зміна фічі в межах одного модуля? У предметно-орієнтованому проєктуванні (domain-driven design) ця сама ідея зветься обмеженим контекстом: частиною системи, усередині якої кожен термін означає щось одне. «Замовлення» означає різне для складу й для білінгу, і саме ця різниця показує, де пройде межа.

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

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

Залежності між модулями мають утворювати граф без циклів: базовий код унизу, бізнес-модулі над ним, і нічого внизу не знає про те, що вище. Досвід Shopify додає уточнення, яке легко пропустити. Його інженери помітили, що розробники розкладають код по модулях за тим, що підказує назва, а не за тим, як код працює. Модель під назвою «налаштування білінгу магазину» жила в пакеті білінгу через свою назву — але вона ж тримала прапорець, що позначає магазин як шахрайський, а він потрібен кожному запиту. Їй було місце в основі графа, доступній усьому. Тож перш ніж вирішувати, якому модулю належить шматок коду, перевірте, де його використовують, а не як він називається.

Як змусити межі триматися

У моноліті ніщо не забезпечує межу, доки ви цього не зробите. Ніщо не заважає розробнику о другій ночі заімпортувати клас з іншого модуля, і ніщо не скаже йому, що так не можна. Між сервісами межу тримає мережа тим, що вона незручна. Усередині моноліту цю незручність треба створити свідомо — у мові, у збірці або в перевірці, що валить . Що доступно, залежить від екосистеми:

ЕкосистемаЩо дають мова чи збіркаПеревірка, що валить CI
Java, KotlinКласи з видимістю в межах пакета; модулі Maven чи Gradle; модулі Java (JPMS) з явним експортомПравила ArchUnit; Spring Modulith, що ще й перевіряє структуру модулів і документує її
.NETОкремий проєкт на модуль; типи internal невидимі іншим збіркамПравила NetArchTest чи ArchUnitNET
GoКаталог internal/ можна імпортувати лише зсередини його батьківського — це забезпечує сам інструментарійЗазвичай більше нічого не треба
Ruby on RailsКожна константа глобальна; певну структуру дають enginesPackwerk — перевірка залежностей від Shopify
PHPПростори імен без видимості між нимиПравила Deptrac
PythonЛише домовленість: підкреслення на початку іменіКонтракти import-linter
TypeScript, JavaScriptПакети робочого простору, чиє поле exports перелічує публічні точки входуПравила меж модулів у Nx, dependency-cruiser, правила імпортів ESLint

Найкращу віддачу дає перевірка, що валить збірку. Десяток рядків, які стверджують, що ніщо поза білінгом не має права імпортувати billing.internal, перетворюють правило, з яким усі згодні й якого ніхто не дотримує, на червону збірку з файлом і номером рядка. Без такої перевірки межі модулів живуть рівно доти, доки в команді лишається людина, якій вони не байдужі. Для наявної кодової бази звичний вхід — записати всі поточні порушення у файл-список і валити збірку лише на нових. Packwerk називає це оголошенням банкрутства. Це працює з одним застереженням, яке Shopify опублікував сам: перевірка знаходить проблеми значно швидше, ніж команда їх виправляє, тож список потребує власника й бюджету, інакше він лише росте.

Як експлуатувати великий моноліт: масштабування, деплої й тести

Моноліт масштабується горизонтально, як і будь-що інше. Якщо застосунок не тримає стану між запитами у власній пам’яті — сесії в базі чи кеші, файли в об’єктному сховищі, — ви запускаєте стільки копій, скільки потребує навантаження, за балансувальником навантаження (). Stack Overflow опублікував свої цифри за 9 лютого 2016 року: 209 мільйонів запитів і 66 мільйонів завантажень сторінок за день, які обслуговував один застосунок на ASP.NET на одинадцяти вебсерверах, і середня сторінка питання рендерилася за 22,71 мілісекунди. Його інженери додали, що на той час сайт міг працювати на одному вебсервері — і кілька разів випадково так і робив. Межею, над якою вони працювали, була продуктивність усередині застосунку, а не кількість одиниць деплою.

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

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

Одна збірка не означає одного пулу однакових процесів. Той самий артефакт можна запустити як вебсервер, як фоновий воркер для однієї черги, як воркер для важкої на черги на більших машинах або як єдиний планувальник, що ніколи не має працювати двічі. Кожен пул отримує власну кількість екземплярів, розмір машин, ліміт пам’яті й політику перезапуску. Це відповідь на рядки 13 і 14 без другої кодової бази. Обробка зображень отримує вшістнадцятеро більше CPU, а вебсервери з цього не отримують нічого, і витік пам’яті в генерації звітів перезапускає воркери звітів, а не оформлення замовлення: радіус ураження () збою звужується до одного пулу. Prime Video 2023 року дійшов до того самого патерну з іншого боку: об’єднавши розподілений сервіс в один процес, він запустив кілька його копій, кожну з власною частиною роботи.

Частота деплою — властивість розміру партії й автоматизації, а не архітектури (рядок 11). GitHub описав свій моноліт на Rails 2023 року: майже два мільйони рядків коду, понад тисяча інженерів, що працюють із ним щодня, і деплої до двадцяти разів на день. Навіть фреймворк оновлюють безперервно: щопонеділка запланована задача відкриває pull request, що переводить застосунок на останній коміт самого Rails, і зміна виходить того ж тижня. Уможливлює це механіка навколо коду: черга злиття, що тестує зміни в тому порядку, в якому вони потраплять у main, -релізи спершу на малу частку трафіку й , щоб деплой коду та вмикання фічі були двома окремими рішеннями.

Тести — місце, де межі модулів окупаються вдруге. Маючи задекларований граф залежностей (рядок 2), CI може визначити, які модулі зміна могла зачепити, і запустити лише їхні тести — Shopify назвав саме це однією з цілей своїх компонентів. Без графа кожна зміна запускає всі тести, і набір росте разом із кодовою базою, доки люди не перестануть запускати його локально. Таблиця перелічує, що росте разом із монолітом, і виправлення, яке не потребує нової архітектури.

Що ростеСимптомВиправлення всередині моноліту
Час тестівCI триває довше за каву, потім довше за обідВибір тестів за зачепленими модулями, паралельні виконавці, карантин для нестабільних тестів
Час збіркиКожна зміна перезбирає всеКеш збірки; окрема компіляція модулів
Конфлікти злиттяMain червоний, бо дві зелені зміни зіткнулисяЧерга злиття; малі pull request
Ризик деплоюРелізи стають більшими, рідшими й страшнішимиДеплоїти кожну зміну; спершу canary; прапорці для незавершених фіч
Час стартуЕкземпляр стартує хвилинами, тож масштабування відстає від навантаженняВантажити код ліниво; виносити стартову роботу зі шляху запиту
Пам’ять на процесКожен екземпляр несе кеші всіх фічОкремі пули для ролей, кожен зі своїм лімітом

Де моноліт справді вичерпується

Реальні межі існують, і чесність щодо них відрізняє аргумент від уподобання. Це рядки 13–16 плюс одна межа, яку жоден рядок добре не передає: кількість команд. Мелвін Конвей 1968 року помітив, що структура системи зрештою віддзеркалює структуру комунікації організації, яка її будує. Для трьох команд один артефакт — зручність. Для тридцяти він стає нарадою, на яку мусять приходити всі. Таблиця перетворює кожну межу на те, що можна виміряти до рішення, — бо в кожної є дешевша відповідь, яку варто спробувати першою.

МежаСигнал, який можна вимірятиСпробувати спершуКоли виділення сервісу окупається
Профіль масштабування (рядок 13)Один вид роботи домінує в CPU чи пам’яті, і під нього виділено ресурси для всьогоОкремий пул процесів для цієї роліЇй потрібне інше залізо чи рантайм, якого в моноліті немає
Ізоляція (рядок 14)Інциденти, у яких одна фіча поклала іншу; межа аудиту, що охоплює весь застосунокПули, таймаути, окремі черги, ліміти пам’ятіЧастині потрібна інша цільова доступність, або її треба вивести з-під межі відповідності — класичний приклад: дані карток під PCI DSS
Координація релізів (рядок 15)Час виконання, у якому домінує очікування на зміни інших командМенші партії, feature flags, власність на модуліКоманди володіють чистими модулями, деплоять щодня й досі блокують одна одну
Рантайм (рядок 16)Виміряний виграш від іншої мови, а не вподобанняБібліотека або невеликий допоміжний процес поруч із монолітомОбслуговування моделей, робота на GPU, жорстка ціль затримки
Кількість командДесятки команд; суперечки про власність на спільні файли; збірка, якою ніхто не володієВласність на модулі й платформна команда для механіки збірки й деплоюМодулі вже чисті, а спотикаються команди об спільну базу й спільний реліз

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

П’ять кейсів і те, чого в кожному бракувало

П’ять кейсів охоплюють обидва напрямки. Дві команди повернулися від сервісів до одного процесу, один проєкт об’єднав власні мікросервіси в один бінарник, одна компанія роками робила гігантський моноліт модульним, а один сайт обслуговував сотні мільйонів запитів на день одним застосунком. Кожен кейс задокументували інженери, які робили цю роботу.

КейсЩо сталосяЩо це показує
Stack Overflow, 2016209 мільйонів запитів на день від одного застосунку на ASP.NET на одинадцяти вебсерверахМежею була продуктивність усередині застосунку, а не кількість одиниць деплою
Segment, 2018Понад 140 сервісів, по одному на кожен напрям доставки, знову злито в один сервіс і один репозиторійСервіси без інструментів, щоб тестувати й випускати спільні зміни в усіх одразу, коштують більше, ніж ізолюють
Istio 1.5, 2020Чотири сервіси площини керування злито в один бінарник — istiodЧастини, які завжди випускають разом і експлуатує одна команда, нічого не виграють від окремого деплою
Prime Video, 2023Безсерверний конвеєр моніторингу вперся в жорстку межу приблизно на 5% очікуваного навантаження; один процес зменшив його вартість більш ніж на 90%Дані, що перетинають мережу на кожному кадрі, платять за це на кожному кадрі
Shopify, 2019–2024Моноліт, який будували понад тисяча розробників, зробили модульним через компоненти й PackwerkМежі, проведені за назвами доменів, не збігаються з тим, як працює код, а перевірка знаходить порушення швидше, ніж команди їх виправляють

Історію Segment цитують найчастіше, бо вона пройшла в обидва боки. Продукт пересилав події клієнтів до понад сотні партнерських . Одна спільна черга означала, що один повільний партнер затримував усіх, тож команда дала кожному напряму доставки власний сервіс і чергу — розумне виправлення реальної проблеми. Потім кожен напрям отримав власний репозиторій, щоб один зламаний тест не блокував усіх інших. Слідом прийшли спільні бібліотеки, і їхні версії в десятках репозиторіїв розійшлися, бо оновити всі одразу було ризиком, на який ні в кого не було часу. Кожен сервіс мав власний профіль навантаження, і масштабувати його доводилося вручну. Зрештою троє інженерів на повну ставку витрачали більшість часу на те, щоб система просто жила. Команда злила понад 140 сервісів в один, закріпила 120 залежностей на одній версії й замінила в тестах живі виклики записаним трафіком, тож увесь набір проходив за мілісекунди замість години. Поліпшень спільних бібліотек стало 46 за рік після зміни проти 32 за рік до неї. У тексті названо й ціну: один баґ тепер може покласти всі напрями, а кеші в пам’яті розмазані по понад 3000 процесів.

Shopify — довга версія і найкорисніша, бо це модульний моноліт, описаний зсередини. 2019 року інженери компанії пояснили, чому вирішили зробити моноліт модульним, а не розділити його: кодова база, над якою понад десять років працювали понад тисяча розробників, і код оформлення замовлення та код тарифів доставки могли вільно викликати одне одного. Вони розклали тисячі файлів у пару десятків компонентів, по одному на напрям комерції, а у вересні 2020 року випустили Packwerk, щоб перевіряти залежності між ними в кожному pull request. У лютому 2024 року та сама команда опублікувала ретроспективу, і вона незвично відверта. Перевірки приватності в Packwerk, що позначали код публічним чи приватним, прибрали, бо команди почали шліфувати інтерфейси замість того, щоб виправляти залежності. Коли команда прибрала всі порушення одного базового пакета, вона знайшла в Packwerk баґ, який показав, що вона, найімовірніше, перший користувач, що взагалі дійшов до кінця списку. Щоб цей один пакет справді стартував і проходив тести ізольовано, знадобилося багато місяців. А компоненти, проведені навколо бізнес-доменів, не збіглися з тим, як працює код, тож тепер усередині них групують менші пакети, проведені за функцією.

Де це вироджується

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

  • JOIN і зовнішні ключі через таблиці модулів, що зв’язують модулі через сховище замість інтерфейсів (рядок 4).
  • Пакети за шарами — контролери, сервіси, репозиторії, — тож поверхнею кожної фічі є вся кодова база (рядок 3).
  • Модуль common чи utils, від якого залежить усе і яким не володіє ніхто (рядок 5).
  • Правила модулів, узгоджені в документі, а не перевірені в CI, тож вони тримаються квартал.
  • Файл-список відомих порушень без власника, який лише росте.
  • ORM-сутності, які передають між модулями, віддаючи сховище одного модуля іншому.
  • Тести, достатньо повільні, щоб їх перестали запускати локально, — зникає той швидкий зворотний зв’язок, що робив моноліт дешевим (рядок 8).
  • Щотижневі релізи, які захищають як обережність, хоча ризик — саме розмір партії (рядок 11).
  • Один пул процесів для всіх видів роботи, тож найважча задача визначає розмір кожної машини (рядок 13).
  • Поділ, щоб полагодити зв’язність, який дає розподілений моноліт: зв’язність лишається, мережа додається.

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

Практична перевірка займає пів дня. Складіть список модулів і команди, що володіє кожним; якщо не виходить — почніть із цього. Пошукайте в запитах JOIN між таблицями, що належать різним модулям. Згенеруйте граф залежностей і пошукайте цикли. З’ясуйте, чи є перевірка, яка валить CI, коли модуль імпортує нутрощі іншого. Заміряйте тести й спитайте, чи запускаються лише ті, що їх зачіпає зміна. Порахуйте деплої за останній місяць. І для кожної скарги на «моноліт», яку ви чуєте, знайдіть її рядок у таблиці вгорі сторінки, перш ніж хтось відкриє пропозицію його розділити.

Коли це застосовувати

Застосовуйте, коли

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

Уникайте, коли

  • Багато команд, які вже володіють чистими модулями, досі блокують одна одну на спільному релізі, і ця ціна виміряна.
  • Одному компоненту потрібне інше залізо чи інший рантайм, і окремого пулу процесів замало.
  • Частини системи мають справді різні цільові рівні доступності чи межі відповідності.
  • Код уже є грудкою бруду — тоді перша робота — модулі, а поділ зараз лише розподілить бруд.

Джерела й метод

На чому стоїть ця стаття

  • 17 джерел
  • Перевірено 13 жовтня 2026 р.

Дослідження з Claude. Пошук і звірку першоджерел зроблено з Claude (Anthropic). Відбір, текст і перевірку фактів — редакція Anatomy of IT Product.

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

Поділіться з тим, хто працює над тією ж задачею.