使用 postgresql 和 ruby​​ on rails 优化时间序列数据检索的数据库查询

Posted

技术标签:

【中文标题】使用 postgresql 和 ruby​​ on rails 优化时间序列数据检索的数据库查询【英文标题】:Optimize database query for time series data retrieving using postgresql and ruby on rails 【发布时间】:2017-10-19 12:06:14 【问题描述】:

大家好,我正在开发一个有趣的实时应用程序。 应用如下。我有一个meter模型和meter_info模型

calss Meter
  has_many :meter_infos
  # filed: id 
end

class MeterInfo
  belongs_to :meter
  # field: meter_id, voltage 
end

每两分钟就有一个新数据被保存到meter_info table。所以你可以想象那里有一个巨大的数据集。

现在我要做的是在 1 天内以 10 分钟的间隔一次准确地找出 10 个meters 的电压记录。 所以结果会是这样的

id           created_at          meter_id      voltage
2001     2017-10-19 15:40:00        2             100
2001     2017-10-19 15:45:00        1             100
2001     2017-10-19 15:39:00        3             100
2001     2017-10-19 15:48:00        4             100
2001     2017-10-19 15:38:00        5             100
2001     2017-10-19 15:42:00        6             100
...
...

我已经尝试了几个查询,但由于查找记录花费了太多时间,因此请求超时。这是我尝试过的

(('2017-07-02 00:00:00').to_datetime.to_i .. 
  ('2017-07-02 23:59:59').to_datetime.to_i).step(10.minutes) do |date|
                query = "SELECT  created_at, meter_id, voltage
                FROM meter_infos
                WHERE created_at between  '#Time.at(date).utc' and 
                '#Time.at(date).utc + 10.minutes'
                AND meter_id in (1,2,3,4,5)
                ORDER BY id desc limit 1"

                voltages = ActiveRecord::Base.connection.execute(query)

end

即使在开发环境中也会超时。 然后我尝试使用Postgresqlgenerated_series,如下所示

  query= "SELECT meter_id,voltage,  count(id) as ids
              , GENERATE_SERIES( timestamp without time zone '2017-10-19',
                  timestamp without time zone '2017-10-19',
                 '10 min') as time_range
              from meter_infos
              where meter_infos.created_at between '2017-10-19 00:00:01'::timestamp and  '2017-10-19 23:59:59'::timestamp
              and meter_infos.meter_id in (1,2,3,4,5)
                  GROUP BY meter_id, voltage
              ORDER BY meter_id ASC limit 1"

            sbps_plot = ActiveRecord::Base.connection.execute(query)

哪个更快,但给了我错误的数据。 我正在使用Ruby on RailsPostgresql。 有人可以帮我编写更快的查询来找出时间数据,或者建议我处理时间序列数据分析的任何程序。 提前致谢。

【问题讨论】:

您是否为这些表编制了索引? 想法:一天只有固定的 10 分钟间隔,对吧?只需先解决这些问题,然后在一个查询中恢复所有内容。或者在您的查询中,将时间戳转换为秒,然后对 600(以秒为十分钟)执行 mod 操作,并仅选择余数在 0 到 119 之间的记录。 @Justme 是的,它已经被索引了。 另一个提示:我很确定您的第一个查询版本只是取回所有记录的缓慢方法。如果您以 10 分钟间隔升级,那么您的查询涵盖 10 分钟范围,您将取回所有内容。您的查询应仅涵盖从 10 分钟开始的 2 分钟范围。 向我们展示解释分析 【参考方案1】:

您每两分钟就有一次记录,但您想从十分钟间隔获取一个样本记录。这是我建议的解决方案:

您可以将created_at 时间戳的纪元时间模数取为 600(十分钟以秒为单位)。然后将其与一些“容差”值(例如 119 秒或更短)进行比较,以防您的记录的时间戳未与完美的十分钟间隔对齐。想象一下,在一天中的每 10 分钟间隔之后的 2 分钟窗口内检索带有 created_at 的第一条记录。

例如,

MeterInfo
  .where(
    meter_id: [1, 2, 3, 4, 5], 
    created_at: your_date.beginning_of_day..your_date.end_of_day
  )
  .where("(cast(extract(epoch from created_at) as integer) % 600) < 119")

试试看它是否适合你。

【讨论】:

它对我很有用。感谢您的解决方案。但我想知道这个(cast(extract(epoch from created_at) as integer) % 600) &lt; 119") 代码实际上是如何工作的。请您详细解释一下。我已经接受了您的回答。谢谢。 当然。 extract(epoch from created_at) 从 created_at 日期为您提供一个纪元日期/时间——这是自 1970 年 1 月 1 日 00:00 以来的秒数。出于模数计算的目的,cast(... as integer) 将其视为整数。 % 600 执行模数除法——除以 600 后得到余数,我们知道它是以秒为单位的 10 分钟。这为您提供了一个介于 0-600 之间的值。 00:01 将是 60,00:05 将是 300,依此类推。这适用于一天中的任何时间,因此 13:41 将是 60,13:45 将是 300。 然后我们要选择

以上是关于使用 postgresql 和 ruby​​ on rails 优化时间序列数据检索的数据库查询的主要内容,如果未能解决你的问题,请参考以下文章

Ruby on Rails+PostgreSQL:自定义序列的使用

在 Ruby on Rails 3.2.14 / Ruby 2.0.0 / PostgreSQL 9.2.4 中使用 activerecord 从序列中检索 nextval

在 Ubuntu 上为 Ruby on Rails 安装 PostgreSQL

设置默认值时出现空约束错误 Ruby On Rails - Postgresql

Heroku上的现有Ruby on Rails Web应用程序(PostgreSQL),Devise身份验证,需要为移动支持添加Rails API

将重复的SQL模型复制到Ruby on Rails postgresql模型中