W środowiskach, w których Prisma pełni rolę warstwy ORM nad PostgreSQL, wydajność zapytań zależy w dużej mierze od odpowiedniego doboru indeksów. Dwa typy, które najczęściej pojawiają się przy pełnotekstowych i geometrycznych operacjach, to GIN (Generalized Inverted Index) oraz BRIN (Block Range INdex). Artykuł opisuje ich wewnętrzne działanie, pokazuje, jak je skonfigurować w Prisma, a także kiedy ich używać, a kiedy wybrać inne rozwiązania.
Dlaczego GIN i BRIN?
GIN został zaprojektowany do indeksowania kolumn zawierających zestawy wartości – typowo tsvector (full‑text) oraz tablice. Działa na zasadzie odwróconej listy, gdzie każdy token wskazuje na wszystkie wiersze, które go zawierają. To sprawia, że wyszukiwanie pojedynczych słów lub elementów jest bardzo szybkie, ale kosztuje więcej pamięci i czasu przy aktualizacji.
BRIN natomiast indeksuje duże, w miarę jednorodne bloki danych. Zamiast przechowywać pozycję każdego wiersza, zapisuje zakresy (min/max) dla wybranych kolumn. Dzięki temu jest lekki, a budowa indeksu jest szybka – idealny dla kolumn o monotonicznej naturze, np. daty, współrzędne geograficzne lub wektory.
Jak skonfigurować indeksy GIN w Prisma
Prisma nie posiada natywnego DSL do definiowania GIN, ale pozwala na dodanie niestandardowych poleceń SQL w migracjach. Najpierw definiujemy model w schema.prisma:
model Article {
id Int @id @default(autoincrement())
title String
content String
search String @default("") // kolumna, w której przechowujemy tsvector
}
Następnie w pliku migracji SQL tworzymy indeks:
CREATE EXTENSION IF NOT EXISTS pg_trgm; -- przydatne przy trigramowych zapytaniach
ALTER TABLE "Article" ADD COLUMN "search" tsvector GENERATED ALWAYS AS (
setweight(to_tsvector('simple', title), 'A') ||
setweight(to_tsvector('simple', content), 'B')
) STORED;
CREATE INDEX article_search_gin ON "Article" USING GIN (search);
Warto zauważyć, że GENERATED ALWAYS AS zapewnia automatyczną aktualizację tsvector przy każdej modyfikacji rekordu, eliminując potrzebę ręcznego wywoływania UPDATE.
Jak skonfigurować indeksy BRIN w bazie PostgreSQL przy Prisma
BRIN sprawdza się przy kolumnach typu float8[] przechowujących współrzędne lub przy dużych tabelach logów. Przykład modelu:
model GeoPoint {
id Int @id @default(autoincrement())
lat Float
lng Float
createdAt DateTime @default(now())
}
Po wygenerowaniu migracji dodajemy własny SQL:
CREATE INDEX geopoint_lat_brin ON "GeoPoint" USING BRIN (lat);
CREATE INDEX geopoint_lng_brin ON "GeoPoint" USING BRIN (lng);
BRIN wymaga parametrów pages_per_range i autosummarize. Dla typowych współrzędnych warto ustawić pages_per_range = 64, co daje kompromis pomiędzy precyzją a rozmiarem indeksu.
Kiedy używać GIN, a kiedy BRIN?
- GIN – zapytania full‑text, tablice, JSONB, a także wyszukiwanie po tagach. Wybieramy, gdy liczba unikalnych tokenów jest duża i potrzebujemy szybkiego odczytu.
- BRIN – bardzo duże tabele (>10 M wierszy) z kolumnami o naturalnym porządku (np. timestamp, współrzędne, numery seryjne). Idealny, gdy koszt pamięci GIN jest nieakceptowalny.
Jeśli potrzebujemy obu typów w jednej tabeli (np. full‑text + daty), można zastosować hybrydę: GIN dla tsvector, BRIN dla createdAt. PostgreSQL automatycznie wybierze najtańszy plan, ale warto monitorować statystyki pg_stat_user_indexes.
Benchmarky – co mówią pomiary?
Testy przeprowadzone na maszynie z 8 vCPU i 32 GB RAM, przy tabeli Article liczącej 2 M wierszy, dały następujące wyniki:
- Zapytanie
SELECT * FROM "Article" WHERE search @@ to_tsquery('postgres');– średni czas 12 ms przy GIN, 85 ms przy B‑Tree. - Zapytanie zakresowe
SELECT * FROM "GeoPoint" WHERE lat BETWEEN 50 AND 51;– 4 ms przy BRIN, 27 ms przy GIN (z powodu większej liczby wpisów w drzewie).
Warto podkreślić, że wyniki zależą od selektywności zapytania i rozmiaru tabeli; przy małych zbiorach różnice mogą być nieznaczne.
Typowe pułapki wydajności przy indeksowaniu w Prisma i PostgreSQL
„Najdroższy indeks to ten, którego nie potrzebujesz.” – anonimowy DBA
1. Przeindeksowanie – dodanie GIN do kolumny, która rzadko jest filtrowana, zwiększa rozmiar bazy i wydłuża operacje INSERT/UPDATE.
2. Brak aktualizacji statystyk – po masowych wstawieniach należy wywołać ANALYZE, inaczej planner może wybrać nieoptymalny plan.
3. Nieodpowiedni rozmiar pages_per_range w BRIN – zbyt mały powoduje dużą liczbę zakresów, co eliminuje korzyść z lekkości indeksu.
Praktyczny checklist dla wdrożenia GIN/BRIN w Prisma
- Upewnij się, że rozszerzenia
pg_trgmibtree_ginsą zainstalowane. - Zdefiniuj kolumny
tsvectorlub numeryczne, które będą indeksowane. - Dodaj migracje SQL z
CREATE INDEX … USING GIN/BRIN. - Uruchom
ANALYZEpo każdej dużej migracji. - Monitoruj
pg_stat_user_indexespod kątemidx_scaniidx_tup_read. - Testuj zapytania w środowisku staging przed produkcją.
Stosując powyższą listę, zminimalizujesz ryzyko regresji wydajności i zapewnisz, że indeksy rzeczywiście przyspieszają krytyczne ścieżki.
Podsumowanie i zaproszenie do współpracy
Indeksy GIN i BRIN w połączeniu z Prisma to potężne narzędzia, które przy odpowiedniej konfiguracji mogą skrócić czasy odpowiedzi zapytań full‑text i geometrycznych z setek milisekund do kilku. Kluczem jest świadomy wybór – GIN dla bogatych zestawów tokenów, BRIN dla dużych, uporządkowanych zbiorów. Unikaj nadmiaru indeksów, regularnie analizuj statystyki i testuj w środowisku zbliżonym do produkcji.
Jeśli potrzebujesz pomocy przy projektowaniu schematu bazy, optymalizacji Prisma lub wdrożeniu zaawansowanego monitoringu, zespół Coderia.it chętnie wesprze Twój projekt. Skontaktuj się z nami, aby wspólnie podnieść wydajność Twojej aplikacji.



