Performances des migrations Drupal avec une indexation des contenus pour Search API

Les migrations de grandes quantités de contenus dans Drupal et l'indexation dans un moteur de recherche Search API interne ne font pas bon ménage. Individuellement ce sont des opérations efficaces mais conjointement c'est une catastrophe. Que faire ?

Lors de la réalisation de nos projets nous sommes souvent amenés à réaliser la migration de grandes quantités de contenus afin d'éviter aux personnes en charge de l'administration du site un travail long et fastidieux de copie. De plus, la plupart des projets de cet ordre font le choix de donner accès à ces contenus par l'entremise d'un ou plusieurs moteurs de recherche aux fonctionnalités plus ou moins avancées. Nous sommes donc régulièrement confrontés à la cohabitation entre le module Migrate qui nous permet de gérer l'import massif de contenus depuis des sources variées et le module Search API qui sert de socle à l'indexation de ces contenus dans les moteurs de recherche.

Migrate comme Search API sont deux outils performants dans l'absolu. Cependant, lorsqu'ils sont utilisés ensemble, le temps nécessaire à l'import et l'indexation des contenus monte en flèche et devient rapidement improductif.

Mais qu'est-ce qui se passe ?

Extrait du sketch "Biouman" des Inconnus

Lorsque Migrate enregistre une entité, cela déclenche automatiquement un processus côté Search API qui peut s'avérer assez coûteux en ressources. En effet, ce dernier va charger tous ses index actifs un par un et vérifier s'il a quelque chose à faire puis procéder à l'indexation en elle-même.

La principale piste qui est généralement identifiée pour résorber ce problème est de désactiver l'option "Indexer les éléments immédiatement". Malheureusement, même si cela réduit effectivement le temps nécessaire aux migrations, ce n'est pas suffisant car Search API a tout de même besoin de consigner l'entité qui devra être indexée plus tard. Il charge donc tous les processeurs de tous ses index actifs pour déterminer si cette entité spécifique sera indexée immédiatement, ultérieurement ou pas du tout. Cette option ne permet donc d'économiser que l'étape d'indexation en elle-même ce qui est insuffisant dans le cas de grandes migrations.

Comment se sortir de cette galère ?

Bob l'éponge a l'air vraiment perdu. Il s'emmêle les bras en
montrant dans toutes les directions

Comme dit précédemment, Search API se préoccupe de tous ses index actifs pour déterminer quoi faire avec le contenu migré. La première solution que vous pouvez donc mettre en place est de désactiver les index avant d'exécuter vos migrations. Cela peut être réalisé avec Drush assez facilement via la commande search-api:disable-all (et sa petite sœur search-api:enable-all lorsque les migrations sont terminées) ou encore carrément en désactivant le serveur via la commande search-api:server-disable (et search-api:server-enable). Si vous ne lancez pas vos migrations via Drush, c'est également réalisable par le code mais nous ne couvrirons pas cela dans cet article.

L'avantage de cette méthode est qu'elle remplit l'objectif de performance en désactivant tout le processus de Search API et qu'elle est relativement simple à mettre en œuvre (une commande avant les migrations, une autre après). Elle présente cependant deux inconvénients importants :

  1. si le serveur ou les index sont désactivés, Search API ne sait pas qu'une entité a été créée ou modifiée et ne l'indexera donc jamais (il ne faisait pas tout ça pour rien quand il était activé avec l'option d'indexation immédiate désactivée) ;
  2. durant toute la durée des migrations, les pages du site s'appuyant sur l'un des index de recherche désactivés ne seront plus accessibles (erreur 500).

Forcer la reconstruction des trackers de Search API

Le premier inconvénient est majeur car cela n'a aucun intérêt d'essayer de résoudre les problèmes de performance de nos migrations si le contenu n'est plus jamais présenté dans les moteurs de recherche. Comme évoqué, cet inconvénient est dû au fait que Search API maintient une liste des contenus qu'il a indexé ou qu'il doit indexer dans la table search_api_item de la base de données. On appelle cette liste son tracker. Lorsque les index sont désactivés, le tracker n'est pas mis à jour et Search API ne sait donc pas qu'il a quelque chose à faire avec ces entités.

Une fois encore, Drush nous facilite la vie en exposant la commande search-api:rebuild-tracker qui, comme son nom l'indique, va imposer à Search API de reconstruire le tracker de ses index et donc de découvrir toutes les entités dont il n'avait pas connaissance auparavant.

Éviter l'indisponibilité des index Search API durant la migration

La seconde problématique n'en est une que si vous avez des migrations qui s'exécutent alors que le site est accessible. Si vos migrations ne font partie que d'un lot joué une seule fois avant la mise en production, cette section ne vous intéresse pas forcément… Cela dit, la solution est tellement simple que cela pourrait vous convaincre davantage que de désactiver les index.

En effet, pour que les index ne soient pas inaccessibles pendant la migration, il suffit de ne pas les désactiver !

Wait… What ?!

Si vous avez suivi depuis le début, vous vous rappelez que l'on a désactivé les index pour que Search API ne perde pas de temps à déterminer s'il faut oui ou non indexer chaque entité que vous importez. Donc si nous les réactivons nous retombons sur le problème de performances que l'on essaie de résoudre ! C'est sans compter une fonctionnalité non documentée de Search API : le paramètre search_api_skip_tracking !

Pour comprendre ce qu'il se passe dans le moteur de Search API, cherchons à comprendre à quel endroit il intervient dans le code. Connaissant un peu la façon dont Drupal est conçu nous pouvons imaginer deux méthodes : un hook ou un événement. Les événements liés à la modification d'une entité n'étant pas courants dans la version actuelle de Drupal, nous allons commencer par nous pencher sur les hooks. Étant donné que c'est une fonctionnalité de bas niveau de Search API, s'il y a un hook il sera situé dans le module search_api et pas l'un de ses sous-modules. De plus, Search API peut indexer tout type d'entité et il est donc peu probable qu'il s'agisse d'une implémentation d'un hook spécifique à un type d'entité donné. Nous allons donc pouvoir chercher dans la base de code une fonction dont le nom commence par search_api_entity_ ce qui nous laisse peu de choix : search_api_entity_delete, search_api_entity_insert, search_api_entity_update, search_api_entity_view et search_api_entity_extra_field_info. Bingo ! Les trois premiers concernent des opérations de création, modification et suppression d'entités génériques.

Lorsque l'on prend une de ces fonctions et que l'on regarde ce qu'il s'y passe en détail, on constate l'appel au service search_api.entity_datasource.tracking_manager et plus spécifiquement à ses méthodes entityInsert, entityUpdate et entityDelete. Chacune d'entre elles teste très rapidement la valeur d'un paramètre search_api_skip_tracking de l'entité et, s'il n'est pas vide, interrompt son exécution.

Sachant donc que cette valeur permet d'arrêter Search API avant qu'il ne consomme des ressources, nous pouvons l'ajouter à notre entité en cours de migration. C'est d'ailleurs très facile car Migrate ne se préoccupe pas vraiment de savoir si une donnée que l'on passe dans son mapping existe ou pas. Il est donc possible d'ajouter une entrée dans les process de nos migrations comme suit :

process:
  # ...
  search_api_skip_tracking:
    plugin: default_value
    default_value: 1
  # ...

Bien évidemment, ceci a pour effet de totalement contourner Search API et il est donc toujours nécessaire de reconstruire ses trackers après la migration comme vu au chapitre précédent.

Le plus simple pour la fin

Maintenant que les contenus ont été importés et que les trackers ont été reconstruits il ne reste plus qu'à indexer tout le contenu pour qu'il devienne disponible sur le site. Une fois encore Drush nous facilite la vie en nous permettant d'accéder à la commande search-api:index mais vous pouvez également passer par l'interface d'admin de Search API si vous préférez.

En résumé

  1. injectez la donnée search_api_skip_tracking dans vos entités via Migrate (ou désactivez vos index mais franchement ça vaut pas le coup) ;
  2. reconstruisez les trackers une fois vos migrations terminées via drush search-api:rebuild-tracker ;
  3. n'oubliez pas d'exécuter l'indexation manuellement une fois les trackers reconstruits via drush search-api:index ;
  4. attendez patiemment le prochain article en vous hydratant bien et en prenant soin de votre santé physique et mentale.

 

Voilà ! Maintenant vous savez comment faire lorsque vous avez à combiner migrations et indexation. Vous vous apercevrez lorsque vous y serez confrontés que l'indexation en elle-même peut prendre un très long temps et même ralentir au fil de son exécution. C'est une problématique a priori liée à la mémoire disponible mais cela fera l'objet d'un prochain article (plus court, c'est promis) !

Votre commentaire

Le contenu de ce champ sera maintenu privé et ne sera pas affiché publiquement.
Votre adresse servira à afficher un Gravatar et à vous notifier des réponses. Votre commentaire sera anonymisé si ce billet est dépublié pendant plus de 3 mois.
Pour lutter contre le spam notre système enregistre votre adresse IP et votre adresse e-mail si vous la partagez.
Nous vous invitons à consulter notre politique de confidentialité pour comprendre les traitements faits de ces données et comment les rectifier.

À propos de Edouard

Expert technique

Après un premier contact douloureux avec Drupal en 2009 en autodidacte, j'ai suivi une formation qui m'a convaincu de mon choix technologique et m'a vraiment mis en selle. Durant plusieurs années suite à cela j'ai accompagné des entreprises locales dans le développement de leurs projets de toutes sortes, de la simple vitrine à l'intranet social en passant par le projet e-commerce.