我正在使用Sidekiq进行高流量测试,使用Mongoid作为Rails 4应用程序中的驱动程序创建基于MongoDB的对象。我看到的问题是,当一个PlayByPlay
文档应该具有唯一的game_id
时,我会看到多个PlayByPlay对象是使用完全相同的game_id创建的。我也对MongoDB实施了唯一约束,这仍然存在。这是我的文档,它是嵌入式文档,以及我如何创建文档的一瞥。问题是这一切都发生在使用Sidekiq的线程环境中,我不确定是否有办法解决它。我的写入问题设置为1
中的mongoid.yml
,看起来safe
选项在主设备中被移除persist_in_safe_mode
。下面的代码 - 任何有关如何正确工作的建议将不胜感激。这不是副本集,它是一个MongoDB服务器,此时执行所有读/写请求。
module MLB
class Play
include Mongoid::Document
include Mongoid::Timestamps
embedded_in :play_by_play
field :batter#, type: Hash
field :next_batter#, type: Hash
field :pitchers#, type: Array
field :pitches#, type: Array
field :fielders#, type: Array
field :narrative, type: String
field :seq_id, type: Integer
field :inning, type: Integer
field :outs
field :no_play
field :home_team_score
field :away_team_score
end
class PlayByPlay
include Mongoid::Document
include Mongoid::Timestamps
embeds_many :plays, cascade_callbacks: true
accepts_nested_attributes_for :plays
field :sport
field :datetime, type: DateTime
field :gamedate, type: DateTime
field :game_id
field :home_team_id
field :away_team_id
field :home_team_score
field :away_team_score
field :season_year
field :season_type
field :location
field :status
field :home_team_abbr
field :away_team_abbr
field :hp_umpire
field :fb_umpire
field :sb_umpire
field :tb_umpire
index({game_id: 1})
index({away_team_id: 1})
index({home_team_id: 1})
index({season_type: 1})
index({season_year: 1})
index({"plays.seq_id" => 1}, {unique: true, drop_dups: true})
#validates 'play.seq_id', uniqueness: true
validates :game_id, presence: true, uniqueness: true
validates :home_team_id, presence: true
validates :away_team_id, presence: true
validates :gamedate, presence: true
validates :datetime, presence: true
validates :season_type, presence: true
validates :season_year, presence: true
def self.parse!(entry)
@document = Nokogiri::XML(entry.data)
xslt = Nokogiri::XSLT(File.read("#{$XSLT_PATH}/mlb_pbp.xslt"))
transform = xslt.apply_to(@document)
json_document = JSON.parse(transform)
obj = find_or_create_by(game_id: json_document['game_id'])
obj.sport = json_document['sport']
obj.home_team_id = json_document['home_team_id']
obj.away_team_id = json_document['away_team_id']
obj.home_team_score = json_document['home_team_score']
obj.away_team_score = json_document['away_team_score']
obj.season_type = json_document['season_type']
obj.season_year = json_document['season_year']
obj.location = json_document['location']
obj.datetime = DateTime.strptime(json_document['datetime'], "%m/%d/%y %H:%M:%S")
obj.gamedate = DateTime.strptime(json_document['game_date'], "%m/%d/%Y %H:%M:%S %p")
obj.status = json_document['status']
obj.home_team_abbr = json_document['home_team_abbr']
obj.away_team_abbr = json_document['away_team_abbr']
obj.hp_umpire = json_document['hp_umpire']
obj.fb_umpire = json_document['fb_umpire']
obj.sb_umpire = json_document['sb_umpire']
obj.tb_umpire = json_document['tb_umpire']
p=obj.plays.build(seq_id: json_document['seq_id'])
p.batter = json_document['batter']
p.next_batter = json_document['next_batter'] if json_document['next_batter'].present? && json_document['next_batter'].keys.count >= 1
p.pitchers = json_document['pitchers'] if json_document['pitchers'].present? && json_document['pitchers'].count >= 1
p.pitches = json_document['pitches'] if json_document['pitches'].present? && json_document['pitches'].count >= 1
p.fielders = json_document['fielders'] if json_document['fielders'].present? && json_document['fielders'].count >= 1
p.narrative = json_document['narrative']
p.seq_id = json_document['seq_id']
p.inning = json_document['inning']
p.outs = json_document['outs']
p.no_play = json_document['no_play']
p.home_team_score = json_document['home_team_score']
p.away_team_score = json_document['away_team_score']
obj.save
end
end
end
**注意**
如果我将sidekiq限制为1名工人,这个问题就会消失,这显然是在我从未做过的现实世界中。
答案 0 :(得分:3)
您已经拥有game_id
的索引,为什么不让它独一无二?这样db就不允许重复输入,即使mongoid无法正确进行验证( @vidaica 的答案描述了mongoid如何无法验证唯一性)。
尝试添加唯一索引
index({"game_id" => 1}, {unique: true})
然后
rake db:mongoid:create_indexes
在mongo中创建它们(请确保它是从mongo shell创建的。)
之后,mongodb不应该保留任何重复game_id
的记录,你必须在ruby层上处理你将从mongodb收到的插入错误。
答案 1 :(得分:2)
这是因为许多线程插入具有相同game_id的对象。让我解释一下。
例如,您有两个sidekiq线程t1和t2。它们并行运行。假设您有一个game_id 1
的文档,但尚未插入数据库。
t1进入parse
方法,它在game_id 1
数据库中看不到任何文档,它会创建一个game_id 1
的文档并继续填充其他数据,但它没有保存了文件。
t2进入parse
方法,它在game_id 1
数据库中看不到任何文档,因为此时t1尚未保存文档。 t2创建一个具有相同game_id 1
。
t1保存文件
t2保存文件
结果:您有两个文档具有相同的game_id 1
。
为了防止这种情况,您可以使用Mutex序列化解析代码的访问权限。要了解如何使用互斥锁,请阅读:http://www.ruby-doc.org/core-2.0.0/Mutex.html
答案 2 :(得分:1)
无论你做什么,你都希望在数据库级别上解决这个问题,因为你几乎肯定会做出最糟糕的工作来实现mongo人所做的独特约束。
假设您希望在某一天进行分片或考虑mongo由于其水平可伸缩性功能(您正在进行高容量测试,因此我认为这是您不希望通过设计排除的),可能没有可行的方法(参见Ramifications of working with a mongodb cluster和sharding concepts):
假设我们在电子邮件上进行分片并希望在用户名上有唯一索引。无法使用群集强制执行此操作。
但是,如果您在game_id
上进行分片,或者您根本没有考虑分片,那么在game_id
上设置唯一索引应该可以防止双重记录(请参阅 @xlembouras 回答)。
但是,由于竞争条件违反此索引,该答案可能无法阻止异常,因此请确保抢救该异常并执行更新而不是在救援区中创建(可能通过与@new_record (click 'Show source')一起玩,将试着抽出时间给你准确的代码。)
更新,简短快速回答
begin
a = Album.new(name: 'foo', game_id: 3)
a.save
rescue
a.id = id_of_the_object_with_same_id_already_in_db
a.instance_variable_set('@new_record', false)
a.save
end
答案 3 :(得分:0)
@ vidaica的回答很有帮助。如果您从内存或数据库中获取并递增ID,则可能会解决您的问题。
但是,您的game_id
未在parse
中生成,而是通过parse
JSON对象传递到entry
。
您的game_id
的生成方式/位置在哪里?
答案 4 :(得分:0)
一种天真的方法是将#parse
的最后一行更改为:
obj.save if where(game_id: obj.game_id).count == 0
或者如果你以某种方式处理它:
if where(game_id: obj.game_id).count == 0
# handle it here
end
但请注意,这仍然存在重复的可能性。
答案 5 :(得分:0)
也许你应该做一个upsert而不是insert:
obj = new(game_id: json_document['game_id'])
obj.upsert