Geo: Enumerate project_repositories instead of projects for verification/replication of Git repos
Everyone can contribute. Help move this issue forward while earning points, leveling up and collecting rewards.
Overview
This issue is part of a multi-step implementation plan to handle Geo verification/replication of projects without Git repositories, reducing errors caused when Gitaly tries to fetch non-existent repos.
Like Design Management Repositories, we want Geo to enumerate the project_repositories table instead of the projects table, because a ProjectRepository record represents an actual Git repo, not a Project itself (in the GitLab Rails code). This will simplify the logic for determining when a Git repo needs to be fetched for verification/replication.
This is the core of V2 project repository replication, gated behind the geo_project_repository_replication_v2 feature flag.
What needs to be done
The branch 546175-geo-enumerate-project_repositories-instead-of-projects contains a proof-of-concept that is not yet mergeable. The following changes are proposed there and need to be completed:
ProjectRepositoryReplicator (ee/app/replicators/geo/project_repository_replicator.rb)
modelis gated to return::ProjectRepositorywhen the FF is enabled,::Projectotherwise.should_publish_replication_event?correctly gates events toProjectRepositoryrecords only under V2.repository,pool_repository, andobject_pool_missing?are delegated throughmodel_recordwhich can now be either aProjector aProjectRepository.
Geo::ProjectRepositoryRegistry (ee/app/models/geo/project_repository_registry.rb)
- A
belongs_to :project_repositoryassociation is added alongside the existingbelongs_to :project. model_classandmodel_foreign_keyare FF-gated to returnProjectRepository/:project_repository_idunder V2.- A
model_accessorhelper is introduced to return the right model record depending on the FF. before_validationandafter_findcallbacks callpopulate_missing_foreign_keysto keep both FKs in sync during the V1/V2 migration period.
EE::ProjectRepository (ee/app/models/ee/project_repository.rb)
pool_repository,object_pool_missing?, andlast_repository_updated_atare delegated toproject, so thatProjectRepositorycan be used as a drop-in forProjectin the replicator.
Factories and specs
spec/factories/projects.rb:project_with_repofactory now callstrack_project_repositoryto ensure aProjectRepositoryrecord exists.ee/spec/factories/geo/project_repository_registry.rb: factory updated to handle both V1 and V2 cases.- Specs for
Geo::ProjectRepositoryRegistry,GeoNodeStatus, the data management API, andGeo::Secondary::RegistryConsistencyWorkerare updated to cover both V1 and V2 paths.
Known hurdle: N+1 queries from the after_find callback
The main technical blocker is an N+1 query problem introduced by the after_find :populate_missing_foreign_keys callback in Geo::ProjectRepositoryRegistry:
# ee/app/models/geo/project_repository_registry.rb
after_find :populate_missing_foreign_keys
def populate_missing_foreign_keys
return unless has_attribute?(:project_id) && has_attribute?(:project_repository_id)
return if project.present? && project_repository.present?
self.project_repository ||= project&.project_repository
self.project ||= project_repository&.project
endThis callback fires for every record loaded from the database. When loading a batch of registry records (e.g. in cron workers), each record may trigger one or two additional SQL queries to load the project or project_repository association. On large instances with millions of project repositories, this produces a severe N+1 that can degrade performance significantly.
The usual mitigation of adding a scope with includes is not applicable here because geo_project_repository_registry lives in the geo database, while projects and project_repositories live in the main database. Cross-database eager loading is not supported.
Possible approaches:
- Move the population logic out of
after_findentirely, and instead resolve the missing FK lazily on demand (e.g. only whenmodel_accessoris called), accepting that some records may temporarily have only one FK populated. - Evaluate whether both FKs need to be populated at all times, or whether the registry can operate correctly with only one FK set and resolve the other only when strictly necessary (e.g. in
populate_missing_foreign_keyscalled explicitly from service-layer code, not from a callback). - Consider whether the
after_findcallback can be removed once the migration period is over and both FKs are always populated by construction.
Acceptance criteria
- Under
geo_project_repository_replication_v2, Geo enumeratesproject_repositoriesinstead ofprojectsfor replication and verification of Git repos. - The
after_findN+1 inGeo::ProjectRepositoryRegistryis resolved. - All existing Geo shared examples pass for both V1 and V2 paths.