When running a service, on rare occasions you may want to extract all the data about a particular user. I'm sure the day will come when we will work on horizontal division and recovery of data that has been physically deleted.
For those of you who have to do such a troublesome task, this time we will introduce how to extract and import data for a specific user.
If you use ActiveRecord, all tables are associated in the model layer. Therefore, if you trace the relationship starting from the User class, you should be able to pull all the necessary data.
This time, I will use my own active_record_depth_query.rb to pull related records in one shot. By using this, you can search the foreign key and get the record by passing Array/Hash as an argument like the preload that you usually use.
In no time, I was able to extract all the records about the user. Easy.
depth_query = ActiveRecordDepthQuery.new(user, [:addresses, { cart: :items, orders: [:line_items, :address }]])
depth_query.each do |relation|
relation #=>ActiveRecord of the record specified by the second argument::Relation comes in turn
end
Introduce the process of copying the data extracted in step 1 to another DB. This time, I used Rails 6's multiple DB function and activerecord-import, so if you have a different environment, please rewrite it as appropriate.
In the following process, in order to move the data from the old DB to the new DB, After extracting records from the old DB, I switch the connection to the new DB and then bulk insert.
Since the work is simple, I was able to extract and save the data of a specific user with the code below.
require 'activerecord-import'
class RecordsFromBackupDatabase
#Related list to be imported
USER_ASSOCIATIONS = [:addresses, { cart: :items, orders: [:line_items, :address] }].freeze
#Old DB connection destination
OLD_DATABASE_HOST = 'old-database.host'
#New DB connection destination
NEW_DATABASE_HOST = 'new-database.host'
# @param user_ids [Array<Integer>]User ID to be imported
#
# @return [void]
def perform(user_ids)
#Old DB/Set up a new DB. Work on the rails console instead of the web server as it affects the sharing process
reconnect_database
ActiveRecord::Base.transaction do
#Connect to old DB
connected_to_old_database do
users = User.where(id: user_ids)
#Recover users first
import_relation(users)
users.find_each do |user|
#Recover data for each user
restore_user(user)
end
end
end
end
private
def reconnect_database
#Disconnect the connected DB
ActiveRecord::Base.connection.disconnect!
# writing/Define two systems of reading so that you can switch between old and new DB
config = ActiveRecord::Base.connection_pool.spec.config
ActiveRecord::Base.configurations = {
"#{Rails.env}": config.merge(host: NEW_DATABASE_HOST, replica: false),
"#{Rails.env}_replica": config.merge(host: OLD_DATABASE_HOST, replica: true)
}
ActiveRecord::Base.connects_to(
database: {
writing: Rails.env.to_sym,
reading: :"#{Rails.env}_replica"
}
)
end
def connected_to_old_database(&block)
ActiveRecord::Base.connected_to(role: :reading, &block)
end
def connected_to_new_database(&block)
ActiveRecord::Base.connected_to(role: :writing, &block)
end
def restore_user(user)
#Search user associations in sequence and import records from the old DB into the new DB
ActiveRecordDepthQuery.new(user, USER_ASSOCIATIONS).each do |relation|
relation.find_in_batches do |records|
klass = relation.klass
#Import records into production DB
connected_to_new_database do
klass.import(records, validate: false)
end
end
end
end
end
This time, I introduced ActiveRecordDepthQuery, which extracts records with a preload-like interface, and how to recover data using it. Hopefully, this process won't be used again, but if you need such an operation, please use it.
Recommended Posts