Zend_Search_Lucene-BestPractice.xml 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!-- EN-Revision: 24249 -->
  3. <!-- Reviewed: no -->
  4. <sect1 id="zend.search.lucene.best-practice">
  5. <title>Bonnes pratiques</title>
  6. <sect2 id="zend.search.lucene.best-practice.field-names">
  7. <title>Nommage des champs</title>
  8. <para>
  9. Il n'y a pas de limitation pour les noms de champs dans
  10. <classname>Zend_Search_Lucene</classname>.
  11. </para>
  12. <para>
  13. Cependant, il est préférable de ne pas utiliser les noms '<emphasis>id</emphasis>'
  14. et '<emphasis>score</emphasis>' afin d'éviter toute ambiguïté dans les propriétés de
  15. <classname>QueryHit</classname>.
  16. </para>
  17. <para>
  18. Les propriétés <property>id</property> et <property>score</property> de
  19. <classname>Zend_Search_Lucene_Search_QueryHit</classname> font toujours référence à
  20. l'identifiant interne du document Lucene et au <link
  21. linkend="zend.search.lucene.searching.results-scoring">score</link> de hit. Si le
  22. document indexé possède les mêmes champs stockés, vous devrez utiliser la méthode
  23. <methodname>getDocument()</methodname> pour y accéder :<programlisting language="php"><![CDATA[
  24. $hits = $index->find($query);
  25. foreach ($hits as $hit) {
  26. // Récupérer le champ de document 'title'
  27. $title = $hit->title;
  28. // Récupérer le champ de document 'contents'
  29. $contents = $hit->contents;
  30. // Récupérer l'id interne du document Lucene
  31. $id = $hit->id;
  32. // Récupérer le score de hit
  33. $score = $hit->score;
  34. // Récupérer le champ de document 'id'
  35. $docId = $hit->getDocument()->id;
  36. // Récupérer le champ de document 'score'
  37. $docId = $hit->getDocument()->score;
  38. // Un autre moyen de récupérer le champ 'title'
  39. $title = $hit->getDocument()->title;
  40. }
  41. ]]></programlisting></para>
  42. </sect2>
  43. <sect2 id="zend.search.lucene.best-practice.indexing-performance">
  44. <title>Performance de l'indexation</title>
  45. <para>
  46. La performance de l'indexation est un compromis entre les ressources utilisées, le
  47. temps d'indexation et la qualité de l'index.
  48. </para>
  49. <para>
  50. La qualité de l'index est complètement déterminée par le nombre de segments de
  51. l'index.
  52. </para>
  53. <para>
  54. Chaque segment d'index et une portion de données entièrement indépendante. Ainsi
  55. plus un index contient de segments, plus il sera gourmand en mémoire et en temps de
  56. calcul lors de la recherche.
  57. </para>
  58. <para>
  59. L'optimisation d'index est un processus consistant à fusionner plusieurs segments
  60. en un seul nouveau segment. Un index totalement optimisé ne contient qu'un seul
  61. segment.
  62. </para>
  63. <para>
  64. L'optimisation complète de l'index peut être effectuée avec la méthode
  65. <methodname>optimize()</methodname> :<programlisting language="php"><![CDATA[
  66. $index = Zend_Search_Lucene::open($indexPath);
  67. $index->optimize();
  68. ]]></programlisting></para>
  69. <para>
  70. L'optimisation d'index fonctionne avec des "data streams" et ne consomme pas pas
  71. une grande quantité de mémoire, mais nécessite des ressources de processeur et du
  72. temps.
  73. </para>
  74. <para>
  75. Par nature, les segments d'index de Lucene ne peuvent pas être mis à jour
  76. (l'opération de mise à jour nécessite une réécriture complète du segment). Ainsi,
  77. l'ajout de nouveau(x) document(s) à un index implique toujours la génération d'un
  78. nouveau segment. De fait, cela dégrade la qualité de l'index.
  79. </para>
  80. <para>
  81. Une optimisation automatique d'un index est effectuée après chaque génération de
  82. segment et consiste en la fusion des segments partiels.
  83. </para>
  84. <para>
  85. Il y a trois options pour contrôler le comportement de l'auto-optimisation (voir
  86. la section <link linkend="zend.search.lucene.index-creation.optimization">Optimisation
  87. d'index</link>) :<itemizedlist>
  88. <listitem>
  89. <para>
  90. <emphasis>MaxBufferedDocs</emphasis> représente le nombre maximum de
  91. documents qui peuvent être mis en mémoire tampon avant qu'un nouveau segment
  92. soit généré et écrit sur le disque dur.
  93. </para>
  94. </listitem>
  95. <listitem>
  96. <para>
  97. <emphasis>MaxMergeDocs</emphasis> représente le nombre maximum de
  98. documents qui seront fusionnés dans un nouveau segment lors du processus
  99. d'auto-optimisation.
  100. </para>
  101. </listitem>
  102. <listitem>
  103. <para>
  104. <emphasis>MergeFactor</emphasis> détermine à quelle fréquence
  105. l'auto-optimisation est effectuée.
  106. </para>
  107. </listitem>
  108. </itemizedlist> <note>
  109. <para>
  110. Toutes ces options sont des propriétés de la classe
  111. <classname>Zend_Search_Lucene</classname>, pas des propriétés d'index. Elles
  112. n'affectent que les instances de <classname>Zend_Search_Lucene</classname> et
  113. peuvent varier selon les scripts.
  114. </para>
  115. </note>
  116. </para>
  117. <para>
  118. <emphasis>MaxBufferedDocs</emphasis> n'a aucun effet si vous n'indexez qu'un seul
  119. document par exécution de script. En revanche, il est très important pour les
  120. indexations massives ("batch indexing"). Plus sa valeur est élevée, meilleures seront
  121. les performances d'indexation, mais plus la consommation de mémoire sera
  122. importante.
  123. </para>
  124. <para>
  125. Il n'existe pas de manière simple de calculer la meilleure valeur pour le
  126. paramètre <emphasis>MaxBufferedDocs</emphasis> car cela dépend de la taille moyenne des
  127. documents, de l'analyseur utilisé et de la mémoire disponible.
  128. </para>
  129. <para>
  130. Une bonne méthode pour trouver une valeur correcte consiste à effectuer plusieurs
  131. tests avec les documents les plus volumineux que vous vous attendez à voir figurer dans
  132. l'index.<footnote>
  133. <para>
  134. <methodname>memory_get_usage()</methodname> et <methodname>memory_get_peak_usage()</methodname>
  135. peuvent être utilisées pour contrôler l'utilisation de la mémoire.
  136. </para>
  137. </footnote>. Une bonne pratique consiste à ne pas utiliser plus de la moitié de la
  138. mémoire allouée.
  139. </para>
  140. <para>
  141. <emphasis>MaxMergeDocs</emphasis> limite la taille d'un segment (en termes de
  142. nombre de documents). De ce fait, il limite également la durée de l'auto-optimisation en
  143. garantissant que la méthode <methodname>addDocument()</methodname> ne sera pas exécutée plus d'un
  144. certain nombre de fois. C'est très important dans le cadre d'applications
  145. interactives.
  146. </para>
  147. <para>
  148. Diminuer la valeur du paramètre <emphasis>MaxMergeDocs</emphasis> peut aussi
  149. améliorer les performances lors de l'indexation en masse ("batch indexing").
  150. L'auto-optimisation est un processus itératif et utilise une technique ascendante. Les
  151. petits segments sont fusionnés vers de plus gros segments qui sont eux-mêmes fusionnés
  152. vers des segments encore plus gros, etc. L'optimisation complète est achevée lorsqu'il
  153. ne reste qu'un seul gros segment.
  154. </para>
  155. <para>
  156. De petits segments détériore généralement la qualité de l'index. Un grand nombre
  157. de petits segments peut aussi déclencher l'erreur "Too many open files" déterminée par
  158. les limitations du système d'exploitation<footnote>
  159. <para>
  160. <classname>Zend_Search_Lucene</classname> maintient chaque segment ouvert
  161. pour améliorer les performances de recherche.
  162. </para>
  163. </footnote>.
  164. </para>
  165. <para>
  166. En général, l'optimisation d'index en arrière-plan devrait être effectuée pour les
  167. modes d'indexation interactifs et la valeur de <emphasis>MaxMergeDocs</emphasis> ne
  168. devrait pas être trop faible pour les indexations de masse ("batch indexing").
  169. </para>
  170. <para>
  171. <emphasis>MergeFactor</emphasis> affecte la fréquence d'auto-optimisation. De
  172. faibles valeurs augmenteront la qualité des index non-optimisés. Des valeurs plus
  173. importantes amélioreront les performances de l'indexation, mais également le nombre de
  174. segments fusionnés. Ce qui peut également déclencher l'erreur "Too many open
  175. files".
  176. </para>
  177. <para>
  178. <emphasis>MergeFactor</emphasis> groupe les segments d'index par leur taille :
  179. <orderedlist>
  180. <listitem>
  181. <para>Pas plus grand que <emphasis>MaxBufferedDocs</emphasis>.</para>
  182. </listitem>
  183. <listitem>
  184. <para>
  185. Plus grand que <emphasis>MaxBufferedDocs</emphasis>, mais pas plus
  186. grand que
  187. <emphasis>MaxBufferedDocs</emphasis>*<emphasis>MergeFactor</emphasis>.
  188. </para>
  189. </listitem>
  190. <listitem>
  191. <para>
  192. Plus grand que
  193. <emphasis>MaxBufferedDocs</emphasis>*<emphasis>MergeFactor</emphasis>, mais
  194. pas plus grand que
  195. <emphasis>MaxBufferedDocs</emphasis>*<emphasis>MergeFactor</emphasis>*<emphasis>MergeFactor</emphasis>.
  196. </para>
  197. </listitem>
  198. <listitem>
  199. <para>...</para>
  200. </listitem>
  201. </orderedlist>
  202. </para>
  203. <para>
  204. <classname>Zend_Search_Lucene</classname> vérifie à chaque appel de
  205. <methodname>addDocument()</methodname> si la fusion de n'importe quel segment pour déplacer le
  206. segment nouvellement créé dans le groupe suivant. Si c'est le cas, la fusion est
  207. effectuée.
  208. </para>
  209. <para>
  210. Ainsi, un index avec N groupes peut contenir <emphasis>MaxBufferedDocs</emphasis>
  211. + (N-1)*<emphasis>MergeFactor</emphasis> segments et contient au moins
  212. <emphasis>MaxBufferedDocs</emphasis>*<emphasis>MergeFactor</emphasis><superscript>(N-1)</superscript>
  213. documents.
  214. </para>
  215. <para>
  216. La formule ci-dessous donne une bonne approximation du nombre de segments dans un
  217. index :
  218. </para>
  219. <para>
  220. <emphasis>Nombre de segments</emphasis> &lt;= <emphasis>MaxBufferedDocs</emphasis>
  221. + <emphasis>MergeFactor</emphasis>*log
  222. <subscript><emphasis>MergeFactor</emphasis></subscript> (<emphasis>Nombre de
  223. documents</emphasis>/<emphasis>MaxBufferedDocs</emphasis>)
  224. </para>
  225. <para>
  226. <emphasis>MaxBufferedDocs</emphasis> est déterminé par la mémoire allouée. Cela
  227. permet pour le facteur de fusion (MergeFactor) approprié d'avoir un nombre raisonnable
  228. de segments.
  229. </para>
  230. <para>
  231. L'optimisation du paramètre <emphasis>MergeFactor</emphasis> est plus efficace
  232. pour les performances de l'indexation de masse (batch indexing) que
  233. <emphasis>MaxMergeDocs</emphasis>. Mais cette méthode manque un peu de finesse. Le mieux
  234. est d'utiliser l'estimation ci-dessus pour optimiser <emphasis>MergeFactor</emphasis>,
  235. puis de jouer avec <emphasis>MaxMergeDocs</emphasis> pour obtenir les meilleures
  236. performances d'indexation de masse (batch indexing).
  237. </para>
  238. </sect2>
  239. <sect2 id="zend.search.lucene.best-practice.shutting-down">
  240. <title>Indexation à l'arrêt du programme</title>
  241. <para>
  242. L'instance de <classname>Zend_Search_Lucene</classname> effectue quelques tâches à
  243. la sortie du programme si des documents ont été ajoutés à l'index mais pas écrits dans
  244. un nouveau segment.
  245. </para>
  246. <para>Elle peut également déclencher le processus d'auto-optimisation.</para>
  247. <para>
  248. L'objet qui représente l'index est automatiquement fermé lorsque lui, ainsi que
  249. tous les objets de résultats de requête qui lui sont associés, sont hors de portée du
  250. script principal.
  251. </para>
  252. <para>
  253. Si l'objet d'index est stocké dans une variable globale, il ne sera fermé qu'à la
  254. fin de l'exécution du script<footnote>
  255. <para>
  256. Cela peut aussi se produire s'il y a une référence à l'index ou à l'un de
  257. ses résultats de recherche dans une structure de données cyclique, car le
  258. ramasse-miettes de <acronym>PHP</acronym> ne récupère les objets avec des références cycliques
  259. qu'en fin d'exécution de script
  260. </para>
  261. </footnote>.
  262. </para>
  263. <para>Le processus d'exception de <acronym>PHP</acronym> est également fermé à ce moment.</para>
  264. <para>
  265. Cela n'empêche pas la fermeture normale du processus de l'index, mais cela peut
  266. empêcher un diagnostic d'erreur précis si une erreur survient durant la
  267. fermeture.
  268. </para>
  269. <para>Il y a deux moyens qui peuvent permettre d'éviter ce problème.</para>
  270. <para>
  271. Le premier est de forcer l'index à sortir de la portée (du scope) :<programlisting
  272. role="php"><![CDATA[
  273. $index = Zend_Search_Lucene::open($indexPath);
  274. ...
  275. unset($index);
  276. ]]></programlisting></para>
  277. <para>
  278. Le second est d'effectuer une opération de commit avant la fin du script exécution
  279. :<programlisting language="php"><![CDATA[
  280. $index = Zend_Search_Lucene::open($indexPath);
  281. $index->commit();
  282. ]]></programlisting> Cette possibilité est également décrite dans la section "<link
  283. linkend="zend.search.lucene.advanced.static">Avancé - Utiliser les propriétés statiques
  284. de l'index</link>".
  285. </para>
  286. </sect2>
  287. <sect2 id="zend.search.lucene.best-practice.unique-id">
  288. <title>Récupération de documents par leur id unique</title>
  289. <para>
  290. C'est une pratique commune de stocker un identifiant unique de document dans
  291. l'index. Par exemple, une url, un chemin ou un identifiant tiré d'une base de
  292. données.
  293. </para>
  294. <para>
  295. <classname>Zend_Search_Lucene</classname> fournit une méthode
  296. <methodname>termDocs()</methodname> pour récupérer des documents contenant les termes
  297. spécifiés.
  298. </para>
  299. <para>
  300. C'est plus efficace que d'utiliser la méthode <methodname>find()</methodname> :<programlisting
  301. role="php"><![CDATA[
  302. // Récupération de documents avec la méthode find()
  303. // en utilisant une querystring
  304. $query = $idFieldName . ':' . $docId;
  305. $hits = $index->find($query);
  306. foreach ($hits as $hit) {
  307. $title = $hit->title;
  308. $contents = $hit->contents;
  309. ...
  310. }
  311. ...
  312. // Récupération de documents avec la méthode find()
  313. // en utilisant l'API de requête.
  314. $term = new Zend_Search_Lucene_Index_Term($docId, $idFieldName);
  315. $query = new Zend_Search_Lucene_Search_Query_Term($term);
  316. $hits = $index->find($query);
  317. foreach ($hits as $hit) {
  318. $title = $hit->title;
  319. $contents = $hit->contents;
  320. ...
  321. }
  322. ...
  323. // Récupération de documents avec la méthode termDocs()
  324. $term = new Zend_Search_Lucene_Index_Term($docId, $idFieldName);
  325. $docIds = $index->termDocs($term);
  326. foreach ($docIds as $id) {
  327. $doc = $index->getDocument($id);
  328. $title = $doc->title;
  329. $contents = $doc->contents;
  330. ...
  331. }
  332. ]]></programlisting></para>
  333. </sect2>
  334. <sect2 id="zend.search.lucene.best-practice.memory-usage">
  335. <title>Utilisation de la mémoire</title>
  336. <para>
  337. <classname>Zend_Search_Lucene</classname> est un module relativement gourmand en
  338. mémoire.
  339. </para>
  340. <para>
  341. Il utilise la mémoire pour mettre en cache certaines informations et optimiser la
  342. recherche, ainsi que les performances de l'indexation.
  343. </para>
  344. <para>La mémoire requise diffère selon les modes.</para>
  345. <para>
  346. L'index du dictionnaire des termes est chargé durant la recherche. Il s'agit de
  347. chaque 128<superscript>ème</superscript><footnote>
  348. <para>
  349. Le format de fichier Lucene permet de configurer ce nombre, mais
  350. <classname>Zend_Search_Lucene</classname> n'expose pas cette possibilité dans
  351. l'API. Cependant vous pouvez toujours configurer ce nombre si l'index est géré
  352. par une autre implémentation de Lucene.
  353. </para>
  354. </footnote> terme du dictionnaire complet.
  355. </para>
  356. <para>
  357. De fait, la consommation de mémoire augmente si vous avez un grand nombre de
  358. termes uniques. Cela peut arriver si vous utilisez des phrases non "tokenizées" comme
  359. champ de recherche ou que vous indexez un large volume d'informations
  360. non-textuelles.
  361. </para>
  362. <para>
  363. Un index non-optimisé consiste en plusieurs segments. Cela augmente également
  364. l'utilisation de mémoire. Les segments étant indépendants, chacun possède son propre
  365. dictionnaire de termes et index de dictionnaire de termes. Si un index consiste en
  366. <emphasis>N</emphasis> segments, il risque, dans le pire des cas, de multiplier par
  367. <emphasis>N</emphasis> la consommation de mémoire. Lancez l'optimisation de l'index en
  368. fusionnant tous les segments afin d'éviter de telles consommations de mémoire.
  369. </para>
  370. <para>
  371. L'indexation utilise la même quantité de mémoire que la recherche, plus de la
  372. mémoire pour mettre les documents en tampon. La quantité de mémoire peut être gérée par
  373. le paramètre <emphasis>MaxBufferedDocs</emphasis>.
  374. </para>
  375. <para>
  376. L'optimisation d'index (complète ou partielle) utilise un processus de type flux
  377. ("streaming") et ne requiert pas une grosse quantité de mémoire.
  378. </para>
  379. </sect2>
  380. <sect2 id="zend.search.lucene.best-practice.encoding">
  381. <title>Encodage</title>
  382. <para>
  383. <classname>Zend_Search_Lucene</classname> travaille avec des chaînes en UTF-8 en
  384. interne. Ainsi toutes les chaînes de caractères retournée par
  385. <classname>Zend_Search_Lucene</classname> sont encodées en UTF-8.
  386. </para>
  387. <para>
  388. Vous ne devriez pas être concernés par l'encodage si vous travaillez avec des
  389. chaîne purement <acronym>ASCII</acronym>, mais vous devez être prudent si ce n'est pas le cas.
  390. </para>
  391. <para>
  392. Un mauvais encodage peut causer des notices (erreur) durant la conversation
  393. d'encodage, voire la perte de données.
  394. </para>
  395. <para>
  396. <classname>Zend_Search_Lucene</classname> offre un large éventail de possibilités
  397. d'encodage pour les documents indexés et les requêtes analysées.
  398. </para>
  399. <para>
  400. L'encodage peut être explicitement spécifié en passant un paramètre optionnel à la
  401. méthode de création d'un champ :<programlisting language="php"><![CDATA[
  402. $doc = new Zend_Search_Lucene_Document();
  403. $doc->addField(Zend_Search_Lucene_Field::Text('title',
  404. $title,
  405. 'iso-8859-1'));
  406. $doc->addField(Zend_Search_Lucene_Field::UnStored('contents',
  407. $contents,
  408. 'utf-8'));
  409. ]]></programlisting> C'est le meilleur moyen d'éviter toute ambiguïté dans les encodages
  410. utilisés.
  411. </para>
  412. <para>
  413. Si le paramètre optionnel de l'encodage est omis, la locale courante est utilisée.
  414. La locale courante peut contenir des données d'encodage en plus des spécification de
  415. langue :<programlisting language="php"><![CDATA[
  416. setlocale(LC_ALL, 'fr_FR');
  417. ...
  418. setlocale(LC_ALL, 'de_DE.iso-8859-1');
  419. ...
  420. setlocale(LC_ALL, 'ru_RU.UTF-8');
  421. ...
  422. ]]></programlisting></para>
  423. <para>
  424. La même approche est utilisée pour définir l'encodage des chaînes de
  425. requête.
  426. </para>
  427. <para>
  428. Si l'encodage n'est pas spécifié, la locale courante est utilisée pour le
  429. déterminer.
  430. </para>
  431. <para>
  432. L'encodage peut être passée comme paramètre optionnel si la requête est analysée
  433. explicitement avant la recherche :<programlisting language="php"><![CDATA[
  434. $query =
  435. Zend_Search_Lucene_Search_QueryParser::parse($queryStr, 'iso-8859-5');
  436. $hits = $index->find($query);
  437. ...
  438. ]]></programlisting></para>
  439. <para>
  440. L'encodage par défaut peut également être spécifié avec la méthode
  441. <methodname>setDefaultEncoding()</methodname> :<programlisting language="php"><![CDATA[
  442. Zend_Search_Lucene_Search_QueryParser::setDefaultEncoding('iso-8859-1');
  443. $hits = $index->find($queryStr);
  444. ...
  445. chaîne vide sous-entend "locale courante".
  446. ]]></programlisting></para>
  447. <para>
  448. Si l'encodage correct est spécifié, il pourra être correctement interprété par
  449. l'analyseur. Le comportement dépend de quel analyseur est utilisé. Consultez la section
  450. sur les <link linkend="zend.search.lucene.charset">Jeu de caractères</link> pour plus de
  451. détails.
  452. </para>
  453. </sect2>
  454. <sect2 id="zend.search.lucene.best-practice.maintenance">
  455. <title>Maintenance de l'index</title>
  456. <para>
  457. Il devrait être clair que <classname>Zend_Search_Lucene</classname> comme toute
  458. autre implémentation de Lucene ne comporte pas de "base de données".
  459. </para>
  460. <para>
  461. Les index ne devrait pas être utilisés pour du stockage de données. Ils ne
  462. fournissent pas de fonctionnalités de backup/restauration partiel, journalisation, logs,
  463. transactions et beaucoup d'autres fonctionnalités assimilées aux systèmes de gestion de
  464. bases de données.
  465. </para>
  466. <para>
  467. Cependant, <classname>Zend_Search_Lucene</classname> tente de garder ses index
  468. dans un état constant en tout temps.
  469. </para>
  470. <para>
  471. Le backup et la restauration d'un index devrait être effectué en copiant le
  472. contenu du répertoire de l'index.
  473. </para>
  474. <para>
  475. Si pour une raison quelconque, un index devait être corrompu, il devrait être
  476. restauré ou complètement reconstruit.
  477. </para>
  478. <para>
  479. C'est donc une bonne idée de faire des backups des gros index et de stocker les
  480. logs de modifications pour pouvoir effectuer des restaurations manuelles et des
  481. opérations de "roll-forward" si nécessaire. Cette pratique réduit considérablement le
  482. temps de restauration.
  483. </para>
  484. </sect2>
  485. </sect1>