Porównanie pełnotekstowej wyszukiwarki PostgreSQL z Elasticsearch

Jedna baza danych czy prawdziwy stos wyszukiwania?

Page content

Prawdziwa debata nie dotyczy tego, czy PostgreSQL może wyszukiwać tekst, czy też Elasticsearch może przechowywać dokumenty. Oba potrafią. Zainteresujące pytanie brzmi: gdzie powinna znajdować się złożoność wyszukiwania.

Pełnotekstowe wyszukiwanie w PostgreSQL istnieje wewnątrz transakcyjnej bazy danych relacyjnych i opiera się na tsvector, tsquery, słownikach, rankingach oraz indeksach GIN. Elasticsearch to rozproszony silnik wyszukiwania i analityczny, zbudowany na Lucene, oferujący analizatory, scoring BM25, skalowanie oparte o shardy, agregacje oraz indeksowanie w niemal czasie rzeczywistym.

postgresql-vs-elasticsearch

To są różne filozofie operacyjne, zanim jeszcze staną się różnymi listami funkcji.

Jeśli mapujesz tę decyzję na magazynowanie, rurociągi (pipelines) i operacje, to przeglądy infrastruktury danych dostarcza szerszego kontekstu systemu.

O czym tak naprawdę jest to porównanie

Na niskim poziomie oba systemy polegają na pomysłach dotyczących indeksów odwróconych, ale pakują je w bardzo różny sposób. PostgreSQL zaleca GIN jako preferowany typ indeksu wyszukiwania pełnotekstowego i opisuje go jako indeks odwrócony nad leksemami w wartościach tsvector. Elasticsearch analizuje pola text i indeksuje je do wyszukiwania pełnotekstowego, a następnie rozdziela te indeksy między shardy i węzły w celu skalowania. W praktyce PostgreSQL sprawia wrażenie wyszukiwania osadzonego w bazie danych aplikacji, podczas gdy Elasticsearch wygląda jak dedykowana platforma wyszukiwania z własną czasami działania, cyklem życia i modelem skalowania.

To porównanie dotyczy głównie natywnego pełnotekstowego wyszukiwania PostgreSQL oraz bardzo powszechnego pomocnika pg_trgm do dopasowań przybliżonych (fuzzy). Ten zakres ma znaczenie, ponieważ szerszy ekosystem PostgreSQL staje się w czasie coraz bardziej obciążony wyszukiwaniem. Rozszerzenia takie jak RUM dodają bogatsze zachowanie indeksów dla wyszukiwania frazowego i skanów zorientowanych na ranking, podczas gdy PGroonga rozszerza PostgreSQL o kolejną ścieżkę indeksowania pełnotekstowego. To nie sprawia, że natywny PostgreSQL jest równy Elasticsearch, ale oznacza to, że granica jest mniej statyczna, niż zakładają wiele starszych porównań.

Moja opiniowana ramka jest prosta. Wyszukiwanie jest zazwyczaj funkcją, dopóki nie staje się powierzchnią produktu. PostgreSQL ma tendencję do zwycięstwa, gdy wyszukiwanie jest wciąż tylko funkcją. Elasticsearch ma tendencję do zwycięstwa, gdy wyszukiwanie staje się tym, co użytkownicy oceniają jako pierwsze. To mniej dotyczy nazw marek, a bardziej tego, gdzie logika trafności, polityka indeksowania i operacyjne bóle są dozwolone.

Jak działa pełnotekstowe wyszukiwanie w PostgreSQL

Pełnotekstowe wyszukiwanie w PostgreSQL zaczyna się od zamiany surowego tekstu na leksemy. to_tsvector tokenizuje tekst, normalizuje go przez skonfigurowane słowniki, usuwa słowa-wyrazy i przechowuje przetrwale leksemy wraz z ich pozycjami. setweight pozwala oznaczyć leksemy z różnych części dokumentu, takich jak tytuł, abstrakt i treść, tak aby te części mogły w różny sposób wpływać na ranking. PostgreSQL obsługuje również wiele predefiniowanych konfiguracji językowych i pozwala budować niestandardowe konfiguracje z parserami i słownikami. Jeśli chcesz zwięzły odniesienie SQL podczas implementowania tych wzorców, ten skrót klawiszowy PostgreSQL oraz ten skrót klawiszowy SQL z najprzydatniejszymi komendami SQL są praktycznymi towarzyszami.

Typowym wzorcem produkcyjnym jest kolumna tsvector generowana i przechowywana wraz z indeksem GIN. Dokumentacja PostgreSQL jest bezkompromisowa: praktyczne wyszukiwanie tekstowe zazwyczaj wymaga indeksu i wyraźnie pokazuje kolumnę generowaną przechowywaną, która zasila indeks GIN. Ten wzorzec unika ponownego obliczania to_tsvector podczas weryfikacji i utrzymuje powierzchnię zapytań czystą.

alter table posts
  add column search_vector tsvector
  generated always as (
    setweight(to_tsvector('english', coalesce(title, '')), 'A') ||
    setweight(to_tsvector('english', coalesce(summary, '')), 'B') ||
    setweight(to_tsvector('english', coalesce(body, '')), 'D')
  ) stored;

create index posts_search_idx
  on posts using gin (search_vector);

select id,
       title,
       ts_rank_cd(
         search_vector,
         websearch_to_tsquery('english', '"query planner" -mysql')
       ) as rank
from posts
where search_vector @@ websearch_to_tsquery('english', '"query planner" -mysql')
order by rank desc
limit 20;

Po stronie zapytań PostgreSQL daje Ci kilka parserów, ponieważ dane wejściowe użytkownika są bardziej chaotyczne niż przyznają blogi inżynierskie. to_tsquery jest jawnym i potężnym. phraseto_tsquery zachowuje kolejność słów przy użyciu operatora <->. websearch_to_tsquery akceptuje dane wejściowe podobne do silnika wyszukiwania, rozumie zacytowane frazy, OR oraz negację - i nigdy nie zgłasza błędów składni przy surowych danych wejściowych użytkownika. PostgreSQL obsługuje również dopasowanie prefiksu, dołączając * do leksenu w to_tsquery.

Ranking to miejsce, gdzie natywny PostgreSQL pokazuje zarówno swoją siłę, jak i swój sufit. ts_rank i ts_rank_cd mogą używać częstotliwości, bliskości i wag strukturalnych, a model wagowy jest zaskakująco dobry dla wielu zadań wyszukiwania w aplikacjach. Jednocześnie własne dokumenty PostgreSQL zauważają, że ranking może być kosztowny, a wbudowane funkcje rankingowe nie używają informacji globalnych. To jest cicha, ale ważna granica natywnego pełnotekstowego wyszukiwania PostgreSQL. Może rankować, ale trafność nie jest środkiem ciężkości silnika.

Kiedy PostgreSQL wystarcza do pełnotekstowego wyszukiwania

PostgreSQL wystarcza częściej, niż dedykowani dostawcy wyszukiwania by chcieli. Jest szczególnie przekonujący, gdy wyszukiwanie pozostaje bardzo blisko wierszy transakcyjnych, joinów, uprawnieniami i świeżymi zapisami. Model MVCC w PostgreSQL zapewnia spójność transakcyjną i odczyty oparte o migawkę, więc ta sama baza danych, która przyjmuje zapis, może odpowiedzieć na wyszukiwanie bez okna odświeżania w stylu Elasticsearch. Gdy pole wyszukiwania jest naprawdę “znajdź rekordy w aplikacji, którą właśnie edytowałem”, ta właściwa jest ważniejsza niż błyszczące dema trafności.

Jest to również wystarczające, gdy filtrowanie SQL stanowi połowę funkcji. Filtry statusu, izolacja najemców, stany publikacji, znaczniki czasu i joine relacyjne często mają znaczenie tak samo jak trafność słów kluczowych w systemach linii biznesowej. W takich przypadkach pełnotekstowe wyszukiwanie PostgreSQL zachowuje się jak kolejny zaindeksowany predykat w planie zapytania relacyjnego, a nie jak oddzielna platforma, która musi być karmiona i utrzymywana w gotowości. To jest nudna architektura, a nudne jest często odpowiednim rodzajem szybkości.

Jak działa Elasticsearch jako silnik wyszukiwania

Elasticsearch prezentuje się bardzo inaczej. Własne dokumenty definiują go jako rozproszony silnik wyszukiwania i analityczny, skalowalny magazyn danych oraz bazę wektorową zbudowaną na Apache Lucene, zoptymalizowaną pod kątem szybkości i trafności w skali produkcyjnej i działającą w niemal czasie rzeczywistym. Elasticsearch dzieli każdy indeks na shardy, replikuje te shardy i rozdziela je między węzły, aby zwiększyć pojemność indeksowania i zapytań. Dlatego Elasticsearch rzadko jest “po prostu indeksem”. To jest architektura klastra.

Pod spodem analizatory wykonują większość ciężkiej pracy. Analizator Elasticsearch to kompozycja filtrów znaków, tokenizatorów i filtrów tokenów. Istnieją wbudowane analizatory, analizatory językowe i niestandardowe analizatory, a obsługa synonimów jest pierwszoplanową częścią analizy. To oznacza, że zachowanie wyszukiwania nie zależy tylko od zapytania. Zależy też od tego, jak dokumenty i zapytania są normalizowane, zanim nawet rozpocznie się scoring.

Dla praktycznego odniesienia API podczas implementowania tych wzorców, ten skrót klawiszowy Elasticsearch zbiera niezbędne komendy i skróty operacyjne.

PUT posts
{
  "mappings": {
    "properties": {
      "title":   { "type": "text" },
      "summary": { "type": "text" },
      "body":    { "type": "text" },
      "tags":    { "type": "keyword" }
    }
  }
}

GET posts/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "multi_match": {
            "query": "query planner",
            "fields": ["title^3", "summary^2", "body"],
            "type": "best_fields"
          }
        }
      ],
      "must_not": [
        { "match": { "body": "mysql" } }
      ]
    }
  },
  "aggs": {
    "by_tag": {
      "terms": {
        "field": "tags"
      }
    }
  },
  "highlight": {
    "fields": {
      "body": {}
    }
  }
}

Query DSL to miejsce, gdzie Elasticsearch zaczyna sprawiać wrażenie natywnego wyszukiwania, a nie bazodanowego. bool łączy klauzule z must, should, filter i must_not. multi_match może wyszukiwać przez wiele pól z wzmocnieniami pól i różnymi trybami wykonania, takimi jak best_fields, most_fields, cross_fields, phrase i bool_prefix. Agregacje, wyróżnienia i filtry mogą wszystkie istnieć obok głównego zapytania w tym samym żądaniu. BM25 jest domyślnym modelem podobieństwa.

Model świeżości jest również jawny. Elasticsearch jest w niemal czasie rzeczywistym, a nie natychmiastowo spójny w wyszukiwaniu. Ostatnie operacje stają się widoczne dla wyszukiwania, gdy odświeżenie otwiera nowy segment, a domyślnie to odświeżenie dzieje się co sekundę na indeksach, które były niedawno wyszukiwane. Dokumenty Elastic ostrzegają również, że odświeżenia są wymagające zasobowo i zalecają czekanie na okresowe odświeżenia lub używanie refresh=wait_for, gdy proces wymaga widoczności wyszukiwania po zapisie. To jest bardzo różny kontrakt niż w przypadku PostgreSQL.

Dlaczego Elasticsearch zazwyczaj lepiej rankuje złożone wyszukiwanie

To jest najgłębszy techniczny powód, dla którego wiele zespołów ostatecznie przechodzi od pełnotekstowego wyszukiwania PostgreSQL do Elasticsearch. Wbudowane funkcje rankingowe w PostgreSQL nie używają informacji globalnych, podczas gdy Elasticsearch domyślnie używa BM25 i udostępnia ustawienia podobieństwa specyficzne dla pola, analizatory, formy zapytań wielopoleowych oraz DSL wyszukiwania zaprojektowany wokół dostrojenia trafności. Gdy wyszukiwanie przestaje chodzić o “czy się dopasowało” i zaczyna chodzić o “dlaczego te dziesięć wyników wygrało”, Elasticsearch zazwyczaj ma więcej miejsca wyrazistego.

Elasticsearch ma również jasne upodnienie do zdewaloryzowanych dokumentów. Jego dokumentacja dotycząca pól join wyraźnie ostrzega przed modelowaniem wielu poziomów relacji w celu replikowania schematu relacyjnego i zaleca zdewaloryzowanie dla lepszego wyszukiwania. Ta decyzja projektowa wyjaśnia wiele sił i frustracji Elasticsearch. Nie próbuje być PostgreSQL z szybszym LIKE. Próbuje być silnikiem wyszukiwania, który może szybko oceniać i pobierać duże kolekcje dokumentów.

Pełnotekstowe wyszukiwanie PostgreSQL vs Elasticsearch w realnych funkcjach

Tolerancja błędów literowych to miejsce, gdzie dwa systemy ostro się rozchodzą. Elasticsearch dostarcza zapytania fuzzy oparte na odległości edycji Levenshteina i oferuje również dedykowane typy pól sugerowania i wpisywania. Natywne pełnotekstowe wyszukiwanie PostgreSQL samo w sobie nie jest tolerancyjne na błędy literowe. Zwykłą odpowiedzią PostgreSQL jest pg_trgm, który dodaje operatory podobieństwa i obsługę indeksów dla podobieństwa trigramów, LIKE i ILIKE. To działa dobrze, ale jest strategią kompozycji, a nie jednym zintegrowanym zestawem funkcji silnika wyszukiwania.

Wyróżnianie (highlighting) istnieje w obu stosach, ale szczegóły implementacji opowiadają historię. PostgreSQL używa ts_headline, który może zwracać przydatne fragmenty, jednak dokumenty zauważają, że używa on oryginalnego dokumentu, może być wolny i nie jest gwarantowany bezpieczny do bezpośredniej wstawki do stron internetowych. Wyróżnianie w Elasticsearch może używać przesunięć postów lub wektorów terminów, co jest szczególnie cenne na dużych polach, ponieważ unika ponownej analizy pełnego tekstu dla każdego żądania wyróżnienia. Krótko mówiąc, PostgreSQL może wyróżniać, podczas gdy Elasticsearch jest zbudowany, aby wyróżniać w skali.

Facety i analityka wyszukiwania to kolejna linia pęknięcia. Elasticsearch traktuje agregacje jako pierwszoplanową część modelu wyszukiwania, z agregacjami metrycznymi, wiaderkowymi i rurociągowymi dostępnymi bezpośrednio w odpowiedzi wyszukiwania. PostgreSQL oczywiście może agregować, ponieważ to SQL, ale gdy policzone wiadra, histogramy i składalne analityki wyszukiwania stają się częścią samego produktu wyszukiwania, Elasticsearch czuje się znacznie bardziej natywny. Różnica nie leży w możliwościach w zasadzie. Leży w tym, ile ergonomii zapytań i polityki wydajności silnik dedykuje dla tego obciążenia.

Autouzupełnianie podąża tym samym wzorcem. PostgreSQL może robić dopasowanie prefiksu w to_tsquery, co jest przydatne i często wystarczające dla narzędzi wewnętrznych. Elasticsearch idzie dalej z polami search_as_you_type, które automatycznie budują wiele zanalizowanych podpoli dla uzupełnienia prefiksu i inkusu, plus sugerowniki zakończeń zaprojektowane specjalnie do szybkich sugestii. Ta luka jest niewielka na panelu administracyjnym, ale ogromna na powierzchni odkrywania dla użytkownika.

Koszt operacyjny ma większe znaczenie niż zrzuty ekranu benchmarków

Pocieszające pytanie o silnik wyszukiwania brzmi: “Czy Elasticsearch jest szybszy niż PostgreSQL dla wyszukiwania?” Szczera odpowiedź brzmi: “dla jakiego kształtu wyszukiwania?” Elasticsearch jest inżynieryjnie zbudowany wokół shardów, replik, masowego indeksowania, polityki odświeżania i zarządzania cyklem życia. Własne dokumenty produkcyjne Elastic zagłębiają się w strategię shardów, rozmiar żądań masowych, przepustowość indeksowania, interwały odświeżania i ILM. PostgreSQL unika drugiego klastra, ale utrzymanie GIN nie jest darmowe. Dokumenty PostgreSQL ostrzegają, że wstawki GIN mogą być wolne, że czyszczenie list oczekujących może powodować fluktuacje czasu odpowiedzi i że strategia autovacuum ma znaczenie, jeśli indeks jest mocno aktualizowany.

To sprawia, że historia wydajności jest bardziej odcieniowa niż przyznaje większość postów porównawczych. Elasticsearch zazwyczaj ma więcej zapasu dla dużego wyszukiwania leksykalnego top-N, facetów, autouzupełniania i rozproszonego wolumenu odczytów, ponieważ jego architektura jest dedykowana do tych zadań. PostgreSQL często wydaje się szybszy dla zapytań relacyjnych aplikacji ze ściśle wymaganą świeżością, ponieważ nie ma drugiej bazy danych, nie ma granicy odświeżania i nie ma ścieżki synchronizacji do debugowania. Zwycięzcą jest zazwyczaj kształt obciążenia, a nie zrzut ekranu benchmarka. To jest częściowo wniosek, ale wynika bezpośrednio z transakcyjnego modelu MVCC PostgreSQL i bliskiego czasu rzeczywistego, opartego o shardy projektu Elasticsearch.

Czy dane transakcyjne i indeksy wyszukiwania powinny istnieć w tym samym systemie? Gdy trafność wyszukiwania jest umiarkowana, ale świeżość, uprawnienia i prawda transakcyjna są krytyczne, projekt tego samego systemu ma oczywiste zalety. Gdy jakość wyszukiwania, facety, polityka synonimów, tolerancja błędów literowych i poziome skalowanie wyszukiwania stają się pierwszoplanowymi problemami produktu, drugi system zaczyna wyglądać na uzasadniony. Własne wskazówki Elasticsearch dotyczące rozmiaru shardów mówią, że nie ma strategii uniwersalnej i zalecają benchmarkowanie danych produkcyjnych na sprzęcie produkcyjnym. To zdanie idealnie oddaje kompromis. Elasticsearch kupuje zapas, prosząc o obsługę bardziej specyficznej dla wyszukiwania architektury.

Praktyczny wyrok

Pełnotekstowe wyszukiwanie PostgreSQL wygrywa pierwsze 80 procent zaskakująco często. Obsługuje tokenizację, słowa-wyrazy, stemowanie, zapytania frazowe, wagi, ranking, wyróżnianie, generowane wektory wyszukiwania, indeksy GIN oraz pomocniki podobieństwa oparte na trigramach. Połączony z semantyka transakcyjną PostgreSQL, daje wielu aplikacjom stos wyszukiwania, który jest prosty, aktualny i blisko danych. Dla biur back-office SaaS, narzędzi wewnętrznych, umiarkowanych stron treści i wyszukiwania natywnego dla aplikacji, ta kombinacja jest trudna do odrzucenia.

Elasticsearch staje się przekonujący, gdy wyszukiwanie nie jest tylko filtrem, ale powierzchnią produktu. BM25 domyślnie, niestandardowe analizatory, filtry synonimów, zapytania fuzzy, ranking wielopoleowy, agregacje, dedykowane opcje autouzupełniania, strategie wyróżniania dużych pól i skalowanie oparte o rozproszone shardy nie są funkcjami pobocznymi. To jest powód istnienia silnika. Dlatego porównania Elasticsearch skupiające się tylko na surowej opóźnieniu zazwyczaj tracą sedno. Większa różnica polega na tym, ile logiki produktu wyszukiwania silnik jest gotowy przejąć.

Najczystszy model mentalny jest taki. Pełnotekstowe wyszukiwanie PostgreSQL jest doskonałe, gdy wyszukiwanie należy do bazy danych. Elasticsearch jest doskonały, gdy baza danych musi zasilać platformę wyszukiwania. Większość zespołów nadmiernie skupia się na szybkości i niedostatecznie na trybach awarii. Prawdziwy kompromis polega na tym, gdzie dostrojenie trafności, świeżość danych i złożoność operacyjna są dozwolone do rezydowania.