Merge "groups_bysubgroups: Batch loading values"
diff --git a/java/com/google/gerrit/server/account/GroupIncludeCache.java b/java/com/google/gerrit/server/account/GroupIncludeCache.java
index d92d9fc..266f858 100644
--- a/java/com/google/gerrit/server/account/GroupIncludeCache.java
+++ b/java/com/google/gerrit/server/account/GroupIncludeCache.java
@@ -16,7 +16,9 @@
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.AccountGroup.UUID;
import java.util.Collection;
+import java.util.Set;
/** Tracks group inclusions in memory for efficient access. */
public interface GroupIncludeCache {
@@ -37,6 +39,14 @@
*/
Collection<AccountGroup.UUID> parentGroupsOf(AccountGroup.UUID groupId);
+ /**
+ * Returns the parent groups of the provided subgroups.
+ *
+ * @param groupId the UUID of the subgroup
+ * @return the UUIDs of all direct parent groups
+ */
+ Collection<AccountGroup.UUID> parentGroupsOf(Set<UUID> groupId);
+
/** Returns set of any UUIDs that are not internal groups. */
Collection<AccountGroup.UUID> allExternalMembers();
diff --git a/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java b/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java
index f203240..fc6087b 100644
--- a/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java
+++ b/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java
@@ -22,6 +22,8 @@
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.AccountGroup;
@@ -45,6 +47,9 @@
import com.google.inject.name.Named;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ExecutionException;
/** Tracks group inclusions in memory for efficient access. */
@@ -70,7 +75,7 @@
cache(
PARENT_GROUPS_NAME,
AccountGroup.UUID.class,
- new TypeLiteral<ImmutableList<AccountGroup.UUID>>() {})
+ new TypeLiteral<ImmutableSet<AccountGroup.UUID>>() {})
.loader(ParentGroupsLoader.class);
/**
@@ -101,7 +106,7 @@
}
private final LoadingCache<Account.Id, ImmutableSet<AccountGroup.UUID>> groupsWithMember;
- private final LoadingCache<AccountGroup.UUID, ImmutableList<AccountGroup.UUID>> parentGroups;
+ private final LoadingCache<AccountGroup.UUID, ImmutableSet<AccountGroup.UUID>> parentGroups;
private final LoadingCache<String, ImmutableList<AccountGroup.UUID>> external;
@Inject
@@ -109,7 +114,7 @@
@Named(GROUPS_WITH_MEMBER_NAME)
LoadingCache<Account.Id, ImmutableSet<AccountGroup.UUID>> groupsWithMember,
@Named(PARENT_GROUPS_NAME)
- LoadingCache<AccountGroup.UUID, ImmutableList<AccountGroup.UUID>> parentGroups,
+ LoadingCache<AccountGroup.UUID, ImmutableSet<AccountGroup.UUID>> parentGroups,
@Named(EXTERNAL_NAME) LoadingCache<String, ImmutableList<AccountGroup.UUID>> external) {
this.groupsWithMember = groupsWithMember;
this.parentGroups = parentGroups;
@@ -137,6 +142,18 @@
}
@Override
+ 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));
+ return parents;
+ } catch (ExecutionException e) {
+ logger.atWarning().withCause(e).log("Cannot load included groups");
+ return Collections.emptySet();
+ }
+ }
+
+ @Override
public void evictGroupsWithMember(Account.Id memberId) {
if (memberId != null) {
logger.atFine().log("Evict groups with member %d", memberId.get());
@@ -194,7 +211,10 @@
}
static class ParentGroupsLoader
- extends CacheLoader<AccountGroup.UUID, ImmutableList<AccountGroup.UUID>> {
+ extends CacheLoader<AccountGroup.UUID, ImmutableSet<AccountGroup.UUID>> {
+ // Be conservative with batching: We don't want to exhaust the number of
+ // results per page and maximum terms per query. Both are usually 1000+.
+ private static final int MAX_BATCH_SIZE = 100;
private final Provider<InternalGroupQuery> groupQueryProvider;
@Inject
@@ -203,13 +223,26 @@
}
@Override
- public ImmutableList<AccountGroup.UUID> load(AccountGroup.UUID key) {
+ public ImmutableSet<AccountGroup.UUID> load(AccountGroup.UUID key) {
try (TraceTimer timer =
TraceContext.newTimer(
"Loading parent groups", Metadata.builder().groupUuid(key.get()).build())) {
- return groupQueryProvider.get().bySubgroup(key).stream()
- .map(InternalGroup::getGroupUUID)
- .collect(toImmutableList());
+ return loadAll(ImmutableList.of(key)).get(key);
+ }
+ }
+
+ @Override
+ public Map<AccountGroup.UUID, ImmutableSet<AccountGroup.UUID>> loadAll(
+ Iterable<? extends AccountGroup.UUID> keys) {
+ int numKeys = Iterables.size(keys);
+ Map<AccountGroup.UUID, ImmutableSet<AccountGroup.UUID>> result =
+ Maps.newHashMapWithExpectedSize(numKeys);
+ try (TraceTimer timer = TraceContext.newTimer("Loading " + numKeys + " parent groups")) {
+ Iterables.partition(keys, MAX_BATCH_SIZE)
+ .forEach(
+ keyPartition ->
+ result.putAll(groupQueryProvider.get().bySubgroups(ImmutableSet.copyOf(keys))));
+ return result;
}
}
}
diff --git a/java/com/google/gerrit/server/account/IncludingGroupMembership.java b/java/com/google/gerrit/server/account/IncludingGroupMembership.java
index 8cec8bf..e1edf10 100644
--- a/java/com/google/gerrit/server/account/IncludingGroupMembership.java
+++ b/java/com/google/gerrit/server/account/IncludingGroupMembership.java
@@ -16,7 +16,6 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.InternalGroup;
@@ -25,7 +24,6 @@
import com.google.inject.assistedinject.Assisted;
import java.util.Collection;
import java.util.HashSet;
-import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -135,7 +133,7 @@
Set<AccountGroup.UUID> r = Sets.newHashSet(direct);
r.remove(null);
- List<AccountGroup.UUID> q = Lists.newArrayList(r);
+ Set<AccountGroup.UUID> q = Sets.newHashSet(r);
for (AccountGroup.UUID g : membership.intersection(includeCache.allExternalMembers())) {
if (g != null && r.add(g)) {
q.add(g);
@@ -143,9 +141,10 @@
}
while (!q.isEmpty()) {
- AccountGroup.UUID id = q.remove(q.size() - 1);
- for (AccountGroup.UUID g : includeCache.parentGroupsOf(id)) {
- if (g != null && r.add(g)) {
+ Collection<AccountGroup.UUID> parents = includeCache.parentGroupsOf(q);
+ q.clear();
+ for (AccountGroup.UUID g : parents) {
+ if (r.add(g)) {
q.add(g);
memberOf.put(g, true);
}
diff --git a/java/com/google/gerrit/server/query/group/InternalGroupQuery.java b/java/com/google/gerrit/server/query/group/InternalGroupQuery.java
index b9b58b8..078acd4 100644
--- a/java/com/google/gerrit/server/query/group/InternalGroupQuery.java
+++ b/java/com/google/gerrit/server/query/group/InternalGroupQuery.java
@@ -17,7 +17,9 @@
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
+import com.google.common.collect.Maps;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.entities.Account;
import com.google.gerrit.entities.AccountGroup;
@@ -27,8 +29,12 @@
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.server.index.group.GroupIndexCollection;
import com.google.inject.Inject;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
/**
* Query wrapper for the group index.
@@ -57,8 +63,29 @@
return query(GroupPredicates.member(memberId));
}
- public List<InternalGroup> bySubgroup(AccountGroup.UUID subgroupId) {
- return query(GroupPredicates.subgroup(subgroupId));
+ /**
+ * Get all immediate parents of the provided {@code subgroupIds}.
+ *
+ * @return map pointing from children to list of its immediate parents
+ */
+ public Map<AccountGroup.UUID, ImmutableSet<AccountGroup.UUID>> bySubgroups(
+ ImmutableSet<AccountGroup.UUID> subgroupIds) {
+ List<Predicate<InternalGroup>> predicates =
+ subgroupIds.stream().map(e -> GroupPredicates.subgroup(e)).collect(Collectors.toList());
+ List<InternalGroup> groups = query(Predicate.or(predicates));
+
+ Map<AccountGroup.UUID, Set<AccountGroup.UUID>> parentsByChild =
+ Maps.newHashMapWithExpectedSize(groups.size());
+ subgroupIds.stream().forEach(c -> parentsByChild.put(c, new HashSet<>()));
+ for (InternalGroup parent : groups) {
+ for (AccountGroup.UUID child : parent.getSubgroups()) {
+ if (subgroupIds.contains(child)) {
+ parentsByChild.get(child).add(parent.getGroupUUID());
+ }
+ }
+ }
+ return parentsByChild.entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, e -> ImmutableSet.copyOf(e.getValue())));
}
private Optional<InternalGroup> getOnlyGroup(
diff --git a/javatests/com/google/gerrit/acceptance/api/group/GroupIndexerIT.java b/javatests/com/google/gerrit/acceptance/api/group/GroupIndexerIT.java
index ece46c5..98ed56c 100644
--- a/javatests/com/google/gerrit/acceptance/api/group/GroupIndexerIT.java
+++ b/javatests/com/google/gerrit/acceptance/api/group/GroupIndexerIT.java
@@ -14,12 +14,14 @@
package com.google.gerrit.acceptance.api.group;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.gerrit.server.group.testing.InternalGroupSubject.internalGroups;
-import static com.google.gerrit.truth.ListSubject.assertThat;
import static com.google.gerrit.truth.OptionalSubject.assertThat;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.InternalGroup;
import com.google.gerrit.exceptions.NoSuchGroupException;
@@ -34,13 +36,12 @@
import com.google.gerrit.server.index.group.GroupIndexer;
import com.google.gerrit.server.query.group.InternalGroupQuery;
import com.google.gerrit.testing.InMemoryTestEnvironment;
-import com.google.gerrit.truth.ListSubject;
import com.google.gerrit.truth.OptionalSubject;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.io.IOException;
-import java.util.List;
import java.util.Optional;
+import java.util.Set;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.junit.Rule;
import org.junit.Test;
@@ -53,6 +54,7 @@
@Inject private GroupCache groupCache;
@Inject @ServerInitiated private GroupsUpdate groupsUpdate;
@Inject private Provider<InternalGroupQuery> groupQueryProvider;
+ @Inject private GroupOperations groupOperations;
@Test
public void indexingUpdatesTheIndex() throws Exception {
@@ -66,8 +68,10 @@
groupIndexer.index(groupUuid);
- List<InternalGroup> parentGroups = groupQueryProvider.get().bySubgroup(subgroupUuid);
- assertThatGroups(parentGroups).onlyElement().groupUuid().isEqualTo(groupUuid);
+ Set<AccountGroup.UUID> parentGroups =
+ groupQueryProvider.get().bySubgroups(ImmutableSet.of(subgroupUuid)).get(subgroupUuid);
+ assertThat(parentGroups).hasSize(1);
+ assertThat(parentGroups).containsExactly(groupUuid);
}
@Test
@@ -83,8 +87,10 @@
groupIndexer.index(groupUuid);
- List<InternalGroup> parentGroups = groupQueryProvider.get().bySubgroup(subgroupUuid);
- assertThatGroups(parentGroups).onlyElement().groupUuid().isEqualTo(groupUuid);
+ Set<AccountGroup.UUID> parentGroups =
+ groupQueryProvider.get().bySubgroups(ImmutableSet.of(subgroupUuid)).get(subgroupUuid);
+ assertThat(parentGroups).hasSize(1);
+ assertThat(parentGroups).containsExactly(groupUuid);
}
@Test
@@ -111,8 +117,10 @@
groupIndexer.reindexIfStale(groupUuid);
- List<InternalGroup> parentGroups = groupQueryProvider.get().bySubgroup(subgroupUuid);
- assertThatGroups(parentGroups).onlyElement().groupUuid().isEqualTo(groupUuid);
+ Set<AccountGroup.UUID> parentGroups =
+ groupQueryProvider.get().bySubgroups(ImmutableSet.of(subgroupUuid)).get(subgroupUuid);
+ assertThat(parentGroups).hasSize(1);
+ assertThat(parentGroups).containsExactly(groupUuid);
}
@Test
@@ -137,6 +145,20 @@
assertWithMessage("Group should have been reindexed").that(reindexed).isTrue();
}
+ @Test
+ public void getMultipleParents() throws Exception {
+ AccountGroup.UUID sub1 = groupOperations.newGroup().create();
+ AccountGroup.UUID sub2 = groupOperations.newGroup().create();
+ AccountGroup.UUID parent1 = groupOperations.newGroup().addSubgroup(sub1).create();
+ AccountGroup.UUID parent2 = groupOperations.newGroup().addSubgroup(sub2).create();
+ AccountGroup.UUID parent3 = groupOperations.newGroup().addSubgroup(sub2).create();
+
+ assertThat(groupQueryProvider.get().bySubgroups(ImmutableSet.of(sub1, sub2)))
+ .containsExactlyEntriesIn(
+ ImmutableMap.of(
+ sub1, ImmutableSet.of(parent1), sub2, ImmutableSet.of(parent2, parent3)));
+ }
+
private AccountGroup.UUID createGroup(String name) throws RestApiException {
GroupInfo group = gApi.groups().create(name).get();
return AccountGroup.uuid(group.id);
@@ -164,9 +186,4 @@
Optional<InternalGroup> updatedGroup) {
return assertThat(updatedGroup, internalGroups());
}
-
- private static ListSubject<InternalGroupSubject, InternalGroup> assertThatGroups(
- List<InternalGroup> parentGroups) {
- return assertThat(parentGroups, internalGroups());
- }
}