En tant que développeur Ruby on Rails, il est important de comprendre l’optimisation des requêtes de base de données afin d’améliorer les performances et l’expérience utilisateur. Active Record, l’outil ORM (Object-Relational Mapping) de Rails, offre de puissantes fonctionnalités pour interroger efficacement les bases de données.

L’optimisation des requêtes est un sujet complexe, qui a fait l’objet de nombreux ouvrages. Nous allons ici explorer quelques techniques et astuces pour optimiser vos requêtes Active Record et augmenter la vitesse et la réactivité de votre application.

Utilisez la récupération sélective des colonnes

L’un des moyens les plus efficaces d’optimiser les requêtes Active Record consiste à n’extraire de la base de données que les colonnes nécessaires. En spécifiant les colonnes exactes dont vous avez besoin, vous minimisez les données transférées entre la base de données et votre application Ruby on Rails. Par exemple, si nous cherchons à utiliser uniquement les noms de la base de données :

# Unoptimized Practice: Retrieving all columns
User.all

# Optimized Practice: Selecting specific columns
User.select(:id, :name)

Employer Eager Loading

Eager Loading permet de réduire le nombre de requêtes à la base de données en chargeant à l’avance les enregistrements associés. En chargeant les associations à l’avance, vous évitez le processus N+1 où des requêtes supplémentaires sont exécutées pour chaque enregistrement associé. Vous trouverez ci-dessous un exemple du problème des requêtes N+1, puis nous présentons une technique alternative appelée Russian Doll Caching.

# N+1 query problem
users = User.all
users.each { |user| puts user.posts.count }  # Executes one query for users and N queries for posts (N = number of users)

Dans l’exemple ci-dessus, nous récupérons tous les utilisateurs, puis nous itérons sur chaque utilisateur pour récupérer le nombre d’articles qui lui sont associés. Il en résulte l’exécution de N requêtes supplémentaires, ce qui entraîne une dégradation des performances.

Pour résoudre ce problème, nous pouvons utiliser le chargement anticipé avec la méthode includes, comme indiqué ci-dessous :

# Eager loading solution
users = User.includes(:posts).all
users.each { |user| puts user.posts.count }  # Executes two queries: one for users and one for posts (regardless of user count)

En utilisant includes(:posts), nous chargeons les articles associés pour tous les utilisateurs en seulement deux requêtes. La méthode includes précharge efficacement les données d’association, ce qui élimine le besoin de requêtes supplémentaires et améliore considérablement les performances.

Autre technique : Russian Doll Caching

Outre le chargement anticipé, une autre technique permet d’optimiser les requêtes de base de données : le Russian Doll Caching. Cette technique consiste à mettre en cache les structures de données hiérarchiques et leurs associations, ce qui permet une récupération efficace sans requêtes redondantes.

Prenons l’exemple d’une liste d’articles de blog et de leurs commentaires :

# Without caching (N+1 query problem)
@posts = Post.all
@posts.each do |post|
  @comments = post.comments
  # Perform actions with comments
end

Dans le code ci-dessus, chaque itération de la boucle déclenche une requête pour récupérer les commentaires de chaque article, ce qui entraîne N requêtes supplémentaires.

Pour mettre en œuvre le Russian Doll Caching, nous pouvons utiliser une approche de mise en cache telle que la mise en cache des fragments. En mettant en cache la vue entière ou partielle, y compris les enregistrements associés, nous pouvons éviter les requêtes redondantes. Voici un exemple :

# With Russian Doll Caching
<% cache @posts do %>
  <% @posts.each do |post| %>
    <% cache post do %>
      <%= post.title %>
      <% post.comments.each do |comment| %>
        <%= comment.content %>
      <% end %>
    <% end %>
  <% end %>
<% end %>

Dans cette implémentation, nous mettons en cache l’objet @posts et chaque article individuel à l’aide de l’aide cache. Lors du rendu de la vue ou de la partie de vue, Rails vérifie le cache avant d’exécuter tout code, ce qui élimine le besoin de requêtes supplémentaires.

En mettant en œuvre le Russian Doll Caching, vous pouvez optimiser les performances en minimisant les requêtes de base de données et en récupérant efficacement les structures de données hiérarchiques et leurs associations.

Eager loading est une technique puissante qui permet d’éviter le problème des requêtes N+1 en préchargeant les associations. En outre, le Russian Doll Caching offre une autre approche pour optimiser les requêtes de base de données en mettant en cache les structures de données hiérarchiques et leurs associations.

En employant ces techniques, vous pouvez améliorer les performances et la réactivité de vos applications Ruby on Rails. Choisissez l’approche qui correspond le mieux aux besoins et aux complexités de votre application.

Il existe des outils qui vous aideront à identifier les requêtes N+1 pendant que vous développez votre application. Des outils comme Bullet, Rack Mini Profiler et Prosopite sont des exemples qui valent la peine d’être essayés sur votre projet.

Utiliser l’indexation

Les index améliorent les performances des requêtes en permettant à la base de données de localiser les enregistrements plus rapidement. Dans Active Record, vous pouvez ajouter des index au schéma de votre base de données, en particulier sur les colonnes fréquemment utilisées dans les requêtes. Par exemple, vous pouvez ajouter des index à votre schéma de base de données :

# Add index to improve performance
add_index :users, :email

En outre, il existe des gemmes qui peuvent vous aider à identifier où vous devriez ajouter des index, telles que les gemmes lol_dba ou database_consistency.

Optimiser les requêtes de base de données avec des conditions

Lorsque vous construisez des requêtes, pensez à utiliser des fonctionnalités spécifiques à la base de données pour les conditions afin d’éviter de récupérer des données inutilement. Active Record propose plusieurs méthodes pour optimiser les conditions de requête, telles que where, limit, offset et order. Voici un exemple :

# Unoptimized query
users = User.all
users.select { |user| user.age > 18 && user.age < 25 }

# Optimized query
users = User.where(age: 19..24).all

Traitement par lots pour les grands ensembles de données

Travailler avec de grands ensembles de données peut avoir un impact sur les performances en raison des contraintes de mémoire. Envisagez d’utiliser des techniques de traitement par lots pour diviser les requêtes en plus petits morceaux, réduisant ainsi l’utilisation de la mémoire. Cette approche est particulièrement utile pour effectuer des opérations telles que la mise à jour ou la suppression d’enregistrements.

Toutefois, il est important d’utiliser correctement le traitement par lots pour obtenir des performances optimales. Examinons un exemple de traitement par lots médiocre et les conséquences négatives qu’il peut avoir sur votre application :

# Unoptimized Practice: Naive batch processing
users = User.all
users.each do |user|
  # Perform operations on user record
end

Dans l’extrait de code ci-dessus, nous récupérons tous les enregistrements d’utilisateurs de la base de données à l’aide de User.all. Cela peut poser un problème de performance important lorsque vous traitez de grands ensembles de données, car tous les enregistrements sont chargés en mémoire en une seule fois. Par conséquent, l’application peut consommer des ressources mémoire excessives et ralentir.

Pour résoudre ce problème, nous allons remanier le code en utilisant une approche de traitement par lots plus optimisée :

# Optimized Practice: Batch processing with `find_in_batches`
User.find_in_batches(batch_size: 1000) do |users_batch|
  users_batch.each do |user|
    # Perform operations on user record
  end
end

Dans cette mise à jour, nous utilisons la méthode find_in_batches fournie par Active Record. Cette méthode récupère les enregistrements par lots plus petits, spécifiés par batch_size, ce qui réduit l’empreinte mémoire. Elle traite chaque lot d’enregistrements dans son propre contexte de mémoire, ce qui améliore considérablement les performances de l’application lorsqu’elle traite de grands ensembles de données.

En utilisant find_in_batches, vous pouvez traiter efficacement de grands ensembles de données d’une manière économe en mémoire. N’oubliez pas d’ajuster le site batch_size en fonction des besoins de votre application et des ressources système disponibles.

Résumé

L’optimisation des requêtes Active Record est cruciale pour améliorer les performances de vos applications Ruby on Rails. En suivant les conseils présentés dans cet article – y compris la récupération sélective des colonnes, le chargement impatient, l’indexation, l’optimisation des conditions et le traitement par lots – vous pouvez améliorer de manière significative la vitesse et l’efficacité de vos requêtes de base de données.

N’oubliez pas que l’optimisation de vos requêtes permet non seulement d’améliorer l’expérience de l’utilisateur, mais aussi de réduire la charge sur votre serveur de base de données. Gardez ces techniques d’optimisation à l’esprit et votre application Ruby on Rails fonctionnera sans problème, même avec de grandes quantités de données. Bon codage !

Lee Sheppard

Lee is an Agile certified full stack Ruby on Rails developer. With over six years in the tech industry he enjoys teaching, coaching Agile, and mentoring others. Lee also speaks at tech related events and has a background in design and illustration.