Retour aux Labs
Ruby on RailsSidekiqPerformance

Ce code Sidekiq fonctionne parfaitement... jusqu'à votre mise en production.

En développement, cette implémentation paraît irréprochable.

En production, lorsque plusieurs centaines de milliers de jobs sont planifiés, elle peut devenir une source importante de latence.

def cancel_ride_caution_emails
  jobs = Sidekiq::ScheduledSet.new
           .scan('EmailSenderJob')
           .select { |job| job.klass == 'EmailSenderJob' }

  jobs.each do |job|
    if job.args.first == 'course_caution_email' &&
       job.args.fifth == ride.id

      job.delete
    end
  end
end

Le début de l'histoire

En développement nous testons généralement avec quelques jobs, quelques dizaines de jobs, parfois quelques centaines.

Tout fonctionne parfaitement. Puis l'application grandit. Les utilisateurs augmentent. Les jobs programmés aussi.

Et soudain une simple annulation devient lente.

Voyez-vous où se cache réellement le problème ?

En développement

Scheduled Jobs

25 Jobs

scan()

25 comparaisons

Suppression

Impossible de remarquer un problème de performances.

En production

Scheduled Jobs

850 000 Jobs

scan()

850 000 comparaisons

Suppression d'un seul job

Une opération simple devient proportionnelle au volume total.

Le code est exactement le même.

La volumétrie, elle, ne l'est plus.

Pourquoi ?

Le problème n'est pas la suppression.

Le problème ne vient pas de job.delete. Il vient du fait qu'il faut parcourir une grande partie des jobs planifiés pour retrouver celui à supprimer.

Plus la volumétrie augmente, plus cette opération devient coûteuse.

Ce genre de problème est presque invisible en développement. Le code est lisible, les tests passent, l'annulation semble instantanée. La fragilité n'apparaît qu'au moment où la production introduit une donnée que le poste local ne simule jamais : le volume.

Améliorations

Comment améliorer cette implémentation ?

Solution 1

Stocker le JID

jid = EmailSenderJob.perform_in(...)

ride.email_job_jid = jid

En conservant le JID lors de la planification, on peut retrouver directement le job sans parcourir tous les jobs programmés.

Très performant

Solution 2

Ne pas supprimer le job

return if ride.cancelled?

Le job démarre mais quitte immédiatement. Pas de scan, pas de recherche globale, et une logique très simple à maintenir.

Souvent suffisant

Solution 3

Créer un index Redis

ride_id
jid
Redis
jid
Suppression directe

On ne recherche plus dans tous les Scheduled Jobs. On connaît immédiatement le job concerné.

Adapté aux très gros volumes

À retenir

Les problèmes de performances ne viennent pas toujours d'un mauvais code.

Ils apparaissent souvent lorsqu'une implémentation parfaitement acceptable en développement rencontre la réalité de la production.

Un code qui parcourt 20 éléments peut devenir un véritable goulot d'étranglement lorsqu'il doit en parcourir plusieurs centaines de milliers.

Performance Rails

Besoin d'optimiser une application Ruby on Rails ?

J'accompagne les équipes sur Ruby on Rails, Sidekiq, Redis, PostgreSQL, architecture backend et optimisation des performances en production.