Tysiąc uruchomień buildów miesięcznie — i żadne nie kosztuje ani grosza ⚙️
Najpierw sprostowanie. Pierwsza wersja tego artykułu twierdziła, że skończyły nam się minuty buildów na GitHubie i że to wymusiło przejście na konto Pro. To było błędne — i wystarczyłby jeden rzut oka do dokumentacji, żeby to wyjaśnić: dla publicznych repozytoriów minuty buildów na GitHubie są darmowe. Artefakty i cache też. Nasze repozytorium jest publiczne od końca maja — więc nigdy nie zapłaciliśmy za ani jedną minutę i nigdy nie zapłacimy. Zamiast po cichu poprawiać tekst, wolimy napisać go od nowa i opowiedzieć wam, co faktycznie pokazały pomiary. Okazuje się to znacznie ciekawsze niż błędna wersja.
Najpierw zgadywanie, potem patrzenie. Impulsem był wieczór, w którym gotowy pull request leżał w przeglądarce, a nie uruchomiła się ani jedna kontrola. Żadnych zielonych ptaszków, sama cisza. Sięgnęliśmy po oczywiste wyjaśnienie — „limit wyczerpany” — i wzięliśmy konto Pro. Potem wszystko znów działało, co zdawało się potwierdzać teorię. Nie potwierdzało: cisza miała inną przyczynę (pull request, którego GitHub w danej chwili nie może czysto scalić z główną gałęzią, nie uruchamia żadnych kontroli). Od tego czasu sprawdziliśmy dane rozliczeniowe każdego pojedynczego uruchomienia. Odpowiedź dla każdego uruchomienia i każdego systemu operacyjnego: czas płatny = 0 milisekund. Wspieranie platformy subskrypcją jest całkiem w porządku — ale jako wyjaśnienie dla tego repozytorium to nie wytrzymuje.
Co jest prawdą: maszyneria urosła. Każdy pull request uruchamia małą fabrykę: budowany jest cały serwer i uruchamiany jego zestaw testów, linter i analiza kodu go przeglądają, CodeQL poluje na dziury w bezpieczeństwie, a powstają obrazy Dockera dla serwera gry, portalu, WorldHost i backendu AI. Wydanie dokłada buildy klienta na Windows, Linux i macOS oraz wersję WebGL dla gry w przeglądarce. Od końca maja daje to 1723 uruchomienia buildów, 1092 z nich w samym lipcu — przy 131 scalonych pull requestach w cztery tygodnie. I tak, ostatni przyspieszony zryw naprawdę ma związek z AI 😉: projekt jest rozwijany w 100% z pomocą AI, a przy większej subskrypcji po prostu więcej dzieje się równolegle. Tylko że walutą nie są pieniądze — to czas oczekiwania.
Gdzie ucieka czas oczekiwania. Zmierzone na przestrzeni ośmiu dni: zwykły przebieg kontroli (CI) 379 minut, analiza bezpieczeństwa CodeQL 230, buildy wydań 126, reszta to drobne. Pojedyncze wydanie kosztuje około 110 minut czasu obliczeniowego, rozłożonego na WebGL (31,6), obraz Dockera (19,4), macOS (13,7), Linux (13,7), duży próg testowy (12,1), Windows (10,9) i pakowanie (7,2). To czas między „gotowe” a „można zainstalować”.
A teraz rzecz, której naprawdę brakuje: cache. Żeby build nie startował za każdym razem od zera, GitHub przechowuje wyniki pośrednie — i na to dostajecie 10 GB na repozytorium, bez względu na to, jak bardzo jest publiczne. Nasze zajmuje obecnie 5,3 GB w 63 wpisach. Problem leży w rozkładzie: mniej więcej dwie trzecie to pośrednie warstwy Dockera, wysyłane od nowa przy każdym uruchomieniu — i wypychają dokładnie to, co pomogłoby najbardziej. To jest prawdziwe wąskie gardło i żadna subskrypcja na świecie się z niego nie wykupi.
Najmilszym znaleziskiem był prawdziwy błąd. Podczas budowania Unity tworzy ogromny folder pośredni (Library), którego ponowne użycie oszczędza minuty. Pomiary pokazały: dla Windows, Linux i macOS ten folder nigdy nie był zapisywany — buildy pilnie próbowały go odtworzyć, ale nikt go nigdy nie zapisał. A build na Windows był jedynym z kluczem cache bez oznaczenia platformy. W rezultacie pobierał 1,7 GB stanu pośredniego z WebGL, który Unity potem pilnie wyrzucało, zanim zaimportowało wszystko od nowa. Log pokazuje to czarno na białym: 257 sekund importu zasobów zamiast 85 w ciepłym przypadku. To prawie dziewięć zmarnowanych minut na wydanie — bo w kluczu zabrakło jednego słowa.
Co jeszcze wyszło. Nasz zestaw testów uruchamia się trzy razy na każdy wydany commit (w pull requeście, po scaleniu i jeszcze raz przy wydaniu). CodeQL sprawdza trzy języki przy każdym pull requeście, choć nie jest nawet wymaganą kontrolą. Całkiem brakuje cache dla pakietów .NET. Emulator obcych architektur procesora jest uruchamiany, mimo że budujemy tylko pod jedną. A 7,4 GB starych artefaktów buildów leży sobie przy domyślnym 90-dniowym przechowywaniu. Nic z tego nie jest dramatyczne — razem to spory kawał życia.
Lekcja jest ta sama, której nauczył nas nasz serwer. Wtedy zmierzyliśmy, że połowa zajętej pamięci nie idzie na grę, tylko na Dockera i monitoring. Tym razem było jeszcze bardziej wstydliwie: założyliśmy rachunek, którego nigdy nie było, i zapłaciliśmy, zanim spojrzeliśmy. Mierz, nie zgaduj — najwyraźniej to zdanie obowiązuje, dopóki naprawdę go sobie nie przyswoicie. Porządki są zaplanowane, ale jeszcze niezrobione; kiedy ruszą, opowiemy wam, co nam dały. 🚀
Komentarze