Merge branch 'stable-2.15' into stable-2.16

* stable-2.15:
  Schema_146: Disable bitmap index re-build during full gc
  Schema_154: Disable bitmap index re-build during full gc
  Schema_154: Periodically run full gc
  Schema_146: Periodically run full gc
  Revert "Keep alive database connection to prevent exceeding wait timeout"

Change-Id: I13e44e3f798a0b48adb59fe505d90e4a7f578b58
diff --git a/java/com/google/gerrit/server/schema/Schema_146.java b/java/com/google/gerrit/server/schema/Schema_146.java
index e0127c0..cdb65c9 100644
--- a/java/com/google/gerrit/server/schema/Schema_146.java
+++ b/java/com/google/gerrit/server/schema/Schema_146.java
@@ -23,7 +23,6 @@
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gwtorm.jdbc.JdbcSchema;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -62,6 +61,7 @@
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevSort;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.pack.PackConfig;
 
 /**
  * Make sure that for every account a user branch exists that has an initial empty commit with the
@@ -101,7 +101,9 @@
   protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
     ui.message("Migrating accounts");
     Set<Entry<Account.Id, Timestamp>> accounts = scanAccounts(db, ui).entrySet();
+    ui.message("Run full gc as preparation for the migration");
     gc(ui);
+    ui.message(String.format("... (%.3f s) full gc completed", elapsed()));
     Set<List<Entry<Account.Id, Timestamp>>> batches =
         Sets.newHashSet(Iterables.partition(accounts, 500));
     ExecutorService pool = createExecutor(ui);
@@ -110,7 +112,7 @@
           .forEach(
               batch -> {
                 @SuppressWarnings("unused")
-                Future<?> unused = pool.submit(() -> processBatch(db, batch, ui));
+                Future<?> unused = pool.submit(() -> processBatch(batch, ui));
               });
       pool.shutdown();
       pool.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
@@ -119,6 +121,9 @@
     }
     ui.message(
         String.format("... (%.3f s) Migrated all %d accounts to schema 146", elapsed(), i.get()));
+    ui.message("Run full gc");
+    gc(ui);
+    ui.message(String.format("... (%.3f s) full gc completed", elapsed()));
   }
 
   private ExecutorService createExecutor(UpdateUI ui) {
@@ -132,7 +137,7 @@
     return Executors.newFixedThreadPool(threads);
   }
 
-  private void processBatch(ReviewDb db, List<Entry<Account.Id, Timestamp>> batch, UpdateUI ui) {
+  private void processBatch(List<Entry<Account.Id, Timestamp>> batch, UpdateUI ui) {
     try (Repository repo = repoManager.openRepository(allUsersName);
         RevWalk rw = new RevWalk(repo);
         ObjectInserter oi = repo.newObjectInserter()) {
@@ -149,29 +154,18 @@
         int count = i.incrementAndGet();
         showProgress(ui, count);
         if (count % 1000 == 0) {
-          gc(repo, true, ui);
-          keepAliveDatabaseConnection(db);
+          boolean runFullGc = count % 100000 == 0;
+          if (runFullGc) {
+            ui.message("Run full gc");
+          }
+          gc(repo, !runFullGc, ui);
+          if (runFullGc) {
+            ui.message(String.format("... (%.3f s) full gc completed", elapsed()));
+          }
         }
       }
     } catch (IOException e) {
       throw new UncheckedIOException(e);
-    } catch (SQLException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
-  private static String dummySelectStatement() {
-    // TODO(davido): Gwtorm doesn't expose dummySelectStatement() method for all supported SQL
-    // dialects.
-    return "SELECT version_nbr FROM schema_version";
-  }
-
-  private static void keepAliveDatabaseConnection(ReviewDb db) throws SQLException {
-    try (Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
-        ResultSet rs = stmt.executeQuery(dummySelectStatement())) {
-      // No Op.
-      // The select is fired to prevent the SQL connection from becoming stale
-      // and being closed on the server side during long running batch operation.
     }
   }
 
@@ -209,6 +203,11 @@
           ui.message(String.format("... (%.3f s) pack refs", elapsed()));
           gc.packRefs();
         } else {
+          // TODO(ms): Enable bitmap index when this JGit performance issue is fixed:
+          // https://bugs.eclipse.org/bugs/show_bug.cgi?id=562740
+          PackConfig pconfig = new PackConfig(repo);
+          pconfig.setBuildBitmaps(false);
+          gc.setPackConfig(pconfig);
           ui.message(String.format("... (%.3f s) gc --prune=now", elapsed()));
           gc.setExpire(new Date());
           gc.gc();
@@ -303,7 +302,7 @@
     return oi.insert(cb);
   }
 
-  private static boolean isInitialEmptyCommit(ObjectId emptyTree, RevCommit c) {
+  private boolean isInitialEmptyCommit(ObjectId emptyTree, RevCommit c) {
     return c.getParentCount() == 0
         && c.getTree().equals(emptyTree)
         && c.getShortMessage().equals(CREATE_ACCOUNT_MSG);
diff --git a/java/com/google/gerrit/server/schema/Schema_154.java b/java/com/google/gerrit/server/schema/Schema_154.java
index 11fa89e..8a39c0d 100644
--- a/java/com/google/gerrit/server/schema/Schema_154.java
+++ b/java/com/google/gerrit/server/schema/Schema_154.java
@@ -16,6 +16,7 @@
 
 import static java.util.stream.Collectors.toMap;
 
+import com.google.common.base.Stopwatch;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.flogger.FluentLogger;
 import com.google.gerrit.reviewdb.client.Account;
@@ -35,16 +36,22 @@
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
+import java.text.ParseException;
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.TimeUnit;
 import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.internal.storage.file.GC;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.TextProgressMonitor;
+import org.eclipse.jgit.storage.pack.PackConfig;
 
 /** Migrate accounts to NoteDb. */
 public class Schema_154 extends SchemaVersion {
@@ -62,6 +69,7 @@
   private final GitRepositoryManager repoManager;
   private final AllUsersName allUsersName;
   private final Provider<PersonIdent> serverIdent;
+  private final Stopwatch sw = Stopwatch.createStarted();
 
   @Inject
   Schema_154(
@@ -84,9 +92,13 @@
         Set<Account> accounts = scanAccounts(db, pm);
         pm.endTask();
         pm.beginTask("Migrating accounts to NoteDb", accounts.size());
+        int i = 0;
         for (Account account : accounts) {
           updateAccountInNoteDb(repo, account);
           pm.update(1);
+          if (++i % 100000 == 0) {
+            gc(repo, ui);
+          }
         }
         pm.endTask();
       }
@@ -147,4 +159,36 @@
   private interface AccountSetter {
     void set(Account a, ResultSet rs, String field) throws SQLException;
   }
+
+  private double elapsed() {
+    return sw.elapsed(TimeUnit.MILLISECONDS) / 1000d;
+  }
+
+  private void gc(Repository repo, UpdateUI ui) {
+    if (repo instanceof FileRepository) {
+      ProgressMonitor pm = null;
+      try {
+        pm = new TextProgressMonitor();
+        FileRepository r = (FileRepository) repo;
+        GC gc = new GC(r);
+        // TODO(davido): Enable bitmap index when this JGit performance issue is fixed:
+        // https://bugs.eclipse.org/bugs/show_bug.cgi?id=562740
+        PackConfig pconfig = new PackConfig(repo);
+        pconfig.setBuildBitmaps(false);
+        gc.setPackConfig(pconfig);
+        gc.setProgressMonitor(pm);
+        pm.beginTask("gc", ProgressMonitor.UNKNOWN);
+        ui.message(String.format("... (%.3f s) gc --prune=now", elapsed()));
+        gc.setExpire(new Date());
+        gc.gc();
+        ui.message(String.format("... (%.3f s) full gc completed", elapsed()));
+      } catch (IOException | ParseException e) {
+        throw new RuntimeException(e);
+      } finally {
+        if (pm != null) {
+          pm.endTask();
+        }
+      }
+    }
+  }
 }