103 lines
4.5 KiB
Ruby
103 lines
4.5 KiB
Ruby
|
# frozen_string_literal: true
|
||
|
|
||
|
module Gitlab
|
||
|
module BackgroundMigration
|
||
|
# Removes orphaned routes, i.e. routes that reference a namespace or project that no longer exists.
|
||
|
# This was possible since we were using a polymorphic association source_id, source_type. However since now
|
||
|
# we have project namespaces we can use a FK on routes#namespace_id to avoid orphaned records in routes.
|
||
|
class CleanupOrphanedRoutes < Gitlab::BackgroundMigration::BatchedMigrationJob
|
||
|
include Gitlab::Database::DynamicModelHelpers
|
||
|
|
||
|
def perform
|
||
|
# there should really be no records to fix, there is none gitlab.com, but taking the safer route, just in case.
|
||
|
fix_missing_namespace_id_routes
|
||
|
cleanup_orphaned_routes
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
def fix_missing_namespace_id_routes
|
||
|
non_orphaned_namespace_routes = non_orphaned_namespace_routes_scoped_to_range(batch_column, start_id, end_id)
|
||
|
non_orphaned_project_routes = non_orphaned_project_routes_scoped_to_range(batch_column, start_id, end_id)
|
||
|
|
||
|
update_namespace_id(batch_column, non_orphaned_namespace_routes, sub_batch_size)
|
||
|
update_namespace_id(batch_column, non_orphaned_project_routes, sub_batch_size)
|
||
|
end
|
||
|
|
||
|
def cleanup_orphaned_routes
|
||
|
orphaned_namespace_routes = orphaned_namespace_routes_scoped_to_range(batch_column, start_id, end_id)
|
||
|
orphaned_project_routes = orphaned_project_routes_scoped_to_range(batch_column, start_id, end_id)
|
||
|
|
||
|
cleanup_relations(batch_column, orphaned_namespace_routes, pause_ms, sub_batch_size)
|
||
|
cleanup_relations(batch_column, orphaned_project_routes, pause_ms, sub_batch_size)
|
||
|
end
|
||
|
|
||
|
def update_namespace_id(batch_column, non_orphaned_namespace_routes, sub_batch_size)
|
||
|
non_orphaned_namespace_routes.each_batch(column: batch_column, of: sub_batch_size) do |sub_batch|
|
||
|
batch_metrics.time_operation(:fix_missing_namespace_id) do
|
||
|
ApplicationRecord.connection.execute <<~SQL
|
||
|
WITH route_and_ns(route_id, namespace_id) AS #{::Gitlab::Database::AsWithMaterialized.materialized_if_supported} (
|
||
|
#{sub_batch.to_sql}
|
||
|
)
|
||
|
UPDATE routes
|
||
|
SET namespace_id = route_and_ns.namespace_id
|
||
|
FROM route_and_ns
|
||
|
WHERE id = route_and_ns.route_id
|
||
|
SQL
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def cleanup_relations(batch_column, orphaned_namespace_routes, pause_ms, sub_batch_size)
|
||
|
orphaned_namespace_routes.each_batch(column: batch_column, of: sub_batch_size) do |sub_batch|
|
||
|
batch_metrics.time_operation(:cleanup_orphaned_routes) do
|
||
|
sub_batch.delete_all
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def orphaned_namespace_routes_scoped_to_range(source_key_column, start_id, stop_id)
|
||
|
Gitlab::BackgroundMigration::Route.joins("LEFT OUTER JOIN namespaces ON source_id = namespaces.id")
|
||
|
.where(source_key_column => start_id..stop_id)
|
||
|
.where(source_type: 'Namespace')
|
||
|
.where(namespace_id: nil)
|
||
|
.where(namespaces: { id: nil })
|
||
|
end
|
||
|
|
||
|
def orphaned_project_routes_scoped_to_range(source_key_column, start_id, stop_id)
|
||
|
Gitlab::BackgroundMigration::Route.joins("LEFT OUTER JOIN projects ON source_id = projects.id")
|
||
|
.where(source_key_column => start_id..stop_id)
|
||
|
.where(source_type: 'Project')
|
||
|
.where(namespace_id: nil)
|
||
|
.where(projects: { id: nil })
|
||
|
end
|
||
|
|
||
|
def non_orphaned_namespace_routes_scoped_to_range(source_key_column, start_id, stop_id)
|
||
|
Gitlab::BackgroundMigration::Route.joins("LEFT OUTER JOIN namespaces ON source_id = namespaces.id")
|
||
|
.where(source_key_column => start_id..stop_id)
|
||
|
.where(source_type: 'Namespace')
|
||
|
.where(namespace_id: nil)
|
||
|
.where.not(namespaces: { id: nil })
|
||
|
.select("routes.id, namespaces.id")
|
||
|
end
|
||
|
|
||
|
def non_orphaned_project_routes_scoped_to_range(source_key_column, start_id, stop_id)
|
||
|
Gitlab::BackgroundMigration::Route.joins("LEFT OUTER JOIN projects ON source_id = projects.id")
|
||
|
.where(source_key_column => start_id..stop_id)
|
||
|
.where(source_type: 'Project')
|
||
|
.where(namespace_id: nil)
|
||
|
.where.not(projects: { id: nil })
|
||
|
.select("routes.id, projects.project_namespace_id")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Isolated route model for the migration
|
||
|
class Route < ApplicationRecord
|
||
|
include EachBatch
|
||
|
|
||
|
self.table_name = 'routes'
|
||
|
self.inheritance_column = :_type_disabled
|
||
|
end
|
||
|
end
|
||
|
end
|