Dokumentacja AlgoJudge0.1

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

RodzajCzego wymagaIle kosztuje
postgresniczegobaza rośnie o każdy plik
filesystemścieżki na wolumeniekopia musi teraz obejmować dwie rzeczy
s3endpointu, kubełka i pary kluczymagazynu 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łaj

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

Na tej stronie