Introduce auth.externalIdsRefExpiry
When reading the `refs/meta/external-ids` ref, Gerrit opens the
`All-Users` repository over an over again just to extract the tip of the
external-ids ref.
This can be really expensive in response to queries that return loads of
changes along with their account identities.
Allow to mitigate this by providing a memoization mechanism that allows
to use the latest read external-ids sha1 for a configurable period of
time, rather than opening the repository an excessive number of times.
Release-Notes: Introduce auth.externalIdsRefExpiry
Change-Id: Id08282c88f3b6b216bbcbdcd558590f97767090a
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 952496d..02643b9 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -535,6 +535,22 @@
+
By default, `false`.
+[[auth.externalIdsRefExpiry]]auth.externalIdsRefExpiry::
++
+Define TTL value for memoizing the refs/meta/external-ids sha1 rather than opening
+the All-Users repo. This allows faster external-ids refs lookup.
++
+Values should use common unit suffixes to express their setting:
++
+* s, sec, second, seconds
+* m, min, minute, minutes
++
++ Setting this to `0` disables memoization.
++
+**Note** during the memoization time exterenal-ids modification are not visible by Gerrit.
++
+By default `0`.
+
[[auth.emailFormat]]auth.emailFormat::
+
Optional format string to construct user email addresses out of
diff --git a/java/com/google/gerrit/server/account/externalids/storage/notedb/ExternalIdReader.java b/java/com/google/gerrit/server/account/externalids/storage/notedb/ExternalIdReader.java
index dbaed04..cf6f010 100644
--- a/java/com/google/gerrit/server/account/externalids/storage/notedb/ExternalIdReader.java
+++ b/java/com/google/gerrit/server/account/externalids/storage/notedb/ExternalIdReader.java
@@ -15,7 +15,9 @@
package com.google.gerrit.server.account.externalids.storage.notedb;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableSet;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.metrics.Description;
@@ -30,6 +32,8 @@
import com.google.inject.Singleton;
import java.io.IOException;
import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
@@ -54,6 +58,13 @@
*/
@Singleton
public class ExternalIdReader {
+
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ /** Defined only for handling the case when externalIdsRefExpirySecs is zero */
+ private static final Supplier<ObjectId> UNUSED_OBJECT_ID_SUPPLIER =
+ Suppliers.ofInstance(ObjectId.zeroId());
+
public static ObjectId readRevision(Repository repo) throws IOException {
Ref ref = repo.exactRef(RefNames.REFS_EXTERNAL_IDS);
return ref != null ? ref.getObjectId() : ObjectId.zeroId();
@@ -73,6 +84,8 @@
private final Timer0 readSingleLatency;
private final ExternalIdFactoryNoteDbImpl externalIdFactory;
private final AuthConfig authConfig;
+ private final Supplier<ObjectId> allUsersSupplier;
+ private final int externalIdsRefExpirySecs;
@VisibleForTesting
@Inject
@@ -98,6 +111,22 @@
.setUnit(Units.MILLISECONDS));
this.externalIdFactory = externalIdFactory;
this.authConfig = authConfig;
+ this.externalIdsRefExpirySecs = authConfig.getExternalIdsRefExpirySecs();
+ this.allUsersSupplier =
+ externalIdsRefExpirySecs > 0
+ ? Suppliers.memoizeWithExpiration(
+ () -> {
+ try {
+ logger.atFine().log("Refreshing external-ids revision from All-Users repo");
+ return readRevision(repoManager, allUsersName);
+ } catch (IOException e) {
+ throw new IllegalStateException(
+ "Couldn't refresh external-ids from All-Users repo", e);
+ }
+ },
+ externalIdsRefExpirySecs,
+ TimeUnit.SECONDS)
+ : UNUSED_OBJECT_ID_SUPPLIER;
}
@VisibleForTesting
@@ -112,6 +141,13 @@
}
public ObjectId readRevision() throws IOException {
+ return externalIdsRefExpirySecs > 0
+ ? allUsersSupplier.get()
+ : readRevision(repoManager, allUsersName);
+ }
+
+ private static ObjectId readRevision(GitRepositoryManager repoManager, AllUsersName allUsersName)
+ throws IOException {
try (Repository repo = repoManager.openRepository(allUsersName)) {
return readRevision(repo);
}
diff --git a/java/com/google/gerrit/server/config/AuthConfig.java b/java/com/google/gerrit/server/config/AuthConfig.java
index b6ffcee..2d118a6 100644
--- a/java/com/google/gerrit/server/config/AuthConfig.java
+++ b/java/com/google/gerrit/server/config/AuthConfig.java
@@ -66,6 +66,7 @@
private final boolean allowRegisterNewEmail;
private final boolean userNameCaseInsensitive;
private final boolean userNameCaseInsensitiveMigrationMode;
+ private final int externalIdsRefExpirySecs;
private GitBasicAuthPolicy gitBasicAuthPolicy;
@Inject
@@ -100,6 +101,9 @@
userNameCaseInsensitive = cfg.getBoolean("auth", "userNameCaseInsensitive", false);
userNameCaseInsensitiveMigrationMode =
cfg.getBoolean("auth", "userNameCaseInsensitiveMigrationMode", false);
+ externalIdsRefExpirySecs =
+ (int)
+ ConfigUtil.getTimeUnit(cfg, "auth", null, "externalIdsRefExpiry", 0, TimeUnit.SECONDS);
if (gitBasicAuthPolicy == GitBasicAuthPolicy.HTTP_LDAP
&& authType != AuthType.LDAP
@@ -218,6 +222,10 @@
return cookieSecure;
}
+ public int getExternalIdsRefExpirySecs() {
+ return externalIdsRefExpirySecs;
+ }
+
public SignedToken getEmailRegistrationToken() {
return emailReg;
}