From 0bd749c3062531252a20fa1b96231de128e00883 Mon Sep 17 00:00:00 2001 From: evazion Date: Thu, 20 Oct 2022 15:06:04 -0500 Subject: [PATCH] reports: increase database timeout; add rate limits. Increase the database timeout to 10 seconds when generating reports. Generating reports tends to be slow, especially for things like graphing posts over time since the beginning of Danbooru. Does not apply to anonymous users. Users must have an account to get higher timeouts so that we can identify users scraping reports too hard. Also add a rate limit of 1 report per 3 seconds to limit abuse. --- app/controllers/reports_controller.rb | 13 ++++++++++++- app/models/application_record.rb | 7 +++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb index 230249ff0..49a25946a 100644 --- a/app/controllers/reports_controller.rb +++ b/app/controllers/reports_controller.rb @@ -3,6 +3,8 @@ class ReportsController < ApplicationController respond_to :html, :json, :xml + rate_limit :show, rate: 1.0/3.seconds, burst: 15 + def index end @@ -75,7 +77,16 @@ class ReportsController < ApplicationController @from = params.dig(:search, :from) || 1.month.ago @to = params.dig(:search, :to) || Time.zone.now - @results = @model.search(params[:search], CurrentUser.user).timeseries(period: @period, from: @from, to: @to, columns: @columns) + if CurrentUser.user.is_member? && CurrentUser.user.statement_timeout < 10_000 + @statement_timeout = 10_000 + else + @statement_timeout = CurrentUser.user.statement_timeout + end + + ApplicationRecord.set_timeout(@statement_timeout) do + @results = @model.search(params[:search], CurrentUser.user).timeseries(period: @period, from: @from, to: @to, columns: @columns) + end + respond_with(@results) end end diff --git a/app/models/application_record.rb b/app/models/application_record.rb index baa3cad66..0b5f26ea7 100644 --- a/app/models/application_record.rb +++ b/app/models/application_record.rb @@ -134,6 +134,13 @@ class ApplicationRecord < ActiveRecord::Base concerning :ActiveRecordExtensions do class_methods do + def set_timeout(n) + connection.execute("SET STATEMENT_TIMEOUT = #{n}") unless Rails.env.test? + yield + ensure + connection.execute("SET STATEMENT_TIMEOUT = #{CurrentUser.user.statement_timeout}") unless Rails.env.test? + end + def without_timeout connection.execute("SET STATEMENT_TIMEOUT = 0") unless Rails.env.test? yield