Do not exceed index.maxTerms when querying group index by subgroups

The optimisation of the GroupIncludeCacheImpl introduced by Ic73e01e933cb
did not check for the underlying indexing backend maxTerms settings when
loading the cache.

Use the index.maxTerms configuration and use it to partition
the input groupIds by several queries of maximum index.maxTerms
per query, keeping the external behaviour and fixing Gerrit setups
where the number of groups exceeds the index.maxTerms configuration
setting.

Bug: Issue 368553867
Release-Notes: Fix support for a number of groups higher than the index.maxTerms
Change-Id: I070bbf117ea4cb6d29bbac3ddeb98a37e07ee9c1
diff --git a/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java b/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java
index fc6087b..5134643 100644
--- a/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java
+++ b/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java
@@ -23,11 +23,13 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.AccountGroup;
 import com.google.gerrit.entities.InternalGroup;
+import com.google.gerrit.index.IndexConfig;
 import com.google.gerrit.proto.Protos;
 import com.google.gerrit.server.cache.CacheModule;
 import com.google.gerrit.server.cache.proto.Cache.AllExternalGroupsProto;
@@ -45,9 +47,11 @@
 import com.google.inject.Singleton;
 import com.google.inject.TypeLiteral;
 import com.google.inject.name.Named;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -62,6 +66,8 @@
   private static final String EXTERNAL_NAME = "groups_external";
   private static final String PERSISTED_EXTERNAL_NAME = "groups_external_persisted";
 
+  private final IndexConfig indexConfig;
+
   public static Module module() {
     return new CacheModule() {
       @Override
@@ -115,10 +121,12 @@
           LoadingCache<Account.Id, ImmutableSet<AccountGroup.UUID>> groupsWithMember,
       @Named(PARENT_GROUPS_NAME)
           LoadingCache<AccountGroup.UUID, ImmutableSet<AccountGroup.UUID>> parentGroups,
-      @Named(EXTERNAL_NAME) LoadingCache<String, ImmutableList<AccountGroup.UUID>> external) {
+      @Named(EXTERNAL_NAME) LoadingCache<String, ImmutableList<AccountGroup.UUID>> external,
+      IndexConfig indexConfig) {
     this.groupsWithMember = groupsWithMember;
     this.parentGroups = parentGroups;
     this.external = external;
+    this.indexConfig = indexConfig;
   }
 
   @Override
@@ -145,7 +153,10 @@
   public Collection<AccountGroup.UUID> parentGroupsOf(Set<AccountGroup.UUID> groupIds) {
     try {
       Set<AccountGroup.UUID> parents = new HashSet<>();
-      parentGroups.getAll(groupIds).values().forEach(p -> parents.addAll(p));
+      for (List<AccountGroup.UUID> groupIdsBatch :
+          Lists.partition(new ArrayList<>(groupIds), indexConfig.maxTerms())) {
+        parentGroups.getAll(groupIdsBatch).values().forEach(p -> parents.addAll(p));
+      }
       return parents;
     } catch (ExecutionException e) {
       logger.atWarning().withCause(e).log("Cannot load included groups");