Wyszukiwanie „wszędzie” w PostgreSQL: full‑text, trigram i indeksy częściowe
Dowiedz się, jak zaprojektować „wyszukiwanie wszędzie” w PostgreSQL dla ekranów wewnętrznych: kiedy użyć full‑text, kiedy trigramów i kiedy indeksów częściowych, żeby uzyskać szybkie wyniki.

Co naprawdę oznacza „wyszukiwanie wszędzie” dla narzędzi wewnętrznych
Na ekranie wewnętrznym „wyszukiwanie wszędzie” zwykle oznacza: „Pomóż mi szybko znaleźć dokładny rekord, o którym myślę, nawet jeśli nie pamiętam go idealnie.” Ludzie nie przeglądają list — próbują przeskoczyć prosto do jednego klienta, ticketu, faktury lub urządzenia.
Dlatego powolne wyszukiwanie wydaje się gorsze niż wolne ładowanie strony. Strona ładuje się raz. Wyszukiwanie odbywa się wiele razy z rzędu, często podczas rozmowy telefonicznej lub triage. Jeśli wyniki zajmują 2–3 sekundy, użytkownicy zmieniają zapytanie, kasują znaki, próbują innego terminu i kończy się większym obciążeniem oraz frustracją.
Z jednego pola wyszukiwania użytkownicy oczekują kilku zachowań: dopasowań częściowych ("alex" znajdzie "Alexander"), tolerancji drobnych literówek ("microsfot" nadal znajdzie "Microsoft"), sensownego porządkowania „najlepszych wyników” (dokładne ID lub e‑maile lądują na górze), lekko preferowana świeżość oraz filtry stosowane domyślnie (otwarte tickety, aktywni klienci).
Trudność polega na tym, że jedno pole często ukrywa wiele intencji. Agent może wkleić numer ticketu, wpisać fragment nazwy, szukać e‑maila lub podać numer telefonu. Każda intencja wymaga innej strategii, innych indeksów i czasem innej reguły rankingu.
Nie zaczynaj więc od indeksów. Zacznij od wypisania kilku intencji, które faktycznie mają twoi użytkownicy, i oddziel pól identyfikujących (ID, e‑maile) od pól rozmytych (nazwy, tematy) oraz długich tekstów (notatki).
Zacznij od nazwania danych i zachowań wyszukiwania
Zanim wybierzesz indeks, zapisz, co ludzie naprawdę wpisują. „PostgreSQL search everywhere” brzmi jak jedna funkcja, ale zwykle to mieszanka bardzo różnych typów wyszukiwań.
Narzędzia wewnętrzne łączą „twarde” identyfikatory (ID zamówienia, numer ticketu, kod faktury) z „miękkim” tekstem (nazwa klienta, e‑mail, notatki, tagi). Te grupy zachowują się inaczej w PostgreSQL, więc traktowanie ich tak samo to szybka droga do wolnych zapytań.
Następnie rozdziel zachowania:
- Wyszukiwanie dokładne: ktoś wpisujący
TCK-104883oczekuje jednego precyzyjnego wyniku. - Wyszukiwanie rozmyte: ktoś wpisujący
john smthchce łagodnego dopasowania w nazwiskach (i być może e‑mailach) i przeglądnie krótką listę. - Wyszukiwanie napędzane filtrami: ktoś wybierający „Status = Open” i „Przypisane do = Ja” głównie filtruje; pole tekstowe jest wtórne.
Zdecyduj wcześnie, czy wyniki muszą być rankowane (najlepsze dopasowania na początku), czy wystarczy zwykłe filtrowanie. Ranking ma znaczenie dla notatek i dłuższych opisów. Dla ID i e‑maili ranking często wygląda przypadkowo i dodaje koszt.
Krótka lista kontrolna zwykle wystarcza:
- Które pola są codziennie przeszukiwane?
- Które wejścia są dokładne (ID, kody), rozmyte (nazwy) lub długie (notatki)?
- Które filtry stosowane są w niemal każdym wyszukiwaniu?
- Czy potrzebujesz kolejności „najlepsze dopasowanie”, czy dowolne dopasowanie wystarczy?
- Jak szybko będzie rosnąć tabela: tysiące, setki tysięcy czy miliony?
Jeśli wymienisz te decyzje wcześniej, wybór indeksów później przestaje być zgadywanką.
Punkt wyjścia: dopasowania dokładne i dlaczego ILIKE często szkodzi
Zablokuj łatwe zwycięstwa. Dla wielu ekranów wewnętrznych zwykły indeks B‑tree daje natychmiastowe wyniki dla dopasowań dokładnych jak ID, numery zamówień, e‑maile i zewnętrzne referencje.
Jeśli ludzie wklejają dokładną wartość, upewnij się, że zapytanie jest naprawdę dokładne. WHERE id = ... lub WHERE email = ... może być ekstremalnie szybkie z normalnym indeksem. Unikatowy indeks na e‑mailu często zwraca podwójną korzyść: szybkość i lepszą jakość danych.
Kłopoty zaczynają się, gdy „wyszukiwanie wszędzie” cicho zamienia się w ILIKE. Zapytanie typu name ILIKE '%ann%' ma wildcard na początku, więc PostgreSQL nie może użyć zwykłego indeksu B‑tree. Kończy się na sprawdzaniu wielu wierszy i przewidywalnie zwalnia wraz ze wzrostem tabeli.
Wyszukiwanie prefiksowe może działać, ale tylko gdy wzorzec jest zakotwiczony na początku: name ILIKE 'ann%'. Nawet wtedy detale mają znaczenie (kolacja, obsługa wielkości liter i czy indeksujesz tę samą ekspresję, którą zapytujesz). Jeśli UI musi być nieczułe na wielkość liter, powszechnym podejściem jest zapytanie lower(name) i utworzenie indeksu na lower(name).
Pomaga też uzgodnić, co znaczy „responsywne”:
- Około 200 ms lub mniej dla pracy bazy na ciepłej pamięci podręcznej
- Poniżej 1 sekundy end‑to‑end, łącznie z siecią i renderowaniem
- Brak widocznego stanu ładowania dla typowych wyszukiwań
Mając takie cele, łatwiej zdecydować, czy wystarczą dopasowania dokładne i prefiksowe, czy czas na full‑text albo indeksy trigramowe.
Kiedy full‑text jest właściwym narzędziem
Full‑text najlepiej sprawdza się, gdy ludzie wpisują naturalny język i oczekują, że system znajdzie odpowiednie elementy, a nie tylko dokładne dopasowania. Pomyśl o treściach ticketów, wewnętrznych notatkach, długich opisach, artykułach bazy wiedzy i logach rozmów.
Dużą zaletą jest ranking. Zamiast zwracać długą listę, w której najlepszy wynik jest ukryty, full‑text może posortować po trafności. W narzędziach wewnętrznych to ma znaczenie: ktoś potrzebuje odpowiedzi w kilka sekund, a nie po przejrzeniu 50 wierszy.
Na wysokim poziomie full‑text ma trzy ruchome części:
tsvector(tekst do przeszukania, przechowywany lub generowany)tsquery(to, co wpisał użytkownik, skonwertowane do zapytania)- konfiguracja języka (jak normalizowane są słowa)
Konfiguracja języka to miejsce, gdzie zachowanie staje się widoczne. PostgreSQL usuwa powszechne stop‑słowa (jak „the” czy „and”) i stosuje stemming, więc „pay”, „paid” i „payment” mogą pasować. To świetne dla notatek i wiadomości, ale może zaskoczyć, gdy ktoś szuka krótkiego, powszechnego słowa i nie dostaje wyników.
Synonimy to kolejny punkt decyzji. Pomagają, gdy firma używa różnych słów dla tej samej rzeczy (na przykład „refund” vs „chargeback”), ale wymagają pielęgnacji. Trzymaj listę synonimów krótką i opartą na tym, co naprawdę wpisuje dział wsparcia lub operacji.
Praktyczny przykład: wyszukiwanie „can’t login after reset” powinno znaleźć tickety, gdzie wiadomość mówi „cannot log in after password reset”, nawet jeśli sformułowanie jest inne. To „znajdź istotne” zachowanie jest tym, do czego jest zaprojektowany full‑text, i zwykle jest lepszym wyborem niż próba uczynienia ILIKE wyszukiwarką.
Kiedy indeksy trigramowe wygrywają
Indeksy trigramowe to mocny wybór, gdy użytkownicy wpisują fragmenty, robią literówki lub pamiętają „coś podobnego”. Świetnie sprawdzają się w krótkich polach, gdzie full‑text jest zbyt restrykcyjny: imiona i nazwiska, nazwy firm, tematy ticketów, SKU, numery zamówień i kody produktów.
Trigram to 3‑znakowy fragment tekstu. PostgreSQL porównuje dwa ciągi po liczbie wspólnych trigramów. Dlatego potrafi dopasować "Jon Smth" do "John Smith" albo "ACM" do "ACME", i znajdzie wyniki, gdy zapytanie znajduje się w środku słowa.
To często najszybsza droga do wyrozumiałego pola „wyszukiwanie wszędzie”, gdy zadaniem jest „znajdź właściwy wiersz”, a nie „znajdź dokumenty na dany temat”.
Gdzie przewyższa full‑text
Full‑text jest świetny do dłuższych tekstów i rankingu po znaczeniu, ale nie radzi sobie naturalnie z częściowymi ciągami i drobnymi literówkami w krótkich polach. Trigram został stworzony dla takiej nieostrości.
Utrzymanie rozsądnego kosztu zapisu
Indeksy trigramowe są większe i dodają narzut przy zapisach, więc bądź selektywny. Indeksuj kolumny, których ludzie faktycznie używają:
- Imię i nazwisko, e‑mail, firma, nazwa użytkownika
- Krótkie identyfikatory (SKU, kod, referencja)
- Zwięzły tytuł (nie duże pole z notatkami/komentarzami)
Jeśli potrafisz wskazać dokładne pola, które zespół wpisuje w polu wyszukiwania, zwykle da się utrzymać trigramy niewielkie i szybkie.
Indeksy częściowe dla filtrów, których ludzie faktycznie używają
Pole „wyszukiwanie wszędzie” zwykle ma ukryte domyślnie filtry. Ludzie szukają w workspace, w aktywnych elementach i bez usuniętych. Jeśli te filtry występują w prawie każdym zapytaniu, przyspiesz częsty przypadek, indeksując tylko wiersze, które je spełniają.
Indeks częściowy to normalny indeks z klauzulą WHERE. PostgreSQL przechowuje go mniejszego, bo zawiera wpisy tylko dla interesujących cię wierszy. To często oznacza mniej stron do odczytu i lepszy wskaźnik trafień w cache.
Typowe cele dla indeksów częściowych to wiersze aktywne (status = 'active'), miękkie usuwanie (deleted_at IS NULL), ograniczenie tenantowe i „okna” ostatnich danych (np. ostatnie 90 dni).
Kluczowe jest dopasowanie do UI. Jeśli ekran zawsze ukrywa usunięte wiersze, twoje zapytania powinny zawsze zawierać deleted_at IS NULL, a indeks częściowy powinien używać tej samej warunku. Małe rozbieżności, jak is_deleted = false w jednym miejscu i deleted_at IS NULL w drugim, mogą spowodować, że planner nie użyje indeksu.
Indeksy częściowe działają też razem z full‑text i trigramami. Na przykład indeksowanie wyszukiwania tekstowego tylko dla nieusuniętych wierszy utrzymuje rozmiar indeksu pod kontrolą.
Wadą jest to, że indeksy częściowe mniej pomagają w rzadkich zapytaniach. Jeśli ktoś sporadycznie szuka wśród usuniętych rekordów lub we wszystkich workspace, PostgreSQL może wrócić do wolniejszego planu. Obsłuż to przez osobną ścieżkę admina albo dodaj drugi indeks tylko wtedy, gdy rzadkie zapytanie stanie się częste.
Mieszanie podejść bez zamiany wyszukiwania w zagadkę
Większość zespołów kończy z mieszanką technik, bo jedno pole musi obsłużyć różne intencje. Celem jest jasne określenie porządku operacji, żeby wyniki były przewidywalne.
Prosty porządek priorytetów pomaga, czy zaimplementujesz go jako osobne zapytania, czy jako jedno zapytanie z wyraźną logiką CASE.
Przewidywalna drabina priorytetów
Zacznij surowo, potem przejdź do rozmytości tylko gdy trzeba:
- Najpierw dopasowanie dokładne (ID, e‑mail, numer ticketu, SKU) używając indeksów B‑tree
- Następnie wyszukiwanie prefiksowe, tam gdzie ma sens
- Potem dopasowanie trigramowe dla literówek i fragmentów w nazwach i tytułach
- Na końcu full‑text dla długich notatek, opisów i treści wolnego formatu
Trzymając się tej samej drabiny, użytkownicy uczą się, co pole oznacza. Przestają myśleć, że system jest zepsuty, gdy „12345” znajduje ticket od razu, a „refund policy” przeszukuje dłuższy tekst.
Filtruj najpierw, potem rozmycie
Wyszukiwanie rozmyte staje się kosztowne, gdy musi rozważać całą tabelę. Zawęź zestaw kandydatów filtrami, które ludzie faktycznie stosują (status, przypisany zespół, zakres dat, konto), a potem uruchom trigram lub full‑text na pozostałych. Nawet szybki indeks trigramowy może wydawać się wolny, jeśli musi ocenić miliony wierszy.
Warto też napisać krótką, jednoparafrazową regułę zrozumiałą dla nietechnicznych współpracowników, np.: „Najpierw dopasowujemy numer ticketu dokładnie, potem nazwisko klienta z tolerancją błędów, a potem przeszukujemy notatki.” Taka wspólna definicja zapobiega sporom o to, dlaczego dany wiersz się pojawił.
Krok po kroku: wybierz podejście i wdrażaj bezpiecznie
Szybkie pole „wyszukiwanie wszędzie” to zestaw małych decyzji. Zapisz je najpierw, a praca z bazą stanie się prostsza.
- Zdefiniuj wejścia. Czy to tylko jedno pole, czy jedno pole plus filtry (status, właściciel, zakres dat)?
- Wybierz typ dopasowania dla każdego pola. ID i kody potrzebują dopasowań dokładnych. Nazwy i e‑maile często wymagają prefiksu lub dopasowania rozmytego. Długie notatki i opisy lepiej obsłuży wyszukiwanie naturalnego języka.
- Dodaj odpowiednie indeksy i potwierdź, że są używane. Stwórz indeks, a potem sprawdź rzeczywiste zapytanie z
EXPLAIN (ANALYZE, BUFFERS). - Dodaj ranking lub sortowanie zgodne z intencją. Jeśli użytkownicy wpisują „invoice 1042”, dokładne dopasowania powinny się pojawić wyżej. Jeśli wpiszą źle napisane nazwisko, powinien wygrać ranking podobieństwa.
- Testuj na rzeczywistych zapytaniach. Wypróbuj literówki, bardzo krótkie terminy (jak „al”), długi wklejony tekst, puste wejście i tryb „tylko filtry”.
Aby bezpiecznie wypuścić zmiany, wprowadzaj jedną rzecz naraz i utrzymuj prosty rollback. Dla nowych indeksów na dużych tabelach preferuj CREATE INDEX CONCURRENTLY, żeby nie blokować zapisów. Jeśli możesz, wdrażaj za flagą funkcji i porównuj opóźnienia przed i po.
Praktyczny wzorzec dla „PostgreSQL search everywhere” to: najpierw dopasowanie dokładne (szybkie i precyzyjne), potem dopasowanie trigramowe dla pól „ludzkich”, gdzie ludzie robią literówki, a na końcu full‑text dla długiego tekstu, który korzysta z rankingu.
Realistyczny przykład: jedno pole wyszukiwania w panelu wsparcia
Wyobraź sobie panel administracyjny wsparcia, gdzie zespół ma jedno pole wyszukiwania, ale oczekuje, że znajdzie klientów, tickety, a nawet notatki. To klasyczny problem „jedno wejście, wiele znaczeń”.
Pierwsze usprawnienie to uczynić intencję widoczną bez dodawania tarcia. Jeśli zapytanie wygląda jak e‑mail lub numer telefonu, traktuj je jako wyszukiwanie klienta. Jeśli wygląda jak ID ticketu (np. "TKT-10482"), kieruj je prosto do ticketów. Wszystko inne odsyłaj do wyszukiwania tekstowego po temacie ticketu i notatkach.
Dla wyszukiwania klientów indeksy trigramowe zwykle sprawdzają się najlepiej. Nazwy i firmy są nieporadne, a ludzie wpisują fragmenty. Trigram może sprawić, że wyszukiwania typu „jon smi” lub „acm” będą szybkie i wyrozumiałe.
Dla notatek ticketów użyj full‑text. Notatki to zdania i zwykle chcesz trafnych wyników, a nie „zawiera ten ciąg”. Ranking pomaga, gdy dziesiątki ticketów wspominają to samo słowo.
Filtry mają większe znaczenie, niż większość zespołów przewiduje. Jeśli agenci żyją w „otwartych ticketach”, dodaj indeks częściowy obejmujący tylko otwarte wiersze. Zrób to samo dla „aktywnych klientów”. To utrzymuje indeksy małe i przyspiesza ścieżkę domyślną.
Bardzo krótkie zapytania zasługują na reguły, inaczej baza wykona drogie operacje na szumie:
- 1–2 znaki: pokaż ostatnie otwarte tickety i niedawno aktualizowanych klientów
- 3+ znaki: uruchom trigram dla pól klienta i full‑text dla tekstu ticketu
- Brak jasnej intencji: pokaż mieszane listy, ale limituj każdą grupę (np. 10 klientów i 10 ticketów)
Typowe błędy, które spowalniają wyszukiwanie lub je mylą
Większość problemów „dlaczego wyszukiwanie jest wolne?” wynika z własnych decyzji. Celem nie jest indeksowanie wszystkiego, tylko tego, co ludzie faktycznie robią.
Częsta pułapka to dodawanie indeksów na wielu kolumnach „na wszelki wypadek”. Odczyty mogą się poprawić, ale każdy insert i update ma teraz dodatkową pracę. W narzędziach wewnętrznych, gdzie rekordy zmieniają się cały dzień (tickety, zamówienia, użytkownicy), prędkość zapisu ma znaczenie.
Inny błąd to używanie full‑text, gdy tak naprawdę potrzebujesz wyszukiwania tolerancyjnego na nazwiskach lub e‑mailach. Full‑text jest świetny dla dokumentów i opisów. Nie jest magicznym rozwiązaniem dla „Jon vs John” albo „gmail.con vs gmail.com”. To zwykle domena trigramów.
Filtry także mogą cicho złamać twój plan. Jeśli większość wyszukiwań ma stały filtr (jak status = 'open' lub org_id = 42), najlepszy indeks może być indeksem częściowym dopasowanym do tego warunku. Jeśli o tym zapomnisz, PostgreSQL może zeskanować znacznie więcej wierszy, niż się spodziewasz.
Kilka powtarzających się błędów:
- Dodawanie wielu indeksów bez mierzenia kosztu zapisów
- Oczekiwanie, że full‑text zachowa się jak autocomplete tolerancyjne na literówki
- Ignorowanie, jak powszechne filtry zmieniają, który indeks może być użyty
- Testowanie na małych, czystych danych zamiast na realistycznej częstości terminów (popularne słowa vs rzadkie ID)
- Sortowanie po kolumnie bez wspierającego indeksu, które wymusza powolne sortowanie
Przykład: ekran wsparcia wyszukuje tickety po temacie, nazwie klienta i numerze ticketu, a potem sortuje po latest_activity_at. Jeśli latest_activity_at nie jest indeksowane dla filtrowanego zestawu (np. otwartych ticketów), sortowanie może zniweczyć zyski z indeksu wyszukiwania.
Szybkie kontrole przed wypuszczeniem
Zanim uznasz funkcję „wyszukiwanie wszędzie” za gotową, bądź konkretny co do zachowania, które obiecujesz.
- Czy ludzie próbują znaleźć rekord po dokładnym identyfikatorze (numer ticketu, e‑mail)?
- Czy oczekują dopasowań tolerancyjnych na literówki?
- Czy chcą wyników uporządkowanych według trafności w dłuższych notatkach i opisach?
Jeśli mieszają się tryby, zdecyduj, który z nich ma pierwszeństwo.
Następnie zidentyfikuj 2–3 pola, które napędzają większość wyszukiwań. Jeśli 80% zapytań dotyczy e‑maili, nazw i numerów ticketów, zoptymalizuj je najpierw, a resztę traktuj jako drugorzędną.
Krótka lista przed wypuszczeniem:
- Potwierdź główny tryb dopasowania dla każdego pola (dokładne, rozmyte czy tekst z rankingiem)
- Wypisz filtry, które użytkownicy stosują codziennie i upewnij się, że indeksy pasują do tych kombinacji
- Zdecyduj, jak obsługiwać bardzo krótkie i puste zapytania (np. wymagać 2–3 znaków przed wyszukiwaniem rozmytym; pokazywać „ostatnie” dla pustych)
- Uczyń sortowanie wytłumaczalnym: najnowsze, najlepsze dopasowanie tekstowe lub prosta reguła łączona
Na koniec testuj na realistycznym rozmiarze danych i obciążeniu, nie tylko poprawność. Zapytanie, które wydaje się natychmiastowe przy 1 000 wierszy, może ciągnąć przy 1 000 000.
Następne kroki: zamień plan w szybki ekran wyszukiwania wewnętrznego
Pole wyszukiwania pozostaje szybkie, gdy zespół zgadza się, co ma robić. Zapisz reguły prostym językiem: co oznacza „dopasowanie” (dokładne, prefiks, tolerancyjne na literówki), które pola są przeszukiwane i jak filtry zmieniają zestaw wyników.
Utrzymuj mały zbiór testowy rzeczywistych zapytań jako zestaw regresji. 10–20 zapytań zwykle wystarczy: kilka popularnych nazwisk, kilka częściowych e‑maili, literówka, długi fragment notatki i przypadek „brak wyników”. Uruchamiaj je przed i po zmianach, żeby praca nad wydajnością nie zepsuła trafności.
Jeśli budujesz narzędzia wewnętrzne z AppMaster (appmaster.io), warto zdefiniować reguły wyszukiwania razem z modelem danych i logiką biznesową, żeby zachowanie UI i wybory bazodanowe nie rozjeżdżały się przy zmianach wymagań.
FAQ
Traktuj to jako „szybko znajdź dokładny rekord, który mam na myśli”, a nie jako przeglądanie. Zacznij od spisania kilku rzeczywistych intencji użytkowników (wyszukiwanie po ID, wyszukiwanie po nazwie/e‑mailu z tolerancją błędów, wyszukiwanie w długich notatkach) i domyślnych filtrów, które są prawie zawsze aktywne. Te decyzje mówią ci, które zapytania uruchamiać i które indeksy warto dodać.
ILIKE '%term%' ma na początku wildcard, więc PostgreSQL zwykle nie może użyć zwykłego indeksu B‑tree i musi skanować wiele wierszy. Na małych tabelach może to wyglądać dobrze, ale wraz ze wzrostem danych wydajność gwałtownie spada. Jeśli potrzeba wyszukiwania podciągu lub tolerancji błędów, zaplanuj trigramy lub full‑text zamiast polegać na ILIKE.
Użyj porównań dokładnych, np. WHERE id = $1 lub WHERE email = $1, i wspieraj je indeksem B‑tree (dla e‑maili lub kodów często unikatowym). Wyszukiwania dokładne są najtańsze i sprawiają, że wyniki są przewidywalne. Jeśli użytkownik wkleja pełny numer ticketu lub e‑mail, najpierw skieruj zapytanie tą ścieżką.
Preferuj wzorzec z prefiksem, np. name ILIKE 'ann%', i konsekwentnie indeksuj tę samą ekspresję. Dla niezawodnego zachowania nieczułego na wielkość liter wiele zespołów stosuje lower(name) w zapytaniu i tworzy indeks na tej samej ekspresji, aby planner mógł go użyć. Jeśli nie możesz zakotwiczyć wzorca na początku, wyszukiwanie prefiksowe nie wystarczy.
Stosuj indeksy trigramowe, gdy użytkownicy wpisują fragmenty, robią drobne literówki lub pamiętają „coś podobnego”, zwłaszcza w krótkich polach jak nazwiska, tematy, kody produktów i nazwy użytkowników. Trigram radzi sobie z dopasowaniem środka ciągu i bliskimi dopasowaniami. Indeksuj tylko kolumny, których ludzie naprawdę używają, ponieważ trigramy zwiększają rozmiar indeksu i koszt zapisu.
Używaj full‑text, gdy ludzie wpisują zdania lub słowa kluczowe w dłuższych treściach jak notatki, wiadomości, opisy czy artykuły pomocy. Główna zaleta to ranking trafności — najlepsze wyniki pojawiają się na górze zamiast zmuszać do skanowania długiej listy. Spodziewaj się zachowań językowych jak stemming i usuwanie stop‑słów, co pomaga przy prozie, ale może zaskoczyć przy bardzo krótkich, powszechnych słowach.
Dodaj indeksy częściowe, gdy większość wyszukiwań zawiera te same filtry, np. deleted_at IS NULL, status = 'open' lub ograniczenie tenant/workspace. Indeks częściowy pokrywa tylko powszechny podzbiór, więc jest mniejszy i często szybszy. Upewnij się, że zapytania używają dokładnie tej samej warunkowej klauzuli, bo inaczej PostgreSQL może go zignorować.
Ustal przewidywalną kolejność priorytetów: najpierw dopasowanie dokładne dla ID/e‑maili, potem prefiks tam gdzie ma sens, dalej trigram dla nazw/tytułów z tolerancją błędów, a na końcu full‑text do dłuższych notatek i opisów. Zastosuj domyślne filtry wcześnie, żeby zmniejszyć zestaw kandydatów dla wyszukiwania rozmytego. To utrzymuje wydajność i trafność wyników w miarę rosnącej bazy.
Wprowadź proste zasady: wymagaj 3+ znaków zanim uruchomisz wyszukiwanie rozmyte i pokazuj krótką listę rekordów ostatnio używanych przy 1–2 znakach. Bardzo krótkie wejścia generują dużo hałasu i mogą uruchamiać kosztowne zapytania o niskiej wartości. Zdecyduj też, co zrobić przy pustym polu, aby UI nie obciążał bazy zapytaniami typu „dopasuj wszystko”.
Stwórz indeks, a potem zweryfikuj rzeczywiste zapytanie za pomocą EXPLAIN (ANALYZE, BUFFERS) na realistycznym zbiorze danych, nie tylko deweloperskim. Wdrażaj zmiany pojedynczo i utrzymuj możliwość rollbacku; przy dużych tabelach twórz indeksy z CREATE INDEX CONCURRENTLY, żeby nie blokować zapisów. Jeśli budujesz ekran w AppMaster (appmaster.io), zdefiniuj reguły wyszukiwania wraz z modelem danych i logiką biznesową, żeby zachowanie UI pozostało spójne przy zmianach wymagań.


