Моноліт
Моноліт — це застосунок, який збирають, тестують і деплоять як одне ціле, незалежно від його розміру й того, як організовано код усередині. Більшість скарг на моноліти насправді стосується відсутніх меж усередині — це інша проблема з дешевшим лікуванням, — а моноліт зі справжніми межами модулів є правильним вибором за замовчуванням для більшості систем. Сторінка починається з шістнадцяти поширених скарг і того, де насправді живе кожна; далі — дві осі, які плутає кожна суперечка про архітектуру, чому один артефакт — вибір за замовчуванням і який найкращий аргумент проти, як провести й утримати межі модулів, як експлуатувати великий моноліт, де він справді вичерпується, п’ять задокументованих кейсів і де все це вироджується.
- Одиниця деплою
- Один артефакт, часто в кількох ролях
- У масштабі
- 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, жорстка ціль затримки, бібліотека, яка є лише в одній екосистемі |
Дві осі, які плутає кожна суперечка про архітектуру
Моноліт — це застосунок, чий код збирають, тестують і деплоять як одне ціле. Це все визначення. Воно нічого не каже про розмір кодової бази чи кількість людей на ній. Не каже воно й того, чи добре організовано код усередині. Продукт на десять людей і 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) ця сама ідея зветься обмеженим контекстом: частиною системи, усередині якої кожен термін означає щось одне. «Замовлення» означає різне для складу й для білінгу, і саме ця різниця показує, де пройде межа.
Модуль володіє чотирма речами: своїм кодом, своїми таблицями, своїм публічним інтерфейсом і подіями, які він публікує. Інші модулі можуть викликати інтерфейс і слухати події — і нічого більше. Через межу мають іти прості дані й ідентифікатори, а не живі об’єкти. Передати ORMObject-Relational Mapping — обʼєкти замість SQLБібліотека, що підставляє обʼєкти мови там, де в базі лежать рядки таблиці, — тож SQL ви майже не пишете. Зручність коштує контролю: згенерований запит буває значно важчим за той, який ви написали б самі, і зʼясовується це вже під навантаженням.Докладніше в статті →Усі терміни-сутність з одного модуля в інший здається нешкідливим, але це віддає всю схему зберігання першого модуля: отримувач тепер може ліниво підвантажувати пов’язані рядки й зберігати зміни, яких власник ніколи не побачить. Те саме з базою даних. Кожна таблиця має рівно один модуль-власник, і жоден інший модуль не робить JOIN із нею й не тримає зовнішнього ключа на неї. Саме порушення цього правила показує діаграма вгорі сторінки.
Залежності між модулями мають утворювати граф без циклів: базовий код унизу, бізнес-модулі над ним, і нічого внизу не знає про те, що вище. Досвід Shopify додає уточнення, яке легко пропустити. Його інженери помітили, що розробники розкладають код по модулях за тим, що підказує назва, а не за тим, як код працює. Модель під назвою «налаштування білінгу магазину» жила в пакеті білінгу через свою назву — але вона ж тримала прапорець, що позначає магазин як шахрайський, а він потрібен кожному запиту. Їй було місце в основі графа, доступній усьому. Тож перш ніж вирішувати, якому модулю належить шматок коду, перевірте, де його використовують, а не як він називається.
Як змусити межі триматися
У моноліті ніщо не забезпечує межу, доки ви цього не зробите. Ніщо не заважає розробнику о другій ночі заімпортувати клас з іншого модуля, і ніщо не скаже йому, що так не можна. Між сервісами межу тримає мережа тим, що вона незручна. Усередині моноліту цю незручність треба створити свідомо — у мові, у збірці або в перевірці, що валить CI/CDContinuous Integration / Continuous Delivery — автоматична збірка й доставкаКожен коміт автоматично складається і проганяється через тести (CI), а придатна збірка так само автоматично їде на сервери (CD). Сенс не в автоматизації як такій, а в тому, що поломку видно за хвилини, а не в день релізу.Докладніше в статті →Усі терміни. Що доступно, залежить від екосистеми:
| Екосистема | Що дають мова чи збірка | Перевірка, що валить CI |
|---|---|---|
| Java, Kotlin | Класи з видимістю в межах пакета; модулі Maven чи Gradle; модулі Java (JPMS) з явним експортом | Правила ArchUnit; Spring Modulith, що ще й перевіряє структуру модулів і документує її |
| .NET | Окремий проєкт на модуль; типи internal невидимі іншим збіркам | Правила NetArchTest чи ArchUnitNET |
| Go | Каталог internal/ можна імпортувати лише зсередини його батьківського — це забезпечує сам інструментарій | Зазвичай більше нічого не треба |
| Ruby on Rails | Кожна константа глобальна; певну структуру дають engines | Packwerk — перевірка залежностей від Shopify |
| PHP | Простори імен без видимості між ними | Правила Deptrac |
| Python | Лише домовленість: підкреслення на початку імені | Контракти import-linter |
| TypeScript, JavaScript | Пакети робочого простору, чиє поле exports перелічує публічні точки входу | Правила меж модулів у Nx, dependency-cruiser, правила імпортів ESLint |
Найкращу віддачу дає перевірка, що валить збірку. Десяток рядків, які стверджують, що ніщо поза білінгом не має права імпортувати billing.internal, перетворюють правило, з яким усі згодні й якого ніхто не дотримує, на червону збірку з файлом і номером рядка. Без такої перевірки межі модулів живуть рівно доти, доки в команді лишається людина, якій вони не байдужі. Для наявної кодової бази звичний вхід — записати всі поточні порушення у файл-список і валити збірку лише на нових. Packwerk називає це оголошенням банкрутства. Це працює з одним застереженням, яке Shopify опублікував сам: перевірка знаходить проблеми значно швидше, ніж команда їх виправляє, тож список потребує власника й бюджету, інакше він лише росте.
Як експлуатувати великий моноліт: масштабування, деплої й тести
Моноліт масштабується горизонтально, як і будь-що інше. Якщо застосунок не тримає стану між запитами у власній пам’яті — сесії в базі чи кеші, файли в об’єктному сховищі, — ви запускаєте стільки копій, скільки потребує навантаження, за балансувальником навантаження (load balancerрозподіляє запити між серверамиСервер чи керований сервіс, що приймає вхідні запити й розподіляє їх між кількома копіями застосунку, оминаючи копії, які не проходять перевірку стану. Це також ще один мережевий вузол із власними тайм-аутами, тож його налаштування й налаштування застосунку мають узгоджуватися.Усі терміни). Stack Overflow опублікував свої цифри за 9 лютого 2016 року: 209 мільйонів запитів і 66 мільйонів завантажень сторінок за день, які обслуговував один застосунок на ASP.NET на одинадцяти вебсерверах, і середня сторінка питання рендерилася за 22,71 мілісекунди. Його інженери додали, що на той час сайт міг працювати на одному вебсервері — і кілька разів випадково так і робив. Межею, над якою вони працювали, була продуктивність усередині застосунку, а не кількість одиниць деплою.
Коли навантаженому моноліту стає тісно, першою зазвичай закінчується база даних, а не застосунок. Відповіді ті самі, що знадобилися б і набору сервісів: кешування, репліки для читання для запитів, яким не страшні трохи застарілі дані, а зрештою — розбиття даних за клієнтом чи регіоном. Поділ коду на сервіси цієї межі не прибирає. Він дає кожному сервісу меншу базу й нову проблему — запити й транзакції, які раніше їх охоплювали.
Одна збірка не означає одного пулу однакових процесів. Той самий артефакт можна запустити як вебсервер, як фоновий воркер для однієї черги, як воркер для важкої на CPUCentral Processing Unit — процесорПроцесор, що виконує інструкції програми. Коли кажуть, що завдання «впирається в CPU», мають на увазі, що воно обмежене обчисленнями, а не очікуванням диска чи мережі, — і лікуються ці два випадки протилежно, тому розрізняти їх варто одразу.Усі терміни черги на більших машинах або як єдиний планувальник, що ніколи не має працювати двічі. Кожен пул отримує власну кількість екземплярів, розмір машин, ліміт пам’яті й політику перезапуску. Це відповідь на рядки 13 і 14 без другої кодової бази. Обробка зображень отримує вшістнадцятеро більше CPU, а вебсервери з цього не отримують нічого, і витік пам’яті в генерації звітів перезапускає воркери звітів, а не оформлення замовлення: радіус ураження (blast radiusнаскільки далеко сягає шкодаЯку частину системи зачіпає, коли щось одне йде не так, — поганий деплой, процес, що впав, витік облікових даних, помилка агента. Його зменшують межами: окремими процесами, вузькими дозволами, поетапними розгортаннями. Компроміс у тому, що кожна межа, яка стримує збій, ще й щось коштує, коли її треба перетнути.Усі терміни) збою звужується до одного пулу. Prime Video 2023 року дійшов до того самого патерну з іншого боку: об’єднавши розподілений сервіс в один процес, він запустив кілька його копій, кожну з власною частиною роботи.
Частота деплою — властивість розміру партії й автоматизації, а не архітектури (рядок 11). GitHub описав свій моноліт на Rails 2023 року: майже два мільйони рядків коду, понад тисяча інженерів, що працюють із ним щодня, і деплої до двадцяти разів на день. Навіть фреймворк оновлюють безперервно: щопонеділка запланована задача відкриває pull request, що переводить застосунок на останній коміт самого Rails, і зміна виходить того ж тижня. Уможливлює це механіка навколо коду: черга злиття, що тестує зміни в тому порядку, в якому вони потраплять у main, canaryвипуск спершу для небагатьохСпершу спрямувати нову версію на малу частку трафіку й подивитись, перш ніж піде решта. Це купує те, чого не дає повний реліз: поломка коштує вам одного відсотка користувачів, а не всіх.Докладніше в статті →Усі терміни-релізи спершу на малу частку трафіку й feature flagперемикач навколо нового кодуПеремикач, що під час роботи вирішує, чи активний шматок коду, — тож код може потрапити в продакшен вимкненим і бути ввімкненим для обраної аудиторії пізніше. Він відділяє деплой від релізу й має ціну: кожен живий прапорець — це розгалуження в коді, яке має працювати в обох станах.Докладніше в статті →Усі терміни, щоб деплой коду та вмикання фічі були двома окремими рішеннями.
Тести — місце, де межі модулів окупаються вдруге. Маючи задекларований граф залежностей (рядок 2), CI може визначити, які модулі зміна могла зачепити, і запустити лише їхні тести — Shopify назвав саме це однією з цілей своїх компонентів. Без графа кожна зміна запускає всі тести, і набір росте разом із кодовою базою, доки люди не перестануть запускати його локально. Таблиця перелічує, що росте разом із монолітом, і виправлення, яке не потребує нової архітектури.
| Що росте | Симптом | Виправлення всередині моноліту |
|---|---|---|
| Час тестів | CI триває довше за каву, потім довше за обід | Вибір тестів за зачепленими модулями, паралельні виконавці, карантин для нестабільних тестів |
| Час збірки | Кожна зміна перезбирає все | Кеш збірки; окрема компіляція модулів |
| Конфлікти злиття | Main червоний, бо дві зелені зміни зіткнулися | Черга злиття; малі pull request |
| Ризик деплою | Релізи стають більшими, рідшими й страшнішими | Деплоїти кожну зміну; спершу canary; прапорці для незавершених фіч |
| Час старту | Екземпляр стартує хвилинами, тож масштабування відстає від навантаження | Вантажити код ліниво; виносити стартову роботу зі шляху запиту |
| Пам’ять на процес | Кожен екземпляр несе кеші всіх фіч | Окремі пули для ролей, кожен зі своїм лімітом |
Де моноліт справді вичерпується
Реальні межі існують, і чесність щодо них відрізняє аргумент від уподобання. Це рядки 13–16 плюс одна межа, яку жоден рядок добре не передає: кількість команд. Мелвін Конвей 1968 року помітив, що структура системи зрештою віддзеркалює структуру комунікації організації, яка її будує. Для трьох команд один артефакт — зручність. Для тридцяти він стає нарадою, на яку мусять приходити всі. Таблиця перетворює кожну межу на те, що можна виміряти до рішення, — бо в кожної є дешевша відповідь, яку варто спробувати першою.
| Межа | Сигнал, який можна виміряти | Спробувати спершу | Коли виділення сервісу окупається |
|---|---|---|---|
| Профіль масштабування (рядок 13) | Один вид роботи домінує в CPU чи пам’яті, і під нього виділено ресурси для всього | Окремий пул процесів для цієї ролі | Їй потрібне інше залізо чи рантайм, якого в моноліті немає |
| Ізоляція (рядок 14) | Інциденти, у яких одна фіча поклала іншу; межа аудиту, що охоплює весь застосунок | Пули, таймаути, окремі черги, ліміти пам’яті | Частині потрібна інша цільова доступність, або її треба вивести з-під межі відповідності — класичний приклад: дані карток під PCI DSS |
| Координація релізів (рядок 15) | Час виконання, у якому домінує очікування на зміни інших команд | Менші партії, feature flags, власність на модулі | Команди володіють чистими модулями, деплоять щодня й досі блокують одна одну |
| Рантайм (рядок 16) | Виміряний виграш від іншої мови, а не вподобання | Бібліотека або невеликий допоміжний процес поруч із монолітом | Обслуговування моделей, робота на GPU, жорстка ціль затримки |
| Кількість команд | Десятки команд; суперечки про власність на спільні файли; збірка, якою ніхто не володіє | Власність на модулі й платформна команда для механіки збірки й деплою | Модулі вже чисті, а спотикаються команди об спільну базу й спільний реліз |
Виділити один сервіс, щоб зняти одне виміряне обмеження, — це не те саме рішення, що перейти на мікросервіси. Перше — одна міграція з чіткою причиною й чітким кінцем. Друге — операційна модель для всієї організації. Як безпечно зробити перше — фасад перед монолітом, перенесення по одній можливості, поділ даних наостанок, — тема сторінки «Моноліт → мікросервіси», а чого коштує друге — на сторінці про мікросервіси.
П’ять кейсів і те, чого в кожному бракувало
П’ять кейсів охоплюють обидва напрямки. Дві команди повернулися від сервісів до одного процесу, один проєкт об’єднав власні мікросервіси в один бінарник, одна компанія роками робила гігантський моноліт модульним, а один сайт обслуговував сотні мільйонів запитів на день одним застосунком. Кожен кейс задокументували інженери, які робили цю роботу.
| Кейс | Що сталося | Що це показує |
|---|---|---|
| Stack Overflow, 2016 | 209 мільйонів запитів на день від одного застосунку на ASP.NET на одинадцяти вебсерверах | Межею була продуктивність усередині застосунку, а не кількість одиниць деплою |
| Segment, 2018 | Понад 140 сервісів, по одному на кожен напрям доставки, знову злито в один сервіс і один репозиторій | Сервіси без інструментів, щоб тестувати й випускати спільні зміни в усіх одразу, коштують більше, ніж ізолюють |
| Istio 1.5, 2020 | Чотири сервіси площини керування злито в один бінарник — istiod | Частини, які завжди випускають разом і експлуатує одна команда, нічого не виграють від окремого деплою |
| Prime Video, 2023 | Безсерверний конвеєр моніторингу вперся в жорстку межу приблизно на 5% очікуваного навантаження; один процес зменшив його вартість більш ніж на 90% | Дані, що перетинають мережу на кожному кадрі, платять за це на кожному кадрі |
| Shopify, 2019–2024 | Моноліт, який будували понад тисяча розробників, зробили модульним через компоненти й Packwerk | Межі, проведені за назвами доменів, не збігаються з тим, як працює код, а перевірка знаходить порушення швидше, ніж команди їх виправляють |
Історію Segment цитують найчастіше, бо вона пройшла в обидва боки. Продукт пересилав події клієнтів до понад сотні партнерських APIApplication Programming Interface — контракт між програмамиПравила, за якими одна програма звертається до іншої: які запити можна надіслати, у якому вигляді й що прийде у відповідь. Для того, хто звертається, це контракт — усе, що за ним, лишається чорною скринькою, і саме тому другий бік можна переписати, не зламавши вас.Докладніше в статті →Усі терміни. Одна спільна черга означала, що один повільний партнер затримував усіх, тож команда дала кожному напряму доставки власний сервіс і чергу — розумне виправлення реальної проблеми. Потім кожен напрям отримав власний репозиторій, щоб один зламаний тест не блокував усіх інших. Слідом прийшли спільні бібліотеки, і їхні версії в десятках репозиторіїв розійшлися, бо оновити всі одразу було ризиком, на який ні в кого не було часу. Кожен сервіс мав власний профіль навантаження, і масштабувати його доводилося вручну. Зрештою троє інженерів на повну ставку витрачали більшість часу на те, щоб система просто жила. Команда злила понад 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 р.
Практики й походження термінів
що таке моноліт, суперечка про те, чи з нього починати, і інструменти меж- СтаттяMartin Fowler — MonolithFirst (відкривається в новій вкладці)martinfowler.com · 03.06.2015
- СтаттяStefan Tilkov — Don’t start with a monolith (відкривається в новій вкладці)martinfowler.com · 09.06.2015
- СтаттяJames Lewis, Martin Fowler — Microservices (відкривається в новій вкладці)martinfowler.com · 25.03.2014
- ДослідженняBrian Foote, Joseph Yoder — Big Ball of Mud (відкривається в новій вкладці)PLoP 1997 · laputan.org · 1997
- СтаттяDHH — The Majestic Monolith (відкривається в новій вкладці)Signal v. Noise · 29.02.2016
- КнигаSam Newman — Monolith to Microservices (відкривається в новій вкладці)O’Reilly · 2019
- ДокументаціяSpring Modulith (відкривається в новій вкладці)spring.io
- ДокументаціяArchUnit (відкривається в новій вкладці)archunit.org
- ДокументаціяShopify/packwerk (відкривається в новій вкладці)GitHub
Кейси
розділ «П’ять кейсів і те, чого в кожному бракувало»- СтаттяNick Craver — Stack Overflow: The Architecture - 2016 Edition (відкривається в новій вкладці)nickcraver.com · 17.02.2016
- СтаттяAlexandra Noonan — Goodbye Microservices: From 100s of problem children to 1 superstar (відкривається в новій вкладці)Segment · Twilio · 07.2018
- СтаттяCraig Box — Introducing istiod: simplifying the control plane (відкривається в новій вкладці)istio.io · 19.03.2020
- СтаттяMarcin Kolny — Scaling up the Prime Video audio/video monitoring service and reducing costs by 90%Prime Video Tech · 22.03.2023
- СтаттяKirsten Westeinde — Deconstructing the Monolith (відкривається в новій вкладці)Shopify Engineering · 2019
- СтаттяUnder Deconstruction: The State of Shopify’s Monolith (відкривається в новій вкладці)Shopify Engineering · 16.09.2020
- СтаттяGannon McGibbon, Chris Salzberg — A Packwerk Retrospective (відкривається в новій вкладці)Shopify Engineering · 07.02.2024
- СтаттяAdam Hess — Building GitHub with Ruby and Rails (відкривається в новій вкладці)The GitHub Blog · 06.04.2023
Дослідження з Claude. Пошук і звірку першоджерел зроблено з Claude (Anthropic). Відбір, текст і перевірку фактів — редакція Anatomy of IT Product.
Було корисно?
Поділіться з тим, хто працює над тією ж задачею.