Premiers pas avec SOLR
Après avoir pas mal touché à Apache Lucene il y a quelques années ce ça ( je vous par le d'un temps que les moins de vingt ans ne peuvent pas connaître...), j'ai décidé de me pencher sur les moteurs de recherche opensource. J'ai donc décidé de me pencher sur SOLR et ElasticSearch. Ces deux projets sont basés sur lucene.
Le cas d'utilisation que je souhaite mettre en œuvre est assez simple pour l'instant : indexer le résultat d'une requête faite dans un SGBD. Celle ci prend énormément de temps ( environ 30 secondes ) et je souhaite avoir un résultat immédiat le tout en REST.
Le cas elasticsearch
J'ai tout d'abord essayé elasticsearch. Ce dernier est le projet qui a le vent en poupe et présente de nombreux sous-projets très intéressants ( logstash, kibana). Le seul moyen d'extraire les données d'un SGBD est le jdbc-river. Ce moyen ne m'a pas trop séduit , il y a pas mal de problèmes liés à la saisie d'une requête complexe. Aussi j'en ai discuté brièvement avec David Pilato ( qui a fait une super présentation sur Kibana) lors du JugSummerCamp . Il m'a confirmé l'extension du scope du projet logstash aux autres sources de données (ex. JDBC). Cette mutation devrait être effective mi 2014. Bref, en attendant j'ai exploré SOLR.
Présentation de SOLR
SOLR est donc un moteur de recherche ( ça vous l'aviez deviné) qui s'appuie sur Apache Lucene ( ça aussi ...) . Il présente des fonctionnalités semblables à ElasticSearch. A ce que j'ai pu lire sur le net ici ou là, ce dernier est supérieur sur la gestion des recherches distribuées. Je n'aurais pas besoin de cette fonctionnalité dans un premier temps. Les fonctionnalités qui m'intéressent sont les suivantes :
- Les API JAVA et REST
- Le facets
- La mise en avant de certaines recherches
- Les requêtes geo spatiales
- La réplication des données
Configuration
Pour mon projet, je me suis basé sur un archetype maven disponible sur le net. Ce dernier crée un exemple de projet qui lance un jetty avec le war de solr
Vous devez également disposer de la distribution de SOLR pour que le livrable soit déployé. C'est la seule solution que j'ai trouvé pour l'instant. Ce n'est pas très élégant, car je préférerai avoir le tout embarqué dans la webapp.
Mon projet s'appelle customer-indexer . Il indexe les données d'une base de clients. Dans le répertoire src/main/resources, j'ai crée un répertoire customers. J'ai copié le contenu du répertoire collection1 présent dans les exemples de la distribution.
J'exposerai ici la configuration indispensable pour mon cas d'étude
le fichier solr.xml
<?xml version="1.0" encoding="UTF-8" ?> <solr persistent="true"> <cores adminPath="/admin/cores" defaultCoreName="refper" host="${host:}" hostPort="${jetty.port:}" hostContext="${hostContext:}" zkClientTimeout="${zkClientTimeout:15000}"> <core name="customers" instanceDir="." /> </cores> </solr>
Dans le répertoire src/main/resources/customers/conf, il faut ajouter a minima les fichiers solrconfig.xml schema.xml et data-config.xml.
solrconfig.xml
Les deux premiers sont déjà présents J'ai modifié le premier avec les informations suivantes :
j'ai mis le chemin en dur de la distribution et non le relatif
<lib dir="c:/java/solr-4.5.0/contrib/extraction/lib" regex=".*\\.jar" /> <lib dir="c:/java/solr-4.5.0/dist/" regex="solr-cell-\\d.*\\.jar" /> <lib dir="c:/java/solr-4.5.0/contrib/clustering/lib/" regex=".*\\.jar" /> <lib dir="c:/java/solr-4.5.0/dist/" regex="solr-clustering-\\d.*\\.jar" /> <lib dir="c:/java/solr-4.5.0/dist/" regex="solr-dataimporthandler-\\d.*\\.jar" /> <lib dir="c:/java/solr-4.5.0/contrib/langid/lib/" regex=".*\\.jar" /> <lib dir="c:/java/solr-4.5.0/dist/" regex="solr-langid-\\d.*\\.jar" /> <lib dir="c:/java/solr-4.5.0/contrib/velocity/lib" regex=".*\\.jar" /> <lib dir="c:/java/solr-4.5.0/dist/" regex="solr-velocity-\\d.*\\.jar" />
La configuration de l'élément elevator semblait erronée
<searchComponent name="elevator" class="solr.QueryElevationComponent" > <!-- pick a fieldType to analyze queries --> <str name="queryFieldType">string</str> <str name="config-file">elevate.xml</str> </searchComponent>
J'ai rajouté également le module d'import des données
<requestHandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler"> <lst name="defaults"> <str name="config">data-config.xml</str> </lst> </requestHandler>
schema.xml
Ce fichier décrit la structure des documents présents dans l'index du moteur de recherche. On définit les données à indexer, les types de données et les filtres à appliquer (exemple: tout passer en minuscule)
<schema name="customers" version="1.5"> <fields> <field name="idpp" type="string" indexed="true" stored="true" required="true" multiValued="false"/> <!-- points to the root document of a block of nested documents --> <field name="_root_" type="string" indexed="true" stored="false"/> <field name="dtcrea" type="date" indexed="true" stored="true" omitNorms="true"/> <field name="dtlastupd" type="date" indexed="true" stored="true"/> <field name="title" type="lowercase" indexed="true" stored="true" omitNorms="true"/> <field name="lastname" type="lowercase" indexed="true" stored="true" multiValued="true"/> <field name="fstname" type="lowercase" indexed="true" stored="true" multiValued="true"/> <field name="dtbirth" type="date" indexed="true" stored="true" termVectors="true" termPositions="true" termOffsets="true"/> <field name="status" type="float" indexed="true" stored="true"/> <field name="content" type="text_general" indexed="false" stored="true" multiValued="true"/> <!-- catchall field, containing all other searchable text fields (implemented via copyField further on in this schema --> <field name="text" type="text_general" indexed="true" stored="false" multiValued="true"/> <!-- catchall text field that indexes tokens both normally and in reverse for efficient leading wildcard queries. --> <field name="text_rev" type="text_general_rev" indexed="true" stored="false" multiValued="true"/> <field name="_version_" type="long" indexed="true" stored="true"/> </fields> <uniqueKey>idpp</uniqueKey> <!-- les copies permettent d'avoir tout le contenu dans un seul champ--> <copyField source="title" dest="text"/> <copyField source="lastname" dest="text"/> <copyField source="firstname" dest="text"/> <copyField source="birthday" dest="text"/> </schema>
data-config.xml
Dans ce fichier on spécifie les données récupérées de la base de données
<dataConfig> <dataSource driver="oracle.jdbc.OracleDriver" url="urljdbc" user="user" password="pwd"/> <document name="customer"> <entity name="customer" query="Marequete"> <field column="id" name="id" /> <field column="LASTNAME" name="lastname" /> <field column="FIRSTNAME" name="firstname" /> <field column="DTCREATION" name="dtcreation" /> <field column="DTLASTUPDATE" name="dtlastupdate" /> <field column="BIRTHDAY" name="birthday" /> <field column="STATUS" name="status" /> </entity> </document> </dataConfig>
Configuration maven
voici le contenu du pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>customer-indexer</groupId> <artifactId>customer-indexer</artifactId> <version>1.0-SNAPSHOT</version> <packaging>war</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <slf4j-api.version>1.7.5</slf4j-api.version> <log4j.version>1.2.17</log4j.version> <solr.version>4.5.0</solr.version> <solr.default.core.directory>customers</solr.default.core.directory> <solr.default.core.name>customers</solr.default.core.name> <solr.solr.home>c:/java/solr-4.5.0/</solr.solr.home> </properties> <dependencies> <!-- SLF4J --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j-api.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>${slf4j-api.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jul-to-slf4j</artifactId> <version>${slf4j-api.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>${slf4j-api.version}</version> </dependency> <!-- Log4j --> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> <dependency> <groupId>com.oracle.ojdbc</groupId> <artifactId>ojdbc</artifactId> <version>10.2.0.2.0</version> </dependency> <!-- Solr 4.3.0 --> <dependency> <groupId>org.apache.solr</groupId> <artifactId>solr</artifactId> <version>${solr.version}</version> <type>war</type> </dependency> </dependencies> <build> <finalName>customers</finalName> <resources> <resource> <filtering>true</filtering> <directory>src/main/resources</directory> </resource> </resources> <plugins> <plugin> <artifactId>maven-antrun-plugin</artifactId> <version>1.7</version> <executions> <execution> <id>copy-to-solr</id> <goals> <goal>run</goal> </goals> <phase>package</phase> <configuration> <target> <copy todir="${solr.solr.home}" includeemptydirs="true" overwrite="true"> <fileset dir="${project.build.outputDirectory}"> </fileset> </copy> </target> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.eclipse.jetty</groupId> <artifactId>jetty-maven-plugin</artifactId> <version>9.0.4.v20130625</version> <configuration> <stopPort>9966</stopPort> <stopKey>stop</stopKey> <webApp> <contextPath>/refper</contextPath> </webApp> </configuration> </plugin> </plugins> </build> </project>
Démarrage
Dans mon cas il me suffit de lancer la commande
mvn clean install jetty:run
Import des données
On peut le faire soit par un appel REST, soit par la console : Sélectionner la collection puis cliquer sur Data Import
Conclusion
Pour l'instant j'ai rapidement fait une première indexation de mes données. je suis conscient qu'il y a pas mal d'améliorations à apporter, notamment sur la modélisation de mon index avec les bonnes entités (ex. une entité customer, une entité address,...)
Je verrai le requêtage dans un autre post.