pgvector w PostgreSQL: jak skonfigurować bazę wektorową pod modele RAG i embeddingi

0
4
Rate this post

Wyobraź sobie sytuację, w której Twój system Retrieval-Augmented Generation (RAG) nagle zwalnia, a użytkownicy czekają kilkanaście sekund na odpowiedź z bazy danych, mimo że silnik LLM działa bez zarzutu. Po krótkiej analizie okazuje się, że wąskim gardłem jest wyszukiwanie podobieństwa w bazie danych, gdzie proste zapytanie wektorowe paraliżuje procesor i wysyca dyskowe I/O. Przeniesienie wektorów do zewnętrznej, dedykowanej bazy NoSQL wydaje się kuszące, jednak rodzi to ogromne problemy ze spójnością danych, synchronizacją transakcji i utrzymaniem dodatkowej infrastruktury.

Rozwiązaniem tego dylematu jest rozszerzenie pgvector w bazie PostgreSQL, które pozwala na przechowywanie embeddingów bezpośrednio obok danych relacyjnych. Dzięki temu możesz wykonywać transakcyjne zapytania ACID łączące tradycyjne filtry SQL z zaawansowanym wyszukiwaniem semantycznym.

Poniżej przeanalizujemy, jak prawidłowo skonfigurować i zoptymalizować pgvector pod kątem wydajności, precyzji wyszukiwania oraz zużycia zasobów sprzętowych.

RAG i wyszukiwanie semantyczne w relacyjnej bazie: rola rozszerzenia pgvector

Architektura Retrieval-Augmented Generation (RAG) opiera się na dostarczaniu modelom językowym precyzyjnego kontekstu pobranego z zewnętrznych źródeł wiedzy. Tradycyjne wyszukiwanie tekstowe oparte na dopasowywaniu słów kluczowych (np. BM25) często okazuje się niewystarczające, ponieważ nie rozumie intencji użytkownika ani synonimów. Wyszukiwanie semantyczne rozwiązuje ten problem poprzez reprezentowanie dokumentów i zapytań jako wielowymiarowych wektorów (embeddingów), generowanych przez modele takie jak te od OpenAI, Cohere czy lokalne transformery z rodziny BERT.

Wdrożenie takiego mechanizmu w środowisku produkcyjnym wymaga bazy danych zdolnej do szybkiego obliczania odległości w wielowymiarowych przestrzeniach geometrycznych. Postgresql baza wektorowa oparta o rozszerzenie pgvector eliminuje konieczność wdrażania osobnego silnika wektorowego (np. Pinecone, Milvus czy Qdrant). Umożliwia to zachowanie pełnej spójności danych, upraszcza architekturę i pozwala na stosowanie zaawansowanych mechanizmów bezpieczeństwa znanych z PostgreSQL, takich jak Row-Level Security (RLS).

Mechanizm działania wektorów w PostgreSQL

Rozszerzenie pgvector wprowadza do PostgreSQL nowy typ danych o nazwie vector. Reprezentuje on tablicę liczb zmiennoprzecinkowych pojedynczej precyzji (32-bitowych floatów). Maksymalna wymiarowość wektora zależy od wersji rozszerzenia, jednak w najnowszych wersjach pozwala na indeksowanie wektorów o długości do 2000 wymiarów (co w zupełności wystarcza dla najpopularniejszych modeli, takich jak text-embedding-3-large o wymiarowości 1536 lub 3072). [2]

W praktyce zapis wektora w bazie wygląda następująco:

CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE documents (
    id SERIAL PRIMARY KEY,
    content TEXT NOT NULL,
    embedding vector(1536) -- Wymiarowość zgodna z modelem OpenAI
);

Podczas wyszukiwania baza oblicza odległość między zapisanymi wektorami a wektorem zapytania użytkownika (query embedding). pgvector udostępnia w tym celu specjalne operatory matematyczne dostosowane do różnych metryk odległości, co pozwala na błyskawiczne znajdowanie najbardziej zbliżonych znaczeniowo fragmentów tekstu.

Typowe wąskie gardła przy łączeniu danych relacyjnych z embeddingami

Największym wyzwaniem w architekturze RAG jest efektywne łączenie filtrów relacyjnych (np. tenant_id = 42, is_active = true, created_at > NOW() - INTERVAL '1 month') z wyszukiwaniem wektorowym (K-Nearest Neighbors – KNN). Zjawisko to nazywa się filtrowaniem hybrydowym i w zależności od kolejności wykonywania operacji może prowadzić do drastycznego spadku wydajności:

  • Post-filtering (filtrowanie po wyszukaniu): Baza danych najpierw pobiera top-K najbardziej podobnych wektorów, a następnie odrzuca te, które nie spełniają warunków relacyjnych. Jeśli warunki są restrykcyjne, może się okazać, że po odfiltrowaniu nie pozostanie żaden dokument spełniający kryteria, co całkowicie psuje precyzję systemu RAG.
  • Pre-filtering (filtrowanie przed wyszukaniem): Baza najpierw filtruje miliony rekordów według kryteriów relacyjnych, a następnie wykonuje porównanie wektorowe na pozostałym podzbiorze. Przy braku odpowiednich indeksów złożonych, operacja ta zmusza silnik bazy do wykonania pełnego skanowania sekwencyjnego (Sequential Scan), co przy dużym ruchu generuje ogromne opóźnienia.

Optymalizacja pgvector polega na takim zaprojektowaniu indeksów, aby planista zapytań PostgreSQL mógł efektywnie łączyć indeksy relacyjne (np. B-Tree) z indeksami wektorowymi w ramach jednego planu zapytania (Bitmap Index Scan).

Wariant 1: Wyszukiwanie dokładne (Exact Nearest Neighbor / Flat Search)

Wyszukiwanie dokładne, znane również jako Flat Search lub brute-force, polega na sekwencyjnym porównaniu wektora zapytania ze wszystkimi wektorami zapisanymi w tabeli. Nie stosuje się tutaj żadnych przybliżeń ani struktur grafowych – silnik bazy danych po prostu oblicza odległość matematyczną dla każdego wiersza i sortuje wyniki.

Kiedy brak indeksu wektorowego jest właściwą decyzją projektową

Wbrew intuicji, brak dedykowanego indeksu wektorowego bardzo często okazuje się najbardziej optymalnym rozwiązaniem produkcyjnym. Skanowanie sekwencyjne jest bezkonkurencyjne w scenariuszach, w których baza danych jest niewielka (do kilkudziesięciu tysięcy wierszy) lub gdy zapytania biznesowe i tak drastycznie zawężają przeszukiwany zbiór za pomocą filtrów relacyjnych.

Jeśli Twoja aplikacja RAG działa w architekturze wielodostępnej (multi-tenant), gdzie każdy klient (tenant) ma dostęp wyłącznie do własnych dokumentów, a pojedynczy klient posiada maksymalnie 10 000 fragmentów tekstu, indeks wektorowy nie przyniesie żadnych korzyści. PostgreSQL najpierw przefiltruje tabelę za pomocą indeksu B-Tree po kolumnie tenant_id, a następnie błyskawicznie wykona pełne porównanie wektorowe na małym podzbiorze w pamięci RAM.

Zasada działania pełnego skanu sekwencyjnego wektorów

Gdy wykonujemy zapytanie bez indeksu, PostgreSQL przechodzi przez każdy blok danych na dysku i oblicza odległość wektorową. Przykładowe zapytanie realizujące takie wyszukiwanie przy użyciu odległości cosinusowej wygląda następująco:

SELECT id, content, 1 - (embedding <=> '[0.012, -0.023, ..., 0.089]') AS similarity
FROM documents
WHERE tenant_id = 105
ORDER BY embedding <=> '[0.012, -0.023, ..., 0.089]'
LIMIT 5;

W powyższym przykładzie operator <=> oznacza odległość cosinusową (Cosine Distance). Ponieważ wektor zapytania jest porównywany tylko z

Zbliżenie białych złączy i niebieskich przewodów w centrum danych
Źródło: Pexels | Autor: Brett Sayles

Ponieważ wektor zapytania jest porównywany tylko z rekordami należącymi do wskazanego tenanta, koszt obliczeń pozostaje przewidywalny. Dla niewielkiego, dobrze zawężonego zbioru takie podejście daje wynik dokładny – bez ryzyka pominięcia najlepszego dopasowania, które występuje w indeksach przybliżonych. Kluczowe jest jednak utworzenie zwykłego indeksu B-Tree dla kolumn używanych w filtrach, na przykład:

CREATE INDEX documents_tenant_id_idx
ON documents (tenant_id);

Problem pojawia się zwykle nie w chwili uruchomienia systemu, lecz kilka miesięcy później, gdy tabela rośnie, a filtr tenant_id przestaje dostatecznie ograniczać liczbę przeszukiwanych fragmentów. Zapytanie, które dla kilku tysięcy embeddingów działa niemal natychmiast, może zacząć zajmować setki milisekund po zwiększeniu liczby dokumentów lub równoległych żądań. W takiej sytuacji nie należy zgadywać: plan wykonania zweryfikuje polecenie EXPLAIN (ANALYZE, BUFFERS). Jeśli widoczny jest kosztowny Seq Scan na dużej części tabeli, Flat Search przestaje być rozsądnym wyborem.

Wyszukiwanie dokładne ma też istotną zaletę operacyjną: nie wymaga budowy ani aktualizacji indeksu wektorowego po imporcie danych. Nowe rekordy są od razu dostępne w wynikach, a usunięcia i aktualizacje nie pogarszają jakości wyszukiwania. To bezpieczny wariant dla paneli administracyjnych, małych baz wiedzy, środowisk testowych oraz danych silnie partycjonowanych logicznie, gdzie relacyjne warunki WHERE redukują zbiór do niewielkiej liczby wierszy.

Dobór między pełnym skanem, IVFFlat i HNSW powinien wynikać z rzeczywistej liczby wektorów po zastosowaniu filtrów, oczekiwanego czasu odpowiedzi oraz tolerancji na wyniki przybliżone. pgvector pozwala zachować te decyzje w obrębie PostgreSQL: od prostego, dokładnego wyszukiwania po indeksy projektowane dla dużych zbiorów embeddingów i obciążeń RAG. [1]

Wariant 2: Indeks IVFFlat (Inverted File Flat)

Indeks IVFFlat (Inverted File Flat) to klasyczny algorytm przybliżonego wyszukiwania najbliższych sąsiadów (ANN – Approximate Nearest Neighbor). Jego głównym zadaniem jest skrócenie czasu wyszukiwania poprzez ograniczenie liczby porównywanych wektorów. Zamiast skanować całą tabelę, pgvector dzieli przestrzeń wielowymiarową na mniejsze obszary (klastry) i przypisuje każdy wektor do najbliższego z nich.

Zasada działania i mechanizm klastrowania

IVFFlat działa dwuetapowo. Podczas tworzenia indeksu algorytm wykorzystuje metodę k-średnich (k-means) do wyznaczenia punktów centralnych (centroidów) dla określonej liczby klastrów. Każdy wektor w bazie zostaje przypisany do najbliższego centroidu, tworząc tzw. listę odwróconą (inverted list).

W trakcie wyszukiwania wektor zapytania nie jest porównywany ze wszystkimi rekordami. Silnik bazodanowy najpierw wyszukuje najbliższe centroidy, a następnie przeszukuje wyłącznie listy (klastry) powiązane z tymi centroidami. Drastycznie zmniejsza to liczbę operacji matematycznych, skracając czas odpowiedzi z kilkuset do kilku milisekund.

Konfiguracja indeksu IVFFlat wymaga zdefiniowania parametru lists, który określa liczbę klastrów generowanych przez algorytm. Oficjalne wytyczne twórców pgvector sugerują stosowanie następujących reguł doboru wartości tego parametru:

  • Dla tabel posiadających poniżej 1 miliona wierszy: wartość lists powinna wynosić około √N (gdzie N to liczba wierszy w tabeli).
  • Dla tabel o rozmiarze powyżej 1 miliona wierszy: zaleca się ustawienie lists na wartość rzędu N / 1000.

Przykładowe polecenie SQL tworzące indeks IVFFlat dla tabeli dokumentów z wykorzystaniem odległości cosinusowej wygląda następująco:

CREATE INDEX documents_embedding_ivfflat_idx 
ON documents 
USING ivfflat (embedding vector_cosine_ops) 
WITH (lists = 100);

Kompromis między szybkością a dokładnością (Recall)

Użycie indeksu przybliżonego wiąże się z ryzykiem pominięcia niektórych wektorów, które w rzeczywistości są najbardziej podobne do zapytania (spadek wskaźnika Recall). W pgvector kontrolę nad tym kompromisem sprawuje parametr sesyjny ivfflat.probes. Określa on, ile klastrów (list) baza danych ma przeszukać podczas jednego zapytania:

-- Zwiększenie precyzji kosztem czasu wykonania zapytania
SET ivfflat.probes = 10;

SELECT id, content 
FROM documents 
ORDER BY embedding <=> '[0.012, -0.023, ..., 0.089]' 
LIMIT 5;

Domyślna wartość parametru probes wynosi 1. Podniesienie tej wartości zwiększa prawdopodobieństwo znalezienia optymalnych dopasowań (wyższy Recall), ale proporcjonalnie wydłuża czas wykonywania zapytania SQL, ponieważ system musi sprawdzić dane z większej liczby klastrów.

Wady i ograniczenia operacyjne IVFFlat

Największą barierą wdrożeniową IVFFlat jest wymóg posiadania reprezentatywnego zbioru danych w tabeli przed zbudowaniem indeksu. Jeśli uruchomisz polecenie CREATE INDEX na pustej lub niemal pustej tabeli, algorytm k-means wyznaczy błędne centroidy, co uniemożliwi efektywne wyszukiwanie po zaimportowaniu docelowej liczby dokumentów. W przypadku znaczącego przyrostu danych indeks IVFFlat wymaga pełnej przebudowy (REINDEX), aby zaktualizować pozycje centroidów i przywrócić optymalną precyzję wyszukiwania.

Wariant 3: Indeks HNSW (Hierarchical Navigable Small World)

Indeks HNSW reprezentuje nowoczesne podejście do wyszukiwania przybliżonego w bazach wektorowych i od wersji 0.5.0 jest w pełni wspierany przez pgvector. Opiera się on na strukturze wielowarstwowego grafu, co pozwala na osiągnięcie niemal idealnej precyzji wyszukiwania (Recall > 95-99%) przy zachowaniu ekstremalnie niskich opóźnień (sub-milisekundowych), nawet na zbiorach rzędu milionów rekordów.

Struktura wielowarstwowego grafu

Algorytm HNSW inspiruje się koncepcją sieci typu „Small World” oraz listami z pomijaniem (skip lists). Tworzy on hierarchię warstwowych grafów, gdzie najwyższe warstwy posiadają rzadkie połączenia między węzłami i służą do szybkiego poruszania się po dużych obszarach przestrzeni wektorowej. Im niższa warstwa, tym gęstsze staje się upakowanie krawędzi.

Plątanina kabli w centrum danych z monitorem na pierwszym planie
Źródło: Pexels | Autor: panumas nikhomkhai

Wyszukiwanie rozpoczyna się od najwyższej warstwy, gdzie algorytm wykonuje duże „skoki” między odległymi węzłami. Po zlokalizowaniu najbliższego sąsiada w danej warstwie, proces schodzi o poziom niżej i powtarza operację na gęstszym grafie. Na najniższym poziomie (warstwa 0) następuje precyzyjne dopasowanie końcowych wyników.

W przeciwieństwie do IVFFlat, indeks HNSW może być budowany na pustej tabeli. Nowo dodawane wiersze są na bieżąco integrowane z grafem bez konieczności ponownego przeliczania całej struktury danych.

Podczas tworzenia indeksu HNSW kluczowe są dwa parametry optymalizacyjne:

  • m: Maksymalna liczba połączeń (krawędzi) dla każdego węzła w grafie. Wyższe wartości poprawiają dokładność wyszukiwania dla wektorów o wysokiej wymiarowości, ale wydłużają czas budowy indeksu oraz zwiększają rozmiar pliku indeksu na dysku.
  • ef_construction: Rozmiar dynamicznej listy kandydatów sprawdzanej podczas budowania grafu. Zwiększenie tego parametru drastycznie wydłuża czas indeksowania, ale bezpośrednio przekłada się na lepszą jakość połączeń w grafie i wyższy Recall.

Przykład wdrożenia indeksu HNSW w PostgreSQL:

CREATE INDEX documents_embedding_hnsw_idx 
ON documents 
USING hnsw (embedding vector_cosine_ops) 
WITH (m = 16, ef_construction = 64);

Zarządzanie czasem zapytania w HNSW

Podobnie jak w IVFFlat, jakość wyszukiwania w fazie odpytywania bazy reguluje dedykowany parametr sesyjny – hnsw.ef_search. Określa on liczbę kandydatów przechowywanych na liście dynamicznej podczas przeszukiwania grafu:

-- Zwiększenie głębokości przeszukiwania grafu
SET hnsw.ef_search = 40;

SELECT id, content 
FROM documents 
ORDER BY embedding <=> '[0.012, -0.023, ..., 0.089]' 
LIMIT 5;

Zwiększenie ef_search pozwala na dokładniejsze zweryfikowanie lokalnego otoczenia wektora w grafie, co minimalizuje ryzyko ugrzęźnięcia w lokalnym minimum i poprawia Recall przy minimalnym narzucie czasowym.

Koszt zasobowy stosowania HNSW

Znakomita wydajność HNSW ma swoją cenę – jest nią bardzo wysokie zużycie pamięci operacyjnej oraz długi czas budowy indeksu. Graf HNSW musi być w całości przechowywany w pamięci RAM (buforach PostgreSQL), aby zapytania mogły być realizowane natychmiastowo. Próba korzystania z HNSW przy niewystarczającym limicie pamięci RAM w systemie prowadzi do ciągłego odczytu stron indeksu z dysku, co degraduje wydajność poniżej poziomu standardowego skanowania sekwencyjnego.

Zestawienie wariantów: Flat vs IVFFlat vs HNSW

Wybór odpowiedniej strategii indeksowania w pgvector zależy od unikalnych wymagań systemu RAG. Każdy z wariantów reprezentuje odmienną filozofię dystrybucji zasobów sprzętowych i kompromisów jakościowych.

Tabela porównawcza mechanizmów wyszukiwania w pgvector

Dokładność (Recall)

Zużycie pamięci RAM

Szybkość wyszukiwania

Czas budowy i aktualizacji

Wpływ nowych danych

Kryterium Flat (Brak indeksu) IVFFlat HNSW
100% (Zawsze dokładny wynik) Średnia do wysokiej (Zależna od probes) Bardzo wysoka (Zależna od ef_search)
Minimalne (Tylko bieżący bufor) Niskie (Przechowuje centroidy i listy) Bardzo wysokie (Graf musi mieścić się w RAM)
Niska dla dużych zbiorów (O(N)) Wysoka (Średnio milisekundy) Ekstremalnie wysoka (Sub-milisekundowa)
Natychmiastowy (Brak indeksu) Średni (Wymaga reindeksacji przy przyroście danych) Bardzo długi (Wysokie obciążenie CPU podczas budowy)
Brak wpływu na jakość wyszukiwania Wymaga częstego REINDEX dla nowych rekordów W pełni przyrostowy (Dynamiczne wsparcie dla INSERT)

Kryteria wyboru rozwiązania

Przed podjęciem ostatecznej decyzji architektonicznej należy przeanalizować następujące aspekty techniczne projektu:

  1. Rozmiar bazy danych i wolumen danych jednego klienta: Jeśli system RAG izoluje dane klientów, a pojedynczy tenant posiada niewielką liczbę rekordów, optymalnym wyborem pozostaje wyszukiwanie dokładne (Flat Search) wsparte relacyjnym indeksem B-Tree.
  2. Ograniczenia budżetowe i infrastrukturalne: Jeśli baza PostgreSQL działa na instancji o ograniczonej pamięci RAM (np. współdzielone środowiska chmurowe), indeks IVFFlat pozwoli zachować relatywnie szybkie czasy odpowiedzi bez ryzyka przekroczenia limitów pamięciowych maszyny.
  3. Wymagania dotyczące opóźnień (Latency SLA): Systemy konwersacyjne czasu rzeczywistego (chatboty oparte o RAG) wymagają generowania odpowiedzi w jak najkrótszym czasie. W takich scenariuszach HNSW jest bezkonkurencyjny.
  4. Dynamika zmian w bazie wiedzy: Jeśli baza wiedzy RAG jest stale aktualizowana (np. co kilka minut importowane są nowe artykuły prasowe lub dokumenty wewnętrzne), HNSW pozwoli na ich natychmiastowe wyszukiwanie. IVFFlat przy częstych zapisach szybko straci swoją precyzję i będzie wymagać uciążliwych, cyklicznych przebudów indeksu.

Rekomendacja wdrożeniowa

Dla większości produkcyjnych wdrożeń systemów RAG, gdzie priorytetem jest precyzja odpowiedzi dostarczanych do LLM oraz stabilny czas odpowiedzi użytkownika, rekomendowanym wyborem jest indeks HNSW. Zapewnia on optymalny profil działania w architekturach dynamicznych, dobrze reaguje na operacje zapisu i oferuje najwyższy wskaźnik Recall przy minimalnych opóźnieniach.

Niebieskie kable podłączone do białych złączy w centrum danych
Źródło: Pexels | Autor: Brett Sayles

Jeżeli jednak Twoja infrastruktura dysponuje ograniczoną ilością pamięci RAM, a baza danych rośnie liniowo bez gwałtownych skoków wolumenu – zastosuj IVFFlat. Pozwoli to zachować stabilność serwera bazodanowego przy akceptowalnym kompromisie czasowym.

Rozpocznij wdrożenie od najprostszego schematu (Flat Search + indeksy B-Tree na kolumnach filtrujących). Dopiero w momencie, gdy profilowanie zapytań za pomocą EXPLAIN ANALYZE wykaże rzeczywiste wąskie gardło na etapie obliczeń wektorowych, przejdź do implementacji indeksów przybliżonych, dopasowując parametry m oraz lists do posiadanych zasobów sprzętowych.

Wpływ wyboru wariantu na konfigurację serwera PostgreSQL

Wybór między Flat, IVFFlat a HNSW determinuje nie tylko strukturę zapytań SQL, ale również wymagania wobec konfiguracji samego serwera bazy danych. Domyślne ustawienia PostgreSQL są zoptymalizowane pod kątem tradycyjnych danych relacyjnych i mogą drastycznie ograniczać wydajność operacji wektorowych. Aby wybrany wariant indeksowania działał efektywnie w systemie RAG, należy dostosować kluczowe parametry w pliku postgresql.conf.

1. Konfiguracja pamięci dla budowy indeksów (maintenance_work_mem)

Zarówno IVFFlat, jak i HNSW wymagają znacznych zasobów pamięci podczas działania polecenia CREATE INDEX. W przypadku HNSW, algorytm buduje strukturę grafu bezpośrednio w pamięci RAM.

  • Dla wariantu Flat: Parametr ten ma minimalne znaczenie, ponieważ nie budujemy indeksu wektorowego.
  • Dla wariantu IVFFlat: Wymaga wystarczającej pamięci do przeprowadzenia algorytmu k-means na próbce danych.
  • Dla wariantu HNSW: Wymaga bardzo dużych przydziałów. Jeśli maintenance_work_mem będzie zbyt mały, proces budowy indeksu zostanie spowolniony przez ciągłe operacje zapisu i odczytu z dysku (swapping), a w skrajnych przypadkach może zakończyć się niepowodzeniem.

W przypadku pracy z dużymi modelami embeddingów (np. 1536 wymiarów), zaleca się tymczasowe zwiększenie tego parametru dla sesji tworzącej indeks:

Kable Ethernet podłączone do portów serwera
Źródło: Pexels | Autor: Brett Sayles
-- Zwiększenie pamięci na potrzeby budowy indeksu HNSW do 2 GB
SET maintenance_work_mem = '2GB';
CREATE INDEX documents_embedding_hnsw_idx ON documents USING hnsw (embedding vector_cosine_ops);

2. Zarządzanie pamięcią podręczną (shared_buffers)

Aby zapytania semantyczne w systemach RAG osiągały czasy odpowiedzi rzędu milisekund, indeksy wektorowe muszą stale znajdować się w pamięci RAM. Dotyczy to w szczególności indeksu HNSW.

  • Dla wariantu HNSW zaleca się, aby parametr shared_buffers był na tyle wysoki, by pomieścić cały wygenerowany graf oraz najczęściej odpytywane dane relacyjne. W środowiskach produkcyjnych standardem jest alokowanie na ten cel od 25% do 40% całkowitej pamięci RAM serwera.
  • Dla wariantu IVFFlat zapotrzebowanie jest mniejsze, jednak szybki dostęp do centroidów i list odwróconych wciąż zależy od ich obecności w buforach bazy.

3. Zrównoleglenie zapytań przy braku indeksu (Flat Search)

Jeśli na podstawie kryteriów wyboru zdecydowano się na wariant Flat Search (np. ze względu na małe, dynamiczne zbiory danych izolowane per klient za pomocą tenant_id), kluczem do zachowania wydajności jest efektywne wykorzystanie procesora (CPU).

Obliczanie odległości cosinusowej dla tysięcy wektorów bez indeksu jest operacją silnie obciążającą procesor. PostgreSQL potrafi zrównoleglić skanowanie sekwencyjne (Parallel Seq Scan). Należy upewnić się, że parametry konfiguracyjne pozwalają na użycie wielu rdzeni procesora do jednego zapytania:

-- Przykładowe korekty w postgresql.conf dla optymalizacji Flat Search
max_parallel_workers_per_gather = 4
max_parallel_workers = 8
dynamic_shared_memory_type = posix

Dzięki temu silnik bazy danych rozdzieli matematyczne obliczenia porównywania wektorów na kilka wątków, co znacznie skróci czas oczekiwania aplikacji RAG na wyniki wyszukiwania kontekstowego.

4. Kontrola kosztów planisty (Query Planner Cost Constants)

Domyślny planer zapytań PostgreSQL zakłada, że odczyt z dysku jest znacznie droższy niż obliczenia procesora. W przypadku wyszukiwania semantycznego, gdzie operacje matematyczne na wektorach o wysokiej wymiarowości są bardzo kosztowne dla CPU, planer może błędnie wybierać pełne skanowanie tabeli zamiast użycia indeksu IVFFlat lub HNSW.

Aby skłonić planer do częstszego i bardziej adekwatnego korzystania z utworzonych indeksów wektorowych, warto dostosować koszt procesora dla operacji na operatorach (CPU operator cost):

-- Zwiększenie kosztu obliczeniowego CPU, aby zachęcić do używania indeksów
SET cpu_operator_cost = 0.005;

Modyfikacja ta sprawia, że baza danych zaczyna lepiej rozumieć rzeczywisty koszt operacji matematycznych na wektorach i częściej podejmuje optymalne decyzje o wyborze ścieżki indeksowanej.

Najważniejsze punkty

  • pgvector pozwala przechowywać embeddingi bezpośrednio w PostgreSQL i łączyć wyszukiwanie semantyczne z filtrami SQL.
  • Wyszukiwanie dokładne bez indeksu wektorowego może być właściwym wyborem dla małych zbiorów lub silnie zawężonych zapytań.
  • Indeks B-Tree dla kolumn używanych w filtrach, takich jak tenant_id, pomaga ograniczyć zbiór wektorów przed obliczeniem podobieństwa.
  • Dobór między pełnym skanem a indeksami przybliżonymi, takimi jak IVFFlat, powinien wynikać z rzeczywistej liczby wektorów po filtrowaniu i wymaganego czasu odpowiedzi.
  • Decyzje dotyczące wydajności warto weryfikować za pomocą EXPLAIN (ANALYZE, BUFFERS), zamiast opierać je wyłącznie na założeniach.

Źródła

Poprzedni artykułMonitoring domowej sieci: jak sprawdzić, co łączy się z routerem
Kamil Wieczorek
Kamil Wieczorek pisze o chmurze, DevOps i niezawodności usług. Interesuje go to, co działa w produkcji: obserwowalność, automatyzacja wdrożeń, IaC i kontrola kosztów. Każdy poradnik buduje na przykładach z konfiguracji i pipeline’ów, a wnioski opiera na testach oraz analizie logów i metryk. Dba o precyzję pojęć i pokazuje kompromisy między szybkością a bezpieczeństwem. Czytelnikom podpowiada, jak planować architekturę, unikać typowych pułapek i utrzymywać systemy w dobrej kondycji.