【问题标题】:I need advice in speeding up this rails method that involves many queries我需要建议来加快这种涉及许多查询的 rails 方法
【发布时间】:2018-07-11 02:24:27
【问题描述】:

我正在尝试显示一个表格,该表格对 webhook 进行计数,并将各种计数按 date_sent、sending_ip 和 esp(电子邮件服务提供商)排列到单元格中。在每个单元格中,控制器需要计算标记为“打开”事件和“发送”事件的 webhook。我们的数据库目前包含数百万个 webhook,并且每天至少增加 100k。这个过程已经花费了很长时间,以至于运行这个索引方法实际上是没有用的。

我希望 Rails 可以使用这样的一行将庞大的模型分解为更小的列表:

@today_hooks = @m_webhooks.where(:date_sent => this_date)

我认为这行之后的查询只会查看部分列表,而不是完整模型。不幸的是,运行这个索引方法会生成数百条 SQL 语句,它们都看起来像这样:

SELECT COUNT(*) FROM "m_webhooks" WHERE "m_webhooks"."date_sent" = $1 AND "m_webhooks"."sending_ip" = $2 AND (m_webhooks.esp LIKE 'hotmail') AND (m_webhooks.event LIKE 'sent') 

这似乎“date_sent”属性包含在所有查询中,这意味着 SQL 正在搜索每个查询的所有 1M 记录。

我已经阅读了十多篇关于提高 Rails 查询性能的文章,但我在其中发现的所有技巧都没有减少完成此方法所需的时间。提前感谢您提供任何见解。

m_webhooks.controller.rb

def index
    def set_sub_count_hash(thip) {
      gmail_hooks: {opened: a = thip.gmail.send(@event).size, total_sent: b = thip.gmail.sent.size, perc_opened: find_perc(a, b)},
      hotmail_hooks: {opened: a = thip.hotmail.send(@event).size, total_sent: b = thip.hotmail.sent.size, perc_opened: find_perc(a, b)},
      yahoo_hooks: {opened: a = thip.yahoo.send(@event).size, total_sent: b = thip.yahoo.sent.size, perc_opened: find_perc(a, b)},
      other_hooks: {opened: a = thip.other.send(@event).size, total_sent: b = thip.other.sent.size, perc_opened: find_perc(a, b)},
     }
    end

    @m_webhooks = MWebhook.select("date_sent", "sending_ip", "esp", "event", "email").all
    @event = params[:event] || "unique_opened"

    @m_list_of_ips = [#List of three ip addresses]

    end_date = Date.today
    start_date = Date.today - 10.days
    date_range = (end_date - start_date).to_i
    @count_array = []
    date_range.times do |n|
      this_date = end_date - n.days
      @today_hooks = @m_webhooks.where(:date_sent => this_date)
      @count_array[n] = {:this_date => this_date}
      @m_list_of_ips.each_with_index do |ip, index|
        thip = @today_hooks.where(:sending_ip => ip)  #Stands for "Today Hooks ip"
        @count_array[n][index] = set_sub_count_hash(thip)
      end
    end

【问题讨论】:

    标签: ruby-on-rails postgresql rails-activerecord


    【解决方案1】:

    嗯,其实你的问题很简单。您必须记住,当您使用where(condition) 时,查询不会直接在数据库中执行。

    Rails 足够智能,可以检测您何时需要具体结果(列表、对象或计数或#size,就像您的情况一样)并在您不需要查询时链接您的查询。在您的代码中,您将条件链接到循环内的主查询 (date_range)。更糟糕的是,您在这个循环中启动另一个循环,为第一个循环中创建的每个查询添加条件。

    然后您将查询(还不是具体的,它尚未执行并且没有结果!)传递给方法set_sub_count_hash,该方法继续多次调用相同的查询。

    因此你有类似的东西:

    10(date_range) * 3(ip list) * 8 # (times the query is materialized in the #set_sub_count method) 
    

    然后你就有问题了。

    您想要做的是一次完成整个查询并将其按dateipemail 分组。之后你应该有一个哈希结构,你可以将它传递给#set_sub_count 方法并做一些红宝石体操来获得你正在寻找的计数。

    我想像这样的查询:

    main_query = @m_webhooks.where('date_sent > ?', 10.days.ago.to_date)
                             .where(sending_ip:@m_list_of_ips)    
    

    好的,现在你有一个查询,这很好,但我认为你应该将查询分成 4 个(gmail、hotmail、yahoo 和其他),这给你 4 个查询(第一个,main_query,不会执行直到你要求物化结果,不要忘记它)。尽管如此,还是快了 100 倍。

    我认为这是应该分组、映射并传递给#set_sub_count 的结果,而不是每次多次传递原始查询和调用方法。进行分组、映射和计数肯定会做一些工作,但是嘿,它更快。 =)

    【讨论】:

    • 所以在我建立 main_query 之后,我需要强制它实现,对吗?我应该使用 .inspect 之类的方法吗?
    • 不,从来没有。 #inspect 仅用于调试。嗯...好吧,当您使用 #count、#size、#to_a、#pluck、#group_by 或 #map 等方法时(我敢肯定还有其他方法),您是在强制执行结果。
    • 感谢您的建议。您的回答帮助我更好地理解 Rails 查询过程,这使我找到了我发布的答案。顺便说一句,我确实喜欢你的缩进方法。正如您在我的回答中看到的那样。 =P
    • 不错!我很高兴!
    【解决方案2】:

    如果这对其他人有帮助,我学习了如何以更简单的方式用计数填充散列。更重要的是,这种方法只运行一个查询(而不是我之前运行的 240 个查询)。

    @count_array[esp_index][j] = MWebhook.where('date_sent > ?', start_date.to_date)
                                  .group('date_sent', 'sending_ip', 'event', 'esp').count
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-01
      • 1970-01-01
      • 2015-07-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多