Gestion des documents supprimés dans Solr

Une des questions les plus fréquentes concernant la gestion au quotidien des index Solr est le traitement des documents supprimés. En effet, certains cas d’usages qui nécessitent des suppressions ou des modifications régulières ou massives de documents peuvent amener à des index avec un fort taux de documents supprimés. Pour le constater, il faut aller dans l’onglet « Overview » des cores. L’image suivante indique un core contenant 8.815.000 documents au total répartis en 5.582.000 documents « visibles » et 3.233.000 document supprimés, soit un taux de 37% de documents supprimés.

Ces documents supprimés restent en fait présents dans les fichiers index durant un certain temps (voir définitivement). Un fort taux de documents supprimés (supérieur à 20% voir jusqu’à 50% ou plus) a des impacts non négligeables sur les performances (I/O disques, utilisation de la mémoire) et sur l’espace disque occupé. Certaines actions permettent de « purger » les documents supprimés mais au risque d’aggraver le problème. Dans cet article nous expliquerons comment fonctionne un index Solr au cours des indexations et comment sont traités naturellement les documents supprimés. Nous expliquerons ce qui ne doit absolument pas être fait sous peine d’empirer le problème et nous expliquerons finalement ce qui peut être fait (et à quels coûts et conditions) pour maintenir un taux raisonnable de documents supprimés dans un index.

Pour rappel, lorsque nous parlons d’un index Solr, nous parlons en fait d’un core, c’est à dire, soit un index au sens Solr « standalone », soit un élément (shard / replica) d’une collection SolrCloud. Pour la suite de l’article nous utiliserons le terme « core ».

Le fonctionnement d’un core Solr durant l’indexation

Un core Solr est constitué de fichiers de segments sur disque. Le cycle de vie des ces fichiers de segments est le suivant :

  • La création des fichiers de segments lors du commit : Un nouveau segment est créé lors de chaque commit « hard », qu’il soit explicitement provoqué ou déclenché par autoCommit / commitWithin. Les suppressions ou modifications de documents sont matérialisées par un fichier distinct qui référence les identifiants de ces documents afin de les ignorer dans les recherches. Les documents supprimés restent présents dans les fichiers de segments.
  • Le « merge » des fichiers de segment lors des commit : lors des commits, un algorithme est déclenché afin de trouver les segments candidats à être « merger ». Cet algorithme applique une politique de merge (TieredMergePolicy) qui généralement permet de maintenir un taux de documents supprimés au environ de 10 à 15%.
    Cette étape tente de sélectionner 10 (par défaut mais configurable) segments à merger. Si un merge est effectué, un nouveau segment résultat est créé et les anciens segments sont supprimés. Cette étape se répète alors tant que 10 segments à merger sont détectés. Le processus s’arrête lorsque qu’il n’y a plus détection de 10 segments à merger.

Le segments créés lors de l’étape de merge ne contiennent pas de documents supprimés, ce qui permet de maintenir bas le nombre de segments et à un nombre raisonnable le taux de documents supprimés. La vidéo suivante permet de bien visualiser ce phénomène de merge au fur et à mesure des indexations :

Je vous invite à lire la documentation Solr à propos du merge des segments et de la TieredMergePolicy : https://lucene.apache.org/solr/guide/8_2/indexconfig-in-solrconfig.html#merging-index-segments

Pourquoi le taux de documents supprimés dans un core Solr peut-il augmenter au dessus d’une limite raisonnable ?

Un des critères de sélection est la taille des segments. Un merge s’applique aux 10 segments les plus petits. Le problème est que certains segments sont rarement candidats au merge s’ils sont gros (car créés par des cycles précédents de merge). Si des documents sont supprimés régulièrement voir massivement de ces gros segments, ils ne sont pas ou très rarement vraiment supprimés. 

L’autre critère est qu’un segment pour être candidat à un merge NE DOIT PAS contenir plus de 50% de documents supprimés. Selon les cas d’usages, les plus vieux (et donc plus gros) segments peuvent donc ne plus être sélectionnés ou être sélectionnés très tardivement pour un merge. 

Quelles sont les conséquences d’un fort taux de documents supprimés dans un core Solr ?

Le premier impact est l’espace disque utilisé. Pour un core de 100 Go de documents non supprimés :

  • avec un taux de 10% de documents supprimés la taille du core est de 111 Go
  • avec un taux de 50% de documents supprimés la taille du core est de 200 Go

Le second impact est les performances des recherches. Avec un taux de 50% de documents supprimés, l’impact peut être de 20 à 50% selon les requêtes (https://www.elastic.co/fr/blog/lucenes-handling-of-deleted-documents).

Comment est réguler le taux de documents supprimés ?

Nous distinguons 2 cas de figure : l’utilisation de Solr en version inférieure à 7.5 et l’utilisation de Solr en version 7.5 ou ultérieure.

Avec Solr en version inférieure à 7.5

Il est très compliqué de bien réguler le taux de documents supprimés dans un core. Les deux actions possibles « Optimize » (forceMerge) et « Commit/expungeDeletes » ont un fonctionnement pouvant entraîner une situation empirée.

L’opération « Optimize » (forceMerge) d’un core consiste à provoquer le merge de TOUS ses segments en 1 ou plusieurs segments.

L’opération « ExpungeDeletes » est réalisé au moyen d’un commit explicite avec un paramètre expungeDeletes= »true ». Le fonctionnement et le résultat sont identiques à un optimize à la différence que seuls les segments avec plus de 10% de documents supprimés sont mergés.

Ces deux opérations permettent effectivement d’obtenir des cores avec aucun (Optimize) ou très peu (expungeDeletes) de documents supprimés, mais elles sont totalement déconseillées pour différentes raisons.

Un de ces raisons est qu’elles sont longues et très coûteuses en ressources et impact les performances des serveurs Solr de différentes manières :

  • L’espace disque nécessaire est très important car les anciens segments ne sont supprimés qu’après créations des nouveaux segments.
  • L’usage de la CPU, de la mémoire et des I/O disques sont importants, ce qui peut pénaliser le fonctionnement des autres tâches de Solr (indexations et recherche sur ce core et/ou les autres cores).
  • Lors d’un fonctionnement en mode master/slave, la totalité du core est copié du master vers les slaves à l’issue de l’opération.
  • Lors d’un fonctionnement en mode SolrCloud si les replicas sont de type NRT, l’optimisation est réalisé pour chaque réplica sur les serveurs qui les hébergent. Si les replicas sont de types TLOG ou PULL, les données sont transférées sur le réseaux à l’issue de l’opération.

Cependant, la raison principale pour ne pas utiliser ces opérations est que le ou les segments issus des merges ne seront plus candidats à un merge avant très longtemps. Les updates ou suppressions de documents futurs vont faire augmenter très vite le taux de documents supprimés sans qu’il ne puisse plus être régulé naturement par les merges futurs. La seule solution va être très vite de faire à nouveau des Optimize encore et encore. On est rentré dans un cercle vicieux dont la seule solution pour en sortir est de recréer totalement la collection en ré-indexant toutes les données.

Avec Solr en version 7.5 ou ultérieure

Depuis la version 7.5, deux évolutions font que ces deux opérations peuvent être envisagées.

  • La première évolution (https://issues.apache.org/jira/browse/LUCENE-7976) est que la TieredMergePolicy respecte le paramètre maxMergedSegmentMB. La conséquence est que le résultat sont des cores avec autant de segments que nécessaire de taille de 5Go maximum (configurable). Les merges naturels pourront continuer à s’appliquer aux cores et ainsi éviter que le taux de documents supprimés augmentent très vite et donc éviter de recommencer ces opérations.
  • La seconde évolution (https://issues.apache.org/jira/browse/LUCENE-8263) est qu’il est possible de contrôler le merge naturel pour le rendre plus agressif. au moyen du paramètre indexPctDeletedTarget de la TieredMergePolicy dans solrconfig.xml.
<mergePolicyFactory class="org.apache.solr.index.TieredMergePolicyFactory">
    <int name="indexPctDeletedTarget">20</int>
</mergePolicyFactory>

Ce paramètre contrôle le pourcentage maximum de documents supprimés dans un segment. Il était de 50% et il est maintenant de 33% par défaut et modifiable de 20 à 50%. Cela permet que les gros segments soient plus fréquemment sélectionnés pour des merges naturels. rendre le merge naturel plus agressif pour les cas d’usage qui le nécessite va rendre les recours à Optimize et/ou ExpungeDeletes moins nécessaires.

Pour résumer les trois événements qui purgent les documents supprimés sont :

Les merges naturels lors des commits. Il n’est pas possible de contrôler le déclenchement de ces merge (les empêcher ou les forcer), mais il est possible d’influencer l’agressivité au moyen du paramètre indexPctDeletedTarget de la TieredMergePolicy. Les gros segments avec peu de documents supprimés seront rarement mergés.

  • Les merges naturels lors des commits. Il n’est pas possible de contrôler le déclenchement de ces merge (les empêcher ou les forcer), mais il est possible d’influencer l’agressivité au moyen du paramètre indexPctDeletedTarget de la TieredMergePolicy. Les gros segments avec peu de documents supprimés seront rarement mergés.
  • Les merges lors des commits explicites avec le paramètre expungeDeletes= »true » (donc pas des commits provoqués par autocommit ou commitWithin). Seuls les segments avec plus de 10% de documents supprimés même avec une taille supérieur à maxMergedSegmentMB rentrent dans le processus de merge.
  • Les merges par Optimize. Avec 2 cas de figure :
    • Le paramètre « maxSegment » est spécifié. Dans ce cas, tous les segments rentrent dans le processus de merge pour générer un nombre de segments égal à « maxSegment ». Le résultat est identique à ce que l’on aurait obtenu avec une version de Solr avant la 7.5 .
    • Le paramètre « maxSegment » n’est pas spécifié. Seuls les segments de tailles inférieure à maxMergedSegmentMB et/ou avec des documents supprimés rentrent dans le processus de merge.

On constate que les Optimize et les expungeDeletes peuvent toujours laisser des segments de taille supérieure à maxMergedSegmentMB. Effectivement, mais alors qu’avant la version 7.5, ces segments ne pouvaient se voir purgés de leurs documents supprimés que lors d’un nouveau expungeDeletes ou Optimize, il peuvent maintenant être purgés de leur documents effacés lors d’un merge naturel si le taux de documents effacés devient très important. Dans ce cas, le segment est recréé sur lui-même sans ses documents effacés (on parle de « singleton merge »). Après plusieurs « singleton merge », la taille du segment va diminuer jusqu’à repasser sous maxMergedSegmentMB et il va pris en compte par les merges naturels en fonction du paramètre indexPctDeletedTarget .

Les bonnes pratiques pour la purge des documents supprimés

Si votre cas d’usage n’implique pas un taux de documents supprimés important, il n’est pas nécessaire de mettre à jour vers une version 7.5 ou supérieure. Dans le cas contraire, cette mise à jour est à impérative car les actions Optimize et expungeDeletes sont totalement à proscrire avant la version 7.5 et ne font que définitivement empirer la situation.

Une fois en version 7.5 ou ultérieure les actions possibles en fonction des cas sont les suivantes :

  • Le taux de documents supprimés reste stable et acceptable au environ de 10 à 20% :
    • Si les mises à jours sont fréquentes, il est préférable de laisser le merge naturel faire son travail sans modification des paramètres de la TieredMergePolicy.
    • Si les mises à jours sont regroupées avec des longues périodes sans mises à jours ou si les mises à jours ont lieu durant des périodes de faible activité de recherche, il est possible de tenter de faire descendre le taux de documents supprimés en diminuant le paramètre indexPctDeletedTarget à une valeur de 20%.
  • Le taux de documents supprimés augmente régulièrement au dessus de 20%
    • Diminuer le paramètre indexPctDeletedTarget à une valeur de 20%.
  • Malgré une diminution du paramètre indexPctDeletedTarget, le taux continue à augmenter de manière significative
    • si vous n’êtes pas en SolrCloud avec un contrainte de recherche NRT, provoquer un expungeDeletes (si possible durant des périodes de faible activités de recherche).
      Attention, il faut prévoir que la taille disque du core va temporairement doubler

Afin de provoquer un expungeDeletes, la commande suivante peut-être utilisée

curl 'http://localhost:8983/solr/<collection>/update?commit=true&expungeDeletes=true'

Dans le cas particulier d’une utilisation de Solr en mode Standard et d’une architecture master/slave, il est possible de rendre plus agressif le merge naturel et/ou de provoquer des expungeDeletes plus fréquemment. En effet, il n’y a pas d’impact sur les slaves hormis les transferts réseaux plus importants.

Dans tous les cas, il est impératif de tester les stratégies mises en place afin de valider que l’augmentation des ressources (CPU, mémoire, I/O disque et réseaux) nécessaires aux merges permet un réel gain en performance de recherche et en espace disque économisé.

Vous souhaitez bénéficier d’une expertise Solr ou intégrer une ressource ponctuelle à vos projets ? Rendez vous sur la page Contact