~1000 requestów na publikację: jak znalazłem ukryty koszt
Kontekst
Backend deweloperski działał na izolowanych feature clusterach opartych na Dockerze i Kubernetesie — po jednym klastrze na developera. Każdy klaster miał własny pod z cache'em tłumaczeń zasilanym danymi z chmurowej usługi tłumaczeniowej. Była to część tej samej platformy B2B2C, którą opisuję w case study platformy mikrofrontendowej.
Jak znalazłem problem
Wszystko zaczęło się od liczby widocznej w panelu chmurowej usługi tłumaczeniowej: przewidywany koszt za dany miesiąc był znacznie wyższy, niż się spodziewałem. Pojawiło się więc proste pytanie: skąd bierze się tak duża liczba płatnych requestów?
Prześledziłem proces publikacji tłumaczeń przez feature clustery i odkryłem, że każda publikacja była przetwarzana niezależnie przez każdy pod. Te same dane były więc wielokrotnie pobierane w różnych środowiskach deweloperskich, zamiast być współdzielone.
Co generowało requesty
Platforma obsługiwała 28 aplikacji w 4 językach. Kod odpowiedzialny za budowę cache'u miał dwa niezależne problemy, które dodatkowo zwiększały skalę niepotrzebnej pracy:
- zawierał zbędne pętle, przez które ta sama praca była wykonywana wielokrotnie,
- po każdej publikacji pobierał wszystkie tłumaczenia zamiast tylko tych, które faktycznie się zmieniły.
W połączeniu z osobnym cache'em w każdym feature clusterze te problemy zamieniały się w dużą liczbę płatnych wywołań API.
Co zrobiłem
- Prześledziłem proces publikacji tłumaczeń i kolejne requesty pomiędzy klastrami, podami, aplikacjami i językami.
- Zidentyfikowałem zbędne pętle oraz pobieranie całego cache'u w kodzie odpowiedzialnym za jego budowanie.
- Zgłosiłem findings zarządowi i wyjaśniłem, skąd dokładnie brała się niepotrzebna liczba requestów.
Poprawka
Kod odpowiedzialny za implementację cache'u należał do innego zespołu, więc to on wdrożył końcową poprawkę na podstawie mojej diagnozy.
Nowe rozwiązanie usunęło zbędne pętle, pobierało tylko tłumaczenia, które faktycznie się zmieniły, i zapisywało je do jednego wspólnego cache'u zamiast powtarzać tę samą pracę niezależnie w każdym feature clusterze.
Mierzalny efekt
Jedna publikacja przeszła z około 1000 powtarzających się requestów do jednego wspólnego pobrania oraz tylko tych dodatkowych requestów, które były potrzebne dla aplikacji, których tłumaczenia rzeczywiście się zmieniły.
- ~1000 → 1 + zmienione aplikacje requestów na publikację
- −99,9% płatnych wywołań API
Najważniejsza zmiana nie polegała tylko na zmniejszeniu liczby requestów. Te same dane przestały być pobierane i przetwarzane niezależnie przez każdy deweloperski klaster.
Co pokazuje ten przypadek
Źródła problemu nie trzeba było szukać wyłącznie we frontendzie. Liczba widoczna w panelu chmurowym doprowadziła do analizy całego przepływu — od aplikacji i requestów API, przez implementację cache'u, aż po środowisko uruchomieniowe oparte na Kubernetesie.
Tego rodzaju problemy są częścią tego, czego szukam podczas audytu wydajności frontendu: nieefektywności, które pojedynczo mogą wyglądać niegroźnie, ale przez architekturę systemu są powielane tak wiele razy, że zaczynają kosztować realne pieniądze.
Praca była częścią tej samej większej platformy opisanej w case study platformy mikrofrontendowej i jest bezpośrednio związana z moim doświadczeniem w architekturze mikrofrontendowej.
Stack
.NET, Kubernetes, Docker, i18n, cloud translation API.