A digitális világban az információ mennyisége exponenciálisan növekszik. Legyen szó weboldalak tartalmáról, dokumentumokról, termékleírásokról vagy blogbejegyzésekről, az adatok közötti hatékony keresés elengedhetetlen a felhasználói élmény és az üzleti hatékonyság szempontjából. A hagyományos, egyszerű adatbázis-keresési módszerek, mint például a LIKE
operátor használata, gyakran lassúak, pontatlanok és nem képesek kezelni a természetes nyelv összetettségét. Itt jön képbe a teljes szöveges keresés, amely lehetővé teszi, hogy a felhasználók releváns eredményeket kapjanak, még akkor is, ha a pontos kifejezéseket nem ismerik.
Szerencsére nem kell bonyolult, külső rendszerekhez nyúlnunk azonnal. A PostgreSQL, a világ egyik legfejlettebb nyílt forráskódú relációs adatbázis-kezelő rendszere, beépített és rendkívül robusztus teljes szöveges keresési (Full-Text Search, FTS) képességekkel rendelkezik. Ezek a képességek lehetővé teszik számunkra, hogy hatékonyan, gyorsan és intelligensen keressünk nagy mennyiségű szöveges adatok között, mindezt az adatbázisunkon belül tartva. Ez a cikk egy átfogó útmutatót nyújt ahhoz, hogyan implementálhatjuk és optimalizálhatjuk a teljes szöveges keresést PostgreSQL adatbázissal.
Miért Válasszuk a PostgreSQL-t a Teljes Szöveges Kereséshez?
Mielőtt belemerülnénk a technikai részletekbe, érdemes megvizsgálni, miért érdemes a PostgreSQL FTS megoldását előnyben részesíteni:
- Integrált Megoldás: Nem kell külön szervert vagy szolgáltatást üzemeltetnünk a keresési funkciókhoz. Minden az adatbázison belül marad, egyszerűsítve az architektúrát.
- Rugalmasság és Erő: Támogatja a különböző nyelveket (stemmelés, stop szavak), a találatok rangsorolását, a kiemelést és a komplex boolean operátorokat.
- Teljesítmény: Megfelelő indexeléssel (különösen a GIN indexekkel) rendkívül gyorsan képes hatalmas adathalmazokon keresni.
- Költséghatékony: Nyílt forráskódú, így nincsenek licencköltségei, és a meglévő PostgreSQL infrastruktúrára épülhet.
A PostgreSQL Teljes Szöveges Keresés Alapjai
A PostgreSQL FTS két fő adattípusra épül: a tsvector
-ra és a tsquery
-re. Ezek a típusok teszik lehetővé a szöveges adatok hatékony tárolását és keresését.
tsvector: A Kereshető Dokumentum
A tsvector
adattípus egy optimalizált reprezentációja egy szöveges dokumentumnak. Nem az eredeti szöveget tárolja, hanem annak tokenizált, normalizált és stop szavaktól megtisztított változatát. Ezt a folyamatot hívjuk parzsolásnak vagy feldolgozásnak. Lássuk a részleteket:
- Tokenizálás (Tokenization): A szöveget kisebb egységekre, úgynevezett tokenekre (szavakra, számokra, szimbólumokra) bontja.
- Normalizálás (Normalization): A tokeneket egységes formára hozza. Ide tartozik a kisbetűssé alakítás és a stemmelés (szótövesítés). A stemmelés lényege, hogy a szavak különböző alakjait (pl. „fut”, „futás”, „futtatott”) egyetlen gyökérformára (pl. „fut”) redukálja, így a keresés pontosabbá és átfogóbbá válik.
- Stop Szavak (Stop Words): A nagyon gyakori, ám a keresési relevanciában kis szerepet játszó szavakat (pl. „a”, „az”, „és”, „vagy” – angolul „the”, „a”, „is”, „of”) eltávolítja. Ezzel csökkenti az index méretét és növeli a relevanciát.
- Súlyozás (Weighting): Opcionálisan súlyokat (A, B, C, D) is hozzárendelhetünk a különböző részekhez (pl. cím: A, tartalom: B), ami később a találatok rangsorolásánál kulcsfontosságú lesz.
Ezeket a lépéseket egy nyelvi konfiguráció (pl. 'english'
, 'hungarian'
) alapján végzi a rendszer, amely tartalmazza a megfelelő tokenizálókat, szótárakat és stop szólistákat.
tsquery: A Keresési Kifejezés
A tsquery
adattípus a keresési kifejezés parzsolt formáját reprezentálja. Ez a típus támogatja a boolean operátorokat (&
: ÉS, |
: VAGY, !
: NEM) és a szótövesítést, így nagyon rugalmas keresési logikát valósíthatunk meg.
Kulcsfontosságú Függvények és Operátorok
A teljes szöveges keresés implementálásának gerincét néhány alapvető függvény és egy operátor alkotja:
to_tsvector(regconfig, text)
Ez a függvény konvertálja a sima szöveges adatot egy tsvector
típusú objektummá. Az első paraméter a nyelvi konfiguráció, a második pedig a feldolozandó szöveg.
SELECT to_tsvector('english', 'The quick brown fox jumps over the lazy dog.');
-- Eredmény: 'brown':3 'dog':8 'fox':4 'jump':5 'lazi':7 'quick':2 'over':6
Látható, hogy a stop szavak (‘The’, ‘the’) eltűntek, a szavak stemmelve vannak (‘jumps’ -> ‘jump’, ‘lazy’ -> ‘lazi’), és a számok a szavak eredeti pozícióját jelölik.
to_tsquery(regconfig, text)
Ez a függvény alakítja át a felhasználó által bevitt keresési sztringet tsquery
típusú objektummá. Szintén figyelembe veszi a nyelvi konfigurációt és a stemmelést.
SELECT to_tsquery('english', 'fox & dog');
-- Eredmény: 'fox' & 'dog'
SELECT to_tsquery('english', 'quick | lazy');
-- Eredmény: 'quick' | 'lazi'
@@
Operátor: Az Egyezés Ellenőrzése
A @@
operátor az, ami összeköti a tsvector
és a tsquery
objektumokat, és ellenőrzi, hogy a dokumentum (tsvector
) tartalmazza-e a keresési kifejezést (tsquery
).
SELECT to_tsvector('english', 'The quick brown fox jumps over the lazy dog.') @@ to_tsquery('english', 'fox & dog');
-- Eredmény: t (true)
SELECT to_tsvector('english', 'A quick brown cat.') @@ to_tsquery('english', 'fox | dog');
-- Eredmény: f (false)
Teljesítmény Optimalizálás: A GIN Index
A fenti műveletek önmagukban még nem lennének hatékonyak nagy adatbázisok esetén. A kulcs a sebességhez a megfelelő indexelés. A PostgreSQL a GIN (Generalized Inverted Index) indexet kínálja a tsvector
típusú oszlopokhoz, ami drámaian gyorsítja a teljes szöveges kereséseket.
A GIN indexek invertált indexként működnek: minden egyedi lexémához eltárolják, hogy melyik dokumentumokban fordul elő. Ez rendkívül gyors keresést tesz lehetővé, mivel nem kell minden dokumentumot átvizsgálni. Két fő megközelítés létezik a GIN indexek használatára:
1. Indexelés Közvetlenül a to_tsvector
Függvényre
Ez a legegyszerűbb módja a GIN index létrehozásának. Az index a függvény eredményét tárolja el.
CREATE TABLE cikkek (
id SERIAL PRIMARY KEY,
cim TEXT NOT NULL,
tartalom TEXT NOT NULL
);
CREATE INDEX idx_cikkek_fts ON cikkek USING GIN (to_tsvector('hungarian', cim || ' ' || tartalom));
Ez működőképes, de van egy hátránya: minden egyes keresésnél újra meg kell hívni a to_tsvector
függvényt a keresési oldalról is. Ezenfelül, ha a cim
vagy tartalom
oszlop frissül, az index nem frissül automatikusan. A jobb megoldás a perzisztált tsvector
oszlop használata.
2. Perzisztált tsvector
Oszlop Triggerrel
Ez az ajánlott legjobb gyakorlat. Létrehozunk egy külön tsvector
típusú oszlopot (pl. tsv
), amelyet egy trigger segítségével automatikusan frissítünk, amikor a releváns szöveges oszlopok (pl. cim
, tartalom
) megváltoznak. Az indexet ezután erre a tsv
oszlopra építjük.
-- Hozzuk létre a táblát
CREATE TABLE termekek (
id SERIAL PRIMARY KEY,
nev TEXT NOT NULL,
leiras TEXT NOT NULL,
cikkszam TEXT UNIQUE,
tsv TSVECTOR
);
-- Hozzuk létre a függvényt, ami generálja a tsvector-t
-- Fontos: itt súlyozhatjuk is a mezőket!
CREATE FUNCTION termek_tsv_update_trigger() RETURNS trigger AS $$
BEGIN
NEW.tsv :=
setweight(to_tsvector('hungarian', NEW.nev), 'A') ||
setweight(to_tsvector('hungarian', NEW.leiras), 'B') ||
setweight(to_tsvector('simple', NEW.cikkszam), 'C'); -- 'simple' konfiguráció számokhoz, azonosítókhoz
RETURN NEW;
END
$$ LANGUAGE plpgsql;
-- Hozzuk létre a triggert
CREATE TRIGGER trg_termek_tsv_update
BEFORE INSERT OR UPDATE ON termekek
FOR EACH ROW EXECUTE FUNCTION termek_tsv_update_trigger();
-- Létrehozzuk az indexet a perzisztált tsv oszlopon
CREATE INDEX idx_termekek_tsv ON termekek USING GIN (tsv);
Ezzel a megközelítéssel a tsvector
generálás csak akkor történik meg, amikor az adatokat módosítjuk, és az index közvetlenül a már feldolgozott adatokra mutat. Ez sokkal gyorsabb lekérdezéseket eredményez.
Haladó Keresési Funkciók
Találatok Rangsorolása (Ranking)
Gyakran nem elég csak megtalálni a dokumentumokat, hanem sorrendbe is kell őket állítani a relevancia alapján. Erre szolgál a ts_rank()
és ts_rank_cd()
függvény. Ezek figyelembe veszik, hogy hányszor szerepel a keresési kifejezés, milyen közel vannak egymáshoz a szavak, és milyen súlyú mezőkben (A, B, C, D) fordultak elő.
-- Szúrjunk be néhány adatot a termekek táblába
INSERT INTO termekek (nev, leiras, cikkszam) VALUES
('Okos TV', 'Smart TV 4K felbontással és Android operációs rendszerrel.', 'TV4K-001'),
('Okosóra', 'Vízálló okosóra pulzusmérővel és lépésszámlálóval.', 'ORA-002'),
('TV asztal', 'Modern TV asztal fa berakással, 120 cm széles.', 'ASZT-003');
-- Keressünk és rangsoroljunk
SELECT nev, leiras, ts_rank(tsv, to_tsquery('hungarian', 'okos & TV')) AS rank
FROM termekek
WHERE tsv @@ to_tsquery('hungarian', 'okos & TV')
ORDER BY rank DESC;
A ts_rank_cd()
(cosine distance) egy alternatív rangsorolási algoritmust használ, ami általában jobban kezeli a nagyon rövid dokumentumokat.
Találatok Kiemelése (Highlighting)
A ts_headline()
függvény segítségével kiemelhetjük a releváns találatokat a szöveges adatokban, ami jelentősen javítja a felhasználói élményt.
SELECT
nev,
ts_headline('hungarian', leiras, to_tsquery('hungarian', 'okos | pulzus'),
'StartSel=, StopSel=, MaxFragments=1, FragmentDelimiter=..., MaxWords=15, MinWords=5') AS kiemelt_leiras
FROM termekek
WHERE tsv @@ to_tsquery('hungarian', 'okos | pulzus');
A fenti példában a StartSel
és StopSel
paraméterekkel adhatjuk meg a kiemeléshez használt HTML tageket, a MaxWords
és MinWords
pedig a kiemelt szövegrész hosszát szabályozza.
Boolean Operátorok és Prefix Keresés
A tsquery
-ben használhatók a &
(AND), |
(OR), !
(NOT) operátorok a komplex lekérdezésekhez, valamint a <->
operátor a szavak egymáshoz való közelségének meghatározására.
'kutya & macska'
: Ahol „kutya” és „macska” is előfordul.'kutya | macska'
: Ahol „kutya” vagy „macska” (vagy mindkettő) előfordul.'!macska'
: Ahol „macska” nem fordul elő.'gyors <-> barna'
: Ahol „gyors” közvetlenül „barna” előtt áll.'gyors <2> barna'
: Ahol „gyors” és „barna” között maximum 2 szó van.
A prefix keresés (előtag alapú keresés) is lehetséges, például 'telefon:*'
megtalálja a „telefon”, „telefonszám”, „telefonálás” szavakat is.
SELECT nev, leiras
FROM termekek
WHERE tsv @@ to_tsquery('hungarian', 'okos & (óra | telefon):*');
Nyelvi Konfigurációk Testreszabása
A PostgreSQL alapértelmezetten számos nyelvi konfigurációt tartalmaz (pl. english
, hungarian
). Ezek a konfigurációk határozzák meg a stop szólistákat, a stemmelő algoritmusokat és a tokenizátorokat. Néha szükség lehet azonban egyedi konfigurációra, például ha speciális szavakat nem szeretnénk stop szóként kezelni, vagy saját szinonima szótárakat szeretnénk használni.
-- Új konfiguráció létrehozása egy meglévő alapján
CREATE TEXT SEARCH CONFIGURATION my_hungarian (COPY = hungarian);
-- Egyéni stop szólista hozzáadása
ALTER TEXT SEARCH CONFIGURATION my_hungarian
ALTER MAPPING FOR asciiword, hword, hword_asciipart, hword_part
WITH simple; -- Itt beállíthatjuk, hogy ne stemmeljen, vagy saját szótárt adjunk hozzá.
-- Esetleg egyéni stop szólista beállítása
ALTER TEXT SEARCH CONFIGURATION my_hungarian
ALTER MAPPING FOR asciiword, hword, hword_asciipart, hword_part
WITH hungarian_stem, stop; -- ahol 'stop' egy egyéni stop szólista.
A simple
konfiguráció hasznos lehet, ha nem akarunk stemmelést vagy stop szó szűrést alkalmazni (pl. cikkszámok vagy azonosítók esetén).
Összefoglalás és Legjobb Gyakorlatok
A PostgreSQL beépített teljes szöveges keresési funkciói egy rendkívül erős és rugalmas eszköztárat kínálnak a szöveges adatok közötti hatékony kereséshez. Ahhoz, hogy a legtöbbet hozza ki belőle, érdemes betartani a következő legjobb gyakorlatokat:
- Használjon Perzisztált
tsvector
Oszlopot: Mindig hozzon létre egy különtsvector
típusú oszlopot, amelyet triggerekkel frissít, és erre építse a GIN indexet. Ez biztosítja a legjobb teljesítményt és konzisztenciát. - Mindig Használjon GIN Indexet: A GIN index elengedhetetlen a gyors lekérdezésekhez nagy adathalmazok esetén.
- Válassza Ki a Megfelelő Nyelvi Konfigurációt: Használja az adatok nyelvét tükröző konfigurációt a pontos stemmelés és stop szó szűrés érdekében. Szükség esetén testreszabhatja a konfigurációkat.
- Súlyozza a Különböző Mezőket: Rendeljen különböző súlyokat (A, B, C, D) a fontosabb mezőkhöz (pl. cím, kulcsszavak) a relevánsabb rangsorolás érdekében.
- Optimalizálja a Lekérdezéseket: Használja ki a boolean operátorokat, a prefix keresést és a
ts_rank()
függvényt a felhasználói igényeknek megfelelő, releváns találatok érdekében. - Tesztelje és Figyelje a Teljesítményt: Mint minden adatbázis-optimalizálásnál, itt is fontos a rendszeres tesztelés és a teljesítmény monitorozása.
Habár léteznek dedikált külső keresőmotorok (pl. Elasticsearch, Apache Solr), amelyek specifikus, extrém skálázhatósági vagy nagyon összetett facetált keresési igények esetén kiválóak, a PostgreSQL teljes szöveges keresése a legtöbb webalkalmazás és adatvezérelt projekt számára több mint elegendő, egy integrált, robusztus és költséghatékony megoldást nyújtva. Ne habozzon kihasználni a benne rejlő lehetőségeket!
Leave a Reply