Case study · własny produkt AOVP
Typer to mój własny produkt, więc mogę pokazać wszystko: liczby z bazy, koszty utrzymania, popełnione błędy i to, co zrobiłbym dziś inaczej. Aplikacja obsłużyła cały turniej mistrzostw świata, od pierwszego gwizdka do finału.
Dane odczytane z produkcyjnej bazy 30.08.2026. Bez danych osobowych, same liczby zbiorcze.
Typowanie wyników w gronie znajomych zwykle kończy się arkuszem kalkulacyjnym albo grupą na komunikatorze. Działa przez tydzień. Potem ktoś wpisze typ po meczu, ktoś inny pomyli kolumnę, a punkty trzeba liczyć ręcznie. Przy 104 meczach to kilkaset operacji.
Typ da się wpisać wyłącznie przed rozpoczęciem meczu. Brzmi banalnie, ale bez tego rywalizacja nie ma sensu, a żaden arkusz tego nie zapewni.
Wyniki pobierane są z zewnętrznego API piłkarskiego, a punkty przeliczają się automatycznie po każdym meczu. Przez cały turniej nie wpisałem ręcznie ani jednego rezultatu.
Klasyfikacja, drabinka pucharowa, historia typów i podsumowanie sezonu. Ranking zmienia się po każdym meczu i to on utrzymuje ludzi przy zabawie do finału.
Aplikacja ma 14 stron i działa jako jeden worker. Nie ma serwera, którym trzeba by administrować.
Next.js z Prismą. Renderowanie po stronie serwera, żeby ranking ładował się od razu, bez czekania, aż przeglądarka doczyta dane.
D1 (SQLite Cloudflare) na dane, R2 na avatary. Baza po całym turnieju zajmuje 380 KB przy limicie 5 GB, więc jej koszt jest praktycznie zerowy.
Pobieranie wyników, przeliczanie punktów i przypomnienia chodzą jako harmonogram wewnątrz workera. Wcześniej były to trzy wpisy w systemowym crontabie na serwerze.
Maile idą przez HTTP API dostawcy poczty, a powiadomienia push przez Web Push. Push działa też na telefonie po dodaniu aplikacji do ekranu głównego.
Opisuję każdą z przyczyną i rozwiązaniem.
Analiza zależności wciągała do paczki silniki bazodanowe dla Postgresa, MySQL-a, SQL Servera i CockroachDB, czyli sterowniki do baz, których ta aplikacja w ogóle nie używa. Po wykluczeniu niepotrzebnych plików paczka spadła z 19 151 KiB do 3 982 KiB, czyli do 39% limitu. Diagnoza zajęła kilka godzin, poprawka trzy linijki konfiguracji.
Lokalny plik z ustawieniami środowiskowymi trafiał do paczki wdrożeniowej i nadpisywał ustawienia produkcyjne. Logowanie zaczęło przekierowywać na adres lokalny. Objaw wyglądał na błąd uwierzytelniania, a przyczyna siedziała w procesie budowania. Dziś skrypt budujący kasuje te pliki z gotowej paczki.
Przy przenoszeniu bazy import przerywał się na kluczach obcych, bo wiersze szły w kolejności alfabetycznej tabel, a baza sprawdza powiązania już w trakcie wczytywania. Zrzut musi być posortowany tak, żeby rekordy nadrzędne trafiały przed zależnymi. Szukałem tego godzinę, a rozwiązanie mieści się w jednym zdaniu.
Wgrywałem avatary do magazynu plików poleceniem, które domyślnie zapisuje pliki lokalnie zamiast w chmurze. Sprawdzenie „czy plik się wgrał” tym samym narzędziem odczytywało tę samą lokalną kopię i potwierdzało sukces. Błąd wyszedł dopiero jako brakujące avatary w działającej aplikacji. Od tamtej pory wynik sprawdzam w działającej aplikacji, innym narzędziem niż to, którym coś zrobiłem.
Aplikacja przez cały turniej działała na VPS-ie za 17 zł netto miesięcznie. Po turnieju przeniosłem ją w całości na Cloudflare. Kilkanaście złotych oszczędności nie miało tu znaczenia. Chciałem przestać administrować serwerem: aktualizacje systemu, certyfikaty, kopie zapasowe, monitoring.
Baza z pliku SQLite na serwerze → D1. Avatary z dysku → R2. Maile z SMTP → HTTP API. Trzy wpisy w crontabie → harmonogram w workerze. Sama aplikacja została ta sama.
Start workera 28 ms. Strona 0,8–1,4 s, logowanie zakończone w 0,64 s. Wcześniej odpowiadał jeden serwer w jednym miejscu, teraz odpowiada najbliższa lokalizacja sieci.
Wszystkie 14 stron przetestowane po przełączeniu, komplet danych porównany co do wiersza, a 11 avatarów pobranych z działającej aplikacji i porównanych bajt w bajt z oryginałami. Pełna pętla resetu hasła sprawdzona na koncie tymczasowym.
Nie stawiałbym tego na własnym serwerze. VPS wydawał się oczywistym wyborem, bo aplikacja ma bazę danych. W praktyce oznaczał kilka miesięcy administrowania maszyną, której jedynym zadaniem było utrzymanie jednej aplikacji dla dwudziestu kilku osób. Migracja zajęła kilka dni roboczych, czyli mniej, niż łącznie kosztowała opieka nad serwerem.
Zadania cykliczne odpalałbym rzadziej. Harmonogram chodzi co minutę, co daje ponad 43 tysiące uruchomień miesięcznie. W dniach meczowych ma to sens, poza nimi to marnotrawstwo, które przy kilku aplikacjach na jednym planie zacznie mieć znaczenie.
Wcześniej dołożyłbym logowanie awarii wysyłki. Przez długi czas nieudana wysyłka maila była całkowicie niema: kod łapał błąd i szedł dalej, więc nikt by się nie dowiedział, że powiadomienia przestały docierać. To najgroźniejszy rodzaj usterki, bo jej nie widać.
Jeśli zastanawiasz się nad zamówieniem aplikacji, z tego opisu wynikają cztery rzeczy.
104 mecze rozliczone automatycznie i 27 osób korzystających z aplikacji w trakcie turnieju, od 11 czerwca do 19 lipca.
Potrafię powiedzieć, ile aplikacja kosztuje miesięcznie po wdrożeniu, bo sam za to płacę. To pytanie, na które wielu wykonawców odpowiada „to zależy”.
Na czterech opisanych wyżej pułapkach straciłem własny czas, przy własnym produkcie. Przy Twoim projekcie omijam je z marszu.
Typer jest dostępny jako produkt white-label z Twoim logo, kolorami i adresem. Cennik jest na stronie Typera. Terminarz, wyniki i ranking mogą być też częścią aplikacji dla klubów sportowych.
Napisz krótko, co miałaby robić. Pierwsza rozmowa i wstępna wycena są bezpłatne.