У меня есть массив под названием records
с тысячами хешей (см. Первый массив, показанный ниже). Каждый хэш в настоящее время содержит два поля id
и parent_id
. Я хочу добавить новое поле под названием updated_at
, которое хранится в базе данных (см. Второй массив ниже).
records = [{"id"=>3, "parent_id"=>2},
{"id"=>4, "parent_id"=>2}]
records = [{"id"=>3, "parent_id"=>2, "updated_at"=>"2014-03-21 20:44:35 UTC"},
{"id"=>4, "parent_id"=>2, "updated_at"=>"2014-03-21 20:44:34 UTC"}]
Мой первый подход заключается в следующем, но он выполняет запрос к базе данных для каждого хэша, поэтому, если у меня есть 1K хэшей в массиве, он будет выполнять 1K запросов, что, на мой взгляд, не очень хорошо с точки зрения производительности Посмотреть.
records.each do |record|
record['updated_at'] = Record.find(record['id']).updated_at.utc.to_s
end
Вы можете предложить мне лучшее решение?
3 ответа
Как насчет этого? Увеличьте количество запросов, объединяя идентификаторы по кусочкам за раз. Отрегулируйте сумму each_slice
на то, что работает хорошо ...
records.each_slice(250) do |records|
ids = records.map { |r| r['id'] }
results = Record.select([:id, :updated_at]).find(ids)
records.each do |rec|
result = results.find { |res| res.id == rec.id }
rec['updated_at'] = result.updated_at.utc.to_s
end
end
.find
в длинном списке (с элементами 1K или более). Я говорю это, потому что я уже пробовал это с 1 запросом, а затем я перенес проблему производительности с MySQL на Ruby, и это было даже медленнее, чем выполнение 1K запросов. Так что что-то смешанное может сработать.
.select([:id, :updated_at])
. Просто добавил это к моему ответу. Если Record
велико, он может выдать еще немного скорости.
.select .. fist
на .find
. Я также думаю, что вы можете получить меньше полей из вашего выбора, но не всю модель записи (вы только что поняли, прежде чем я написал это, спасибо!). Надеюсь увидеть больше подходов.
Как насчет этого?
plucked_records = Record.pluck(:id, :updated_at).find(records.map { |a| a.fetch("id") })
records.map! do |record|
plucked_records.each do |plucked_record|
record["updated_at"] = plucked_record.last.utc.to_s if plucked_record.first == record["id"]
end
record
end
Может быть, кто-то лучше импровизирует. :)
select
вместо pluck
, так как я работаю на Rails 3, а pluck не принимает более одного аргумента в этой версии. Но оставьте как есть, потому что я не упомянул версию Rails, и она лучше, чем select
для этого случая.
Проведя множество тестов и попробовав разные алгоритмы, я пришел к решению, которое работает очень быстро и кажется наиболее эффективным на данный момент.
Идея состоит в том, чтобы преобразовать результирующий массив записей db в хэш, поэтому поиск элементов в хэше происходит намного быстрее, чем в массиве.
Время результатов было получено из тестов, запущенных с использованием массива примерно из 4,5 КБ хэшей.
# My last approach
# Converting the returning records Array into a Hash (thus faster searchs)
# Benchmarks average results: 0.5 seconds
ids = records.map { |rec| rec['id'] }
db_records = Record.select([:id, :updated_at]).find(ids)
hash_records = Hash[db_records.map { |r| [r.id, r.updated_at.utc.to_s] }]
records.each do |rec|
rec["updated_at"] = hash_records[rec["id"]]
end
# Original approach
# Doing a SQL query for each pair (4.5K queries against MySQL)
# Benchmarks average results: ~10 seconds
records.each do |rec|
db_rec = Record.find(pair['id'])
rec["updated_at"] = db_rec.updated_at.utc.to_s
end
# Kirti's approach (slightly improved). Thanks Kirti!
# Unfortunaly searching into a lar
# Doing a single SQL query for all the pairs (then find in the array)
# Benchmarks average results: ~18 seconds
ids = records.map { |rec| rec['id'] }
db_records = Record.select([:id, :updated_at]).find(ids)
records.each do |rec|
db_rec = db_records.find { |f| f.id == pair["id"] }
rec["updated_at"] = db_rec.updated_at.utc.to_s
end
# Nick's approach. Thanks Nick! very good solution.
# Mixed solution levering in SQL and Ruby using each_slice.
# Very interesting results:
# [slice, seconds]:
# 5000, 18.0
# 1000, 4.3
# 500, 2.6
# 250, 1.5
# 100, 1.0
# 50, 0.9 <- :)
# 25, 1.0
# 10, 1.8
# 5, 2.3
# 1, 10.0
# Optimal slice value is 50 elements! (for this scenario)
# An scenario with a much costly SQL query might require a higher slice number
slice = 50
records.each_slice(slice) do |recs|
ids = recs.map { |pair| pair['id'] }
db_records = Record.select([:id, :updated_at]).find(ids)
recs.each do |rec|
db_rec = db_records.find { |f| f.id == rec["id"] }
rec["updated_at"] = db_rec.updated_at.utc.to_s
end
end
Похожие вопросы
Связанные вопросы
Новые вопросы
ruby
Ruby - это многоплатформенный динамический объектно-ориентированный интерпретируемый язык с открытым исходным кодом. Тег [ruby] предназначен для вопросов, связанных с языком Ruby, включая его синтаксис и его библиотеки. Вопросы Ruby on Rails должны быть помечены [ruby-on-rails].