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.

Pień drzewa z krótkimi gałęziami wracającymi do niego, obraz trunk based development

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.

Ciemne biurko w nocy z laptopem pokazującym terminal i świecącym powiadomieniem na telefonie

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.

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.

Rząd przełączników ściennych, część włączona, część wyłączona, jak flagi funkcjonalności

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.

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.

Krótkie boczne tory łączące się w jedną główną linię kolejową

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ą.

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ś.

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.

  1. Zapisz obecny czas życia gałęzi i medianę rozmiaru pull requesta. To Twoje wartości bazowe.

  2. Ustaw limit, na przykład żadna gałąź starsza niż dwa dni, i pokaż go na dashboardzie.

  3. Napraw najwolniejszą część CI. Jeśli build trwa 30 minut, nic innego nie ma jeszcze znaczenia.

  4. Wprowadź jedną flagę dla jednej prawdziwej funkcji. Wyślij ją ukrytą. Usuń flagę w ciągu sprintu.

  5. 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ć.

Frequently asked questions

Co to jest trunk based development w prostych słowach?
To model pracy, w którym wszyscy programiści scalają małe zmiany do jednej wspólnej gałęzi, czyli trunk lub main, co najmniej raz dziennie. Trunk musi być przez cały czas gotowy do wydania.
Jak często trzeba scalać zmiany do trunk?
Zgodnie z raportem DORA: co najmniej raz dziennie, przy trzech lub mniej aktywnych gałęziach w repozytorium. Gałęzie robocze żyją godzinami, a nie tygodniami.
Czy trunk based development oznacza commity bezpośrednio do main?
Nie zawsze. Małe zespoły mogą commitować bezpośrednio, a większe korzystają z krótko żyjących gałęzi i pull requestów. Żaden z tych wariantów nie służy wstrzymywaniu pracy przed integracją.
Czym trunk based development różni się od GitFlow?
GitFlow utrzymuje develop, gałęzie funkcjonalne, wydań i hotfixów, więc praca może być odizolowana tygodniami. Trunk based development ogranicza czas życia gałęzi do godzin i wymaga flag funkcjonalności albo branch by abstraction.
Do czego służą flagi funkcjonalności w tym modelu?
Oddzielają deploy od release. Niedokończony kod trafia na produkcję wyłączony, a flaga decyduje, kto go zobaczy. Każda flaga powinna mieć właściciela i datę wygaśnięcia.
Kiedy nie warto przechodzić na trunk based development?
Gdy zestaw testów trwa godzinę i nie da się go zrównoleglić, gdy dostarczasz wersjonowane artefakty klientom na lata, gdy nie masz systemu flag albo gdy zespół nie ufa buildowi.
Jak zacząć bez migracji typu big-bang?
Zmierz obecny czas życia gałęzi i rozmiar pull requestów, ustaw limit wieku gałęzi, napraw najwolniejszy etap CI i wprowadź jedną flagę dla jednej prawdziwej funkcji.