如何从Rails中取消昂贵的Postgresql查询?

时间:2017-04-26 11:49:47

标签: ruby-on-rails postgresql heroku

在我的Rails应用程序中,我有一个负责生成报告的控制器。其中一些报告需要花费大量时间来生成,超过Heroku的30秒限制。在这种情况下,我想在25秒后向用户显示通知,并取消数据库查询。我最初的想法是使用Timeout

class ReportsController < ApplicationController
  def expensive_report
    Timeout.timeout(25) do
      @results = ExpensiveQuery.new(params).results
    end
  rescue Timeout::Error
    render action: "timeout"
  end
end

定时工作正常,但相应的查询未被取消。它很容易在Rails控制台中重现

begin
  Timeout.timeout(1) do
    ActiveRecord::Base.connection.execute("SELECT pg_sleep(10)")
  end
rescue Timeout::Error
  puts "Timeout"
end

result = ActiveRecord::Base.connection.execute("SELECT 1 AS value")
puts result[0]["value"]

此代码将输出“超时”,然后在result = ActiveRecord::Base.connection.execute("SELECT 1 AS value")上阻止,直到pg_sleep查询完成。

如何从Rails中取消此类查询?我在Heroku上托管我的应用程序,因此权限仅限于运行pg_cancel_backendpg_terminate_backend等命令。

3 个答案:

答案 0 :(得分:1)

您可以在会话级别设置statement_timeout(无需事务并跳过local)。

或交易:

t=# begin; set local statement_timeout to 1; select pg_sleep(3);end;
BEGIN
Time: 0.096 ms
SET
Time: 0.098 ms
ERROR:  canceling statement due to statement timeout
Time: 1.768 ms
ROLLBACK
Time: 0.066 ms

或作为用户的默认值:

alter user no_long_qries set statement_timeout to 1;

答案 1 :(得分:0)

通过将此超时添加到config/database.yml

,可以使该超时适用于所有数据库请求
default: &default
  adapter: postgresql
  ...
  variables:
    statement_timeout: 25000

或者这是config/environment.rb

的结尾
ActiveRecord::Base.connection.execute('set statement_timeout to 25000')

答案 2 :(得分:0)

我找到了不需要修改当前查询的很酷的解决方案。

class ReportsController < ApplicationController
  def expensive_report
    Timeout.timeout(25) do
      @results = ExpensiveQuery.new(params).results
    end
  rescue Timeout::Error
    ActiveRecord::Base.connection.raw_connection.cancel
    render action: "timeout"
  end
end

在控制台中运行时,我需要添加检查连接是否处于活动状态。

begin
  Timeout.timeout(1) do
    ActiveRecord::Base.connection.execute("SELECT pg_sleep(10)")
  end
rescue Timeout::Error
  ActiveRecord::Base.connection.raw_connection.cancel
  ActiveRecord::Base.connection.active?
  puts "Timeout"
end

result = ActiveRecord::Base.connection.execute("SELECT 1 AS value")
puts result[0]["value"]

如果不调用ActiveRecord::Base.connection.active?,则会引发ActiveRecord::StatementInvalid: PG::QueryCanceled

这非常好用,但我确定是否有任何隐藏的问题。