Kamelot Blog

Aller au contenu | Aller au menu | Aller à la recherche

samedi 31 décembre 2011

sam
31
dec '11

Sphinx : Tri par titre en multi index ...

Puisque sphinx ne connaît que des valeurs scalaires pour faire un tri alphabétique, il faut transformer les chaînes de caractères en nombre.

sql_attr_str2ordinal sert à ça.

Sauf que .... ce qu'on stocke c'est un n° d'ordre dans l'index, c'est pas une représentation numérique de la chaîne.

Le nom est bien 2ordinal et pas 2num

Pour confirmation cet extrait de la doc :

9.1.20. sql_attr_str2ordinal
(...)
When indexing ordinals, string values are fetched from database,
temporarily stored, sorted, and then replaced by their respective ordinal 
numbers in the array of sorted strings.
So, the ordinal number is an integer such that sorting by it produces
the same result as if lexicographically sorting by original strings.
by string values lexicographically.

Donc pour les recherches sur plusieurs indexs ça coince.

En effet si ma chaîne est 3eme dans un index, ca ne veut pas dire qu'elle serait avant celle qui est 3ème, 5ème,100ème dans l'autre index.

Bref en multi index, ça foire.

C'est un problème facile à repérer parce que vos résultats arrivent en petites séries de blocs triés. Comme un 45T dont l'aiguille saute :)

C'est bien dommage parce qu'une manière d’accélérer vos indexations et vos recherches c'est de découper les indexs en "portions".

Que faire quand on veut profiter des avantages de ce découpage et des possibilités de trier alphabétiquement ?

Solution depuis Sphinx 1.10

Allez Louya, on a un nouveau type de champs. coûteux mais efficace. sql_field_string

En créant un attribut de ce type, et en l'utilisant dans "sort" sur vos chunk... ca marche.

Et en bonus, vous pouvez en récupérer le contenu. C'est-à-dire que si l'info vous suffit , vous ne devez plus aller la rechercher dans votre source.

Solution avant Sphinx 1.10

Transformez vous même la chaîne en nombre :(

Si vous utilisez une source Mysql, j'ai une solution. Bof bof pour ca. Utiliser la fonction HEX de mysql.

J'ai expliqué cela il y a quelque temps : StrToNum en Mysql ou comment convertir une chaine en nombre ?


Reste encore 2 autres problèmes à régler mais ça sera pour une autre fois.

  • avant de trier il faut "TRIM" tous les espaces blancs. Et ca, Mysql ne le fait pas aussi bien que php. En effet mysql ne retire que les ASCII 32.
  • pour trier il faut une bonne collation. Et ca je n'ai pas encore trouvé comment faire en sorte que sphinx s'en sorte aussi bien que Mysql
le "œ"  est-il bien entre "n" et "p" ?

samedi 18 juin 2011

sam
18
juin '11

Group by avec Sphinx. via SphinxSe

Cette semaine j'ai réussi à faire un truc avec sphinx que je n'ai pas spécialement trouvé bien documenté donc je le raconte ici.

Quand on utilise une table mysql avec le plugin sphinxSE, on doit obligatoirement commencer par les colonnes

  • id
  • weight
  • query
  • group_id
CREATE TABLE t1
(
    id          INTEGER UNSIGNED NOT NULL,
    weight      INTEGER NOT NULL,
    query       VARCHAR(3072) NOT NULL,
    group_id    INTEGER,
    INDEX(query)
) ENGINE=SPHINX CONNECTION="sphinx://localhost:9312/test";

mais on peut lui ajouter des colonnes

on a les colonnes qui représentent les attributs scalaires mais aussi des colonnes "virtuelles"

  • _sph_groupby,
  • _sph_count
  • _sph_distinct

c'est à dire

CREATE TABLE t1
(
    id          INTEGER UNSIGNED NOT NULL,
    weight      INTEGER NOT NULL,
    query       VARCHAR(3072) NOT NULL,
    group_id    INTEGER,
     _sph_groupby INTEGER NOT NULL,
     _sph_count INTEGER NOT NULL,
     _sph_distinct INTEGER NOT NULL,
 
    INDEX(query)
) ENGINE=SPHINX CONNECTION="sphinx://localhost:9312/test";

Si vous utilisez un

WHERE query='test;groupby=attr:nomdunattributscalaire;';

_sph_groupby contiendra la valeur et _sph_count le nombre de matches

Pour donner une "idée" de la vitesse.

J'ai fait une recherche avec un terme courant avec un group by sur un critère qui peut contenir un chiffre de 0 à 9. Dans une collection de plus de 45M rows. Il y a entre 50.000 et 160.000 matches pour chaque ligne de mon group by Et il me donnait le résultat en 300ms

Avec un terme moins répandu et 500x moins de résultats j'avais un temps de réponse de 15ms

lundi 14 février 2011

lun
14
fév '11

Paramètre commentaire de SphinxClient::query

Je fait pas mal de tests sur sphinx pour le moment.

En lisant PHP: SphinxClient::query - Manual

[php]
public array SphinxClient::query ( string $query [, string $index = "*" [, string $comment = "" ]] )

Mon attention a été attirée par le 3ème paramètre. (supporté par sphinx depuis la Version 0.9.8 du 14 juillet 2008)

En fait ce paramètre accepte une chaine de caractère qui se retrouve dans le query.log

[php]
$result = $hSphinx->query('leMotQueJeCherche', '*', 'mon commentaire');

Ce paramètre existe aussi en SphinxSE mais ce n'est pas documenté,

Il suffit d'ajouter ';comment=mon test' dans la chaine query,

query='leMotQueJeCherche;comment=mon commentaire';

Et voila dans le query.log

[query.log] [Mon Feb 14 14:54:00.216 2011] 0.091 sec [any/0/rel 14094 (5000,99)] [*] [ios=0 kb=0.0 ioms=0.0 cpums=91.5] [mon commentaire] leMotQueJeCherche

Ce qui est très pratique avec un tail -f et un grep pour ne voir que les rêquetes faisant partie du test,

Lire la suite...

samedi 4 septembre 2010

sam
04
sep '10

Sphinx : j'en dit un peu plus sur ce moteur de recherche

Je parlais il y a 2 jours de sphinx en expédiant une explication en 2 secondes.

Le lendemain, je vois sur Planet Mysql que Shlomi Noach publiait un article pour remettre les pendules à l'heure sur ce qu'est sphinx

Je vais donc en dire un peu plus.

Sphinx est un moteur d'indexation/recherche open-source sous GPLv2.

On utilise indexer pour scanner ce qu'il faut indexer, cela génère des fichiers que searchd rend consultables.

Pour attaquer searchd, on a search en ligne de commande.

Mais on a aussi

  • SphinxSE: un engine mysql, il sera inclus directement dans les prochaines versions de mariaDb
  • SphinxAPI: que l'on peut utiliser en php avec un package pecl dedié mais aussi avec les apis officielles Php, Python, Java, Ruby, pur C, fournies avec sphinx, ou bien encore Riddle pour Ruby, Sphinx::Search pour perl, C++ Sphinx client, Haskell Sphinx client, C# .NET client, ...
  • SphinxQL: une syntaxe sql adaptée

Recherche

Pour la recherche elle même on a les syntaxes avancées sur le texte ...

  • et, ou , mais pas,
"hello world" @title "example program"~5 @body python -(php|perl) @* code
  • les quorum (je donne 7 mots et je veux qu'il y en aie au moins 4 dans chaque résultats)
  • la pertinence (ce mot là a plus de poids dans le titre, celui ci partout, ... )
  • la distance dans le texte : ces 2 mots là doivent être séparés par moins de n mots
  • Des remplacements
    • de caractères pour les problèmes de charset
    • les remplacements magiques (si je cherche email, je trouve aussi les contenus avec le mot courriel) sur base de vos listes (voir exemple en bas)
    • Morphologie linguistique, stemming (gestion des pluriels),
    • listes de mots interdits
    • listes de dictionnaires personnels pour la correction magique
  • ...

.. Les critères de filtre et tri annexe

  • groupes (avec decompte par groupe)
  • temporels
  • géographiques
  • ...

J'en passe bien sur.

Le service searchd est consultable via réseau (donc si vous utilisez SphinxSE il n'est pas embarqué dans la DB il peut/ils peuvent être ailleurs) avec ssl et tout le brol.

Indexation

La première chose à distinguer c'est qu'il n'est pas limité à mysql ni même à des bases de données. On peut indexer tout ce qui peut être parsé, moyennant une conversion vers un fichier xml.

On peut indexer des sources hétérogènes les mélanger comme on peut indexer partiellement une source monolithique.

On peut indexer "sur le coté" pendant que l'index précédent reste actif et swapper à la fin (très difficile à faire : il faut ajouter un --rotate)

On peut indexer par petit morceaux et exploiter les partitions ou les merger

On peut répartir les indexs sur plusieurs serveurs

Brefs il y a moyen de distribuer la recherche et l'indexation.

Il y a des techniques de RealTime indexing mais c'est dans la 1.10-beta

Lire la suite...

mercredi 1 septembre 2010

mer
01
sep '10

Nombre de résultats d'une recherche SphinxSe

Logo Sphinx Sphinx est un moteur de recherche full-text

On peut l'interroger au travers de son api, SphinxQL, en ligne de commande ou avec l'engine MySql SphinxSE

J'utilise MySqlSE;

SELECT * from INFORMATION_SCHEMA.ENGINES;

ENGINE SUPPORT COMMENT                     TRANSACTIONS  XA      SAVEPOINTS
------ ------- --------------------------- ------------  ------  ----------
...
SPHINX YES     Sphinx storage engine 0.9.9 NO            NO      NO        

La question du jour était : Y a-t-il un moyen de connaître le nombre total de résultats quand on utilise un limit ?

En Mysql simple, il y a SQL_CALC_FOUND_ROWS

mysql> SELECT SQL_CALC_FOUND_ROWS * 
    -> FROM tbl_name
    -> WHERE id > 100 LIMIT 10;
mysql> SELECT FOUND_ROWS();

Le second SELECT retourne un nombre indiquant combien de lignes le premier SELECT aurait retourné s'il n'avait pas été écrit avec une clause LIMIT.

Mais avec avec SPHINX

En testant sur ma table tbl_name_sphinx qui contient 2410 rows.

select SQL_CALC_FOUND_ROWS * from tbl_name_sphinx WHERE query='' LIMIT 10;
SELECT FOUND_ROWS();
Résultat
FOUND_ROWS()
------------
          20

20 parce que c'est la valeur par défaut du limit de sphinx

# limit - amount of matches to retrieve from result set, default is 20; (ref)

En effet si je force ce limit à 10

select SQL_CALC_FOUND_ROWS *
 from tbl_name_sphinx 
 WHERE query=';limit=10';

SELECT FOUND_ROWS();
Résultat

FOUND_ROWS() -> 10

Pourquoi ? parce que c'est sphinx qui fait la vraie recherche et remonte son résultat à MySql

Donc quand je fait

select SQL_CALC_FOUND_ROWS * 
 from tbl_name_sphinx 
 WHERE query=';limit=1000' 
 LIMIT 10;

Je reçois 10 résultats sur 2410 réels et sur les 1000 que sphinx a remonté

Donc

SELECT FOUND_ROWS(); -> affiche 1000 et pas 2410.

Donc je vais monter mon limit à 1000000.

Gloups, je viens de demander à Sphinx de me préparer en résultat de 100K rows, tout renvoyer à mysql dans une table temporaire qui me retournera uniquement les 10 premiers.... Fameux gaspillage

Quand on sait que sphinx ne me retourne que les id et que donc il faut faire un join avec la table de données, ca fait mal.

la solution

SHOW ENGINE SPHINX STATUS;

On oublie le 'SQL_CALC_FOUND_ROWS'

Et on remplace FOUND_ROWS() par SHOW ENGINE SPHINX STATUS;

On remet le limit 10 au niveau de sphinx;

select  * 
 from tbl_name_sphinx 
 WHERE query=';limit=10';

SHOW ENGINE SPHINX STATUS;

Type    Name    Status                                           
------  ------  -------------------------------------------------
SPHINX  stats   total: 1000, total found: 2410, time: 0, words: 0

Bingo j'ai mon info, planquée dans une "chaine" mais je l'ai.

re bingo

 SHOW STATUS LIKE 'sphinx_%';

Mais je préfère INFORMATION_SCHEMA.

SELECT *
 from information_schema.GLOBAL_STATUS
 WHERE VARIABLE_NAME like 'SPHINX%';
VARIABLE_NAME       VARIABLE_VALUE
------------------  --------------
SPHINX_ERROR        208409        
SPHINX_TIME         0             
SPHINX_TOTAL        1000          
SPHINX_TOTAL_FOUND  2410          
SPHINX_WORD_COUNT   0             
SPHINX_WORDS                      

Et je suis un heureux.

Si vous êtes intéressés par Sphinx, voici un bon article pour l'installer.

Si vous vous êtes déjà intéressé à Zend_Search_Lucene, (bien décrit ici) il me semble avoir que celui-ci peut utiliser Sphinx comme backend. je corrigerai si je retrouve la source.

Tags