Postgres FTS vs Elasticsearch do wyszukiwania produktów
Gdy sklep potrzebuje wyszukiwania, odruchem jest Elasticsearch — ale wyszukiwanie pełnotekstowe Postgresa dorosło. Pojedynek dla przypadku e-commerce: szeregowanie, fasety, tolerancja literówek i koszt operacyjny, który to rozstrzyga.
Wcześniej pisałem o dodawaniu Elasticsearch do aplikacji Rails i zakończyłem ostrzeżeniem: sprawdź, czy Postgres może wykonać robotę, zanim weźmiesz na siebie drugi skład danych. Dla katalogu produktów konkretnie to pytanie zasługuje na porządny pojedynek, bo wyszukiwanie jest centralne dla sklepu — to często najlepiej konwertująca ścieżka do sprzedaży — a wybór między wyszukiwaniem pełnotekstowym Postgresa a Elasticsearch kształtuje zarówno twoją trafność, jak i operacje. Wysławszy oba, oto uczciwe porównanie.
Co naprawdę daje wyszukiwanie pełnotekstowe Postgresa
FTS Postgresa jest zdolniejsze, niż ludzie oczekują. Tokenizuje i rdzeniuje tekst w
tsvector, dopasowuje go do tsquery, szereguje wyniki i indeksuje całość GIN-em dla
szybkości. Co kluczowe, wspiera ważenie pól, czyli dokładnie to, czego potrzebuje
wyszukiwanie produktów — dopasowanie w nazwie produktu powinno bić dopasowanie w
opisie:
-- ważony dokument wyszukiwania: name waga A, description waga B
ALTER TABLE products ADD COLUMN search_doc tsvector;
UPDATE products SET search_doc =
setweight(to_tsvector('english', name), 'A') ||
setweight(to_tsvector('english', coalesce(description,'')), 'B');
CREATE INDEX index_products_search ON products USING gin(search_doc);
-- szeregowane zapytanie
SELECT id, ts_rank(search_doc, query) AS rank
FROM products, to_tsquery('english', 'running & shoes') query
WHERE search_doc @@ query
ORDER BY rank DESC;
To rdzeniowanie, wielopolowe ważenie, szeregowanie i indeksowane wyszukiwania — w całości
wewnątrz bazy, którą już uruchamiasz. Trzymaj search_doc zaktualizowany triggerem albo
callbackiem modelu, a masz prawdziwe wyszukiwanie z zerową dodatkową infrastrukturą,
transakcyjną spójnością i bez problemu synchronizacji, bo indeks wyszukiwania żyje w tej
samej bazie co dane i aktualizuje się w tej samej transakcji.
Dla tolerancji literówek sparuj to z pg_trgm (podobieństwo trigramowe), by „manchestr”
wciąż znajdował „manchester” — omówione we wcześniejszym wpisie. Między FTS dla trafności a
pg_trgm dla rozmycia Postgres obsługuje sporo z tego, czego potrzebuje sklep.
Gdzie Elasticsearch wysuwa się naprzód
Dlaczego więc Elasticsearch dominuje w wyszukiwaniu e-commerce? Bo na wymagającym końcu robi rzeczy, których FTS Postgresa nie robi, a kilka z nich to konkretnie to, czego chce duży katalog:
- Wyszukiwanie fasetowe / agregacje. „Pokaż mi buty, filtrowane po marce, kolorze i cenie, z żywym licznikiem obok każdej opcji.” Agregacje Elasticsearch liczą te liczniki fasetowe wydajnie jako część wyszukiwania; zrobienie tego samego w Postgresie oznacza stertę osobnych zapytań agregujących, które robią się wolne i niezgrabne. Dla katalogu z bogatym filtrowaniem samo to często rozstrzyga.
- Głębia strojenia trafności. Punktacja funkcyjna, boostowanie popularnością lub marżą, funkcje wygaszania („preferuj w magazynie i świeże”), słowniki synonimów („trainers” = „sneakers”), analizatory per język — Elasticsearch to wyszukiwarka z głębokimi gałkami dokładnie do tego. Szeregowanie FTS Postgresa jest porównywalnie tępe.
- Tolerancja literówek i autouzupełnianie wbudowane. Rozmyte dopasowanie,
autouzupełnianie edge-ngram i „search-as-you-type” są pierwszoklasowe; w Postgresie
składasz je z
pg_trgmi troski. - Skala i wydajność specyficzna dla wyszukiwania. Dla bardzo dużych katalogów i wysokich wolumenów zapytań dedykowana, poziomo skalowalna wyszukiwarka przewyższa wyszukiwanie doczepione do twojej głównej bazy — i zdejmuje to obciążenie z bazy serwującej wszystko inne.
Koszt, który to rozstrzyga
Czynnikiem decydującym rzadko jest checkbox funkcji; to koszt operacyjny wobec potrzeby. Elasticsearch to drugi skład danych: kolejny system do uruchamiania, monitorowania, zabezpieczania i skalowania, i — część, która gryzie — do trzymania w synchronizacji z twoją bazą. Jak wyłożył wcześniejszy wpis, ta synchronizacja to prawdziwa ciągła praca: indeksowanie w zadaniach w tle, okresowe pełne przeindeksowania do naprawy dryfu, atomowe podmiany aliasów do odbudów, obsługa operacji masowych omijających callbacki. Nie jest darmowa i nigdy nie przestaje kosztować.
Więc framework decyzji, którego używamy:
- Zacznij od FTS Postgresa, jeśli wyszukiwanie jest ważne, ale nie głównym daniem — skromny katalog, dopasowanie nazwa-i-opis, podstawowe szeregowanie, lekkie filtrowanie. Dostajesz prawdziwe wyszukiwanie bez nowej infrastruktury i bez problemu synchronizacji. Dla bardzo wielu sklepów to naprawdę wystarcza, a „brak drugiego składu danych” jest wart wiele.
- Przejdź na Elasticsearch, gdy wyszukiwanie staje się podstawową funkcją i uderzasz w sufit Postgresa: potrzebujesz bogatego filtrowania fasetowego z żywymi licznikami, poważnego strojenia trafności, synonimów, wyrafinowanego autouzupełniania, albo jesteś na skali, gdzie liczy się dedykowana wydajność wyszukiwania. W tym momencie Elasticsearch zarabia na swój ciężar operacyjny, bo wyszukiwanie jest powierzchnią produktu, a jego jakość napędza przychody.
Błąd w obu kierunkach jest realny. Sięganie po Elasticsearch pierwszego dnia dla małego katalogu podwaja twój ciężar operacyjny dla faset, których jeszcze nie potrzebujesz. Trzymanie się FTS Postgresa, gdy biznes naprawdę potrzebuje fasetowego, drobno strojonego wyszukiwania, produkuje frustrujący sklep i utracone sprzedaże. Umiejętnością jest dopasowanie narzędzia do tego, gdzie wyszukiwanie siedzi w wartości produktu.
Pragmatyczna droga środka
W praktyce możesz też wrosnąć w Elasticsearch. Zbuduj najpierw na FTS Postgresa, trzymaj
wyszukiwanie za czystym interfejsem (obiekt ProductSearch, który woła reszta aplikacji),
a gdy go przerośniesz, podmień implementację za tym interfejsem na Elasticsearch bez
dotykania wołających. Zaprojektowanie szwu z góry oznacza, że migracja jest zamkniętym
kawałkiem pracy, a nie przepisaniem — ten sam instynkt „izoluj granicę”, który opłaca się
wszędzie.
Werdykt
Dla wyszukiwania produktów FTS Postgresa jest znacznie bardziej wykonalne, niż sugeruje
jego reputacja: ważone, rdzeniowane, szeregowane, indeksowane wyszukiwanie bez dodatkowej
infrastruktury i bez problemu synchronizacji, i pg_trgm dla rozmycia. Elasticsearch jest
naprawdę lepszy na wymagającym końcu — agregacje fasetowe, głębokie strojenie trafności,
synonimy, autouzupełnianie i skala — ale płacisz za niego drugim składem danych, który
musisz uruchamiać i wiecznie trzymać w synchronizacji. Decyduj według tego, gdzie
wyszukiwanie siedzi w twoim produkcie: domyślnie Postgres, póki wyszukiwanie jest funkcją,
awansuj do Elasticsearch, gdy wyszukiwanie staje się tą funkcją, i ukryj wybór za
interfejsem, by wzrost z jednego do drugiego był podmianą, a nie odbudową.