Partycjonowanie PostgreSQL dla tabel zdarzeń w logowaniu audytu
Partycjonowanie PostgreSQL dla tabel zdarzeń: dowiedz się, kiedy się opłaca, jak wybrać klucz partycji i co zmienia to dla filtrów w panelu administracyjnym i retencji.

Dlaczego tabele zdarzeń i audytu stają się problemem
Tabele zdarzeń i tabele audytu wyglądają podobnie, ale istnieją z różnych powodów.
Tabela zdarzeń rejestruje to, co się dzieje: odsłony stron, wysłane e-maile, wywołania webhooków, uruchomienia zadań. Tabela audytu rejestruje, kto co i kiedy zmienił: zmiana statusu, aktualizacja uprawnień, zatwierdzenie wypłaty, często z detalami „przed” i „po”.
Obie rosną szybko, ponieważ są append-only. Rzadko usuwasz pojedyncze wiersze, a nowe wiersze pojawiają się co minutę. Nawet mały produkt może wygenerować miliony wierszy logów w ciągu tygodni, gdy uwzględnisz zadania tła i integracje.
Ból pojawia się w codziennej pracy. Panele administracyjne potrzebują szybkich filtrów typu „błędy z wczoraj” lub „akcje przez tego użytkownika”. W miarę jak tabela rośnie, te podstawowe ekrany zaczynają lagować.
Zwykle zobaczysz kilka objawów najpierw:
- Filtry zajmują sekundy (albo kończą się time-outem) nawet przy wąskim zakresie dat.
- Indeksy rosną tak bardzo, że insert-y zwalniają, a koszty przechowywania rosną.
- VACUUM i autovacuum zajmują więcej czasu i zaczynasz zauważać prace konserwacyjne.
- Retencja staje się ryzykowna: usuwanie starych wierszy jest wolne i tworzy bloat.
Partycjonowanie to jeden ze sposobów radzenia sobie z tym. Mówiąc prosto, dzieli jedną dużą tabelę na wiele mniejszych tabel (partycji) pod wspólną nazwą logiczną. PostgreSQL kieruje każdy nowy wiersz do właściwej partycji według reguły, zwykle czasu.
Dlatego zespoły patrzą na partycjonowanie PostgreSQL dla tabel zdarzeń: może utrzymać dane niedawne w mniejszych kawałkach, tak że PostgreSQL może pominąć całe partycje, gdy zapytanie dotyczy tylko okna czasowego.
Partycjonowanie nie jest magicznym przełącznikiem prędkości. Może bardzo pomóc przy zapytaniach typu „ostatnie 7 dni” i upraszcza retencję (usuwanie starych partycji jest szybkie). Ale może też wprowadzić nowe problemy:
- Zapytania, które nie używają klucza partycji, mogą musieć sprawdzić wiele partycji.
- Więcej partycji to więcej obiektów do zarządzania i więcej sposobów na błędną konfigurację.
- Niektóre unikalne ograniczenia i indeksy stają się trudniejsze do wymuszenia na całym zbiorze danych.
Jeśli twój panel administracyjny mocno opiera się na filtrach czasu i przewidywalnych zasadach retencji, partycjonowanie może być realnym zyskiem. Jeśli większość zapytań to „znajdź wszystkie akcje użytkownika X w całej historii”, może to wprowadzić komplikacje, chyba że projekt UI i indeksy będą dobrze przemyślane.
Typowe wzorce dostępu dla logów i audytów
Tabele zdarzeń i audytu rosną w jednym kierunku: w górę. Mają stały strumień insertów i prawie żadnych update'ów. Większość wierszy jest zapisywana raz, potem czytana później podczas pracy wsparcia, przeglądów incydentów lub kontroli zgodności.
Ten kształt „append-only” ma znaczenie. Wydajność zapisu jest stałą troską, bo insert-y dzieją się cały dzień, podczas gdy wydajność odczytu ma znaczenie w skokach (gdy wsparcie lub operacje potrzebują szybkich odpowiedzi).
Większość odczytów to filtry, nie losowe wyszukiwania. W panelu administracyjnym ktoś zwykle zaczyna szeroko (ostatnie 24 godziny), a potem zawęża do użytkownika, encji lub akcji.
Typowe filtry to:
- Zakres czasowy
- Aktor (ID użytkownika, konto serwisowe, adres IP)
- Cel (typ encji + id encji, np. Order #1234)
- Typ akcji (utworzono, zaktualizowano, usunięto, nieudane logowanie)
- Status lub poziom ważności (sukces/błąd)
Zakres czasowy to naturalny „pierwszy podział”, bo prawie zawsze występuje. To kluczowy wniosek za partycjonowaniem PostgreSQL dla tabel zdarzeń: wiele zapytań chce wycinek czasu, a wszystko inne jest drugim filtrem wewnątrz tego wycinka.
Retencja to drugi stały element. Logi rzadko żyją wiecznie. Zespoły często przechowują szczegółowe zdarzenia przez 30 lub 90 dni, potem usuwają lub archiwizują. Logi audytu mogą mieć dłuższe wymogi (365 dni lub więcej), ale nawet wtedy zwykle chcesz przewidywalnego sposobu usuwania starych danych bez blokowania bazy.
Audyt ma też dodatkowe oczekiwania. Zwykle chcesz, żeby historia była niemodyfikowalna, żeby każdy rekord był możliwy do prześledzenia (kto/co/kiedy plus kontekst żądania lub sesji) i żeby dostęp był kontrolowany (nie każdy powinien widzieć zdarzenia związane z bezpieczeństwem).
Te wzorce pojawiają się bezpośrednio w projekcie UI. Filtry, których ludzie oczekują domyślnie — selektory dat, wybór użytkownika, wyszukiwanie encji, lista typów akcji — to te same filtry, które twoja tabela i indeksy muszą wspierać, jeśli chcesz, aby doświadczenie admina pozostało szybkie przy rosnącej objętości.
Jak sprawdzić, czy partycjonowanie się opłaca
Partycjonowanie nie jest domyślną najlepszą praktyką dla logów audytu. Opłaca się, gdy jedna tabela staje się tak duża, że codzienne zapytania i rutynowe utrzymanie zaczynają sobie przeszkadzać.
Prosty hint rozmiarowy: gdy tabela zdarzeń osiąga dziesiątki milionów wierszy, warto zacząć mierzyć. Gdy tabela i indeksy rosną do dziesiątek gigabajtów, nawet „proste” wyszukiwania po zakresie dat mogą stać się wolne lub nieprzewidywalne, bo z dysku trzeba czytać więcej stron danych, a indeksy stają się drogie w utrzymaniu.
Najjaśniejszy sygnał zapytania to sytuacja, gdy regularnie prosisz o mały wycinek czasu (ostatni dzień, ostatni tydzień), ale PostgreSQL i tak dotyka dużej części tabeli. Zobaczysz to jako wolne ekrany „ostatnia aktywność” lub audyty filtrowane po dacie plus użytkownik, typ akcji lub id encji. Jeśli plany zapytań pokazują duże skany lub liczba odczytanych buforów jest stale wysoka, płacisz za dane, których nie chciałeś czytać.
Sygnały operacyjne są równie istotne:
- VACUUM i autovacuum zajmują znacznie więcej czasu niż wcześniej.
- Autovacuum nie nadąża i martwe krotki (bloat) narastają.
- Indeksy rosną szybciej niż oczekiwano, zwłaszcza indeksy wielokolumnowe.
- Zauważalne staje się blokowanie przy kolizji konserwacji z ruchem.
Koszty operacyjne to powolny kapitał, który popycha zespoły do partycjonowania. Kopie zapasowe i przywracanie stają się wolniejsze, gdy jedna tabela rośnie, przestrzeń dyskowa się powiększa, a zadania retencji stają się drogie, bo duże DELETE'y tworzą bloat i dodatkową pracę vacuum.
Jeśli twoje główne cele to czysta polityka retencji i szybsze zapytania „ostatni okres”, partycjonowanie zwykle jest warte poważnego rozważenia. Jeśli tabela jest umiarkowana, a zapytania już są szybkie dzięki dobremu indeksowaniu, partycjonowanie może dodać złożoność bez jasnego zysku.
Opcje partycjonowania pasujące do tabel zdarzeń i audytu
Dla większości danych zdarzeń i audytu najprostszy wybór to partycjonowanie zakresowe według czasu. Logi przychodzą w porządku czasowym, zapytania często dotyczą „ostatnich 24 godzin” lub „ostatnich 30 dni”, a retencja zazwyczaj jest oparta na czasie. Przy partycjonach czasowych usuwanie starych danych może być tak proste, jak usunięcie starej partycji zamiast uruchamiania dużego DELETE'a, który tworzy bloat.
Partycjonowanie czasowe także utrzymuje indeksy mniejszymi i bardziej skoncentrowanymi. Każda partycja ma własne indeksy, więc zapytanie za ostatni tydzień nie musi przeszukiwać jednego gigantycznego indeksu obejmującego lata historii.
Istnieją inne style partycjonowania, ale pasują do mniejszej liczby przypadków logów i audytów:
- List (tenant lub klient) może działać, gdy masz niewielką liczbę bardzo dużych tenantów i zapytania zwykle mieszczą się w jednym tenancie. Staje się bolesne przy setkach lub tysiącach tenantów.
- Hash (równe rozłożenie zapisu) może pomóc, gdy nie masz zapytań opartych na oknach czasowych i chcesz równomiernie rozłożyć zapisy. Dla logów audytu jest to mniej powszechne, bo utrudnia retencję i przeglądanie po czasie.
- Subpartycjonowanie (czas plus tenant) może być potężne, ale złożoność szybko rośnie. To głównie dla bardzo wysokich wolumenów z surową izolacją tenantów.
Jeśli wybierasz zakresy czasowe, dobierz rozmiar partycji tak, aby pasował do sposobu przeglądania i retencji. Partycje dzienne mają sens przy bardzo dużym wolumenie lub surowej retencji. Partycje miesięczne są łatwiejsze w zarządzaniu przy umiarkowanym wolumenie.
Praktyczny przykład: jeśli zespół administracyjny sprawdza nieudane logowania co rano i filtruje po ostatnich 7 dniach, partycje dzienne lub tygodniowe sprawią, że zapytanie dotknie tylko najnowszych partycji. PostgreSQL będzie mógł zignorować resztę.
Bez względu na podejście zaplanuj nudne części: tworzenie przyszłych partycji, obsługę późno przychodzących zdarzeń i zdefiniowanie, co dzieje się na granicy (koniec dnia, koniec miesiąca). Partycjonowanie się opłaca, gdy te rutyny pozostają proste.
Jak wybrać właściwy klucz partycji
Dobry klucz partycji odpowiada temu, jak czytasz tabelę, a nie temu, jak dane wyglądają na diagramie.
Dla logów i audytów zacznij od panelu administracyjnego: jaki filtr ludzie używają pierwszy, prawie za każdym razem? Dla większości zespołów to zakres czasu (ostatnie 24 godziny, ostatnie 7 dni, niestandardowe daty). Jeśli to prawda, partycjonowanie oparte na czasie zwykle daje największy i najbardziej przewidywalny zysk, bo PostgreSQL może pominąć całe partycje poza wybranym zakresem.
Traktuj wybór klucza jako długoterminową obietnicę. Optymalizujesz dla zapytań, które będziesz wykonywać lata.
Zacznij od „pierwszego filtra”, którego ludzie używają
Większość ekranów administracyjnych podąża za wzorcem: zakres czasowy plus opcjonalnie użytkownik, akcja, status lub zasób. Partycjonuj wg tego, co zawęża wyniki wcześnie i konsekwentnie.
Krótka kontrola rzeczywistości:
- Jeśli widok domyślny to „ostatnie zdarzenia”, partycjonuj po znaczniku czasu.
- Jeśli widok domyślny to „zdarzenia dla jednego tenant/konta”,
tenant_idmoże mieć sens, ale tylko jeśli tenanci są wystarczająco duzi, by to uzasadnić. - Jeśli pierwszy krok to zawsze „wybierz użytkownika”,
user_idmoże kusić, ale zwykle powoduje zbyt wiele partycji do zarządzania.
Unikaj kluczy o wysokiej krotności wartości
Partycjonowanie działa najlepiej, gdy każda partycja to sensowny kawałek danych. Klucze jak user_id, session_id, request_id lub device_id mogą prowadzić do tysięcy lub milionów partycji. To zwiększa narzut metadanych, komplikuje utrzymanie i często spowalnia planowanie.
Partycje oparte na czasie utrzymują liczbę partycji przewidywalną. Wybierasz dzienne, tygodniowe lub miesięczne w zależności od wolumenu. Za mało partycji (jedna na rok) nie pomoże dużo. Za dużo (jedna na godzinę) szybko doda narzut.
Wybierz właściwy znacznik czasu: created_at vs occurred_at
Bądź konkretny, co oznacza czas:
- occurred_at: kiedy zdarzenie miało miejsce w produkcie.
- created_at: kiedy baza danych je zarejestrowała.
Dla audytów „occurred” jest często tym, czym admini się interesują. Ale opóźnione dostawy (klienci mobilni offline, ponowne próby, kolejki) oznaczają, że occurred_at może przyjść później. Jeśli opóźnione przybycia są częste, partycjonowanie po created_at i indeksowanie occurred_at do filtrowania może być bardziej stabilne operacyjnie. Inną opcją jest zdefiniowanie jasnej polityki backfill i akceptacja, że stare partycje czasem otrzymają późne zdarzenia.
Zdecyduj też, jak przechowywać czas. Używaj spójnego typu (często timestamptz) i traktuj UTC jako źródło prawdy. Formatuj do strefy widza w UI. To utrzymuje granice partycji stabilne i unika niespodzianek związanych ze zmianą czasu.
Krok po kroku: plan i wdrożenie partycjonowania
Partycjonowanie jest najłatwiejsze, gdy potraktujesz je jak mały projekt migracyjny, a nie szybkie poprawienie. Celem są proste zapisy, przewidywalne odczyty i retencja, która staje się rutynową operacją.
Praktyczny plan wdrożenia
-
Wybierz rozmiar partycji dopasowany do wolumenu. Partycje miesięczne zwykle wystarczają przy kilkuset tysiącach wierszy miesięcznie. Jeśli wstawiasz dziesiątki milionów miesięcznie, partycje tygodniowe lub dzienne utrzymują indeksy mniejsze i pracę VACUUM w ryzach.
-
Zaprojektuj klucze i ograniczenia dla tabel partycjonowanych. W PostgreSQL unikatowe ograniczenie musi zawierać klucz partycji (lub być egzekwowane inaczej). Częsty wzorzec to
(created_at, id), gdzieidjest generowane, acreated_atto klucz partycji. To unika niespodzianek później, gdy odkryjesz, że oczekiwane ograniczenie nie jest dozwolone. -
Twórz przyszłe partycje zanim będą potrzebne. Nie czekaj, aż inserty zaczną się nie powodzić z powodu braku pasującej partycji. Zdecyduj, jak daleko do przodu je tworzyć (np. 2–3 miesiące) i zrób z tego rutynowe zadanie.
-
Trzymaj indeksy per-partycja małe i celowe. Partycjonowanie nie sprawia, że indeksy są darmowe. Większość tabel zdarzeń potrzebuje klucza partycji plus jednego lub dwóch indeksów zgodnych z realnymi filtrami admina, takimi jak
actor_id,entity_idczyevent_type. Omijaj indeksy „na wszelki wypadek”. Możesz je dodać później do nowych partycji i backfillować starsze, gdy zajdzie potrzeba. -
Plan retencji wokół usuwania partycji, nie usuwania wierszy. Jeśli przechowujesz 180 dni logów, usuwanie starej partycji jest szybkie i unika długotrwałych DELETE'ów i bloatu. Spisz regułę retencji, kto ją uruchamia i jak weryfikujesz, że wykonała się poprawnie.
Mały przykład
Jeśli twoja tabela audytu dostaje 5 milionów wierszy tygodniowo, partycje tygodniowe po created_at są rozsądnym punktem startowym. Twórz partycje na 8 tygodni do przodu i trzymaj dwa indeksy na partycję: jeden do wyszukiwań po actor_id i drugi po entity_id. Gdy okno retencji minie, usuwaj najstarszą tygodniową partycję zamiast deletować miliony wierszy.
Jeśli budujesz wewnętrzne narzędzia w AppMaster, warto zdecydować klucz partycji i ograniczenia wcześnie, żeby model danych i wygenerowany kod trzymały te same założenia od dnia pierwszego.
Co partycjonowanie zmienia dla filtrów w panelu administracyjnym
Po partycjonowaniu tabela logów przestaje być „tylko UI”. Filtry stają się głównym czynnikiem decydującym, czy zapytanie dotknie kilku partycji, czy przeskanuje miesiące danych.
Największa praktyczna zmiana: czas nie może być już opcjonalny. Jeśli użytkownicy mogą uruchomić nieograniczone wyszukiwanie (bez zakresu dat, tylko „pokaż mi wszystko dla użytkownika X”), PostgreSQL może musieć sprawdzić każdą partycję. Nawet jeśli każde sprawdzenie jest szybkie, otwieranie wielu partycji dodaje narzut i strona robi się powolna.
Zasada sprawdzająca się w praktyce: wymagaj zakresu czasu dla wyszukiwań logów i audytów i ustaw rozsądny domyśl (np. ostatnie 24 godziny). Jeśli ktoś rzeczywiście potrzebuje „cały czas”, niech to będzie świadoma decyzja z ostrzeżeniem o wolniejszych wynikach.
Dopasuj filtry do pruning partycji
Pruning partycji pomaga tylko wtedy, gdy klauzula WHERE zawiera klucz partycji w formie, którą PostgreSQL potrafi wykorzystać. Filtry typu created_at BETWEEN X AND Y pozwalają na czyste przycinanie. Wzorce, które często psują pruning, to rzutowania znaczników czasu na daty, owinięcie kolumny w funkcję lub filtrowanie głównie po innym polu czasu niż klucz partycji.
Wewnątrz każdej partycji indeksy powinny odpowiadać temu, jak ludzie faktycznie filtrują. W praktyce kombinacje, które się liczą, to czas plus jedno dodatkowe kryterium: tenant/workspace, użytkownik, typ akcji, id encji lub status.
Sortowanie i paginacja: trzymaj je płytko
Partycjonowanie samo w sobie nie naprawi wolnej paginacji. Jeśli panel administracyjny sortuje po newest first, a użytkownicy skaczą do strony 5000, głębokie OFFSET nadal zmusza PostgreSQL do przetwarzania wielu wierszy.
Paginacja kursora zachowuje się lepiej dla logów: „ładuj zdarzenia sprzed tego timestamp/id”. Pozwala używać indeksów zamiast pomijania ogromnych offsetów.
Przydatne są też presety. Kilka opcji zwykle wystarcza: ostatnie 24 godziny, ostatnie 7 dni, dzisiaj, wczoraj, zakres niestandardowy. Presety zmniejszają przypadkowe „skany wszystkiego” i czynią doświadczenie admina bardziej przewidywalnym.
Częste błędy i pułapki
Większość projektów partycjonowania zawodzi z prostych powodów: partycjonowanie działa, ale zapytania i UI nie pasują do niego. Jeśli chcesz, żeby partycjonowanie się opłaciło, zaprojektuj je wokół realnych filtrów i retencji.
1) Partycjonowanie po złej kolumnie czasu
Pruning działa tylko wtedy, gdy WHERE pasuje do klucza partycji. Częsty błąd to partycjonowanie po created_at, podczas gdy panel filtruje po event_time (albo odwrotnie). Jeśli zespół wsparcia zawsze pyta „co się stało między 10:00 a 10:15”, ale tabela jest partycjonowana po czasie ingestu, nadal możesz dotykać więcej danych niż się spodziewasz.
2) Tworzenie zbyt wielu maleńkich partycji
Partycje godzinowe (lub mniejsze) wyglądają schludnie, ale dodają narzut: więcej obiektów do zarządzania, więcej pracy dla planera zapytań i większe ryzyko brakujących indeksów czy niespójnych uprawnień.
O ile nie masz ekstremalnie wysokiego wolumenu zapisów i surowej retencji, partycje dzienne lub miesięczne są zwykle łatwiejsze w obsłudze.
3) Zakładanie, że „globalna unikalność” nadal działa
Tabele partycjonowane mają ograniczenia: niektóre unikalne indeksy muszą zawierać klucz partycji, inaczej PostgreSQL nie jest w stanie ich egzekwować między partycjami.
To często zaskakuje zespoły, które oczekują, że event_id będzie unikalne na zawsze. Jeśli potrzebujesz unikalnego identyfikatora, użyj UUID i egzekwuj unikalność razem z kluczem czasu albo egzekwuj ją na poziomie aplikacji.
4) Pozwalanie UI na szerokie, nieograniczone wyszukiwania
Panele administracyjne często wypuszczają przyjazne pole wyszukiwania, które działa bez filtrów. W przypadku tabeli partycjonowanej może to oznaczać skanowanie każdej partycji.
Wyszukiwanie pełnotekstowe po treści payloadu jest szczególnie ryzykowne. Dodaj zabezpieczenia: wymuszaj zakres czasu, ogranicz domyślny przedział i spraw, by „cały czas” był świadomą opcją.
5) Brak planu retencji (i planu dla partycji)
Partycjonowanie samo nie rozwiąże retencji. Bez polityki kończysz z górą starych partycji, chaosem w przechowywaniu i wolniejszym utrzymaniem.
Prosty zestaw zasad operacyjnych zwykle to zapobiega: zdefiniuj, jak długo surowe zdarzenia mają być przechowywane, zautomatyzuj tworzenie przyszłych partycji i usuwanie starych, stosuj indeksy konsekwentnie, monitoruj liczbę partycji i daty graniczne oraz testuj najwolniejsze filtry administracyjne na realistycznych wolumenach danych.
Krótka lista kontrolna przed podjęciem decyzji
Partycjonowanie może być dużym zyskiem dla logów audytu, ale dodaje rutynową pracę. Zanim zmienisz schemat, sprawdź, jak ludzie naprawdę używają tabeli.
Jeśli główny ból to timouty na stronach admina przy otwarciu „Ostatnie 24 godziny” lub „Ten tydzień”, jesteś blisko dobrego dopasowania. Jeśli większość zapytań to „user ID w całej historii”, partycjonowanie może pomagać mniej, chyba że zmienisz sposób, w jaki UI kieruje wyszukiwaniami.
Krótka lista, która trzyma zespoły przy zdrowych zasadach:
- Zakres czasowy jest domyślnym filtrem. Większość zapytań admina zawiera wyraźne okno (od/do). Jeśli często zdarzają się wyszukiwania otwarte, pruning partycji pomaga mniej.
- Retencja jest egzekwowana przez usuwanie partycji, nie przez DELETEy. Jesteś w stanie usuwać stare partycje i masz jasną regułę przechowywania.
- Liczba partycji pozostaje rozsądna. Oszacuj partycje na rok (dziennie, tygodniowo, miesięcznie). Zbyt wiele maleńkich partycji zwiększa narzut. Zbyt mało dużych partycji zmniejsza korzyści.
- Indeksy pasują do filtrów, których ludzie faktycznie używają. Poza kluczem partycji nadal potrzebujesz właściwych indeksów per-partycja do typowych filtrów i porządkowania.
- Partycje są tworzone automatycznie i monitorowane. Rutynowe zadanie tworzy przyszłe partycje i wiesz, kiedy zawiedzie.
Praktyczny test: spójrz na trzy filtry, których zespół wsparcia lub operacji używa najczęściej. Jeśli dwa z nich zwykle spełnia „zakres czasu + jedno dodatkowe warunek”, partycjonowanie PostgreSQL dla tabel zdarzeń często warto poważnie rozważyć.
Realistyczny przykład i praktyczne kolejne kroki
Zespół wsparcia trzyma otwarte dwa ekrany cały dzień: „Zdarzenia logowania” (udane i nieudane loginy) oraz „Audyty bezpieczeństwa” (reset hasła, zmiany ról, aktualizacje kluczy API). Gdy klient zgłasza podejrzaną aktywność, zespół filtruje po użytkowniku, sprawdza ostatnie godziny i eksportuje krótki raport.
Przed partycjonowaniem wszystko siedzi w jednej dużej tabeli events. Rośnie szybko i nawet proste wyszukiwania zaczynają ciągnąć, bo baza pracuje przez wiele starych wierszy. Retencja też boli: nocne zadanie usuwa stare wiersze, ale duże DELETE'y trwają długo, tworzą bloat i konkurują z normalnym ruchem.
Po partycjonowaniu według miesiąca (używając znacznika czasu zdarzenia) workflow się poprawia. Panel administracyjny wymaga filtra czasu, więc większość zapytań dotyka tylko jednej lub dwóch partycji. Strony ładują się szybciej, bo PostgreSQL może zignorować partycje poza wybranym zakresem. Retencja staje się rutynowa: zamiast usuwać miliony wierszy, dropujesz stare partycje.
Jedna rzecz nadal jest trudna: wyszukiwanie pełnotekstowe „przez cały czas”. Jeśli ktoś szuka adresu IP lub niejasnej frazy bez ograniczenia daty, partycjonowanie nie uczyni tego tanim. Rozwiązanie zwykle leży w produkcie: ustaw domyślne wyszukiwanie na okno czasowe i uczynienie opcji „ostatnie 24 godziny / 7 dni / 30 dni” oczywistą ścieżką.
Praktyczne następne kroki, które zwykle działają dobrze:
- Najpierw odwzoruj filtry panelu administracyjnego. Wypisz pola, których ludzie używają i które muszą być wymagane.
- Wybierz partycje, które pasują do twojego sposobu przeglądania. Partycje miesięczne to często dobry start; przejdź do tygodniowych tylko, gdy wolumen tego wymusi.
- Uczyń zakres czasu pierwszorzędnym filtrem. Jeśli UI pozwala na „brak daty”, spodziewaj się wolnych stron.
- Wyrównaj indeksy z prawdziwymi filtrami. Gdy czas jest zawsze obecny, strategia indeksów zaczynająca się od czasu jest często właściwą bazą.
- Ustaw reguły retencji dopasowane do granic partycji (np. przechowuj 13 miesięcy i dropuj wszystko starsze).
Jeśli budujesz wewnętrzny panel z AppMaster (appmaster.io), warto modelować te założenia wcześnie: traktuj filtry ograniczone czasem jako część modelu danych, a nie tylko wybór UI. Ta mała decyzja chroni wydajność zapytań wraz ze wzrostem wolumenu logów.
FAQ
Partycjonowanie najbardziej pomaga, gdy typowe zapytania są ograniczone czasowo (np. „ostatnie 24 godziny” lub „ostatnie 7 dni”), a tabela jest na tyle duża, że indeksy i utrzymanie stają się uciążliwe. Jeśli główne zapytania to „cała historia dla użytkownika X”, partycjonowanie może dodać narzut, chyba że wymusisz filtry czasowe w UI i dodasz właściwe indeksy dla każdej partycji.
Partycjonowanie zakresowe według czasu jest zwykle najlepszym domyślnym wyborem dla logów i audytów, ponieważ zapisy przychodzą w kolejności czasowej, zapytania często zaczynają się od okna czasowego, a retencja jest oparta na czasie. Partycjonowanie listowe lub hash może zdać egzamin w specjalnych przypadkach, ale często utrudnia retencję i przeglądanie danych w scenariuszach audytowych.
Wybierz pole, po którym użytkownicy najpierw filtrują i robią to niemal zawsze. W większości paneli administracyjnych to zakres czasowy, więc partycjonowanie według czasu jest najbardziej przewidywalne. Traktuj ten wybór jako długoterminowe zobowiązanie — zmiana klucza partycji później to poważna migracja.
Używaj kluczy, które dają zarządzalną liczbę partycji. Unikaj wysokiej krotności wartości jak user_id, session_id czy request_id, ponieważ mogą stworzyć tysiące partycji, zwiększyć narzut planowania i skomplikować operacje bez stałego przyśpieszenia zapytań.
Partycjonuj po created_at, gdy potrzebujesz stabilności operacyjnej i nie możesz ufać późnym dostawom zdarzeń (kolejki, ponowne próby, klienci offline). Partycjonuj po occurred_at, gdy głównym przypadkiem użycia jest „co się stało w tym oknie” i czas zdarzenia jest wiarygodny. Często kompromisem jest partycjonowanie po created_at i indeksowanie occurred_at do filtrowania.
Tak — większość paneli administracyjnych powinna wymagać zakresu czasu po wprowadzeniu partycjonowania. Bez filtru czasowego PostgreSQL może sprawdzać wiele lub wszystkie partycje, co spowalnia strony, nawet jeśli każda partycja jest zindeksowana. Dobrym domyślnym wyborem jest „ostatnie 24 godziny”, a „cały czas” traktuj jako świadomy wybór z ostrzeżeniem.
Tak. Owinięcie klucza partycji w funkcję (np. rzutowanie na date) może uniemożliwić pruning partycji, a filtrowanie po innym polu czasowym niż klucz partycji zmusi skanowanie dodatkowych partycji. Utrzymuj filtry w prostej formie jak created_at BETWEEN X AND Y, żeby pruning działał niezawodnie.
Unikaj głębokiej paginacji z OFFSET dla widoków logów, bo zmusza to bazę do pomijania wielu wierszy. Zamiast tego używaj paginacji kursora, np. „ładuj zdarzenia przed tym (timestamp, id)”, co jest przyjazne dla indeksów i zachowuje wydajność przy rosnącej tabeli.
W PostgreSQL niektóre unikalne ograniczenia dla tabel partycjonowanych muszą zawierać klucz partycji, więc globalna unikalność id może nie działać jak oczekujesz. Praktycznym wzorcem jest złożona unikalność jak (created_at, id) kiedy created_at to klucz partycji. Jeśli potrzebujesz globalnie unikalnego identyfikatora na zewnątrz, użyj UUID i starannie wymuś unikalność.
Usuwanie starych partycji jest szybkie i unika bloatu oraz nadmiernej pracy VACUUM spowodowanej dużymi DELETE'ami. Kluczowe jest dopasowanie zasad retencji do granic partycji i automatyzacja procesu: twórz przyszłe partycje z wyprzedzeniem i usuwaj wygasłe według harmonogramu. Bez automatyzacji partycjonowanie szybko staje się ręczną, uciążliwą czynnością.


