Skip to content

Follow-up from "Fix remaining N+1 queries in EnvironmentSerializer"

The following discussion from !82746 (merged) should be addressed:

  • @shinya.maeda started a discussion: (+1 comment)

    While having this preloader is a nice improvement, it has a duplicate logic with the current SSOT method. This might gives us hard time to keep synchronizing these logic in the long run, for example, project-level protected environment could also use tier for the matching.

    I wonder if we can use the SSOT method in this preloader as well. Something like:

    diff --git a/ee/app/models/preloaders/environments/protected_environment_preloader.rb b/ee/app/models/preloaders/environments/protected_environment_preloader.rb
    index a1da48bbdb8..61b36e5c67b 100644
    --- a/ee/app/models/preloaders/environments/protected_environment_preloader.rb
    +++ b/ee/app/models/preloaders/environments/protected_environment_preloader.rb
    @@ -16,15 +16,14 @@ def initialize(environments)
           def execute
             return if environments.empty?
     
    -        project = environments.first.project
    -        project_id = project.id
    -        group_ids = project.ancestors_upto_ids
    +        associated_protected_environments = ProtectedEnvironment
    +          .for_environments(environments).preload(:deploy_access_levels)
     
    -        names = environments.map(&:name)
    -        tiers = environments.map(&:tier)
    +        project_protected_environments = associated_protected_environments
    +          .select(&:project_level?).index_by(&:name)
     
    -        project_protected_environments = ProtectedEnvironment.preload(:deploy_access_levels).where(project_id: project_id, name: names).index_by(&:name)
    -        group_protected_environments = ProtectedEnvironment.preload(:deploy_access_levels).where(group_id: group_ids, name: tiers).index_by(&:name)
    +        group_protected_environments = associated_protected_environments
    +          .select(&:group_level?).index_by(&:name)
     
             environments.each do |environment|
               protected_environments ||= []
    diff --git a/ee/app/models/protected_environment.rb b/ee/app/models/protected_environment.rb
    index 33146642fa8..9fc52ea03f4 100644
    --- a/ee/app/models/protected_environment.rb
    +++ b/ee/app/models/protected_environment.rb
    @@ -37,17 +37,28 @@ def deploy_access_levels_by_group(group)
             .where(group: group)
         end
     
    -    def for_environment(environment)
    +    def for_environment_with_cache(environment)
           raise ArgumentError unless environment.is_a?(::Environment)
     
           key = "protected_environment:for_environment:#{environment.id}"
     
    -      ::Gitlab::SafeRequestStore.fetch(key) do
    -        from_union([
    -          where(project: environment.project_id, name: environment.name),
    -          where(group: environment.project.ancestors_upto_ids, name: environment.tier)
    -        ])
    +      ::Gitlab::SafeRequestStore.fetch(key) { for_environments([environment]) }
    +    end
    +
    +    def for_environments(environments)
    +      if environments.map(&:project_id).uniq.size > 1
    +        raise ArgumentError, 'Environments must be in the same project'
           end
    +
    +      project_id = environments.project_id
    +      group_ids = environments.first.project.ancestors_upto_ids
    +      names = environments.map(&:name)
    +      tiers = environments.map(&:tier)
    +
    +      from_union([
    +        where(project: project_id, name: names),
    +        where(group: group_ids, name: tiers)
    +      ])
         end
       end

    I do not strongly feel that this refactoring should be placed in this MR, so feel free to move on as-is. However, we'd like to catch this up at some point.