[RUBY] Comment supprimer de grandes quantités de données dans Rails et problèmes

introduction

Bonjour à tous! Je suis @hiroki_tanaka, le producteur de Mayu Sakuma.

Je suis impliqué dans la maintenance des applications Rails, et l'autre jour, il s'est avéré qu'il y avait beaucoup de données indésirables dans l'environnement de production. C'est pourquoi j'ai envisagé comment supprimer une grande quantité de données dans Rails, j'ai donc résumé ce que j'avais étudié.

Différence entre détruire et supprimer dans Rails

Premièrement, Rails a deux méthodes de suppression de données, détruire et supprimer. Je voudrais résumer brièvement les différences entre eux.

destroy/destroy! Supprime un enregistrement spécifié via ActiveRecord. Les méthodes de rappel (telles que before_destroy et ʻafter_destroy) et la validation fonctionnent via ActiveRecord. De plus, s'il y a un modèle associé avec dependant :: destroy` défini dans le modèle à supprimer, le modèle d'ensemble est également supprimé. Si destroy rencontre une erreur lors de l'exécution et ne peut pas être supprimé, il renvoie uniquement false et ne renvoie pas d'exception. En revanche, destroy! Renvoie une exception. Par conséquent, si vous souhaitez intercepter explicitement l'erreur de suppression, il est préférable d'utiliser destroy!.

delete Émettez SQL (instruction DELETE) directement dans la base de données sans passer par ActiveRecord pour supprimer l'enregistrement cible. La méthode de rappel et la validation ne fonctionnent pas car elles ne passent pas par ActiveRecord. De plus, même s'il y a un modèle associé à dependant :: destroy dans le modèle à supprimer, il ne sera pas supprimé. Le comportement au moment de l'échec est comme destroy, il ne renvoie que false et ne renvoie pas d'exception. Depuis delete! N'existe pas dans delete, je pense qu'il vaut mieux utiliser destroy! Obéissant quand "je veux supprimer des données et renvoyer une erreur en cas d'échec".

destory_all Un seul enregistrement peut être supprimé avec destroy / destroy!, Mais history_all peut spécifier plusieurs enregistrements et supprime tous les enregistrements spécifiés. Comme destroy, history_all passe également par ActiveRecord, donc la méthode de rappel et la validation · dependant :: destroy fonctionnent. Comme destroy, destory_all provoque une erreur lors de l'exécution, et si le processus de suppression échoue au milieu, il ne renvoie que false et ne renvoie pas d'exception. Cependant, il n'y a pas de méthode appelée destory_all!, Donc si vous voulez supprimer une grande quantité de données, mais si cela échoue, vous devez renvoyer une erreur correctement. (La méthode sera décrite plus tard.)

delete_all Supprime les multiples enregistrements spécifiés sans passer par ActiveRecord. Tout comme la suppression, la méthode de rappel et la validation · dependant :: destroy ne fonctionnent pas. Comme delete, delete_all provoque également une erreur lors de l'exécution, et si le processus de suppression échoue au milieu, il ne renvoie que false et ne renvoie pas d'exception. Personnellement, je ne pense pas qu'il y ait beaucoup de situations où il est utilisé, mais le traitement sans ActiveRecord est plus rapide que destroy et story_all. Par conséquent, je pense qu'il peut être utilisé dans des situations où vous souhaitez supprimer une grande quantité de données à la fois sans vous soucier des exceptions, des rappels et des éléments associés.

Comment supprimer une grande quantité de données

Je voudrais voir comment supprimer une grande quantité de données dans le sujet principal.

supposition

Parmi les modèles à supprimer cette fois, il y a aussi des modèles associés au callback et à dependante :: destroy. En tant qu'exigence, le modèle associé doit également être supprimé et je souhaite détecter explicitement s'il y a une erreur pendant le traitement. Delete / delete_all / destroy_all n'est pas disponible à ce stade. Ensuite, vous devez effectuer le processus de suppression dans les coulisses de l'exécution de l'application de production. Donc, je ne veux pas prendre une méthode qui met une charge extrême sur la base de données.

Méthode (1): Détruisez! Un par un sans créer de transaction

  animals = Animal.where(type: 'dog') #Extraction de données à supprimer
  animals.each do |animal|
    animal.destroy!
  end

La façon la plus simple d'y penser est d'avoir un code comme celui-ci. Cependant, il y a deux problèmes.

―― La charge est lourde car destroy continue de fonctionner 100 000 fois.

Par conséquent, s'il y a de la capacité disponible dans la base de données, si l'application a une heure de fermeture claire et s'il n'y a pas de problème même si la suppression ne revient pas au milieu de l'échec, cette méthode convient si l'intégrité des données n'est pas un problème. Je pense.

Méthode (2): Après avoir effectué une transaction, détruisez! Un par un

  animals = Animal.where(type: 'dog') #Extraction de données à supprimer
  ActiveRecord::Base.transaction do
    animals.each do |animal|
      animal.destroy!
    end
  end

Le processus de suppression de la méthode (1) est défini sur une transaction. Si une erreur se produit au milieu du processus de suppression en effectuant une seule transaction, tous les processus de suppression seront annulés et la restauration sera effective. Par conséquent, si vous utilisez cette méthode, vous pouvez supprimer en toute sécurité toutes les données qui nécessitent l'intégrité des données.

Une mise en garde est que vous devez toujours utiliser destroy! Lorsque vous créez explicitement une transaction. Cela est dû au fait que destroy ne déclenche pas d'exception et renvoie false même si une erreur se produit, de sorte que le processus ne s'arrête pas et la transaction ne peut pas être quittée.

Méthode ③: Après avoir effectué une transaction, extrayez les données tous les 1000 cas et effectuez destroy!. Insérez un sommeil de 0,1 seconde entre les processus de suppression.

  animals = Animal.where(type: 'dog') #Extraction de données à supprimer
  ActiveRecord::Base.transaction do
    animals.in_batches.each do |delete_target_animals|
      delete_target_animals.map(&:destroy!)
      sleep(0.1)
    end
  end

La méthode ci-dessus est la méthode adoptée cette fois. Utilisez la méthode ActiveRecord :: Relation # in_batches pour combiner 100 000 enregistrements en 1 000 unités. Et détruisez! Pour chacun des morceaux. Ensuite, lorsque le processus de suppression pour 1000 éléments est terminé, le processus est arrêté pendant 0,1 seconde avec sleep (0,1) pour réduire la charge sur la base de données. De plus, comme une transaction importante est placée à l'extérieur, même si une erreur se produit pendant le processus de suppression, tout sera annulé et la refonte sera efficace, donc c'est sûr.

  animals = Animal.where(type: 'dog') #Extraction de données à supprimer
  ActiveRecord::Base.transaction do
    animals.in_batches(of: 10000).each do |delete_target_animals|
      delete_target_animals.map(&:destroy!)
      sleep(0.1)
    end
  end

en conclusion

Je pense qu'il existe différentes façons de supprimer de grandes quantités de données, en fonction de vos besoins. Par conséquent, si vous avez la meilleure pratique, j'aimerais avoir de vos nouvelles (o._.) O Peco

Recommended Posts

Comment supprimer de grandes quantités de données dans Rails et problèmes
[Webpacker] Résumé de l'installation de Bootstrap et jQuery dans Rails 6.0
Comment utiliser JQuery dans Rails 6 js.erb
Comment modifier le nombre maximum et maximum de données POST dans Spark
Comment créer une combinaison unique de données dans la table intermédiaire des rails
[Rails] Comment supprimer les données MySQL de l'environnement de production après les avoir placées dans l'environnement de développement
[Rails] Comment définir des macros dans Rspec et standardiser le traitement
[Rails] Différentes façons de supprimer des données
Comment installer jQuery dans Rails 6
Comment installer Swiper in Rails
[Rails] Comment obtenir l'URL de la source de transition et la rediriger
Comment obtenir et ajouter des données depuis Firebase Firestore dans Ruby
Comment implémenter la fonctionnalité de recherche dans Rails
Comment changer le nom de l'application dans les rails
[Docker] Comment sauvegarder et restaurer les données de base de données de l'application Rails sur docker-compose [MySQL]
Comment insérer une vidéo dans Rails
Comment utiliser MySQL dans le didacticiel Rails
[rails] Comment configurer le routage dans les ressources
Comment implémenter la fonctionnalité de classement dans Rails
Comment supprimer des données avec une clé externe
Emplacement de la définition de la méthode Résumé de la vérification Lorsque défini dans le projet et Rails / Gem
Comment écraser les données Firebase avec Swift
Comment utiliser credentials.yml.enc introduit à partir de Rails 5.2
[Rails] Je souhaite envoyer des données de différents modèles dans un formulaire
Promesse JDBC et exemple d'écriture
[Order method] Définit l'ordre des données dans Rails
[Rails] Comment utiliser les boîtes de sélection dans Ransack
Comment traduire Rails en japonais en général
Comment ajouter conditionnellement une classe html.erb dans Rails
Comment implémenter une fonctionnalité similaire dans Rails
Comment créer facilement un pull-down avec des rails
Comment créer une API avec GraphQL et Rails
[Rails] Comment émettre des messages de réussite et d'erreur
Rails "Comment supprimer les fichiers de migration NO FILE"
[Rails] Comment utiliser PostgreSQL dans l'environnement Vagrant
Comment vérifier les commandes Rails dans le terminal
[Rails] Classement et pagination par J'aime
Comment stocker simultanément des données dans un modèle associé à une forme imbriquée (Rails 6.0.0)
Résumé des commandes fréquemment utilisées dans Rails et Docker
Comment effacer toutes les données d'une table particulière
Comment supprimer / mettre à jour le champ de liste de OneToMany
Comment régler l'heure d'affichage sur l'heure japonaise dans les rails
Comment implémenter la connexion invité en 5 minutes sur le portefeuille de rails
Comment installer Docker dans l'environnement local d'une application Rails existante [Rails 6 / MySQL 8]
Comment écrire des rails
[Java] Types de commentaires et comment les rédiger
[Rails, JS] Comment implémenter l'affichage asynchrone des commentaires
Changer la date et l'heure en notation japonaise dans Rails
[Rails] Comment rechercher dans les colonnes de modèles associés (parents et enfants) dans Ransack
Comment créer un URI de données (base64) en Java