Fulltextové vyhledávání Sphinx

Školení, která pořádám

Fulltextové vyhledávání v MySQL je ve většině případů dobře použitelné, ve srovnání s ostatními fulltextovými vyhledávači se ale řadí spíše k průměru. Mezi špičku ve fulltextovém vyhledávání se podle uvedeného srovnání řadí Sphinx, proto jsem se ho rozhodl vyzkoušet.

Sphinx běží jako démon, se kterým se komunikuje přes TCP socket pomocí vlastního protokolu. Sphinx nabízí API pro několik programovacích jazyků včetně PHP, takže jsme od tohoto protokolu v podstatě odstíněni. Nicméně pokud chceme Sphinx používat, tak musí běžet vyhledávací démon (když pominu hledání z příkazového řádku). Při modifikaci prohledávaných dat je navíc potřeba z příkazového řádku spustit indexovač, API tuto funkci nenabízí.

Napojení na MySQL

Sphinx lze díky SphinxSE používat také jako další úložiště přímo v MySQL (vedle MyISAM, InnoDB a dalších). Před příchodem MySQL 5.1 je k tomu ale potřeba vlastní kompilace nebo použití upravené verze MySQL. Toto úložiště navíc není s MySQL integrované zdaleka tak úzce, jak bych si představoval – čekal jsem, že se data budou ukládat do tabulky typu SPHINX stejně jako do ostatních tabulek a při použití funkce MATCH se data vyhledají stejně jako v tabulkách MyISAM. Bohužel tomu tak ale není – data je potřeba ukládat do normálních tabulek, indexování probíhá klasicky programem příkazového řádku a dotaz se určuje řetězcem porovnaným s jedním sloupcem v tabulce typu SPHINX. SphinxSE dokonce nedokáže využít ani hodnoty klauzulí WHERE, ORDER BY a LIMIT, tyto je potřeba specifikovat jinou syntaxí také přímo v dotazu:

SELECT * FROM sphinx WHERE query = 'test it;sort=attr_asc:group_id;limit=20';

Vzhledem k tomu, že SphinxSE nezpřístupňuje ani vracení úryvků nalezeného textu, tak jeho rozumné využití vidím hlavně v přímém spojení s ostatními databázovými tabulkami pomocí JOIN.

Praktická zkouška

Pro vyzkoušení fulltextového vyhledávání jsem nechal indexovat tabulku článků (sloupce nadpis a zpráva) a diskusí (sloupce autor a příspěvek). Sphinx dovoluje vytvořit více zdrojů a vyhledávat nad nimi, nad všemi objekty ale musí existovat jednoznačné číselné ID. Pokud je potřeba prohledávat více objektů různého typu, je potřeba jim jednoznačné ID nějak přiřadit – např. tak, že několik horních bitů ID vyhradíme pro uložení typu objektu. Vzhledem k tomu, že diskusní příspěvky se zobrazují společně s textem článku, tak bylo možné je připojit k článkům a vyhledávat nad souhrnným textem:

SELECT clanky.id, clanky.skupina, UNIX_TIMESTAMP(clanky.publikovano) AS publikovano, clanky.nadpis, clanky.zprava, GROUP_CONCAT(diskuse.autor, ': ', diskuse.zprava ORDER BY diskuse.poradi) AS diskuse
FROM clanky
LEFT JOIN diskuse ON clanky.id = diskuse.clanek
WHERE clanky.publikovano < NOW()
GROUP BY clanky.id

Po spuštění programu indexer a démona searchd bylo možné začít z PHP vyhledávat:

<?php
$sphinx = new SphinxClient();
$sphinx->SetFieldWeights(array("nadpis" => 10, "zprava" => 2, "diskuse" => 1));
$fulltext = $sphinx->Query($_GET["query"]);
?>

Sphinx sice podporuje kódování UTF-8, ale ve výchozím nastavení indexuje jen anglické a ruské znaky. Proto je potřeba v konfiguraci určit, aby se indexovaly i znaky Latinky.

Úryvky nalezeného textu

Další užitečnou funkcí vyhledávání Sphinx je vracení úryvků nalezeného textu. Ty bohužel nevrátí přímo metoda Query, ale je potřeba je získat další metodou BuildExcerpts. Ta si navíc nedokáže vytáhnout nalezený text a naopak je potřeba jí ho předat:

<?php
$ids = implode(", ", array_keys($fulltext["matches"]));
mysql_query("SET group_concat_max_len = 65535");

$odkazy = array();
$uryvky = array();
$result = mysql_query("
    SELECT clanky.url, clanky.nadpis, clanky.zprava, clanky.publikovano, GROUP_CONCAT(diskuse.autor, ': ', diskuse.zprava ORDER BY diskuse.poradi SEPARATOR '; ') AS diskuse
    FROM clanky
    LEFT JOIN diskuse ON clanky.id = diskuse.clanek
    WHERE clanky.id IN ($ids)
    GROUP BY clanky.id
    ORDER BY FIELD(clanky.id, $ids)
");
while ($row = mysql_fetch_assoc($result)) {
    $odkazy[] = "<a href='$row[url].php'>" . htmlspecialchars($row["nadpis"]) . "</a> ($row[publikovano])";
    $uryvky[] = strip_tags($row["zprava"]) . " - " . htmlspecialchars($row["diskuse"]);
}
mysql_free_result($result);
foreach ($sphinx->BuildExcerpts($uryvky, "clanky", $_GET["query"]) as $key => $val) {
    echo "<li>" . $odkazy[$key] . "<br />$val</li>\n";
}
?>

Ukázka výsledků vyhledávání výrazu CAPTCHA na tomto blogu

Ohýbání slov

Sphinx má vestavěnou podporu i pro ohýbání slov, která od verze 0.9.9 podporuje kromě angličtiny a ruštiny také češtinu ale opět pouze pro angličtinu a ruštinu. Jako vystudovaný lingvista jsem si pohrával s myšlenkou doplnit i podporu pro češtinu, bohužel to ale je mimo mé časové možnosti. Druhou možností je využít knihovnu Snowball, kde je jazyků více, čeština mezi nimi ale není.

Závěr

Přestože je fulltextový vyhledávač Sphinx velice schopný a věřím tomu, že i výkonný, používá se poměrně krkolomně. Proto zatím zůstanu u fulltextového vyhledávání MySQL.

Přijďte si o tomto tématu popovídat na školení Návrh a používání MySQL databáze.

Jakub Vrána, Seznámení s oblastí, 23.4.2008, diskuse: 24 (nové: 0)

Diskuse

ikona Joelp:

Moc zajímavé. Už jsem myslel, že začnu používat Sphinx. Krkolomnost je ale tak velká, že zůstanu u MySQL. Tam mi vůbec nevadí, že neumí ohýbat slova, ale že nedokáže vyhledávat v InnoDB a musím mít 2 identické tabulky (druhou pro fulltext). Taky mi vadí že hvězdičkovou sintaxi podporuje jen v BOOLEAN MODE. A na to IMO nepotřebuji FullText.

ikona Michal Aichinger:

No na to sice nepotrebujes fultext, ale asi nevyhledáváš nad moc daty, jinak bys to snad ani neříkal. Při boolean mode bez indexu se vždy prohledávají všechny data.

ikona Joelp:

No moc jich není, jen něco kolem 1000 záznamů.
Pokud je mi ale známo, tak v boolean modu se indexy ani nepoužijí přesto že se generují.

ikona Jakub Vrána OpenID:

Při BOOLEAN vyhledávání se index používá (pokud existuje).

ikona Joelp:

Jo máš pravdu, v literatuře to bylo ne dost jednoznačně napsáno. Pochopil jsem to trochu jinak, než to autor myslel.

ikona Michal Aichinger:

"ve srovnání s ostatními fulltextovými vyhledávači se ale řadí spíše k průměru" koukal jsem se na to PDF a nenašel jsem tam v čem se řadí k průměru a co ten průmer je.

Dělat jakékoli závěry jen po krátkém seznámení je obecně trošku na škodu, protože autor není obecně schopen zachytit různé podrobnosti. Jediný velký nedostatek Sphinxu vidím v tom, že doteď umí pouze vyhledávání po celých slovech. Nejde vyhledávat jen zadáním části. Napojení na MySQL pres Sphinx SE bych řekl moc lidí nevyužívá, a spíše se používá přístup k datům přes démona a API, ale to je spíš na samostatný blog post.

ikona Jakub Vrána OpenID:

K průměru se řadí z hlediska efektivity – rychlost indexování, velikost indexu, rychlost vyhledávání (běžné, boolean, fráze).

Vojtěch Semecký:

Kubo, tohle mi nějak nesedí. Sphinx jsem kdysi aktivně používal, v době kdy mi ještě SrovnaniCen.cz říkalo pane, a v těch vlastnostech, které uvádíš (rychlost indexování, velikost indexu, rychlost vyhledávání) Sphinx právě dost vyniká. Konec konců to vyplývá i z toho srovnání, na které na začátku článku odkazuješ.

ikona Jakub Vrána OpenID:

Uvedené vlastnosti se vztahují k fulltextovému vyhledávání v MySQL, Sphinx v nich naopak vyniká, je to jasně uvedené na začátku článku.

zipi:

Mysql fulltext je velmi dobrý na malé objemy a hlavně jednoduché dotazy typy jedno slovo. Pokud chcete použít lehce složitější dotaz, tak u větších objemů dat v řádu statisíců či miliónů záznamů o průměrné delce cca. 10kB je to už zcela nepoužitelné.

Sphinx je zcela jiné kafe. jeho síla je v rychlosti hledání a indexace, možnosti rozdělení na několik indexů, rozložení zátěže, možností další filtrace podle dat z indexace atd. Fakt je, že je rozhodně univerzálější v té variantě samostatného démona, něž přímé integrace v MySQL.

Hledání na základě částí slova, čili počítám "hvězdičku", plně podporuje. Jen je nutné si to nastavit, v defaultu je to vypnuté.

ikona ehmo:

neviem ako vy, ale pouzivaju ho dost masivne weby ako isohunt a mininova, ktore maju niekolko milionov hladani denne. bezne mysql by taky napor neprezilo. rovnako mysql pouzivat pri vyhladavani v 50 mil + zaznamoch uz nie je ziadna "salama"

Jakub:

Lze pomocí Sphinx nějak elegantně řešit specifika českého jazyka (množná čísla, synonyma).  Jak se tímto problémem perete?

ikona Jakub Vrána OpenID:

Množnými čísly a skloňováním se zabývá tzv. stemming. Pro češtinu není ve Sphinxu k dispozici.

Radovan Kepák:

V nové verzi 0.9.9 která je sice pouze v RC stádiu, avšak poměrně dobře použitelná již možnost pro češtinu je, není to sice lematizace, avšak stemmer, ale funguje poměrně dobře :)

Jouza:

Dokazal by to nekdo porovnat s http://www.mnogosearch.org/
Idealne nekdo kdo delal s obojim ;)

ikona Jakub Vrána OpenID:

mnoGoSearch se účastnil porovnání v odkazované prezentaci, ale nedokázal data zaindexovat ani za 80násobek času Sphinx, proto na grafech není.

Robert Vlach:

Jako jednu z alternativ mohu doporučit kombinaci Sitemaps exportu s placenou službou Google Custom Search Engine Business Edition XML, kterou používám pro vyhledávání na portále Na volné noze:
http://navolnenoze.cz/

Informace o obou technologiích:
http://www.sitemaps.org/
http://www.google.com/coop/cse/

Michal Holub:

My pouzivame sphinx na validaci adres (asi 4 miliony radku s cca 4Kb na radek). I hledani 's hvezdickou' funguje, sice je index velky, ale funguje a je to sakra rychle. Co ovsem v soucasne verzi nejde je 'or' (napr. mesto=xxx or ulice=xxx) - prevest na and to taky nejde, protoze priorita pomoci zavorek nejde. Ale i pres to vsechno dela sphinx dobry job, load testy neukazaly vaznejsi problemy. Na projektu se stale pracuje, takze snad i to 'or' prijde

Robert Vlach:

Mimořádně výkonný nástroj na validaci adres je standalone freeware prográmek do Windows s trochu ulítlým názvem Xenu's Link Sleuth:
http://home.snafu.de/tilman/xenulink.html

Letus:

Ad ta krkolomnost, neni to tak zle, behem hodiny po precteni tohoto clanku jsem mel fulltext search rozchozeny misto stavajiciho mysql vyhledavani, pracuji s ctvrtmilionem zaznamu, cas vyhledavani slova vracejiciho pres tisicovku zaznamu se propadl z 4 sekund nad mysql na 0,2 sekundy nad sphinxem, pouzivam SPH_MATCH_EXTENDED a oboustranne hvezdickove rozsireni pro slova od tri pismen. Diakritika a vyhledavani s ni funguje bez problemu (ale jedu v W-1250, nikoliv v UTF-8). Nepouzivam napojeni na mysql, mam starsi verzi. Ale opravdu me to zaujalo, jak krasne a hladce to pracuje, takze to nezavrhujte na prvni pokus, mrknete do dokumentace, jedte podle toho jeho "quistartu", a behem chvile mate bleskove rychly fulltext search.

Petr Nemeth:

Sphinx jsem iplementoval jako produktovy vyhledavac (cca 3mil produktu). Nepouzivam SE (zda se byt opravdu velice krkolomny). Doba hledani oproti klasickemu MySQL Fulltext se zkratila z cca 5s na > 0,3. Relevance pro nase potreby sice neni uplne idealni, nicmene po osetreni vstupnich dat (pouzity pribuznych slov atd.) a modifikaci vyhledavacich parametru lze ziskat velice vykony nastroj.

ikona Jakub Vrána OpenID:

Pro Sphinx je už k dispozici i PHP extenze: http://www.php.net/manual/en/book.sphinx.php

Juraj Krivda:

Skúsil som si Sphinx. Výsledky dobré, indexovanie rýchle. Trošku mi vadil spustený démon a preto som otestoval SOLR. Výsledky porovnateľné, bez démona, preto, pri jednoduchých weboch ostávam pri MySQL fulltexte, pri zložitých použijem SOLR.

Milan Lochovský:

Jinak Sphinx alespoň verze 2.0.1-beta má spolu také nástroj spelldump, který umí vytvořit databázi synonym a převod na kořen slova ze slovníků, které používá OpenOffice. Sice má taková databáze 70MB, ale funguje to výborně! :)

Vložit příspěvek

Používejte diakritiku. Vstup se chápe jako čistý text, ale URL budou převedeny na odkazy a PHP kód uzavřený do <?php ?> bude zvýrazněn. Pokud máte dotaz, který nesouvisí s článkem, zkuste raději diskusi o PHP, zde se odpovědi pravděpodobně nedočkáte.

Jméno: URL:

avatar © 2005-2016 Jakub Vrána. Publikované texty můžete přetiskovat pouze se svolením autora. Ukázky kódu smíte používat s uvedením autora a URL tohoto webu bez dalších omezení Creative Commons. Můžeme si tykat. Skripty předpokládají nastavení: magic_quotes_gpc=Off, magic_quotes_runtime=Off, error_reporting=E_ALL & ~E_NOTICE a očekávají předchozí zavolání mysql_set_charset. Skripty by měly být funkční v PHP >= 4.3 a PHP >= 5.0.