NoteDbMigrator: Use new pack-based inserter implementation
The migrator can potentially create many thousands of new objects for
NoteDb data, more than we want to leave loose in the repository.
Upstream JGit added a new ObjectInserter implementation that inserts
objects directly into packs instead. We can now use this to share a
single inserter for the whole change repository.
Only use this strategy during migration. We would rather make loose
objects in a live server, since object lookup is linear in the number of
packs, so we wouldn't want to continually create new packs without
compacting them.
Bug: Issue 7399
Change-Id: I3963875525e7963f209081a6881c4c77e015122e
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/OnlineNoteDbMigrationIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/OnlineNoteDbMigrationIT.java
index 51e2964..6b14263 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/OnlineNoteDbMigrationIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/OnlineNoteDbMigrationIT.java
@@ -60,8 +60,17 @@
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
@@ -374,10 +383,14 @@
Change.Id id1 = r1.getChange().getId();
Change.Id id2 = r2.getChange().getId();
- migrate(b -> b.setThreads(threads));
- assertNotesMigrationState(NOTE_DB, false, false);
+ Set<String> objectFiles = getObjectFiles(project);
+ assertThat(objectFiles).isNotEmpty();
+ migrate(b -> b.setThreads(threads));
+
+ assertNotesMigrationState(NOTE_DB, false, false);
assertThat(sequences.nextChangeId()).isEqualTo(503);
+ assertThat(getObjectFiles(project)).containsExactlyElementsIn(objectFiles);
ObjectId oldMetaId = null;
int rowVersion = 0;
@@ -531,4 +544,23 @@
private void addListener(NotesMigrationStateListener listener) {
addedListeners.add(listeners.add(listener));
}
+
+ private SortedSet<String> getObjectFiles(Project.NameKey project) throws Exception {
+ SortedSet<String> files = new TreeSet<>();
+ try (Repository repo = repoManager.openRepository(project)) {
+ Files.walkFileTree(
+ ((FileRepository) repo).getObjectDatabase().getDirectory().toPath(),
+ new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
+ String name = file.getFileName().toString();
+ if (!attrs.isDirectory() && !name.endsWith(".pack") && !name.endsWith(".idx")) {
+ files.add(name);
+ }
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+ return files;
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
index 3f20766..3e36de9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
@@ -309,14 +309,14 @@
public NoteDbUpdateManager setChangeRepo(
Repository repo, RevWalk rw, @Nullable ObjectInserter ins, ChainedReceiveCommands cmds) {
checkState(changeRepo == null, "change repo already initialized");
- changeRepo = new OpenRepo(repo, rw, ins, cmds, false, true);
+ changeRepo = new OpenRepo(repo, rw, ins, cmds, false, saveObjects);
return this;
}
public NoteDbUpdateManager setAllUsersRepo(
Repository repo, RevWalk rw, @Nullable ObjectInserter ins, ChainedReceiveCommands cmds) {
checkState(allUsersRepo == null, "All-Users repo already initialized");
- allUsersRepo = new OpenRepo(repo, rw, ins, cmds, false, true);
+ allUsersRepo = new OpenRepo(repo, rw, ins, cmds, false, saveObjects);
return this;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java
index 3f72ee5..2981174 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/NoteDbMigrator.java
@@ -67,6 +67,7 @@
import com.google.gerrit.server.notedb.RepoSequence;
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder.NoPatchSetsException;
import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.update.ChainedReceiveCommands;
import com.google.gerrit.server.util.ManualRequestContext;
import com.google.gerrit.server.util.ThreadLocalRequestContext;
import com.google.gwtorm.server.OrmException;
@@ -89,9 +90,14 @@
import java.util.function.Predicate;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.TextProgressMonitor;
+import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.io.NullOutputStream;
@@ -741,6 +747,12 @@
return ImmutableListMultimap.copyOf(out);
}
+ private static ObjectInserter newPackInserter(Repository repo) {
+ return repo instanceof FileRepository
+ ? ((FileRepository) repo).getObjectDatabase().newPackInserter()
+ : repo.newObjectInserter();
+ }
+
private boolean rebuildProject(
ReviewDb db,
ImmutableListMultimap<Project.NameKey, Change.Id> allChanges,
@@ -750,8 +762,26 @@
ProgressMonitor pm =
new TextProgressMonitor(
new PrintWriter(new BufferedWriter(new OutputStreamWriter(progressOut, UTF_8))));
- try (NoteDbUpdateManager manager =
- updateManagerFactory.create(project).setSaveObjects(false).setAtomicRefUpdates(false)) {
+ try (Repository changeRepo = repoManager.openRepository(project);
+ ObjectInserter changeIns = newPackInserter(changeRepo);
+ ObjectReader changeReader = changeIns.newReader();
+ RevWalk changeRw = new RevWalk(changeReader);
+ NoteDbUpdateManager manager =
+ updateManagerFactory
+ .create(project)
+ .setSaveObjects(false)
+ .setAtomicRefUpdates(false)
+ // Only use a PackInserter for the change repo, not All-Users.
+ //
+ // It's not possible to share a single inserter for All-Users across all project
+ // tasks, and we don't want to add one pack per project to All-Users. Adding many
+ // loose objects is preferable to many packs.
+ //
+ // Anyway, the number of objects inserted into All-Users is proportional to the
+ // number of pending draft comments, which should not be high (relative to the total
+ // number of changes), so the number of loose objects shouldn't be too unreasonable.
+ .setChangeRepo(
+ changeRepo, changeRw, changeIns, new ChainedReceiveCommands(changeRepo))) {
Set<Change.Id> skipExecute = new HashSet<>();
Collection<Change.Id> changes = allChanges.get(project);
pm.beginTask(FormatUtil.elide("Rebuilding " + project.get(), 50), changes.size());
@@ -763,8 +793,6 @@
staged = true;
} catch (NoPatchSetsException e) {
log.warn(e.getMessage());
- } catch (RepositoryNotFoundException e) {
- log.warn("Repository {} not found while rebuilding change {}", project, changeId);
} catch (Throwable t) {
log.error("Failed to rebuild change " + changeId, t);
ok = false;
@@ -812,6 +840,10 @@
} catch (OrmException | IOException e) {
log.error("Failed to save NoteDb state for " + project, e);
}
+ } catch (RepositoryNotFoundException e) {
+ log.warn("Repository {} not found", project);
+ } catch (IOException e) {
+ log.error("Failed to rebuild project " + project, e);
}
return ok;
}