Skip to main content

Audyt zapytań bez limitu (.all())

Dokument opisuje, które zapytania do D1 zwracają nieograniczoną liczbę wierszy, jak oceniamy ich ryzyko i jakie limity obowiązują. Powstał w ramach AP-784 (ograniczanie zużycia pamięci Workera, limit 128 MB na isolate).

👤 Dla zespołu — co to zmienia w praktyce

  • Wyszukiwarka klientów wymaga co najmniej 2 znaków. Wpisanie jednej litery nie zwraca już wyników — wcześniej takie zapytanie przeszukiwało całą bazę graczy, żeby zwrócić listę, z której i tak nie dało się skorzystać.
  • Lista wyników wyszukiwania jest przycięta do 20 pozycji (maksymalnie 50). Jeżeli szukanej osoby nie ma na liście, doprecyzuj frazę zamiast przewijać.
  • Tablica zgłoszeń (tickets) pokazuje maksymalnie 500 najnowszych zgłoszeń. Przy większej liczbie użyj filtrów statusu lub wyszukiwania.

🛠️ Dokumentacja techniczna

Obowiązujące limity

MiejsceStałaWartość
Minimalna długość frazy wyszukiwania graczyPLAYER_SEARCH_MIN_LENGTH2 znaki
Domyślny limit wyszukiwarki graczyPLAYER_SEARCH_DEFAULT_LIMIT20
Maksymalny limit wyszukiwarki graczyPLAYER_SEARCH_MAX_LIMIT50
Maksymalna strona listy graczyPLAYER_PAGE_MAX_LIMIT200
Maksymalna liczba zgłoszeń na tablicyTICKETS_MAX_ROWS500
Strona odbiorców kampanii mailowejRECIPIENT_PAGE_SIZE500

Limity podane przez wywołującego są przycinane (Math.min), a nie przyjmowane na wiarę — dzięki temu żaden komponent nie obejdzie zabezpieczenia, przekazując Number.MAX_SAFE_INTEGER.

Metodyka audytu

Przeskanowano wszystkie wywołania db.prepare(...).all() w lib/actions/. Z 167 zapytań SELECT bez klauzuli LIMIT wydzielono te, które nie są ograniczone predykatem klucza (id = ?, player_id = ?, IN (${placeholders}) itd.) — pozostały 72 zapytania filtrowane wyłącznie zakresowo (tenant_id, status, zakres dat).

Kluczowa obserwacja: brak LIMIT sam w sobie nie jest wadą. Ryzyko zależy od tego, czy liczba wierszy rośnie w czasie. Dlatego stosunek .all() do LIMIT (użyty jako heurystyka w opisie zadania) okazał się mylący — np. invoices.ts ma 8 wywołań .all() i 2 LIMIT, ale każde z nich jest ograniczone przez player_id = ? lub owner_email = ? i zwraca pojedyncze wiersze.

Klasyfikacja

Do naprawy — naprawione w AP-784

MiejsceProblemRozwiązanie
players.ts getPlayersByNameLIKE po 12 kolumnach; fraza 1-znakowa skanowała całą tabelęminimalna długość frazy + przycięcie limitu
players.ts getPlayersWithExtensionspageLimit przyjmowany bez ograniczeń, a podzapytania json_group_array budują tablicę JSON na każdy wierszprzycięcie do PLAYER_PAGE_MAX_LIMIT
players.ts getAllOwnPlayers / getAllOwnPlayersIncludingInactivepageLimit: Number.MAX_SAFE_INTEGERjawny limit 200 (zapytanie i tak zawężone do owner_email bieżącego użytkownika)
tickets.ts getTicketspełna lista zgłoszeń tenanta bez LIMIT, plus zbiorcze pobranie etykiet i przypisań po wszystkich idLIMIT 500

Świadomie dopuszczone — tabele konfiguracyjne o niskiej liczności

Rosną z konfiguracją klubu, nie z ruchem ani z upływem czasu. Dodanie paginacji pogorszyłoby czytelność bez zysku.

  • court.tsSELECT * FROM court, DISTINCT city / surface / street (dziesiątki wierszy)
  • activity-types.ts — lista typów zajęć i kategorii
  • app-settings.ts, app-settings-general.ts — ustawienia, DISTINCT city / category
  • route-access.ts — reguły dostępu do tras
  • checklist.ts — definicje zadań (checklist_task)
  • quality.ts — lista pracowników
  • mail-campaigns.tsmail_segment, mail_campaign (jednostki na tenanta)

Świadomie dopuszczone — ograniczone zakresem biznesowym

Liczba wierszy wynika z rozmiaru encji nadrzędnej, nie z wieku bazy.

  • camp-*.ts, tennis-course-*.ts — rejestracje, lista oczekujących i instruktorzy w obrębie jednego turnusu/sezonu (limit miejsc)
  • game.ts — zapytania po recurring_series_id (liczba wystąpień serii w sezonie)
  • membership.ts getExpiringMemberships — ograniczone oknem dat
  • checklist.ts checklist_completion — ograniczone zakresem dat
  • players.ts appendSuspensionStatusBatch — przetwarzane porcjami po 90 adresów
  • activity-log.ts — gałęzie zapytania korzystają z fetchCap
  • statistics.ts — zapytania agregujące (GROUP BY) zwracają pojedyncze wiersze podsumowań

Obserwowane — bez zmian, do rewizji przy wzroście

  • critical-notifications.tsWHERE is_resolved = 0: ograniczone liczbą nierozwiązanych powiadomień. Bezpieczne dopóki są rozwiązywane; przy zaniedbaniu obsługi może rosnąć bez ograniczeń.

Zasada dla nowego kodu

Przed dodaniem .all() zadaj pytanie: czy liczba wierszy rośnie z czasem lub z liczbą klientów? Jeśli tak, zapytanie musi mieć LIMIT albo predykat klucza. Jeśli nie (tabela konfiguracyjna, zakres jednej encji) — dopisz je do sekcji „świadomie dopuszczone" powyżej.