Co to jest trunk based development? Przewodnik dla SRE
Summary
Trunk based development to model pracy, w którym zespół scala małe zmiany do jednej wspólnej gałęzi co najmniej raz dziennie i utrzymuje ją w stanie gotowym do wydania. Działa dzięki krótkim gałęziom, szybkiemu CI, uczciwemu review oraz flagom funkcjonalności, które oddzielają deploy od release. Model zawodzi, gdy testy trwają godzinę, a zespół nie ma systemu flag. Zacznij od pomiaru czasu życia gałęzi i rozmiaru pull requestów.
Trzecia w nocy, a gałąź release nie daje się scalić. Czterdzieści commitów sprzed trzech tygodni, dotykających te same pliki co main. Ten ból to właśnie powód, dla którego ludzie pytają: co to jest trunk based development? To model pracy na gałęziach, w którym wszyscy scalają małe zmiany do jednej wspólnej gałęzi, nazywanej trunk lub main, przynajmniej raz dziennie, i utrzymują ją w stanie gotowym do wydania przez cały czas.
Ten przewodnik jest dla platform engineerów, którzy znają już Gita. Opisuje, czego ten model wymaga, przed czym chroni i gdzie po cichu zawodzi.
Co to jest trunk based development w ujęciu operacyjnym
Sprowadźmy definicję do ograniczeń. Wszyscy programiści integrują się z jedną gałęzią. Każda inna gałąź żyje godzinami, a nie tygodniami. Trunk buduje się i przechodzi testy przy każdym commicie, dzięki czemu można go wdrożyć na żądanie.
Raport DORA o tej zdolności podaje konkrety: trzy lub mniej aktywnych gałęzi w repozytorium, scalenia do trunk co najmniej raz dziennie, brak zamrożeń kodu oraz cykl build i testów trwający kilka minut. Te liczby są sednem sprawy. Ten model to budżet na pętlę sprzężenia zwrotnego, a nie preferencja co do stylu pracy na gałęziach.

Małe zespoły czasem commitują bezpośrednio do trunk. Większe korzystają z krótko żyjących gałęzi i pull requestów do review oraz kontroli buildu, ale nigdy po to, by wstrzymywać pracę przed integracją. Materiał referencyjny trunkbaseddevelopment.com opisuje oba tryby i przywołuje Google, które utrzymuje około 35 000 programistów na jednym trunk w monorepo.
Dlaczego długo żyjące gałęzie zawodzą według przewidywalnego harmonogramu
Gałąź funkcjonalna to pożyczka. Odsetkami są konflikty przy scalaniu, a rosną z każdym commitem, który trafia do main, gdy Ty jesteś poza nią. Im dłużej gałąź żyje, tym większy diff, a im większy diff, tym mniej uważnie ktokolwiek go czyta.
To przekłada się na wskaźnik nieudanych zmian. Scalenie na 2000 linii dostaje przelotne spojrzenie i approve. Scalenie na 60 linii zostaje naprawdę przeczytane. Recenzenci nie są leniwi, tylko racjonują uwagę. Małe partie to jedyny mechanizm, który skaluje jakość review.
Warto też zauważyć, że długa gałąź zmienia sposób myślenia o kodzie. Programista zaczyna optymalizować pod własną gałąź, a nie pod system, do którego kod trafi. Decyzje architektoniczne zapadają w izolacji, a potem wszyscy odkrywają, że inne zespoły zrobiły to samo w innym kierunku. Trunk wymusza rozmowę wcześniej, kiedy zmiana jest jeszcze tania.
Drugi koszt jest niewidoczny aż do wydania. Dwie gałęzie, z których każda przechodzi CI, mogą się nawzajem zepsuć, gdy się spotkają. Dowiadujesz się o tym w chwili integracji, czyli w najgorszym możliwym momencie, z terminem na karku.
Czego trunk potrzebuje, zanim zaufasz jego zieleni
Przejście na trunk bez praktyk wspierających to sposób, w jaki zespoły wracają do gałęzi funkcjonalnych w ciągu kwartału. Najpierw muszą istnieć cztery rzeczy.
Build i testy trwające poniżej około dziesięciu minut. Jeśli CI trwa 40 minut, programiści grupują zmiany, a grupowanie niszczy model.
Testy, które zawodzą z realnych powodów. Niestabilny zestaw uczy ludzi ponawiać i scalać mimo to.
Szybki, uczciwy proces review. DORA wymienia ciężki i asynchroniczny code review jako częstą przeszkodę, bo pcha programistów do grupowania pracy.
Bezpieczny sposób na wysyłanie niedokończonej pracy. To następna sekcja.
Pominięcie któregokolwiek z tych elementów sprawia, że trunk staje się miejscem, w którym narasta awaria, zamiast miejscem, w którym ją się wyłapuje.
Jak scalać niedokończoną pracę bez wysyłania jej na produkcję
To pytanie zadaje każdy sceptyk i ma nudną odpowiedź: oddzielasz deploy od release. Kod trafia na produkcję w ukryciu, a flaga decyduje, kto go zobaczy.
// checkout.ts
import { flags } from "./flags";
export async function renderCheckout(user: User) {
if (await flags.isEnabled("new-payment-flow", { userId: user.id })) {
return renderNewPaymentFlow(user); // scalone do trunk, domyślnie wyłączone
}
return renderLegacyCheckout(user);
}Nowy przepływ scala się pierwszego dnia, za flagą wyłączoną domyślnie. Włączasz go dla użytkowników wewnętrznych, potem dla 1 procenta, potem 10, z bramką SLO na każdym kroku. Jeśli wskaźnik błędów przekroczy budżet, flaga się wyłącza i zmiana jest praktycznie cofnięta bez deployu. Zespoły oceniające hostowane usługi flag często zaczynają od LaunchDarkly, choć ten wzorzec działa z każdym dostawcą albo z własnym magazynem konfiguracji.
Druga technika to branch by abstraction, przy dużych refaktorach. Wprowadzasz interfejs, kierujesz wywołania przez niego, budujesz nową implementację za nim i podmieniasz ją, gdy jest gotowa. Bez długo żyjącej gałęzi i bez scalania typu big-bang.

Uczciwy koszt: flagi to dług z połowicznym okresem trwałości
Na konferencjach nikt nie chce o tym mówić. Każda dodana flaga to warunek na produkcji, który ktoś musi kiedyś usunąć. Widzieliśmy zespoły po 80 inżynierów, które dźwigały kilkaset przestarzałych flag, a każda z nich była gałęzią w kodzie, której nikt nie jest w stanie przetestować w pełni.
Traktuj flagi jako krótkotrwałe z założenia. Przypisz każdej właściciela i datę wygaśnięcia w momencie utworzenia. Ustaw alert na flagi starsze niż ich wygaśnięcie. Usuń martwą ścieżkę kodu w tym samym pull requeście, który usuwa flagę.
Ryzyko kombinatoryczne też jest realne. Dziesięć niezależnych flag logicznych daje 1024 możliwe konfiguracje. Nigdy nie przetestujesz wszystkich. Ogranicz interakcje między flagami i trzymaj flagi wydań osobno od długo żyjących przełączników operacyjnych, takich jak kill switch.
Strategie wydań na trunk i obserwowalność, czyli druga połowa umowy
Są dwa częste sposoby cięcia wydania z trunk i żaden nie wymaga długo żyjącej gałęzi.
Wydanie bezpośrednio z trunk. Każdy zielony commit jest kandydatem. Błędy naprawia się do przodu, nowym commitem, a nie łatając starą gałąź. Pasuje do zespołów z mocnymi automatycznymi testami i szybkimi deployami.
Gałąź wydania cięta w ostatniej chwili. Odgałęziasz się od znanego, dobrego commita na trunk, utwardzasz go, wdrażasz i kasujesz. Poprawki trafiają najpierw na trunk i są przenoszone z powrotem przez cherry-pick. Pasuje do zespołów z wolniejszymi bramkami wydań albo wymaganym zatwierdzeniem regulacyjnym.
To, którą opcję wybierzesz, zależy od czasu wykrycia. Jeśli zauważysz złe wdrożenie w ciągu pięciu minut i cofniesz je w dwie, napraw do przodu. Jeśli średni czas wykrycia wynosi godzinę, gałąź wydania daje Ci grunt pod nogami.

Trunk based development podnosi częstotliwość wdrożeń, a to zwiększa liczbę momentów, w których coś może pójść źle. Model działa tylko wtedy, gdy regresję widzisz w ciągu minut od scalenia. Chodzi o wskaźniki błędów, percentyle opóźnień i nasycenie powiązane z konkretnym wydaniem, a nie o dashboard, który ktoś sprawdza w poniedziałek.
Prosty test: po scaleniu, czy umiesz odpowiedzieć na pytanie „czy ta zmiana przesunęła p99 albo wskaźnik błędów?” bez otwierania pięciu kart? Jeśli nie, nie jesteś gotów scalać dziesięć razy dziennie.
Niezależnie od stosu wymaganie jest to samo: znaczniki deployów na wykresach, alerty burn-rate dla SLO i ścieżka cofnięcia, którą zmęczona osoba wykona o 3 w nocy bez zastanawiania się.
Trunk, GitFlow, GitHub Flow i metryki DORA
Te trzy podejścia bywają mylone, więc rozdzielmy je jedną własnością: jak długo praca pozostaje poza wspólną gałęzią.
GitFlow utrzymuje gałąź develop, gałęzie funkcjonalne, gałęzie wydań i hotfixów. Praca może pozostawać odizolowana tygodniami. Zaprojektowano go pod wersjonowane, planowane wydania i widać to, gdy próbujesz wdrażać dziesięć razy dziennie.
GitHub Flow jest blisko trunk. Krótko żyjące gałęzie, pull requesty, scalenie do main, deploy. Różnicę, którą wskazuje strona referencyjna, stanowi głównie to, skąd biorą się wydania.
Trunk based development jest najostrzejszy z tych trzech, jeśli chodzi o czas życia gałęzi, i zakłada flagi funkcjonalności albo branch by abstraction dla wszystkiego, czego nie da się wysłać w jednej małej zmianie.
Jeśli Twój zespół już działa w GitHub Flow z gałęziami żyjącymi krócej niż dwa dni, jesteś bliżej trunk, niż myślisz. Brakuje zwykle dyscypliny flag i szybkości review, a nie narzędzi.
Zanim zaczniesz, zainstrumentuj metryki DORA, inaczej potem będziesz argumentować na podstawie odczuć. Częstotliwość wdrożeń i czas realizacji zmiany reagują pierwsze, bo mniejsze partie szybciej przepływają przez pipeline. Wskaźnik nieudanych zmian i czas przywrócenia idą za nimi i zależą od obserwowalności oraz ścieżki cofnięcia, a nie samego modelu gałęzi.
Nie zamieniaj liczb w kartę oceny poszczególnych osób. Opisują system. Gdy czas realizacji rośnie, patrz najpierw na kolejkę review i czas CI, a dopiero potem na ludzi.
Kiedy trunk based development jest złym wyborem
Odpuść go albo przynajmniej odłóż w kilku przypadkach. Bądź uczciwy, w którym jesteś.
Zestaw testów trwa godzinę i nie da się go zrównoleglić. Będziesz grupować zmiany, a grupowanie łamie model.
Dostarczasz wersjonowane artefakty klientom, którzy przez lata zostają na starych wersjach. Potrzebujesz gałęzi utrzymaniowych. To uzasadnione, a i tak możesz integrować codziennie na trunk.
Nie masz systemu flag i nie masz ochoty go zbudować. Niedokończona praca wycieknie do wydań.
Zespół nie ufa buildowi. Najpierw napraw testy. Trunk tego za Ciebie nie zrobi.
Trunk based development nie gwarantuje też niczego. Wnioski DORA to korelacja z danych z 2016 i 2017 roku: zespoły stosujące te praktyki pokazywały lepsze wyniki dostarczania i operacji. Nie wynika z tego, że zmiana nazw gałęzi naprawi pipeline.
Jak zacząć bez migracji typu big-bang
Nie ogłaszaj polityki. Najpierw zmierz, potem zmniejszaj.
Zapisz obecny czas życia gałęzi i medianę rozmiaru pull requesta. To Twoje wartości bazowe.
Ustaw limit, na przykład żadna gałąź starsza niż dwa dni, i pokaż go na dashboardzie.
Napraw najwolniejszą część CI. Jeśli build trwa 30 minut, nic innego nie ma jeszcze znaczenia.
Wprowadź jedną flagę dla jednej prawdziwej funkcji. Wyślij ją ukrytą. Usuń flagę w ciągu sprintu.
Zdejmij zamrożenia kodu na końcu, gdy ścieżka cofnięcia została przetestowana w praktyce.
Wróć do tego za miesiąc. Wskaźniki, które ruszają się pierwsze, to zwykle rozmiar pull requesta i czas do scalenia. Wskaźnik nieudanych zmian zajmuje dłużej, a czasem pogarsza się, zanim się poprawi, bo w końcu widzisz awarie wcześniej.
Co powie Twój post-mortem? To użyteczne pytanie przed wdrożeniem. Jeśli ostatni incydent wynikał ze scalenia, którego nikt nie zdążył przejrzeć, z gałęzi rozjechanej przez trzy tygodnie albo z wydania, które spakowało czterdzieści zmian, to już wiesz, kiedy spłacasz tę pożyczkę. Policz swoje trzy ostatnie awarie. Ile z nich dotyczyło dużej, późnej integracji? Ta liczba jest Twoim uzasadnieniem biznesowym.
Jeśli wynik jest wysoki, nie zaczynaj od narzędzi. Zacznij od rozmowy z zespołem o tym, które zmiany były najtrudniejsze do scalenia i dlaczego. Najczęściej odpowiedź nie leży w Git, tylko w tym, kto na co czeka: na review, na build, na decyzję o wydaniu albo na zgodę, której nikt nie potrafi wskazać.