Skip to content

Instantly share code, notes, and snippets.

@wellington1993
Created August 16, 2024 14:57
Show Gist options
  • Save wellington1993/9cd3a8a87c38fa964c28d11e7597b11e to your computer and use it in GitHub Desktop.
Save wellington1993/9cd3a8a87c38fa964c28d11e7597b11e to your computer and use it in GitHub Desktop.
namespace :cleanup do
desc "Cleanup old versions and keep only the last year of data / Limpeza de versões antigas e manter apenas os dados do último ano"
task versions: :environment do
require 'benchmark'
class Version < ApplicationRecord
self.table_name = 'versions'
end
class TempVersion < ApplicationRecord
self.table_name = 'temp_versions'
end
class Maintenance < ApplicationRecord
self.table_name = 'maintenances'
end
ActiveRecord::Schema.define do
create_table :maintenances do |t|
t.string :maintenance_type
t.string :status
t.datetime :started_at
t.datetime :ended_at
t.decimal :total_time
t.decimal :create_table_time
t.decimal :copy_time
t.decimal :rename_time
t.decimal :drop_time
t.timestamps
end unless ActiveRecord::Base.connection.table_exists?('maintenances')
end
memcached_available = false
client = ActionDispatch::Session::CacheStore.new(Rails.application.config.session_options[:cache])
memcached_available = client.cache.instance_variable_get(:@data)&.alive?
if !memcached_available || client.cache.write('cleanup_versions_lock', 1, unless_exist: true, expires_in: 1.hour)
begin
maintenance = Maintenance.create!(
maintenance_type: 'cleanup_versions',
status: 'in_progress',
started_at: Time.current
)
puts "Creating temporary table... / Criando tabela temporária..."
create_table_time = Benchmark.realtime do
ActiveRecord::Base.transaction do
# Verifica se a tabela já existe
unless ActiveRecord::Base.connection.table_exists?('temp_versions')
# Se a tabela não existir, cria-a com particionamento inicial
ActiveRecord::Base.connection.execute <<-SQL.squish
CREATE TABLE temp_versions (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
item_type VARCHAR(255),
item_id BIGINT,
event VARCHAR(255),
whodunnit VARCHAR(255),
object TEXT,
created_at DATETIME,
object_changes TEXT,
transaction_id BIGINT,
INDEX index_temp_versions_on_created_at (created_at)
)
PARTITION BY RANGE (YEAR(created_at)) (
PARTITION p#{Date.current.year} VALUES LESS THAN (#{Date.current.year + 1}),
PARTITION p#{Date.current.year + 1} VALUES LESS THAN (#{Date.current.year + 2})
);
SQL
else
# Se a tabela já existir, ajusta os particionamentos conforme necessário
current_year = Date.current.year
next_year = current_year + 1
# Verifica os particionamentos existentes
partitions = ActiveRecord::Base.connection.select_values("SHOW PARTITIONS FROM temp_versions")
# Cria particionamento para o ano atual se não existir
unless partitions.include?("p#{current_year}")
ActiveRecord::Base.connection.execute <<-SQL.squish
ALTER TABLE temp_versions
ADD PARTITION (
PARTITION p#{current_year} VALUES LESS THAN (#{next_year})
);
SQL
end
# Cria particionamento para o próximo ano se não existir
unless partitions.include?("p#{next_year}")
ActiveRecord::Base.connection.execute <<-SQL.squish
ALTER TABLE temp_versions
ADD PARTITION (
PARTITION p#{next_year} VALUES LESS THAN (#{next_year + 1})
);
SQL
end
end
end
end
batch_size = 100_000
total_copied = 0
copy_time = 0
Version.where('created_at >= ?', 1.year.ago).find_in_batches(batch_size: batch_size) do |batch|
copy_time += Benchmark.realtime do
TempVersion.transaction do
batch.each do |version|
TempVersion.create!(version.attributes.except('id'))
version.destroy
end
end
end
total_copied += batch.size
puts "Copied #{total_copied} records. Copy time: #{copy_time.round(2)} seconds. / Copiados #{total_copied} registros. Tempo de cópia: #{copy_time.round(2)} segundos."
row_count = ActiveRecord::Base.connection.execute(<<-SQL.squish).first[0]
SELECT COUNT(*)
FROM versions
WHERE created_at >= DATE_SUB(NOW(), INTERVAL 1 YEAR)
SQL
break if row_count == 0
end
rename_time = Benchmark.realtime do
puts "Renaming tables... / Renomeando tabelas..."
ActiveRecord::Base.connection.execute <<-SQL
RENAME TABLE versions TO versions_old, temp_versions TO versions;
SQL
end
drop_time = Benchmark.realtime do
puts "Dropping old table... / Excluindo tabela antiga..."
ActiveRecord::Base.connection.execute <<-SQL
DROP TABLE versions_old;
SQL
end
maintenance.update!(
status: 'completed',
ended_at: Time.current,
total_time: (Time.current - maintenance.started_at).round(2),
create_table_time: create_table_time.round(2),
copy_time: copy_time.round(2),
rename_time: rename_time.round(2),
drop_time: drop_time.round(2)
)
puts "Process completed successfully! Total records copied: #{total_copied}. Rename time: #{rename_time.round(2)} seconds. Drop time: #{drop_time.round(2)} seconds. / Processo concluído com sucesso! Total de registros copiados: #{total_copied}. Tempo de renomeação: #{rename_time.round(2)} segundos. Tempo de exclusão: #{drop_time.round(2)} segundos."
ensure
client.cache.delete('cleanup_versions_lock') if memcached_available
end
else
puts "Another process is running. Exiting... / Outro processo está em execução. Saindo..."
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment