0
votes

Rails Postgres requis ne renvoie pas de nouveaux enregistrements

Ce code RAILS est censé empêcher les enregistrements dupliqués d'être enregistré par le serveur dans les 20 secondes dans les 20 secondes: xxx pré>

Cependant, cela ne fonctionne pas. Je peux voir dans ma base de données de production (hébergée sur Heroku) Deux disques différents sont créés avec la même balise 10 secondes d'intervalle. P>

Les journaux affichent la requête correcte est exécutée sur la deuxième demande, mais il ne renvoie aucun résultat et enregistre de toute façon un nouvel enregistrement. P>

Pourquoi cela se produit-il? Je pensais que le niveau d'isolement par défaut de lecture par défaut des postgres empêcherait cela de se produire. La requête qui renvoie aucun enregistrement devrait manquer le cache SQL Rails. Les journaux affichent les deux demandes ont été gérées par la même web.1 Dyno sur Heroku, et mon puma.rb est mis en place pour 4 travailleurs et 5 threads. P>

Qu'est-ce que je manque? P>

Voici les deux enregistrements de la DB: P>

SHOW default_transaction_isolation;

 default_transaction_isolation 
-------------------------------
 read committed
(1 row)


10 commentaires

Vous devrez peut-être montrer comment @Transit est initialisé


Ajout de l'initialisation @Transit


Il y a une faute de frappe. le lieu où ne devrait pas avoir )


Pouvez-vous montrer les 2 requêtes dans le journal et les 2 enregistrements de la DB?


J'ai ajouté les journaux et les enregistrements DB comme sortie d'une console de rails. J'ai également corrigé la faute de frappe de ma question mentionnée par @leninrajrajrasekaran (qui n'était pas dans mon code actuel)


Avez-vous essayé d'extraire transit.where (tag: @ transit.tag). Où ("créé_at>?", 20.seconde.ago) .First dans une variable et imprimez le résultat?


Je peux essayer ça. Je soupçonne que cela doit être évalué à NIL car il ne consomme pas "TAG" DUPLIQUE ". Des problèmes sont que je n'ai vu que cela une fois sur mon serveur de production, donc je devrai créer un moyen de reproduire, peut-être avec un script Curl qui appelle le point de terminaison du contrôleur pour créer à plusieurs reprises des transits avec la même balise toutes les quelques secondes.


J'ai ajouté la journalisation pour afficher le résultat cru de transit.where (tag: @ transit.tag). Où ("créé_at>?", 20.Seconde.ago) .Pierst et je reçois soit nil ni un record de transit récent comme prévu. J'ai créé un script CURL pour essayer à plusieurs reprises de reproduire le problème sur mon serveur de production et je ne parviens pas à reproduire. La détection duplicate fonctionne parfaitement chaque fois que j'essaie. Pourtant, je suis incapable d'expliquer les journaux et les enregistrements créés hier en moins de 20 secondes les uns des autres.


Vous pouvez avoir un problème d'accès aux données simultané. En savoir plus à MakandraCards.com/makandra/...


@tkhoynh, convenu que cela est probablement un problème de concurrence. Je doutais que depuis que l'insert commit a été enregistré à 7:35:56 et la lecture a été enregistrée 14 secondes plus tard. Mais puisque les transactions de rails continuent d'asynchrones après les retours de ActionCord, si le contrat a pris 15 secondes pour l'appliquer, cela entraînerait ce problème. Cela semble long et improbable, mais possible. Je ne peux pas prouver que c'est ce qui s'est passé, mais il semble être la seule explication jusqu'à présent. Empêcher cela nécessiterait une procédure stockée DB ou comme Philippewright suggérée ou un verrou distribué comme vous et Kwerle suggéré.


3 Réponses :


-1
votes

C'est ce que le test est pour. xxx

vous testez le code: xxx

etc

P.S. Pensez à utiliser Redis ou quelque chose comme ça. Sinon, vous voulez faire quelque chose comme des serrures de table pour vous assurer de ne pas marcher sur vous-même. Et vous ne voulez probablement pas faire des serrures de table.


1 commentaires

J'ai une spécification de contrôleur pour cela, et cela passe, alors je pense qu'il est peu probable qu'il s'agisse d'un problème avec la logique.



0
votes

Un moyen d'empêcher les doublons dans les rails est avec des validations: Correction correcte de prévenir les enregistrements en double dans les rails

Cependant Vos critères sont plus complexes car ils traitent de plus d'une ligne. Je pense que vos critères sont, n'autorisent pas l'entrée d'un enregistrement de transit si l'enregistrement de transit le plus récent a été créé il y a moins de 20 secondes. Est-ce correct? P>

Essayer de faire respecter une contrainte qui consiste à regarder des données de nombreuses lignes est mentionnée comme indésirable ici: SQL Sous-requêtes dans la contrainte de contrôle P>

Un déclencheur pourrait être utilisé pour appliquer votre contrainte au niveau de la base de données. On pourrait attraper la gâchette dans une exception. Il y a un gemme nommé huillard qui pourrait être utile, pas sûr. P>

Prendre des idées d'ici: https : //karolganancienk.com/blog/2016/05/06/When-validation-is-not-Eveng-Postgresql-triggers-for-Data-Integrity/ P>

Exemple avec la gâchette postgreSQL : p> xxx pré>

Voici la partie de notre requête qui donne la dernière entrée de transit avec notre étiquette p> xxx pré>

modifiant pour donner La différence entre le courant_timettamp (maintenant) et la dernière entrée de transit avec notre balise. Cette différence est un intervalle dans PostgreSQL. Utilisation de UTC pour correspondre aux rails: P>

#!/usr/bin/env ruby

require 'active_record'
require 'action_view'

path = "/home/user1/rails/dup_test/app/models"
require "#{path}/application_record.rb"
Dir.glob(path + "/*.rb").sort.each do | file |
  require file
end

ActiveRecord::Base.establish_connection(
  :adapter => "postgresql",
  :database  => 'dup_test_development',
  encoding: "unicode",
  username: "user1",
  password: nil
)
class Test
  def initialize()
  end
  def go()
    begin
      t = Transit.new(tag: 'test_tag')
      t.save
    rescue ActiveRecord::StatementInvalid => e
      p e
      p e.cause.message
    end
  end
end

def main
  begin
    t = Test.new()
    t.go()
  rescue Exception => e
    puts e.message
  end
end

main


0 commentaires

0
votes

Je crois que c'était une question de concurrence.

Les transactions RAILS continuent de manière asynchrone après les retours Activerecord. À tout moment, le commit prend 15 secondes pour l'appliquer, cela entraînera ce problème. Ceci est long et improbable, mais possible.

Je ne peux pas prouver que c'est ce qui s'est passé, mais il semble être la seule explication. Empêcher cela nécessiterait une procédure stockée DB ou comme @philipwright suggérée ou un verrou distribué comme vous et @kwerle suggéré.


0 commentaires