allow additional columns to be ignored through Consul

refs AE-124

When running pre-deploy migrations that add a new column, if an application server loads the schema on a shard where the migration has already run, and tries to run queries on a shard where the migration has not already run, it can lead to errors. Fix this by allowing ignored_columns to append to its value dynamically through Consul.

Change-Id: I3b13e0fd2ac066e1439d3d314d71f2ebd8938e0c
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/310561
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Jacob Burroughs <jburroughs@instructure.com>
QA-Review: Aaron Ogata <aogata@instructure.com>
Product-Review: Aaron Ogata <aogata@instructure.com>
This commit is contained in:
Aaron Ogata 2023-02-07 06:58:02 -08:00
parent fe4f7009d4
commit 0aca0be810
3 changed files with 67 additions and 0 deletions

View File

@ -2219,3 +2219,13 @@ if Rails.version >= "6.1"
# rubocop:enable Lint/RescueException
# rubocop:enable Naming/RescuedExceptionsVariableName
end
module AdditionalIgnoredColumns
def ignored_columns
return super unless superclass <= ActiveRecord::Base && !abstract_class?
columns_to_ignore = DynamicSettings.find("activerecord/ignored_columns", tree: :store)[table_name]&.split(",") || []
super + columns_to_ignore
end
end
ActiveRecord::Base.singleton_class.include(AdditionalIgnoredColumns)

View File

@ -11,3 +11,35 @@ end
MultiCache.delete("schema_cache")
end
end
namespace :db do
desc "Clear columns to be ignored for each model"
task :clear_ignored_columns, [:table_name] => :environment do |_t, args|
Diplomat::Kv.delete(
"store/canvas/#{DynamicSettings.environment}/activerecord/ignored_columns/#{args[:table_name]}"
)
MultiCache.delete("schema_cache")
end
desc "Get columns to be ignored for each model"
task :get_ignored_columns, [:table_name] => :environment do |_t, args|
ignored_columns = Diplomat::Kv.get(
"store/canvas/#{DynamicSettings.environment}/activerecord/ignored_columns/#{args[:table_name]}"
)
puts "Ignored Columns: #{ignored_columns}"
rescue Diplomat::KeyNotFound
puts "Ignored Columns: -"
end
desc "Set columns to be ignored for each model"
task :set_ignored_columns, [:table_name, :columns] => :environment do |_t, args|
Diplomat::Kv.put(
"store/canvas/#{DynamicSettings.environment}/activerecord/ignored_columns/#{args[:table_name]}",
args[:columns]
)
MultiCache.delete("schema_cache")
end
end

View File

@ -338,6 +338,31 @@ module ActiveRecord
.to eq [user]
end
end
describe ".ignored_columns" do
it "ignores additional columns specified in Consul" do
# If this test is the first one to run that requires User - preload User so that the correct
# accessors (getters / setters) already exist since the "ensure" block won't create them. In
# a real situation, we would first perform a rolling restart after having unset this key and
# finished pre-deploy migrations everywhere.
User.create!(name: "user u1")
allow(DynamicSettings).to receive(:find).with(any_args).and_call_original
allow(DynamicSettings).to receive(:find).with("activerecord/ignored_columns", tree: :store).and_return(
{
"users" => "name"
}
)
User.reset_column_information
expect { User.create!(name: "user u2") }.to raise_exception(ActiveModel::UnknownAttributeError)
ensure
allow(DynamicSettings).to receive(:find).with("activerecord/ignored_columns", tree: :store).and_call_original
User.reset_column_information
User.create!(name: "user u2")
end
end
end
describe ".asset_string" do