Programmazione 5 commenti
Sphinx, “the Russian black magic” / 1
diSphinx è un motore di ricerca full text sviluppato da Andrew Aksyonoff con alcune caratteristiche che lo rendono particolarmente interessante per una vasta gamma di applicazioni web-based:
- indipendenza dal tipo di base dati utilizzata
- estrema velocità di indicizzazione e ricerca
- possibilità di distribuire gli indici su sistemi diversi per scalabilità e ridondanza
- funzionalità avanzate come il grouping per attributo
- API per i principali linguaggi
- protocollo di interrogazione molto semplice che permette di sviluppare client per altri linguaggi in poco tempo
Per queste caratteristiche, in particolare la velocità di indicizzazione e ricerca su basi dati di parecchie decine di Gb (BoardReader ad esempio indicizza più di un miliardo di post per circa 1.5Tb di dati), Sphinx è stato definito Russian black magic. La definizione è meno azzardata di quanto sembri: come tutte le arti magiche che si rispettano Sphinx è abbastanza esoterico, e imparare ad utilizzarlo con successo richiede tempo e costanza, per recuperare frammenti di informazione indispensabili dal forum degli utilizzatori e sperimentarne le funzionalità.
Questa serie di articoli su Sphinx vuole quindi essere una introduzione a questo eccezionale motore di ricerca, illustrandone l'installazione e l'utilizzo attraverso una serie di esempi pratici, e fornendo i dati e il codice per eseguirli. La serie è composta da tre articoli:
- installazione e utilizzo di base (questo articolo)
- funzionalità avanzate di indicizzazione e ricerca
- benchmark di utilizzo, in cui Sphinx verrà comparato con Solr e i motori di full text search di MySQL e PostgreSQL
Prerequisiti
Per gli esempi di questa serie di articoli utilizzeremo
- MySQL, che è la base dati più diffusa sui servizi di shared hosting e probabilmente la più utilizzata per lo sviluppo di applicazioni web, specialmente in PHP
- Python 2.5 (o 2.4) o, in alternativa, PHP 4.x o 5.x
- la versione di sviluppo di Sphinx (0.9.8-x), che offre alcune funzionalità non presenti nella versione stabile (0.9.7) e dovrebbe venire comunque rilasciata in versione definitiva fra non molto
- se volete provare le funzionalità di stemming per la lingua italiana (non è indispensabile, soprattutto per questo primo articolo), una versione recente di libstemmer del progetto Snowball
Se siete su Linux/Unix vi servirà anche un compilatore C++, e gli
header e le librerie necessarie per compilare Sphinx. Gli esempi che
troverete sotto presuppongono che sphinx sia stato compilato con il
supporto a MySQL (--with-mysql), e sia installato in
/opt/sphinx/. Se usate PostgreSQL e/o avete installato Sphinx in
una cartella diversa, aggiustate le istruzioni SQL e i percorsi degli
esempi di conseguenza.
Se invece siete su Windows potete utilizzare gli eseguibili rilasciati da Andrew e quelli ufficiali di MySQL, e dovrete ovviamente sostituire i percorsi Unix con quelli della vostra installazione.
I dati utilizzati per gli esempi di questo articolo sono un dump
semplificato degli articoli pubblicati su Qix.it,
dato che sono una buona base per illustrare le funzionalità di Sphinx,
non hanno problemi di copyright (non per me, almeno), e sono già
disponibili in formato SQL. Potete scaricarli qui e, dopo aver creato un
nuovo database sphinx su MySQL, caricarli con i comandi qui sotto.
$ mysql --user=root -p
mysql> CREATE DATABASE sphinx CHARACTER SET utf8 COLLATE utf8_general_ci;
mysql> GRANT ALL on sphinx.* TO sphinx@localhost IDENTIFIED BY 'sphinx';
mysql> \q
$ cat dataset.sql |mysql --user=sphinx -p sphinx
Se siete su Windows, sostituite il comando type a cat.
Architettura e configurazione
L'architettura di Sphinx è piuttosto semplice:
-
un eseguibile per l'indicizzazione,
indexer, che viene di solito lanciato periodicamente viacron -
un demone TCP,
searchd, che accetta le richieste, estrae i risultati e li restituisce ai client - un eseguibile,
search, con cui effettuare ricerche da linea di comando -
delle librerie client per vari linguaggi che permettono di effettuare
ricerche utilizzando
searchd
Gli eseguibili e il demone leggono le proprie impostazioni da
sphinx.conf, un file di configurazione in formato pseudo-shell che
definisce le caratteristiche delle fonti dati, degli indici
disponibili, e i parametri di esecuzione di indexer e
searchd.
Iniziamo quindi la definizione del file di configurazione che utilizzeremo per gli esempi di questo articolo, specificando la prima fonte di dati.
Definizione di una fonte di dati
Le fonti di dati (source) descrivono dove e come recuperare i dati da
indicizzare, e come indicizzarli (per la ricerca, come attributi per il
raggruppamento, ecc.). Le tipologie di source accettate da Sphinx
sono tre: MySQL, PostgreSQL, e una fonte generica definita XML pipe.
Le fonti SQL sono le più sofisticate, dato che offrono diversi
parametri per alleggerire il carico sul DB o definire fonti
incrementali, che esamineremo in dettaglio nel secondo articolo di
questa serie. La fonte XML è più semplice, e serve come “fonte
universale” in quelle situazioni dove non sia disponibile una base dati
compatibile con Sphinx. Per questo articolo utilizzeremo, come detto in
precedenza, una base dati MySQL e la relativa tipologia di source
in Sphinx.
Per definire una fonte dobbiamo avere chiaro il modello della base dati, e ovviamente sapere cosa ci interessa indicizzare. La base dati di esempio mette a disposizione tre tabelle (ne arriveranno altre per la seconda parte dell'articolo):
+-------------------+
| Tables_in_sphinx |
+-------------------+
| sphinx_entry |
| sphinx_tag |
| sphinx_entry_tags |
+-------------------+
Le tabelle contengono — ovviamente — i post di Qix, le singole tag utilizzate, e le relazioni tra post e tag. Esaminiamo brevemente la struttura delle singole tabelle, nell'ordine in cui sono riportate sopra:
+-----------------+--------------+
| Field | Type |
+-----------------+--------------+
| id | int(11) |
| slug | varchar(255) |
| author_id | int(11) |
| updated | datetime |
| title | varchar(255) |
| comment_count | int(11) |
| trackback_count | int(11) |
| body | longtext |
+-----------------+--------------+
+-------+-------------+
| Field | Type |
+-------+-------------+
| id | int(11) |
| slug | varchar(50) |
| name | varchar(50) |
+-------+-------------+
+----------+---------+
| Field | Type |
+----------+---------+
| id | int(11) |
| entry_id | int(11) |
| tag_id | int(11) |
+----------+---------+
Come accennato, i dati indicizzati da sphinx possono essere divisi in due macrocategorie: quelli utilizzati per la ricerca full text vera e propria; e i dati che servono per limitare l'ambito della ricerca e ordinare o raggruppare i risultati, che Sphinx definisce “attributi”. Mentre per i primi non ci sono grandi limitazioni — devono ovviamente essere campi testo — per gli attributi Sphinx limita la scelta a dati di tipo numerico (interi o float nell'ultima versione) e date. La limitazione è dovuta all'architettura degli indici e ad esigenze di prestazioni, e gli eventuali problemi di compatibilità con strutture dati esistenti (ad esempio tabelle senza una ID numerica) possono essere facilmente aggirati, come vedremo negli articoli successivi.
Tornando al nostro esempio, descriviamo una prima semplice sorgente di dati che ci permette di:
- effettuare ricerche full text sul contenuto e il titolo dei post
- limitare, ordinare, o raggruppare i risultati per data di pubblicazione, autore, numero di commenti, tag
Creiamo quindi un nuovo file sphinx.conf nella cartella etc sotto
il percorso dove abbiamo installato Sphinx (ad es.
/opt/sphinx/etc/sphinx.conf), e utilizziamo come riferimento i
commenti alla configurazione di esempio rilasciata insieme ai sorgenti
(/opt/sphinx/etc/sphinx.conf.dist). Iniziamo con la definizione della
connessione a MySQL, utilizzando se possibile il socket invece della
connessione TCP per il collegamento, e specificando il charset (nel nostro
caso UTF-8):
source qix_1 {
type = mysql
sql_host = localhost
sql_port = 3306
sql_sock = /var/run/mysqld/mysqld.sock # opzionale
sql_db = sphinx
sql_user = sphinx
sql_pass = sphinx
sql_query_pre = SET NAMES utf8
Come potete notare, il comando sql_query_pre con cui impostiamo il
charset accetta una o — ripetendolo — più query generiche. Potremmo
quindi usarlo anche per altri scopi: ad esempio per impostare un lock
che impedisca a due processi di indicizzazione di girare
contemporaneamente, per innescare una transazione, o per manipolare un
contatore che ci permetta di implementare l'indicizzazione
incrementale. Vedremo alcune di queste tecniche nel secondo articolo di
questa serie.
Definito il collegamento a MySQL, indichiamo a Sphinx come recuperare i dati da indicizzare. La query deve rispettare alcune semplici condizioni: il primo campo deve essere l'id numerico che identifica univocamente ogni record; gli attributi devono essere interi a 32 bit non negativi (o float nell'ultima versione); i campi non dichiarati come id o attributi (vedremo sotto come) vengono indicizzati come full text. La nostra query quindi sarà:
sql_query = \
SELECT \
id, author_id, unix_timestamp(updated) as updated, \
comment_count + trackback_count as num_comments, \
title, body \
FROM sphinx_entry
Definita la query principale, indichiamo a sphinx quali sono gli attributi che ci serviranno per filtrare o raggruppare i risultati delle ricerche. Gli attributi possono essere definiti come
sql_attr_uint, per cui Sphinx usa interi a 32 bitsql_attr_bool, attributi boolean che richiedono meno spaziosql_attr_timestamp, date in formato Unix-
sql_attr_str2ordinal, attributi in formato stringa che vengono ordinati da Sphinx in memoria durante l'indicizzazione e convertiti in interi che rispecchiano l'ordinamento, servono per il sorting dei risultati sql_attr_float, attributi in formato float
Definiamo quindi gli attributi estratti dalla query SQL:
sql_attr_uint = author_id
sql_attr_timestamp = updated
sql_attr_uint = num_comments
L'ultima operazione che ci rimane per completare la definizione della
sorgente di dati è recuperare le tag, e definirle come attributo
multivalore (MVA, Multi-Valued Attribute), una funzionalità
introdotta con la versione 0.9.8 di Sphinx. Gli attributi MVA, come
quelli semplici visti sopra, possono essere di tipo uint o
timestamp e possono venire estratti in due modi:
-
field, uno dei campi della query principale viene utilizzato per recuperare i valori di un singolo record; questo metodo non è ancora supportato (cf.sphinx-0.9.8-svn-rxxxx/src/indexer.cppintorno alla riga 340), ma dovrebbe tornare utile per quei tipi di attributi che è più conveniente recuperare con istruzioni tipoGROUP_CONCAT, o che sono salvati in campi singoli -
query, una query SQL che restituisce coppie di valori costituite dalla id del record principale (nel nostro caso i singoli post) e dalla id dell'attributo (nel nostro caso le tag distinte dei post)
La definizione del nostro attributo MVA, le tag, è piuttosto semplice:
sql_attr_multi = uint tag from query; \
SELECT entry_id, tag_id FROM sphinx_entry_tags
Ci sono altre istruzioni che possono essere definite per una sorgente
di dati, ne vedremo alcune nella seconda parte di questa serie quando
ci occuperemo di indicizzazione avanzata. Per adesso manca una sola
istruzione, che serve all'eseguibile da linea di comando search per
recuperare i dati delle singole entry contenute nei risultati della
ricerca:
sql_query_info = SELECT * FROM sphinx_entry WHERE id=$id
}
Definita la fonte di dati, passiamo a vedere come configurare l'indice che la utilizza.
Definizione di un indice
La definizione dell'indice serve a Sphinx per sapere quali parametri fisici utilizzare (file e percorsi, utilizzo della memoria per gli attributi, ecc.) e come manipolare i dati forniti dalla sorgente. Anche per l'indice, in questo articolo esamineremo solo le istruzioni fondamentali, riservando al secondo articolo della serie le istruzioni destinate ad utilizzi avanzati.
Cominciamo con il definire il nome dell'indice, la fonte di dati utilizzata (una fonte di dati può ovviamente essere utilizzata da più indici), il percorso per i file fisici generati, e il metodo di storage degli attributi (nel nostro caso in un file separato, data la dimensione limitata del dataset utilizzato)
index qix_1 {
source = qix_1
path = /opt/sphinx/var/data/qix1
docinfo = extern
Le altre istruzioni che ci interessano a questo livello riguardano il trattamento dei dati ricevuti:
-
morphologydefinisce lo stemmer da utilizzare, o in alternativa se farne a meno; gli stemmer inclusi con Sphinx sonostem_enper la lingua inglese,stem_ruper il russo,stem_enruche combina inglese e russo,soundexemetaphoneche utilizzano algoritmi fonetici; se Sphinx è stato compilato con il supporto perlibstemmer, sono disponibili inoltre tutti gli stemmer supportati da Snowball -
stopwordsdefinisce il percorso della lista opzionale di parole da escludere dall'indicizzazione (ad esempio congiunzioni, articoli, ecc.), che possono essere ottenute facendo generare aindexeruna lista della frequenza dei termini di una o più fonti di dati (indexer --buildstopso--buildfreqs) -
synonimsdefinisce il percorso della lista opzionale di sinonimi -
min_word_lendefinisce la lunghezza minima delle parole da indicizzare, con il valore1vengono indicizzate tutte le parole (ed evitati problemi con ricerche per frasi, tipo “e lui disse”) -
charset_typedefinisce se l'encoding dei dati deve essere single byte (ad esempioISO-8859-1) oUTF-8 -
charset_tabledefinisce una tabella di conversione dei caratteri, tipicamente per mappare caratteri accentati sui loro equivalenti traslitterati (ad es. per mappare 'à' su 'a') -
enable_star, infine, attiva il funzionamento della wildcard '*' nelle ricerche
Altre istruzioni utili per un utilizzo non avanzato, che però non utilizzeremo per gli esempi di questo articolo, sono:
html_striprimuove il codice HTML dai campi testo indicizzatihtml_index_attrsdefinisce attributi HTML da indicizzare, ad esempioimg=alt,title; a=title;
Completiamo quindi la definizione del nostro indice ed esaminiamo brevemente l'ereditarietà di indici e fonti di dati, per poi passare alle impostazioni del demone, all'indicizzazione, e a qualche esempio di utilizzo delle API:
morphology = none
stopwords =
synonyms =
min_word_len = 1
charset_type = utf-8
charset_table = 0..9, A..Z->a..z, _, a..z, \
U+C0, U+C8, U+E0, U+E8, U+E9, U+EC, U+F2, U+F9, \
U+C0->U+61, U+C8->e, U+E0->a, U+E8->e, U+E9->e, \
U+EC->i, U+F2->o, U+F9->u, U+2019->', U+2018->',
enable_star = 1
}
La configurazione di Sphinx permette di utilizzare l'ereditarietà sia per le fonti di dati che per gli indici: in pratica, dove due indici (o due fonti) condividono un buon numero di impostazioni, è sufficiente definirne uno solo per esteso, e implementare l'altro come estensione del primo. L'ereditarietà è utilissima soprattutto per configurazioni avanzate, e la utilizzeremo a fondo nel prossimo articolo. Per ora, limitiamoci ad utilizzarla per definire un secondo indice uguale a quello che abbiamo già definito, che utilizza però un algoritmo di stemming:
index qix_1_stemmed : qix_1 {
morphology = stem_en
}
Tutte le impostazioni che non definiamo specificamente per il nuovo
indice (fonte di dati, encoding, ecc.) saranno identiche all'indice
indicato come progenitore, nel nostro caso qix_1.
Definizione delle impostazioni di runtime
L'ultima parte del file di configurazione definisce le impostazioni
utilizzate dai due processi principali di Sphinx: indexer per
l'indicizzazione, e searchd per la ricerca. Definiamo quindi i
parametri necessari per poter utilizzare gli esempi:
indexer {
mem_limit = 32M
}
searchd {
address = 127.0.0.1
port = 3312
log = /opt/sphinx/var/log/searchd.log
query_log = /opt/sphinx/var/log/query.log
max_children = 15
pid_file = /opt/sphinx/var/log/searchd.pid
max_matches = 1000
}
I parametri che abbiamo impostato non sono tutti quelli disponibili, e sono uguali ai valori di default: li abbiamo riportati per darvi un'idea delle principali opzioni di runtime che potete controllare.
Indicizzazione e lancio del demone
Salviamo il file di configurazione (qui la versione completa) nella
posizione predefinita, nel nostro caso /opt/sphinx/etc/sphinx.conf
ed eseguiamo una prima indicizzazione dei dati.
Assicuratevi prima che
- l'utente MySQL che avete indicato in
sphinx.confesista e abbia permessi di lettura sulla base dati -
l'utente con cui fate l'indicizzazione (meglio ovviamente se non è
roote se è lo stesso con cui farete girare il demone) abbia i permessi di scrittura sui percorsi degli indici
A questo punto, possiamo lanciare il comando /opt/sphinx/bin/indexer --all
che utilizzerà la configurazione di default per rigenerare
tutti gli indici che abbiamo definito. La mia istanza di Sphinx gira
come utente sphinx, modificate il comando sotto in maniera
appropriata per le vostre impostazioni:
# su - sphinx -c "/opt/sphinx/bin/indexer --all"
Sphinx 0.9.8-dev (r1038)
Copyright (c) 2001-2007, Andrew Aksyonoff
using config file '/opt/sphinx/etc/sphinx.conf'...
indexing index 'qix_1'...
collected 743 docs, 1.1 MB
collected 906 attr values
sorted 0.0 Mvalues, 100.0% done
sorted 0.2 Mhits, 100.0% done
total 743 docs, 1099344 bytes
total 0.474 sec, 2317931.66 bytes/sec, 1566.59 docs/sec
indexing index 'qix_1_stemmed'...
collected 743 docs, 1.1 MB
collected 906 attr values
sorted 0.0 Mvalues, 100.0% done
sorted 0.2 Mhits, 100.0% done
total 743 docs, 1099344 bytes
total 0.602 sec, 1824949.25 bytes/sec, 1233.41 docs/sec
Omettendo l'opzione --all e specificando i nomi di uno o più
indici, indexer rigenererà solo quelli. Le opzioni più importanti
oltre a quelle già viste (buildstops, buildfreqs e all)
sono:
--config, che imposta il file di configurazione da utilizzare-
--rotate, utilizzata quando il demonesearchdè in esecuzione per rigenerare gli indici senza toccare quelli in uso, e riavviaresearchdin automatico con i nuovi indici una volta finito il processo di indicizzazione -
--merge, per unire due indici distinti, utile quando si utilizzano i delta index, come vedremo nel prossimo articolo
Una volta che gli indici sono stati generati con successo, possiamo
lanciare il demone searchd e iniziare a usare Sphinx per le
ricerche. Anche per searchd valgono le osservazioni fatte sopra
sull'utenza con cui lanciare il processo e i permessi di accesso a
indici e log. Se utilizzate un sistema Unix che supporta init SysV, in
sphinx-0.9.8-svn-rxxxx/contrib/scripts/searchd è disponibile un
file per avviare searchd compatibile con chkconfig di
RedHat/CentOS. Su Debian/Ubuntu potete utilizzare il mio, aggiustando il
percorso del file pid e l'utente nella configurazione. Lanciamo quindi
searchd da linea di comando:
# su - sphinx -c "/opt/sphinx/bin/searchd"
Sphinx 0.9.8-dev (r1038)
Copyright (c) 2001-2007, Andrew Aksyonoff
using config file '/opt/sphinx/etc/sphinx.conf'...
Ricerche base
Ora che i dati sono indicizzati e searchd è attivo, possiamo
eseguire alcune ricerche di base, utilizzando l'eseguibile search
incluso nella distribuzione. Per brevità, le ricerche che vedete qui
sotto utilizzano l'opzione -q di search per non ricevere il
testo completo dei risultati, potete ovviamente ometterla nei vostri
esperimenti per vedere il testo ed i dati completi dei risultati.
Cominciamo con una semplice ricerca per keyword:
$ /opt/sphinx/bin/search -i qix_1 -e "sphinx" -q
Sphinx 0.9.8-dev (r1038)
Copyright (c) 2001-2007, Andrew Aksyonoff
using config file '/opt/sphinx/etc/sphinx.conf'...
index 'qix_1': query 'sphinx ': returned 2 matches of 2 total in 0.008 sec
displaying matches:
1. document=682, weight=1703, author_id=1, updated=Fri Oct 5 14:21:38 2007, num_comments=4, tag=(21,172)
2. document=743, weight=1703, author_id=1, updated=Fri Oct 5 14:21:37 2007, num_comments=7, tag=(140,190)
words:
1. 'sphinx': 2 documents, 2 hits
Modalità di ricerca
La ricerca che abbiamo appena eseguito utilizza la modalità di default, che cerca una corrispondenza esatta con tutte i termini di ricerca inseriti. Sphinx supporta cinque modalità di ricerca differenti:
SPH_MATCH_ALL, la modalità di default che abbiamo appena utilizzatoSPH_MATCH_ANY(-ausandosearch), che cerca una corrispondenza con almeno uno dei termini di ricercaSPH_MATCH_PHRASE(-p), cerca una corrispondenza esatta con i termini, interpretandoli come una unica frase nell'ordine in cui sono stati inseritiSPH_MATCH_BOOLEAN(-b), permette l'utilizzo di operatori booleani (& | + -) e parentesi per il raggruppamento, considerando un&implicito tra i termini di ricercaSPH_MATCH_EXTENDED(-e), modalità estesa che utilizza il linguaggio di interrogazione interno di Sphinx
La modalità estesa è quella più interessante sia per l'utente comune che per eseguire ricerche avanzate: all'utente comune offre il riconoscimento degli operatori booleani e del raggruppamento con virgolette, permettendo quindi di utilizzare una sintassi di ricerca simile a quella base di Google e in genere dei motori di ricerca su web; all'utente avanzato, offre la possibilità di restringere l'applicazione di alcuni termini a singoli campi, e un operatore di prossimità.
Vediamo un esempio semplice di funzionamento della ricerca estesa.
Eseguiamo prima una ricerca con più keyword in modalità SPH_MATCH_ALL:
$ /opt/sphinx/bin/search -i qix_1 '"classifica dei blog"' -q
Sphinx 0.9.8-dev (r1038)
Copyright (c) 2001-2007, Andrew Aksyonoff
using config file '/opt/sphinx/etc/sphinx.conf'...
index 'qix_1': query '"classifica dei blog" ': returned 21 matches of 21 total in 0.001 sec
displaying matches:
1. document=682, weight=4, author_id=1, updated=Fri Oct 5 14:21:38 2007, num_comments=4, tag=(21,172)
2. document=452, weight=3, author_id=1, updated=Fri Oct 5 14:21:39 2007, num_comments=16, tag=(21,100,208,214)
[...]
20. document=681, weight=1, author_id=1, updated=Fri Oct 5 14:21:38 2007, num_comments=6, tag=(16,100,215)
words:
1. 'classifica': 26 documents, 47 hits
2. 'dei': 342 documents, 628 hits
3. 'blog': 297 documents, 773 hits
Sphinx estrae i documenti che contengono i singoli termini e li incrocia, restituendo tutti i 21 documenti che contengono — in qualsiasi posizione e rapporto tra loro — i tre termini che abbiamo indicato. Eseguiamo la stessa ricerca in modalità estesa:
$ /opt/sphinx/bin/search -i qix_1 -e '"classifica dei blog"' -q
Sphinx 0.9.8-dev (r1038)
Copyright (c) 2001-2007, Andrew Aksyonoff
using config file '/opt/sphinx/etc/sphinx.conf'...
index 'qix_1': query '"classifica dei blog" ': returned 6 matches of 6 total in 0.006 sec
displaying matches:
1. document=665, weight=3548, author_id=1, updated=Fri Oct 5 14:21:38 2007, num_comments=20, tag=(21,23,100,214)
2. document=672, weight=3548, author_id=1, updated=Fri Oct 5 14:21:38 2007, num_comments=6, tag=(23,100,141,214)
[...]
6. document=659, weight=3545, author_id=1, updated=Fri Oct 5 14:21:38 2007, num_comments=2, tag=(199,234)
words:
1. 'classifica': 26 documents, 47 hits
2. 'dei': 342 documents, 628 hits
3. 'blog': 297 documents, 773 hits
Come potete notare, i risultati sono cambiati radicalmente, dato che Sphinx cerca adesso i documenti in cui è presente la frase intera che abbiamo specificato. Possiamo anche approssimare il riconoscimento della frase utilizzando l'operatore di prossimità, dicendo a Sphinx che i singoli termini che compongono la frase non devono essere consecutivi, ma possono anche essere separati da un massimo di 10 parole:
$ /opt/sphinx/bin/search -i qix_1 -e '"classifica dei blog"~10' -q
Sphinx 0.9.8-dev (r1038)
Copyright (c) 2001-2007, Andrew Aksyonoff
using config file '/opt/sphinx/etc/sphinx.conf'...
index 'qix_1': query '"classifica dei blog"~10 ': returned 7 matches of 7 total in 0.001 sec
displaying matches:
1. document=665, weight=3548, author_id=1, updated=Fri Oct 5 14:21:38 2007, num_comments=20, tag=(21,23,100,214)
2. document=672, weight=3548, author_id=1, updated=Fri Oct 5 14:21:38 2007, num_comments=6, tag=(23,100,141,214)
[...]
7. document=578, weight=577, author_id=1, updated=Fri Oct 5 14:21:38 2007, num_comments=11, tag=(21,214)
words:
1. 'classifica': 26 documents, 47 hits
2. 'dei': 342 documents, 628 hits
3. 'blog': 297 documents, 773 hits
Il risultato non cambia molto, dato che il set di dati su cui stiamo eseguendo le ricerche è abbastanza limitato, ma l'utilizzo dell'operatore di prossimità ha comunque allargato il campo di ricerca e restituito un documento in più.
Un altro operatore disponibile nella ricerca avanzata è quello che
permette di restringere l'applicazione di uno o più termini di ricerca
ad un singolo campo. Vediamo una semplice ricerca prima e dopo
l'applicazione di uno dei termini al campo title:
$ /opt/sphinx/bin/search -i qix_1 -e 'classifica blog' -q
Sphinx 0.9.8-dev (r1038)
Copyright (c) 2001-2007, Andrew Aksyonoff
using config file '/opt/sphinx/etc/sphinx.conf'...
index 'qix_1': query 'classifica blog ': returned 24 matches of 24 total in 0.000 sec
displaying matches:
1. document=587, weight=2618, author_id=1, updated=Fri Oct 5 14:21:38 2007, num_comments=38, tag=(21,100,214)
2. document=452, weight=2604, author_id=1, updated=Fri Oct 5 14:21:39 2007, num_comments=16, tag=(21,100,208,214)
[...]
20. document=678, weight=1566, author_id=1, updated=Fri Oct 5 14:21:38 2007, num_comments=12, tag=(101,102,115,172)
words:
1. 'classifica': 26 documents, 47 hits
2. 'blog': 297 documents, 773 hits
restringendo l'applicazione del termine "blog" al solo titolo dei post, i risultati cambiano drasticamente:
$ /opt/sphinx/bin/search -i qix_1 -e 'classifica @title blog' -q
Sphinx 0.9.8-dev (r1038)
Copyright (c) 2001-2007, Andrew Aksyonoff
using config file '/opt/sphinx/etc/sphinx.conf'...
index 'qix_1': query 'classifica @title blog ': returned 6 matches of 6 total in 0.000 sec
displaying matches:
1. document=587, weight=2618, author_id=1, updated=Fri Oct 5 14:21:38 2007, num_comments=38, tag=(21,100,214)
2. document=452, weight=2604, author_id=1, updated=Fri Oct 5 14:21:39 2007, num_comments=16, tag=(21,100,208,214)
[...]
6. document=570, weight=2568, author_id=1, updated=Fri Oct 5 14:21:38 2007, num_comments=1, tag=(138,180)
words:
1. 'classifica': 26 documents, 47 hits
2. 'blog': 297 documents, 773 hits
Infine, gli operatori booleani funzionano allo stesso modo che per le
ricerche su Google e altri motori di ricerca. Proviamo ad esempio una
delle ricerche qui sopra, escludendo un termine con l'operatore -:
$ /opt/sphinx/bin/search -i qix_1 -e 'classifica blog -technorati' -q
Sphinx 0.9.8-dev (r1038)
Copyright (c) 2001-2007, Andrew Aksyonoff
using config file '/opt/sphinx/etc/sphinx.conf'...
index 'qix_1': query 'classifica blog -technorati ': returned 20 matches of 20 total in 0.001 sec
displaying matches:
1. document=581, weight=2570, author_id=1, updated=Fri Oct 5 14:21:38 2007, num_comments=15, tag=(21,100,214)
2. document=682, weight=2570, author_id=1, updated=Fri Oct 5 14:21:38 2007, num_comments=4, tag=(21,172)
[...]
20. document=813, weight=1564, author_id=1, updated=Thu Nov 15 16:45:57 2007, num_comments=3, tag=(20)
words:
1. 'classifica': 26 documents, 47 hits
2. 'blog': 297 documents, 773 hits
Escludendo il termine "technorati" il numero di risultati restituiti è passato da 24 a 20.
Ordinamento
Come le modalità di ricerca, anche l'ordinamento dei risultati supporta diverse modalità:
SPH_SORT_RELEVANCE(default), ordina per rilevanza dei risultatiSPH_SORT_ATTR_DESC, ordina secondo il valore di un attributo (che deve essere specificato), con ordinamento decrescenteSPH_SORT_ATTR_ASC, ordina secondo il valore di un attributo (che deve essere specificato), con ordinamento crescenteSPH_SORT_TIME_SEGMENTS, ordina temporalmente per segmenti (ora/giorno/settimana/mese) su un attributo (che deve essere specificato), e all'interno dei segmenti temporali per rilevanza decrescenteSPH_SORT_EXTENDED, ordina utilizzando un'espressione con operatori simili a SQL
L'ordinamento per segmenti temporali è particolarmente comodo per la ricerca di notizie, o dati dove la freschezza di pubblicazione ha un'importanza pari o superiore alla rilevanza dei termini di ricerca, ed è spiegata con qualche dettaglio in più sul manuale di Sphinx.
L'ordinamento esteso utilizza due operatori: @id, l'id del
risultato; @rank (o @weight @relevance), la rilevanza. Gli
operatori possono essere combinati tra di loro e con gli attributi,
specificando per ognuno se l'ordinamento deve essere crescente o
decrescente. Un esempio di ordinamento esteso dal manuale di Sphinx è
@relevance DESC, price ASC, @id DESC.
Proviamo a ripetere una delle query precedenti, ordinando questa volta i risultati per time segments:
$ /opt/sphinx/bin/search -i qix_1 -e 'classifica blog -technorati' -q --sort=ts
Sphinx 0.9.8-dev (r1038)
Copyright (c) 2001-2007, Andrew Aksyonoff
using config file '/opt/sphinx/etc/sphinx.conf'...
index 'qix_1': query 'classifica blog -technorati ': returned 20 matches of 20 total in 0.001 sec
displaying matches:
1. document=813, weight=1564, author_id=1, updated=Thu Nov 15 16:45:57 2007, num_comments=3, tag=(20)
2. document=682, weight=2570, author_id=1, updated=Wed Nov 22 14:28:58 2006, num_comments=4, tag=(21,172)
3. document=581, weight=2570, author_id=1, updated=Wed Jul 19 18:39:40 2006, num_comments=15, tag=(21,100,214)
4. document=643, weight=2569, author_id=1, updated=Mon Oct 9 00:33:12 2006, num_comments=22, tag=(21,100)
5. document=570, weight=2568, author_id=1, updated=Sat Jul 1 12:10:56 2006, num_comments=1, tag=(138,180)
6. document=691, weight=1607, author_id=1, updated=Sun Dec 3 00:01:42 2006, num_comments=25, tag=(16,21,37,100,214,215)
[...]
20. document=595, weight=1564, author_id=1, updated=Thu Aug 24 22:32:02 2006, num_comments=1, tag=(7,86)
words:
1. 'classifica': 26 documents, 47 hits
2. 'blog': 297 documents, 773 hits
Come potete notare, il primo risultato restituito ha una rilevanza più bassa rispetto ai seguenti, ma è molto più recente e quindi viene portato in prima posizione. Altri ordinamenti, come quello esteso o quello per attributi, possono essere utilizzati solo dalle API e li vedremo quindi in seguito.
Filtri
Gli attributi non servono solo per ordinare i risultati, ma anche per restringerli a determinati valori di uno o più attributi utilizzando dei filtri. I filtri possono specificare un attributo e un valore che deve avere in tutti i risultati restituiti, o un attributo e un range di valori. Come per l'ordinamento, i tipi di filtri avanzati possono essere utilizzati solo dalle API, e li mostreremo nei prossimi articoli. Vediamo quindi un semplice tipo di filtro, che limita i risultati restituiti ad un singolo autore. I risultati senza filtro sono:
$ /opt/sphinx/bin/search -i qix_1 -e 'ebay ' -q
Sphinx 0.9.8-dev (r1038)
Copyright (c) 2001-2007, Andrew Aksyonoff
using config file '/opt/sphinx/etc/sphinx.conf'...
index 'qix_1': query 'ebay ': returned 37 matches of 37 total in 0.001 sec
displaying matches:
1. document=332, weight=2693, author_id=3, updated=Thu Mar 10 09:37:00 2005, num_comments=0, tag=()
2. document=121, weight=2671, author_id=3, updated=Fri Aug 13 17:05:00 2004, num_comments=0, tag=()
[...]
18. document=739, weight=1639, author_id=1, updated=Mon Apr 2 23:07:02 2007, num_comments=14, tag=(126,234)
19. document=794, weight=1639, author_id=1, updated=Sun Oct 14 14:25:13 2007, num_comments=4, tag=(13,42,83)
20. document=1, weight=1601, author_id=2, updated=Wed Aug 4 17:40:00 2004, num_comments=0, tag=()
words:
1. 'ebay': 37 documents, 81 hits
e applicando un filtro che limita l'id dell'autore a 1:
$ /opt/sphinx/bin/search -i qix_1 -e 'ebay ' -q -f author_id 1
Sphinx 0.9.8-dev (r1038)
Copyright (c) 2001-2007, Andrew Aksyonoff
using config file '/opt/sphinx/etc/sphinx.conf'...
index 'qix_1': query 'ebay ': returned 15 matches of 15 total in 0.000 sec
displaying matches:
1. document=118, weight=1685, author_id=1, updated=Mon Jul 19 17:35:02 2004, num_comments=0, tag=()
2. document=300, weight=1671, author_id=1, updated=Mon Jan 10 21:49:51 2005, num_comments=3, tag=()
[...]
15. document=804, weight=1601, author_id=1, updated=Sat Oct 27 23:17:29 2007, num_comments=10, tag=(120,124)
words:
1. 'ebay': 37 documents, 81 hits
Raggruppamenti
Oltre che per ordinare e filtrare i risultati, gli attributi possono
essere utilizzati anche per creare raggruppamenti, che isolano i
singoli valori di un attributo presenti nei risultati e restituiscono
il documento con la migliore corrispondenza per ogni valore, insieme a
due nuovi attributi: @groupby, che indica il valore dell'attributo
utilizzato per il raggruppamento; @count, che indica il numero di
documenti tra i risultati che hanno l'attributo di raggruppamento
corrispondente al valore di @groupby. I raggruppamenti funzionano
ancora solo su attributi singoli, se (come me) siete interessati ad
utilizzarli su attributi MVA, magari per replicare le funzionalità di
faceting di
Apache Solr, aggiungetevi a questo
bug sul tracker di
Sphinx.
Vediamo un semplice raggruppamento per id dell'autore:
$ /opt/sphinx/bin/search -i qix_1 -e 'ebay ' -q -g author_id
Sphinx 0.9.8-dev (r1038)
Copyright (c) 2001-2007, Andrew Aksyonoff
using config file '/opt/sphinx/etc/sphinx.conf'...
index 'qix_1': query 'ebay ': returned 4 matches of 4 total in 0.000 sec
displaying matches:
1. document=71, weight=1659, author_id=6, updated=Thu Aug 26 16:23:00 2004, num_comments=0, tag=(), @groupby=6, @count=1
2. document=332, weight=2693, author_id=3, updated=Thu Mar 10 09:37:00 2005, num_comments=0, tag=(), @groupby=3, @count=19
3. document=1, weight=1601, author_id=2, updated=Wed Aug 4 17:40:00 2004, num_comments=0, tag=(), @groupby=2, @count=2
4. document=118, weight=1685, author_id=1, updated=Mon Jul 19 17:35:02 2004, num_comments=0, tag=(), @groupby=1, @count=15
words:
1. 'ebay': 37 documents, 81 hits
Come potete notare abbiamo quattro risultati, uno per ogni id di autore
restituito dalla ricerca. Ogni risultato ha il documento con la
corrispondenza migliore ai termini di ricerca per l'autore identificato
in @groupby, e il numero di documenti che la ricerca restituirebbe
impostando un filtro su quell'autore contenuto in @count. L'autore
con id 3, ad esempio, è quello che ha scritto più post su ebay
(19), e il suo post dove ebay è utilizzato di più è il numero 332.
Se utilizzassimo le API invece del comando search, potremmo
ordinare i risultati, oltre che per rilevanza come mostrato qui sopra,
anche per numero di post per ogni autore (@count) o id dell'autore
(@groupby).
Utilizzo delle API
Il sorgente di Sphinx contiene tre client direttamente supportati per PHP, Python, e Java; e due sviluppati dagli utenti per Perl e Ruby. Tutti i client si uniformano alla sintassi del modulo PHP, sviluppato direttamente da Andrew e l'unico disponibile con le prime versioni di Sphinx, in modo da facilitare la documentazione ufficiale e semplificare il passaggio da un linguaggio all'altro.
Il risvolto negativo di questa scelta (fatta direttamente da Andrew) è
che l'utilizzo dei client non rispetta le convenzioni dei singoli
linguaggi e risulta quindi poco intuitivo (e a volte decisamente
macchinoso). Fortunatamente il protocollo utilizzato da searchd è
molto semplice, e scrivere un client “personalizzato” (o uno per un
linguaggio non supportato direttamente) utilizzando come punto di
partenza uno dei client disponibili è questione di poche ore. Nei
prossimi articoli di questa serie vedremo come, esaminando il client
alternativo per Python che ho sviluppato per BlogBabel, che è l'unico oltre a
quello ufficiale in PHP che — per ora — supporta alcune nuove
funzionalità come le query multiple, che veicolano più set di
richieste/risposte all'interno di un'unica connessione TCP.
Per stuzzicarvi l'appetito, ecco due semplici esempi di utilizzo del driver standard per PHP
require_once 'sphinxapi.php';
$c =& new SphinxClient();
$cl->SetServer('localhost', 3312);
if ($err = $cl->GetLastError())
die("Error while querying Sphinx: " . $err);
$cl->setMode(SPH_MATCH_EXTENDED);
$res = $cl->Query('sphinx', 'qix_1');
if (!$res)
die("Error while querying Sphinx: " . $cl->GetLastError());
e del mio driver per Python
import sphinx
try:
c = sphinx.SphinxClient('localhost', 3312)
r = c.query('sphinx', index='qix_1', mode=sphinx.SPH_MATCH_EXTENDED)
except sphinx.SphinxError, e:
raise SystemExit("Error while querying Sphinx: %s" % e)
Spero che questa prima infarinatura di Sphinx sia stata sufficiente a farvi intravedere le potenzialità di questo motore di ricerca. Continueremo ad esaminarlo nei prossimi articoli, se avete qualche curiosità o argomento che vi piacerebbe fosse trattato, segnalatemelo nei commenti.
- Pubblicato il
- 25 Gen 2008
- Tag
Trackback
Commenti
-
A costo di essere ripetitivo...pubblicate articoli davvero molto interssanti. Questo in particolare ha stuzzicato la mia curiosità! Aspetto il seguito!
-
I javaisti all'ascolto avranno probabilmente piu' familiarita' con Lucene, che poi altro non e' che l'engine alla base di Solr. Un anno e mezzo fa questo benchmark dava Lucene vincente su Sphinx con l'aumentare del numero di thread, ma come ben evidenziato nei commenti e ammesso dall'autore stesso i risultati sono solo parzialmente comparabili (e l'autore non ha di certo tirato sphinx come avrebbe dovuto).
Sono molto interessato a vedere il prosieguo. Ottimo lavoro!
-
Beh, tieni conto che confrontare Lucene a Sphinx non è completamente fair: Sphinx ha una componente di rete, mentre Lucene lo usi direttamente tramite API.
I risultati sono comunque paragonabili, e la mia breve esperienza con Solr mi fa pensare che su query più complesse, con raggruppamenti e/o filtri e ordinamenti particolari, Sphinx sia nettamente più veloce. Oltretutto, Solr ha il grande vantaggio di permettere live update (cosa che Sphinx può fare solo nell'ultima versione, e solo con modifiche sugli attributi) che però paghi pesantemente con i warm up richiesti ai singoli thread.
Vedremo come saranno i risultati quando arriverò a fare i benchmark promessi, io sono davvero curioso...
-
Very nice article thanks ! I had to use some translators since my italian is no so good This deserves an english translation at least !
Many thanks by he way
-
Ottimo. Davvero ottimo articolo, complimenti.


mbdkxcbe