Make the sql statement that clears old container logs more efficient by
[arvados.git] / services / api / lib / audit_logs.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 require 'current_api_client'
6 require 'db_current_time'
7
8 module AuditLogs
9   extend CurrentApiClient
10   extend DbCurrentTime
11
12   def self.delete_old(max_age:, max_batch:)
13     act_as_system_user do
14       if !File.owned?(Rails.root.join('tmp'))
15         Rails.logger.warn("AuditLogs: not owner of #{Rails.root}/tmp, skipping")
16         return
17       end
18       lockfile = Rails.root.join('tmp', 'audit_logs.lock')
19       File.open(lockfile, File::RDWR|File::CREAT, 0600) do |f|
20         return unless f.flock(File::LOCK_NB|File::LOCK_EX)
21
22         sql = "select clock_timestamp() - interval '#{'%.9f' % max_age} seconds'"
23         threshold = ActiveRecord::Base.connection.select_value(sql).to_time.utc
24         Rails.logger.info "AuditLogs: deleting logs older than #{threshold}"
25
26         did_total = 0
27         loop do
28           sql = Log.unscoped.
29                 select(:id).
30                 order(:created_at).
31                 where('event_type in (?)', ['create', 'update', 'destroy', 'delete']).
32                 where('created_at < ?', threshold).
33                 limit(max_batch).
34                 to_sql
35           did = Log.unscoped.where("id in (#{sql})").delete_all
36           did_total += did
37
38           Rails.logger.info "AuditLogs: deleted batch of #{did}"
39           break if did == 0
40         end
41         Rails.logger.info "AuditLogs: deleted total #{did_total}"
42       end
43     end
44   end
45
46   def self.tidy_in_background
47     max_age = Rails.configuration.AuditLogs.MaxAge.to_i
48     max_batch = Rails.configuration.AuditLogs.MaxDeleteBatch
49     return if max_age <= 0 || max_batch <= 0
50
51     exp = (max_age/14).seconds
52     need = false
53     Rails.cache.fetch('AuditLogs', expires_in: exp) do
54       need = true
55     end
56     return if !need
57
58     Thread.new do
59       Thread.current.abort_on_exception = false
60       begin
61         delete_old(max_age: max_age, max_batch: max_batch)
62       rescue => e
63         Rails.logger.error "#{e.class}: #{e}\n#{e.backtrace.join("\n\t")}"
64       ensure
65         # Rails 5.1+ makes test threads share a database connection, so we can't
66         # close a connection shared with other threads.
67         # https://github.com/rails/rails/commit/deba47799ff905f778e0c98a015789a1327d5087
68         if Rails.env != "test"
69           ActiveRecord::Base.connection.close
70         end
71       end
72     end
73   end
74 end