PostgreSQL full-text search wystarcza dla wielu aplikacji. Użyj prostej reguły decyzyjnej, zapytania startowego i checklisty indeksowania, żeby wiedzieć, kiedy dodać silnik wyszukiwania.

Większość osób nie prosi o „full-text search”. Chcą pola wyszukiwania, które działa szybko i znajduje to, co mieli na myśli, już na pierwszej stronie wyników. Jeśli wyniki są wolne, hałaśliwe lub dziwnie uporządkowane, użytkownicy nie dbają, czy użyłeś PostgreSQL full-text search, czy oddzielnego silnika — po prostu przestają ufać wyszukiwaniu.
To jedna decyzja: trzymać wyszukiwanie w Postgresie, czy dodać dedykowany silnik wyszukiwania. Cel nie jest idealna relewantność. To solidna baza, którą szybko wypuścisz, łatwo uruchomisz i która jest wystarczająca do rzeczywistego użycia twojej aplikacji.
Dla wielu aplikacji PostgreSQL full-text search wystarcza przez długi czas. Jeśli masz kilka pól tekstowych (tytuł, opis, notatki), podstawowe rankingowanie i jeden lub dwa filtry (status, kategoria, tenant), Postgres da radę bez dodatkowej infrastruktury. Masz mniej ruchomych części, prostsze backupy i mniej sytuacji „dlaczego wyszukiwanie nie działa, a aplikacja tak?”.
„Wystarczające” zwykle oznacza, że można jednocześnie trafić w trzy cele:
Konkretny przykład: panel SaaS, w którym użytkownicy wyszukują projekty po nazwie i notatkach. Jeśli zapytanie typu „onboarding checklist” zwraca właściwy projekt w pierwszej piątce, w mniej niż sekundę, i nie jesteś ciągle przy tuningu analizatorów albo reindeksowaniu, to jest to „wystarczające”. Gdy nie da się osiągnąć tych celów bez narastającej złożoności, wtedy pytanie „wbudowane wyszukiwanie vs silnik wyszukiwania” staje się realne.
Zespoły często opisują wyszukiwanie funkcjami, a nie rezultatami. Przydatny ruch to przetłumaczenie każdej funkcji na koszt budowy, strojenia i utrzymania.
Wczesne prośby zwykle brzmią: tolerancja literówek, fasety i filtry, highlights, „inteligentne” rankingowanie i autouzupełnianie. Dla pierwszej wersji oddziel must-have od miłych dodatków. Podstawowe pole wyszukiwania zwykle musi znaleźć istotne pozycje, obsłużyć zwykłe formy słów (liczby mnogie, czasy), respektować proste filtry i pozostać szybkie wraz ze wzrostem tabeli. Właśnie tutaj PostgreSQL full-text search ma sens.
Postgres błyszczy, gdy treść znajduje się w normalnych polach tekstowych i chcesz, aby wyszukiwanie było blisko danych: artykuły pomocy, wpisy blogowe, zgłoszenia wsparcia, dokumentacja wewnętrzna, tytuły i opisy produktów lub notatki przy rekordach klientów. To głównie problemy typu „znajdź właściwy rekord”, a nie „zbuduj produkt wyszukiwania”.
Miłe dodatki to miejsce, gdzie wkrada się złożoność. Tolerancja literówek i rozbudowane autouzupełnianie zwykle popychają w stronę dodatkowych narzędzi. Fasety są możliwe w Postgresie, ale jeśli chcesz wielu faset, głębokich analiz i natychmiastowych zliczeń na ogromnych zbiorach, dedykowany silnik staje się bardziej atrakcyjny.
Ukryty koszt rzadko jest opłatą licencyjną. To drugi system. Gdy dodasz silnik wyszukiwania, dokładasz też synchronizację danych i backfille (i błędy, które z tego wynikają), monitoring i aktualizacje, „dlaczego wyszukiwanie pokazuje stare dane?” w supportcie oraz dwa zestawy pokręteł relewantności.
Jeśli masz wątpliwości, zacznij od Postgresa, wypuść coś prostego i dodaj inny silnik tylko wtedy, gdy pojawi się wyraźne wymaganie, którego nie da się spełnić. Wielu aplikacji nigdy nie przerasta to i unikasz uruchamiania i synchronizowania drugiego systemu za wcześnie.
Użyj reguły trzech sprawdzeń. Jeśli przejdziesz wszystkie trzy, zostań przy PostgreSQL full-text search. Jeśli jedno zawiedzie mocno, rozważ dedykowany silnik wyszukiwania.
Potrzeby relewantności: czy akceptowalne są wyniki „wystarczająco dobre”, czy potrzebujesz niemal doskonałego rankingu dla wielu przypadków brzegowych (literówki, synonimy, „ludzie też szukali”, spersonalizowane wyniki)? Jeśli tolerujesz okazjonalne niedoskonałe uporządkowanie, Postgres zwykle wystarczy.
Wolumen zapytań i opóźnienia: ile wyszukiwań na sekundę spodziewasz się w szczycie i jaki masz realny budżet latencji? Jeśli wyszukiwanie to mały kawałek ruchu i możesz utrzymać szybkie zapytania przy właściwych indeksach, Postgres jest ok. Jeśli wyszukiwanie staje się głównym obciążeniem i konkuruje z podstawowymi odczytami i zapisami, to sygnał ostrzegawczy.
Złożoność: czy przeszukujesz jedno lub dwa pola tekstowe, czy łączysz wiele sygnałów (tagi, filtry, wygaszanie czasowe, popularność, uprawnienia) i wiele języków? Im bardziej skomplikowana logika, tym bardziej poczujesz opór wewnątrz SQL.
Bezpieczny start to proste: wypuść bazę w Postgresie, loguj wolne zapytania i wyszukiwania „bez wyników”, a potem podejmij decyzję. Wiele aplikacji nigdy z tego nie wyrasta i unikasz uruchamiania oraz synchronizowania drugiego systemu za wcześnie.
Czerwone flagi, które zwykle wskazują dedykowany silnik:
Zielone flagi dla pozostania w Postgresie:
PostgreSQL full-text search to wbudowany mechanizm, który zamienia tekst w formę, którą baza może przeszukiwać szybko, bez skanowania każdego wiersza. Działa najlepiej, gdy twoje treści już żyją w Postgresie i chcesz szybkiego, przyzwoitego wyszukiwania z przewidywalnymi operacjami.
Są trzy elementy warte zapamiętania:
ts_rank (lub ts_rank_cd), aby ustawić bardziej istotne wiersze wyżej.Konfiguracja języka ma znaczenie, bo zmienia, jak Postgres traktuje słowa. Przy właściwej konfiguracji „running” i „run” mogą pasować do siebie (stemming), a typowe słowa wypełniające mogą być ignorowane (stop words). Przy złej konfiguracji wyszukiwanie może wydawać się zepsute, bo zwykłe sformułowania użytkownika przestaną pasować do tego, co zostało zindeksowane.
Dopasowanie prefiksowe to funkcja, do której sięgają ludzie chcący zachowania typu „typeahead”, np. dopasowanie „dev” do „developer”. W Postgres full-text search robi się to zwykle operatorem prefiksu (np. term:*). Może poprawić odbiór jakości, ale często zwiększa pracę na zapytanie, więc traktuj to jako opcjonalne ulepszenie, nie domyślne ustawienie.
Czego Postgres nie próbuje być: kompletną platformą wyszukiwania z każdą funkcją. Jeśli potrzebujesz korekty pisowni fuzzy, zaawansowanego autouzupełniania, learning-to-rank, złożonych analizatorów per pole czy rozproszonego indeksowania na wielu węzłach, jesteś poza strefą komfortu wbudowanego rozwiązania. Dla wielu aplikacji jednak PostgreSQL full-text search daje większość oczekiwań użytkowników przy znacznie mniejszej liczbie ruchomych części.
Oto mały, realistyczny kształt dla treści, które chcesz przeszukiwać:
-- Minimal example table
CREATE TABLE articles (
id bigserial PRIMARY KEY,
title text NOT NULL,
body text NOT NULL,
updated_at timestamptz NOT NULL DEFAULT now()
);
Dobry baseline dla PostgreSQL full-text search to: zbudować zapytanie z tego, co wpisał użytkownik, najpierw filtrować wiersze (gdy to możliwe), a potem rankować pozostałe dopasowania.
-- $1 = user search text, $2 = limit, $3 = offset
WITH q AS (
SELECT websearch_to_tsquery('english', $1) AS query
)
SELECT
a.id,
a.title,
a.updated_at,
ts_rank_cd(
setweight(to_tsvector('english', coalesce(a.title, '')), 'A') ||
setweight(to_tsvector('english', coalesce(a.body, '')), 'B'),
q.query
) AS rank
FROM articles a
CROSS JOIN q
WHERE
a.updated_at >= now() - interval '2 years' -- example safe filter
AND (
setweight(to_tsvector('english', coalesce(a.title, '')), 'A') ||
setweight(to_tsvector('english', coalesce(a.body, '')), 'B')
) @@ q.query
ORDER BY rank DESC, a.updated_at DESC, a.id DESC
LIMIT $2 OFFSET $3;
Kilka szczegółów, które oszczędzają czas później:
WHERE przed rankingiem (status, tenant_id, zakresy dat). Rankujesz mniej wierszy, więc pozostaje szybciej.ORDER BY (np. updated_at, potem id). To utrzymuje stabilność paginacji, gdy wiele wyników ma ten sam rank.websearch_to_tsquery dla wejścia użytkownika. Obsługuje cudzysłowy i proste operatory w sposób, którego użytkownicy oczekują.Gdy ten baseline działa, przenieś wyrażenie to_tsvector(...) do przechowywanej kolumny. Unikniesz przeliczania przy każdym zapytaniu i ułatwisz indeksowanie.
Większość historii „PostgreSQL full-text search jest wolne” sprowadza się do jednego: baza buduje dokument wyszukiwania przy każdym zapytaniu. Napraw to najpierw, zapisując wcześniej zbudowany tsvector i indeksując go.
tsvector: kolumna generowana czy trigger?Kolumna generowana to najprostsza opcja, gdy dokument wyszukiwania jest budowany z kolumn w tym samym wierszu. Zostaje poprawna automatycznie i trudno o jej zapomnienie przy aktualizacjach.
Użyj tsvector utrzymywanego triggerem, gdy dokument zależy od powiązanych tabel (np. łączenie wiersza produktu z nazwą kategorii), albo gdy chcesz logikę niestandardową, trudną do wyrażenia jedną generowaną ekspresją. Triggery dokładają ruchome części, więc trzymaj je małe i testuj.
Utwórz indeks GIN na kolumnie tsvector. To baseline, który sprawia, że PostgreSQL full-text search wydaje się natychmiastowy dla typowego wyszukiwania aplikacji.
Konfiguracja działająca dla wielu aplikacji:
tsvector w tej samej tabeli co wiersze, które najczęściej przeszukujesz.tsvector.@@ przeciw zapisanym tsvector, a nie to_tsvector(...) obliczanego w locie.VACUUM (ANALYZE) po dużych backfillach, aby planner rozpoznał nowy indeks.Przechowywanie wektora w tej samej tabeli jest zwykle szybsze i prostsze. Osobna tabela wyszukiwań może mieć sens, jeśli tabela bazowa jest bardzo ciężka w zapisach, lub jeśli indeksujesz połączony dokument rozciągający się na wiele tabel i chcesz aktualizować go w swoim tempie.
Indeksy częściowe mogą pomóc, gdy przeszukujesz tylko podzbiór wierszy, np. status = 'active', jeden tenant w aplikacji multi-tenant lub konkretny język. Zmniejszają rozmiar indeksu i mogą przyspieszyć wyszukiwania, ale tylko jeśli twoje zapytania zawsze zawierają ten sam filtr.
Możesz uzyskać zaskakująco dobre wyniki z PostgreSQL full-text search, jeśli utrzymasz zasady relewantności proste i przewidywalne.
Najłatwiejszy sukces to ważenie pól: dopasowania w tytule powinny liczyć się bardziej niż dopasowania głęboko w treści. Zbuduj złączony tsvector, gdzie tytuł ma wagę wyższą niż opis, a potem oceniaj za pomocą ts_rank lub ts_rank_cd.
Jeśli chcesz, aby „świeże” lub „popularne” pozycje wypływały wyżej, rób to ostrożnie. Mały bonus jest ok, ale nie pozwól, by nadmiernie przesłonił relewantność tekstową. Praktyczny wzorzec: najpierw rankuj po tekście, potem rozbijaj remisy świeżością, lub dodaj limitowany bonus, aby nieistotny nowy element nie przebił idealnego, starszego dopasowania.
Synonimy i dopasowania fraz często rozchodzą oczekiwania. Synonimy nie są automatyczne — otrzymasz je tylko jeśli dodasz tezaurus lub słownik niestandardowy, albo rozszerzysz zapytanie samodzielnie (np. traktując „auth” jako „authentication”). Dopasowanie frazy też nie jest domyślne: zwykłe zapytania pasują do słów w dowolnym miejscu, nie do „dokładnej frazy”. Jeśli użytkownicy wpisują cytaty lub długie pytania, rozważ phraseto_tsquery lub websearch_to_tsquery, aby lepiej trafić w ich intencję.
Treści mieszane językowo wymagają decyzji. Jeśli znasz język per dokument, przechowuj go i generuj tsvector z odpowiednią konfiguracją (English, Russian itp.). Jeśli nie, bezpieczne wyjście to indeksowanie z konfiguracją simple (bez stemmingu) lub trzymanie dwóch wektorów: jednego specyficznego dla języka, gdy znany, i jednego simple dla wszystkiego.
Aby walidować relewantność, trzymaj to małe i konkretne:
To zwykle wystarcza dla wyszukiwarek aplikacyjnych typu „szablony”, „dokumentacja” czy „projekty”.
Większość historii „PostgreSQL full-text search jest wolne lub nieistotne” pochodzi z kilku unikanych błędów. Naprawa ich zwykle jest prostsza niż dodawanie nowego systemu.
Jedna pułapka to traktowanie tsvector jak wartości obliczonej, która pozostaje poprawna sama z siebie. Jeśli zapisujesz tsvector w kolumnie, ale nie aktualizujesz go przy każdym insert/ update, wyniki będą wyglądać losowo, bo indeks nie będzie pasował do tekstu. Jeśli obliczasz to_tsvector(...) w locie w zapytaniu, wyniki mogą być poprawne, ale wolniejsze, i możesz stracić korzyść dedykowanego indeksu.
Inny prosty sposób na pogorszenie wydajności to ranking zanim zawęzisz zestaw kandydatów. ts_rank jest użyteczny, ale zwykle powinien działać po tym, jak Postgres użyje indeksu do znalezienia pasujących wierszy. Jeśli obliczysz rank dla ogromnej części tabeli (lub najpierw dołączysz inne tabele), możesz zamienić szybkie wyszukiwanie w skanowanie tabeli.
Ludzie też oczekują, że „contains” będzie się zachowywać jak LIKE '%term%'. Dziki początek nie przekłada się dobrze na FTS, bo FTS opiera się na słowach (leksemy), nie na dowolnych podciągach. Jeśli potrzebujesz wyszukiwania podciągów dla kodów produktów lub częściowych ID, użyj innego narzędzia (np. indeksu trigram) zamiast obwiniać FTS.
Problemy wydajności często wynikają z obsługi wyników, a nie z dopasowania. Dwa wzorce do obserwacji:
OFFSET, które sprawia, że Postgres pomija coraz więcej wierszy podczas przewijania.Również operacje operacyjne mają znaczenie. Bloat indeksu może narastać po wielu aktualizacjach, a reindeksowanie może być kosztowne, jeśli poczekasz aż będzie źle. Mierz rzeczywiste czasy zapytań (i sprawdzaj EXPLAIN ANALYZE) przed i po zmianach. Bez liczb łatwo „naprawić” PostgreSQL full-text search, pogarszając go w inny sposób.
Zanim obwinisz PostgreSQL full-text search, uruchom te kontrole. Większość błędów „Postgres search jest wolne lub nieistotne” wynika z braków podstaw, a nie z samej funkcji.
Zbuduj prawdziwy tsvector: przechowuj go w kolumnie generowanej lub utrzymywanej (nie obliczaj przy każdym zapytaniu), użyj właściwej konfiguracji języka (english, simple itp.) i zastosuj wagi, jeśli łączysz pola (tytuł > podtytuł > treść).
Normalizuj to, co indeksujesz: trzymaj hałaśliwe pola (ID, boilerplate, teksty nawigacyjne) poza tsvector i skracaj ogromne blob-y, jeśli użytkownicy ich nigdy nie szukają.
Utwórz właściwy indeks: dodaj indeks GIN na kolumnie tsvector i potwierdź, że jest używany w EXPLAIN. Jeśli tylko podzbiór jest przeszukiwalny (np. status = 'published'), indeks częściowy może zmniejszyć rozmiar i przyspieszyć odczyty.
Utrzymuj tabele zdrowe: martwe krotki mogą spowalniać skany indeksów. Regularne vacuumowanie ma znaczenie, zwłaszcza przy często aktualizowanej treści.
Miej plan reindeksacji: duże migracje lub spuchnięte indeksy czasem wymagają kontrolowanego okna reindexu.
Gdy dane i indeks wyglądają dobrze, skup się na kształcie zapytania. PostgreSQL full-text search jest szybki, gdy może wcześnie zawęzić zestaw kandydatów.
Filtruj najpierw, potem rankuj: stosuj ścisłe filtry (tenant, język, published, kategoria) przed rankingiem. Rankowanie tysięcy wierszy, które potem odrzućesz, to strata pracy.
Używaj stabilnego porządku: sortuj po rank i potem po tie-breakerze jak updated_at lub id, żeby wyniki nie skakały między odświeżeniami.
Unikaj „zapytanie robi wszystko”: jeśli potrzebujesz fuzzy matching czy tolerancji literówek, rób to świadomie (i mierz). Nie zmuszaj przypadkiem skanów sekwencyjnych.
Testuj rzeczywiste zapytania: zbierz top 20 wyszukiwań, sprawdź relewantność ręcznie i utrzymuj małą listę oczekiwanych wyników, żeby łapać regresje.
Obserwuj wolne ścieżki: loguj wolne zapytania, przeglądaj EXPLAIN (ANALYZE, BUFFERS) i monitoruj rozmiar indeksu oraz wskaźnik trafień w cache, aby zauważyć, kiedy wzrost zmienia zachowanie.
Centrum pomocy SaaS to dobre miejsce na start, bo cel jest prosty: pomóc ludziom znaleźć artykuł, który odpowiada na ich pytanie. Masz kilka tysięcy artykułów, każdy z tytułem, krótkim podsumowaniem i treścią. Większość odwiedzających wpisuje 2–5 słów jak „reset password” czy „billing invoice”.
Z PostgreSQL full-text search może to być gotowe zaskakująco szybko. Przechowujesz tsvector dla połączonych pól, dodajesz indeks GIN i rankujesz po relewantności. Sukces wygląda tak: wyniki pojawiają się w mniej niż 100 ms, top 3 wyniki zwykle są poprawne i nie musisz pilnować systemu na bieżąco.
Potem produkt rośnie. Support chce filtrować po obszarze produktu, platformie (web, iOS, Android) i planie (free, pro, business). Autorzy dokumentacji chcą synonimów, „czy chodziło ci o” i lepszej obsługi literówek. Marketing chce analityki jak „top searches z zerowymi wynikami”. Ruch rośnie i wyszukiwanie staje się jednym z najbardziej obciążających endpointów.
To są sygnały, że dedykowany silnik może się opłacać:
Praktyczna ścieżka migracji: zachowaj Postgres jako źródło prawdy nawet po dodaniu silnika wyszukiwania. Zacznij od logowania zapytań i przypadków „brak wyników”, potem uruchom asynchroniczne zadanie synchronizacji, które kopiuje tylko pola przeszukiwalne do nowego indeksu. Uruchomaj oba równolegle przez pewien czas i przełączaj się stopniowo, zamiast stawiać wszystko na jedną kartę od razu.
Jeśli twoje wyszukiwanie to głównie „znajdź dokumenty zawierające te słowa”, a zbiór danych nie jest ogromny, PostgreSQL full-text search zazwyczaj wystarcza. Zacznij tam, sprawdź jak działa, i dodaj dedykowany silnik tylko wtedy, gdy potrafisz nazwać brakującą funkcję albo ból skali.
Przypomnienie warte zapamiętania:
tsvector, dodać indeks GIN i twoje potrzeby rankingowe są podstawowe.Praktyczny następny krok: zaimplementuj starterowe zapytanie i indeks z wcześniejszych sekcji, potem loguj kilka prostych metryk przez tydzień. Śledź p95 czasu zapytania, wolne zapytania i prosty sygnał sukcesu jak „wyszukiwanie -> klik -> brak natychmiastowego odrzutu” (nawet podstawowy licznik zdarzeń pomaga). Szybko zobaczysz, czy potrzebujesz lepszego rankingu, czy po prostu lepszego UX (filtry, podświetlenia, lepsze fragmenty).
Zacznij planować dedykowany silnik, gdy któryś z tych stanie się rzeczywistym wymaganiem (nie tylko miłym dodatkiem): silne autouzupełnianie lub natychmiastowe wyszukiwanie na każde naciśnięcie klawisza w skali, solidna tolerancja literówek i korekta pisowni, fasetowanie i agregacje z szybkimi zliczeniami po wielu polach, zaawansowane narzędzia relewantności (zbiory synonimów, learning-to-rank, per-query boosts) lub utrzymujący się wysoki load i duże indeksy trudne do utrzymania w szybkim stanie.
Jeśli chcesz szybko działać po stronie aplikacji, Koder.ai (koder.ai) może być przydatny do prototypowania UI i API wyszukiwania przez chat, a potem iterowania bezpiecznie używając snapshotów i rollbacku, podczas gdy mierzysz, co naprawdę robią użytkownicy.
PostgreSQL full-text search jest „wystarczające”, gdy jednocześnie spełniasz trzy warunki:
Jeśli osiągniesz to za pomocą zapisanego tsvector + indeksu GIN, zazwyczaj jesteś w dobrej sytuacji.
Domyślnie zacznij od PostgreSQL full-text search. Wypuszcza się szybciej, trzyma dane i wyszukiwanie w jednym miejscu i oszczędza budowy i utrzymania oddzielnego pipeline’u indeksowania.
Przejdź do dedykowanego silnika, gdy pojawi się wyraźny wymóg, którego Postgres nie spełnia dobrze (wysokiej jakości tolerancja błędów/ literówek, rozbudowane autouzupełnianie, obszerne fasety, lub ruch wyszukiwania, który konkuruje z pracą głównej bazy danych).
Proste zasadnicze kryterium: zostań w Postgres jeśli przechodzisz wszystkie trzy testy:
Jeśli jedno z tych wymagań zawodzi znacząco (szczególnie błędy/literówki/autouzupełnianie lub duży ruch), rozważ dedykowany silnik.
Użyj Postgres FTS, gdy wyszukiwanie to głównie „znajdź właściwy rekord” w kilku polach jak tytuł/treść/notatki z prostymi filtrami (tenant, status, kategoria).
To dobre dopasowanie do centrum pomocy, dokumentacji wewnętrznej, zgłoszeń, wyszukiwania artykułów/blogów i pul aplikacji SaaS, gdzie użytkownicy szukają po nazwach projektów i notatkach.
Dobry bazowy kształt zapytania zazwyczaj:
websearch_to_tsquery.Zapisz wcześniej zbudowany tsvector i dodaj indeks GIN. To zapobiega ponownemu obliczaniu to_tsvector(...) przy każdym żądaniu.
Praktyczna konfiguracja:
Użyj kolumny generowanej gdy dokument wyszukiwania jest budowany z kolumn tej samej tabeli (proste i trudne do uszkodzenia).
Użyj kolumny utrzymywanej przez trigger gdy tekst wyszukiwania zależy od powiązanych tabel lub wymaga niestandardowej logiki.
Domyślny wybór: najpierw kolumna generowana, triggery tylko gdy naprawdę potrzebujesz kompozycji między-tabelowej.
Zacznij od przewidywalnej relewantności:
Waliduj na małej liście rzeczywistych zapytań i spodziewanych wyników.
Postgres FTS działa na słowach (leksemy), a nie na podciągach. Dlatego nie zachowuje się jak LIKE '%term%' dla dowolnych fragmentów.
Jeśli potrzebujesz wyszukiwania po podciągu (kody, fragmenty ID), obsłuż to oddzielnie (np. indeks trigramowy), zamiast zmuszać FTS do zadania, do którego nie jest przeznaczony.
Sygnały, że przerastasz Postgres FTS:
Praktyczna ścieżka: zachowaj Postgres jako źródło prawdy i dodaj asynchroniczne indeksowanie, gdy wymaganie stanie się jasne.
@@ do zapisanego tsvector.ts_rank/ts_rank_cd z stabilnym tie-breakerem jak updated_at, id.To utrzymuje wyniki trafne, szybkie i stabilne dla paginacji.
tsvector w tej samej tabeli, której używasz.tsvector_column @@ tsquery.To najczęstsza naprawa, gdy wyszukiwanie wydaje się wolne.