Gdzie trafiają pliki
Trzy miejsca, w których instalacja może trzymać przesłane pliki, ile każde z nich kosztuje i jak przenieść to, co już leży, niczego nie tracąc.
Paczki z zadaniami, przesłany kod źródłowy, logi kompilatora i tabele testów to pliki. Instalacja decyduje, gdzie leżą ich bajty, i musi to rozstrzygnąć: miejsca na własne pliki nie dziedziczy się po domyślnej wartości, której nikt nie przeczytał.
Magazyn to jedno skonfigurowane miejsce, w którym mogą leżeć bajty. Wdrożenie
może mieć ich kilka, w tym kilka tego samego rodzaju, a każdy zapisany plik
pamięta, który z nich go trzyma — na zawsze. Dlatego identyfikatora magazynu
nie wolno użyć ponownie dla innej lokalizacji: plik zapisany pod objects będzie
dalej szukał swoich bajtów w objects.
Trzy rodzaje i koszt każdego z nich
| Rodzaj | Czego wymaga | Ile kosztuje |
|---|---|---|
postgres | niczego | baza rośnie o każdy plik |
filesystem | ścieżki na wolumenie | kopia musi teraz obejmować dwie rzeczy |
s3 | endpointu, kubełka i pary kluczy | magazynu obiektów do utrzymania albo kupienia |
postgres jest domyślny, bo nie ma zależności. Jeden kontener, a pg_dump
sam w sobie jest kompletną kopią — i właśnie na tym opierają się
Kopie zapasowe. Dla małej instalacji to dobra odpowiedź,
dla dużych zawodów zła: każdy przesłany plik ląduje wtedy w tej samej bazie, z
której czytany jest ranking.
Pozostałe dwa to ten sam kompromis innymi słowami: mniejsze obciążenie bazy w zamian za kopię, która przestaje być jedna.
Gdzie filesystem naprawdę je kładzie — warto wiedzieć, zanim zrobisz kopię
nie tego, co trzeba: w nazwanym wolumenie objects, podmontowanym w kontenerze
Servera pod /var/lib/algojudge/objects, a na hoście widocznym jako
algojudge_objects. STORAGE_PATH przesuwa ścieżkę wewnątrz kontenera, a
nie sam wolumen. s3 kładzie je tam, gdzie wskazuje STORAGE_ENDPOINT, a kopia
tego należy już do tamtej usługi, nie do tego stosu.
Zmiana rodzaju zmienia też Twoją kopię zapasową
Zrzut obejmuje całą instalację tylko dopóki pliki są w bazie. Przenieś je na
system plików albo do magazynu obiektów, a przestanie być kompletny i nic o tym
nie powie — poza jednym: skrypt kopii raportuje wtedy coverage=INCOMPLETE.
Poświadczenia i to, czego nigdy nie ujawniamy
Poświadczenia są w środowisku i nigdzie indziej. Żaden endpoint nie ustawia
magazynu, nic o nim nie trafia do bazy ani do publicznej odpowiedzi:
/api/v1/health
mówi, że magazyn jest ok albo degraded, i nigdy który to magazyn, backend,
kubełek czy ścieżka. Szczegóły są na powierzchni operatora, za pętlą zwrotną i
tokenem.
Same ustawienia są na stronie Konfiguracja Servera.
Server nigdy nie zakłada kubełka. U części dostawców szyfrowanie w spoczynku włącza się przy zakładaniu kubełka i nie da się go potem wiarygodnie dołożyć, więc założenie go należy do operatora, a nie do nas. Deweloperski plik Compose jest jedynym wyjątkiem i mówi o tym tam, gdzie ustawia tę flagę.
Przeniesienie tego, co już leży
Zmiana domyślnego magazynu rozstrzyga tylko o tym, gdzie trafi następne przesłanie. Niczego nie przenosi. Przeniesienie tego, co już leży, to osobna, świadoma czynność — i taka, przed którą warto zrobić kopię:
docker compose exec server aj-admin storage status # gdzie pliki są teraz
docker compose exec server aj-admin storage migrate # przenieś je do domyślnego
docker compose exec server aj-admin storage cancel # odwołajNie zaczyna się od razu. Migracja czeka na swoje okno — domyślnie 02:00
UTC — oraz na opróżnienie kolejki oceniania i zamknięcie każdej rundy, żeby
nic nie ruszało się w trakcie zawodów. storage status mówi, na co dokładnie
czeka.
Każdy plik zostaje odczytany, porównany z własną sumą kontrolną, zapisany w miejscu docelowym i dopiero wtedy jego wiersz zaczyna wskazywać nowy magazyn. Stara kopia leży jeszcze przez godzinę, żeby ten, kto odczytał wiersz chwilę wcześniej, wciąż znalazł bajty.
Jeden przebieg pracuje pół godziny i wraca w następnym oknie. Zabicie procesu niczego nie kosztuje: postęp jest zapisany na samych plikach, więc następny przebieg podejmuje pracę tam, gdzie stanęła, a nie od początku.
Użycie czegoś innego niż to, na czym testujemy
Sprawdzamy dwie implementacje S3: RustFS, którą uruchamia stos deweloperski, i SeaweedFS, uruchamianą ręcznie przed wydaniem.
Cokolwiek innego — MinIO, Ceph, dostawca chmurowy — jest nieznane, a nie niewspierane, i ta różnica ma znaczenie. Utrzymujemy zestaw testów zgodności S3, który da się skierować na dowolny endpoint bez zmiany kodu; implementacja, która je przechodzi, spełnia kontrakt, na którym Server się opiera, a takiej, na której nikt ich nie uruchomił, po prostu nikt nie sprawdził. Jeśli masz zamiar postawić na niej zawody, uruchom ten zestaw najpierw — to kosztuje najmniej ze wszystkiego, co możesz w tej sprawie zrobić.
Jednej pozycji z tego zestawu nie da się uruchomić na żadnej z dostępnych tu implementacji: zapisuje znany ciąg, włącza domyślne szyfrowanie kubełka i szuka tego ciągu w plikach samego magazynu. SeaweedFS trzyma obiekty czytelnie, ale odmawia wywołania szyfrowania; RustFS przyjmuje wywołanie i tak czy inaczej trzyma obiekty nieczytelnie. Na endpoincie, który obsługuje jedno i drugie, ta pozycja daje się uruchomić.