Écrivez-vous du code dans Rails en pensant à la mémoire?
Rails utilise le ramasse-miettes de Ruby ** * 1 **, vous pouvez donc écrire du code sans vous soucier de libérer de la mémoire.
* 1 Ruby collecte les objets qui ne sont plus utilisés et libère automatiquement la mémoire.
Par conséquent, il existe des cas où le serveur de production tombe soudainement en panne (en raison d'une erreur de mémoire) sans le remarquer, même si l'implémentation est telle qu'il consomme de la mémoire sans le savoir.
La raison pour laquelle je peux dire cela est que ce phénomène s'est produit sur le site où je travaille actuellement.
J'étais en charge de la modification de l'implémentation, mais j'ai beaucoup appris de cette expérience, je vais donc laisser une note pour ne pas l'oublier.
Tout d'abord, vous devez rechercher où se produit l'erreur de mémoire.
J'ai utilisé ʻObjectSpace.memsize_of_all` pour étudier l'utilisation de la mémoire dans Rails.
En utilisant cette méthode, vous pouvez étudier l'utilisation de la mémoire consommée par tous les objets vivants en octets.
Nous installerons cette méthode comme point de contrôle à l'endroit où le processus d'exécution est susceptible de chuter, et étudierons régulièrement où il consomme une grande quantité de mémoire.
■ Exemple d'utilisation pour vérifier l'utilisation de la mémoire
class Hoge
def self.hoge
puts 'Nombre de mémoires d'objets avant l'extension de la mémoire par carte'
puts '↓'
puts ObjectSpace.memsize_of_all <====Point de contrôle
array = ('a'..'z').to_a
array.map do |item| <==== ①
puts "#{item}Nombre de mémoires d'objets de"
puts '↓'
puts ObjectSpace.memsize_of_all <====Point de contrôle
item.upcase
end
end
end
■ Résultat d'exécution
irb(main):001:0> Hoge.hoge
Nombre de mémoires d'objets avant l'extension de la mémoire par carte
↓
137789340561
Nombre de mémoires d'objets d'un
↓
137789342473
Nombre de mémoires d'objets en b
↓
137789342761
Nombre de mémoires d'objets en c
↓
137789343049
Nombre de mémoires d'objets en d
↓
137789343337
Nombre de mémoires d'objets de e
↓
137789343625
.
.
.
Nombre de mémoires d'objets de x
↓
137789349097
Nombre de mémoires d'objets de y
↓
137789349385
Nombre de mémoires d'objets en z
↓
137789349673
=> ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]
À partir de ce résultat d'exécution, vous pouvez voir que les données passées par la carte sont d'abord développées en mémoire à la fois et que la consommation de mémoire y a augmenté. (Partie ①)
Vous pouvez également voir que la consommation de mémoire augmente avec chaque processus de boucle.
Il n'y a aucun problème si le processus est simple comme cet exemple de code.
Si la quantité de données transmises est importante et que la mise en œuvre effectuée par le traitement en boucle est compliquée, la consommation de mémoire sera réduite.
** J'obtiens une erreur de mémoire (une erreur qui se produit lorsque le traitement de la mémoire ne peut pas suivre). ** **
Cette enquête a également été étudiée par la procédure ci-dessus et, par conséquent, il a été conclu qu'une erreur de mémoire s'était produite car la quantité de données transmises était importante et le traitement intensif des requêtes de crachat avec la carte a été mis en œuvre.
Je comprends la cause.
Pensons ensuite aux contre-mesures.
Les premières mesures que j'ai proposées sont les trois suivantes.
1.Augmentez la mémoire avec le pouvoir de l'argent
2. Thread(fil)Effectuer un traitement parallèle avec
3.Le traitement par lots
Pour être honnête, c'est le plus rapide, et vous n'avez qu'à augmenter les spécifications de mémoire du serveur avec la puissance de l'argent, alors faisons-le!
J'ai pensé.
Il n'y a pas d'implémentation gourmande en mémoire autre que ce processus, donc j'ai pensé qu'il serait insensé de dépenser de l'argent juste pour cette partie, alors j'ai arrêté cette idée.
J'ai proposé le traitement parallèle de Ruby comme prochaine contre-mesure, mais si le goulot d'étranglement est le temps de traitement (timeout), il est correct car il sera plus rapide si plusieurs threads sont configurés et calculés en parallèle et fusionnés, mais cette fois Étant donné que le goulot d'étranglement est la pression de la mémoire due à une erreur de mémoire, la quantité de données gérées par plusieurs threads ne change pas, donc on s'attend à ce qu'une erreur de mémoire se produise à la fin, j'ai donc arrêté cette idée.
La principale cause de cette erreur de mémoire est une erreur de mémoire qui se produit lorsqu'une grande quantité de données est développée à la fois et que le traitement à charge élevée est répété dans une boucle.
Par conséquent, j'ai pensé qu'il serait bon qu'une grande quantité de données puisse être implémentée tout en économisant de la mémoire en la divisant en unités telles que 1000 par traitement par lots sans augmenter la mémoire à la fois.
Rails fournit une méthode appelée find_in_batches
, qui peut être utilisée pour traiter 1000 éléments à la fois par défaut.
Exemple) 10,1 pour 000,Divisez en 000 processus et divisez en 10 processus par lots.
find_in_Une image qui utilise moins de mémoire en limitant le traitement avec des lots.
** Traitement par lots à l'aide de find_in_batches **
Une fois que vous savez comment y faire face, il ne vous reste plus qu'à le mettre en œuvre.
Mettons-le en œuvre. (Comme il n'est pas possible d'afficher réellement le code de l'entreprise, seule l'image est affichée)
■ Image de mise en œuvre
User.find_in_batches(batch_size: 1000) do |users|
#Quelque chose de traitement
end
Même si 10 000 données utilisateur sont acquises, 1 000 seront traitées à l'aide de find_in_batches.
En d'autres termes, c'est une image qui se divise en 10 000/1 000 = 10 processus.
La consommation de mémoire a été réduite à 1/100.
** Cependant, le plus gros inconvénient de cette implémentation est qu'elle prend trop de temps de traitement. ** **
Si vous utilisez heroku etc., cette implémentation entraînera une erreur ** RequestTimeOut * 1 **.
* 1 Dans heroku, le traitement qui prend 30 secondes ou plus entraînera une erreur Request Time Out.
Par conséquent, je pense qu'il est préférable de déplacer cette implémentation de traitement à charge élevée vers le traitement en arrière-plan.
Si vous utilisez Rails, vous pouvez le faire en utilisant Sidekiq.
Je pense que vous devriez travailler avec la procédure suivante.
STEP1. find_in_Utilisez des lots pour réduire la consommation de mémoire
STEP2.Lorsque STEP1 est terminé, cela prendra un certain temps, mais il devrait être dans un état de fonctionnement sans erreur de mémoire.
Cependant, le traitement prend du temps, alors déplacez ce processus en arrière-plan.
Au début, je pensais que c'était une tâche ennuyeuse.
J'ai beaucoup appris et je suis content de l'avoir mis en œuvre maintenant.
https://techblog.lclco.com/entry/2019/07/31/180000 https://qiita.com/kinushu/items/a2ec4078410284b9856d
Recommended Posts