【发布时间】:2020-10-17 05:25:21
【问题描述】:
我们在 Ruby on Rails / Postgres 中有一个数据库表,其中包含多达 100.000 个跨年的天气数据点,按小时计算:
01/01/1999 00:00
01/01/1999 01:00
...
01/01/2000 00:00
日期保存在名为timestamp 的datetime 变量中。
我们正在迭代weather_data,有时我们需要跳回 1-3 小时,再次检查不同的条件。
然后我们有多个活动,每个活动持续 1-6 小时,具体取决于天气是否足够好,或者是否需要等到天气好转。
用户可以选择一年中的哪一天开始检查,但它会从那一天开始检查数据库中的每一年。
如果用户选择“1997 年 4 月 3 日”,它应该从该日期开始运行所有活动,并查看所有活动需要多长时间。
然后它应该对“1998 年 4 月 3 日”和 1999 年以及 weather_data 中的所有可用年份重复该过程
有些活动可能需要 2 小时,但他们需要提前 4 小时了解天气,即使下一个活动可以在 2 小时后开始。所以有一点重叠。我希望用变量来解决这个问题,但无法弄清楚,因此我想到了在循环中来回“跳跃”。
简化示例:
# Collect all the years
the_years = weather_data.map { |y| y.timestamp.year }.uniq
the_years.each do |year|
start_date = DateTime.new(year, user_input.month, user_input.day)
# We could have ~100 activities
activities.each do |activity|
consecutive_good_weather_hours = 0
weather_data.where("timestamp >= ?", start_date).each do |point|
start_date += 1.hour
# checking if point.wind_speed > activity.wind_speed etc.
if weather_is_good
# ...
consecutive_good_weather_hours += 1
# if this activity needs 3 hours of good weather, and we have 2/3
# we go to the next data point, to check the next hour.
# go to next activity if all criteria is met
if activity_finished
# if this activity was 3 hours long, but we were checking 2
# hours extra into the future, we need to 'jump back' 2 hours
# where the next activity should start, a bit of overlap
start_date -= 2.hours
break
end
else
# bad weather, reset counter, and go to next weather hour
# try again to find x many hours of consecutive good weather
consecutive_good_weather_hours = 0
end
end
end
end
这有多优化?
看起来我们正在执行 300 次新的 SQL 查询,加载约 100k 的数据集(虽然每次都会缩小一点)。
我们可以在循环中向后“跳”3 步,而不是一直调用.where?如果是,怎么做?
编辑 1
我们将weather_data.where("timestamp >= ?", start_date).each do |point| 替换为以下内容:
while true
point = weather_data.find_by_timestamp(year_start_date)
我们还尝试将weather_data 复制到带有.to_a 的数组中(在所有循环之外),然后执行以下操作:
while true
point = data_array.find { |i| i.timestamp == year_start_date }
但事实证明速度较慢,请参阅基准。
20k 数据点和 4 个活动的基准测试:
| Option | points | ms | Allocations |
|-------------------|--------|------|-------------|
| where | 20k | 3028 | 5931134 |
| find_by_timestamp | 20k | 1101 | 725407 |
| data_array.find | 20k | 1304 | 1393532 |
我认为find_by_timestamp 会比array.find 慢,因为它会在每一点上执行SELECT,但它看起来是三个中最快的。
我们正在使用 Heroku,但我们的 1GB 实例在更大的数据集上内存不足。
【问题讨论】:
-
哈希应该更快,但这仍然不是你真正的问题,这可以通过记忆来解决,你应该询问 start_date 和一小时之间的每一点,进行计算并保存它们进入哈希表,然后在询问查询之前检查哈希和结果,如果值不存在,请检查查询,观看此视频,它应该为您提供一个良好的起点youtube.com/watch?v=P8Xa2BitN3I,您目前正在做类似的事情O(n!) 时间,如果我做对了,它应该在 O(n) 时间内完成,或者根据你的基准 ~3s
标签: ruby-on-rails ruby loops query-optimization