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

Mic*_*iak 5 postgresql ruby-on-rails 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
Run Code Online (Sandbox Code Playgroud)

超时工作正常,但不会取消相应的查询。在 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"]
Run Code Online (Sandbox Code Playgroud)

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

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

Mic*_*iak 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
Run Code Online (Sandbox Code Playgroud)

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

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"]
Run Code Online (Sandbox Code Playgroud)

不跟注ActiveRecord::Base.connection.active?就会加注ActiveRecord::StatementInvalid: PG::QueryCanceled。

这工作得很好,但我还不确定是否存在任何隐藏的问题。