Schedule groups-backends in a queue
When loading groups from an auth backend (for example LDAP), Gerrit is
subject to its latency. This might be a problem because a slow backend,
or a very large number of groups, or both, might cause the warm-cache
command to last longer than the allowed timeouts imposed by Gerrit (or a
load balancer that fronts it).
Schedule the loading of the cache asynchronously over a new dedicated
queue named: "Groups-Backend-Cache-Warmer".
This allows to terminate the SSH command in a timely manner, while
loading the cache in the background.
The status of the cache loading can be observed via the `show-queue`
command as well as the `queue/groups_backend_cache_warmer/*` metrics.
Currently the size of the queue is not configurable and it is set to 8
threads.
Change-Id: Iccbb1f05bcf04c540a68e1d70cd6be9078a50815
diff --git a/admin/warm-cache-1.0.groovy b/admin/warm-cache-1.0.groovy
index 88a8148..c3c4892 100644
--- a/admin/warm-cache-1.0.groovy
+++ b/admin/warm-cache-1.0.groovy
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import com.google.common.flogger.FluentLogger
import com.google.gerrit.common.data.GlobalCapability
import com.google.gerrit.sshd.*
import com.google.gerrit.extensions.annotations.*
@@ -20,9 +21,14 @@
import com.google.gerrit.server.IdentifiedUser
import com.google.gerrit.reviewdb.client.AccountGroup
import com.google.inject.*
-import org.kohsuke.args4j.*
+import com.google.gerrit.server.git.WorkQueue
+import org.apache.sshd.server.Environment
+
+import java.util.concurrent.ExecutorService
abstract class BaseSshCommand extends SshCommand {
+ protected static final FluentLogger logger = FluentLogger.forEnclosingClass()
+
void println(String msg) {
stdout.println msg
@@ -142,32 +148,70 @@
@Export("groups-backends")
@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
class WarmGroupsBackendsCache extends WarmAccountsCache {
+ private static final THREAD_POOL_SIZE = 8
+ private static final QUEUE_NAME = "Groups-Backend-Cache-Warmer"
@Inject
IdentifiedUser.GenericFactory userFactory
- public void run() {
- println "Loading groups ..."
+ @Inject WorkQueue queues
+
+ private static class GroupsBackendsTask implements Runnable {
+ IdentifiedUser user
+
+ GroupsBackendsTask(IdentifiedUser identifiedUser) {
+ user = identifiedUser
+ }
+
+ @Override
+ void run() {
+ def threadStart = System.currentTimeMillis()
+ def groupsUUIDs = user.getEffectiveGroups()?.getKnownGroups()
+ def threadElapsed = (System.currentTimeMillis() - threadStart)
+ logger.atInfo().log("Loaded %d groups for account %d in %s millis", groupsUUIDs.size(), user.getAccountId().get(), threadElapsed)
+ }
+
+ @Override
+ String toString() {
+ return "Warmup backend groups [accountId: ${user.getAccountId().get()}]"
+ }
+ }
+
+ ExecutorService executorService
+
+ private ExecutorService executor() {
+ def existingExecutor = queues.getExecutor(QUEUE_NAME)
+ if(existingExecutor != null) {
+ return existingExecutor
+ }
+ return queues.createQueue(THREAD_POOL_SIZE, QUEUE_NAME, true);
+ }
+
+ @Override
+ void start(Environment env) throws IOException {
+ super.start(env)
+ executorService = executor()
+ }
+
+ void run() {
+ println "Scheduling backend groups loading ..."
def start = System.currentTimeMillis()
- def loaded = 0
- def allGroupsUUIDs = new HashSet<AccountGroup.UUID>()
+ def scheduled = 0
def lastDisplay = 0
for (accountId in accounts.allIds()) {
- def user = userFactory.create(accountId)
- def groupsUUIDs = user?.getEffectiveGroups()?.getKnownGroups()
- if (groupsUUIDs != null) { allGroupsUUIDs.addAll(groupsUUIDs) }
+ scheduled++
+ executorService.submit(new GroupsBackendsTask(userFactory.create(accountId)))
- loaded = allGroupsUUIDs.size()
- if (loaded.intdiv(1000) > lastDisplay) {
- println "$loaded groups"
- lastDisplay = loaded.intdiv(1000)
+ if (scheduled.intdiv(1000) > lastDisplay) {
+ println "Scheduled loading of groups for $scheduled accounts"
+ lastDisplay = scheduled.intdiv(1000)
}
}
- def elapsed = (System.currentTimeMillis()-start)/1000
- println "$loaded groups loaded in $elapsed secs"
+ def elapsed = (System.currentTimeMillis() - start) / 1000
+ println "Scheduled loading of groups for $scheduled accounts in $elapsed secs"
}
}