Fil d'Ariane
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 ?
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 ?
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 :
- 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) ;
- 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 !
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é
- 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) ; - reconstruisez les trackers une fois vos migrations terminées via
drush search-api:rebuild-tracker
; - n'oubliez pas d'exécuter l'indexation manuellement une fois les trackers reconstruits via
drush search-api:index
; - 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
À 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.