Don't require ChangeNotes when rebuilding changes

Previously, ChangeRebuilderImpl was creating new ChangeNotes to pass
to each of its ChangeUpdates. Unfortunately, eager loading of
ChangeNotes means that rebuilding changes with a large number of
events exhibits quadratic behavior, as all N-1 commits in the meta
history were being read in order to produce the Nth commit.

It turns out we don't really need a ChangeNotes in the ChangeUpdate at
all, so we can skip this expensive re-parsing step. This should
improve rebuild speed noticeably.

Change-Id: Icc942dba25fb29dddcdf0ba5411147e63fc779df
This commit is contained in:
Dave Borowitz
2016-05-27 09:50:17 -04:00
parent 814f77c560
commit 3361511353
4 changed files with 78 additions and 43 deletions

View File

@@ -17,6 +17,7 @@ package com.google.gerrit.server.notedb;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
@@ -43,10 +44,12 @@ public abstract class AbstractChangeUpdate {
protected final NotesMigration migration;
protected final ChangeNoteUtil noteUtil;
protected final String anonymousCowardName;
protected final ChangeNotes notes;
protected final Account.Id accountId;
protected final PersonIdent authorIdent;
protected final Date when;
@Nullable private final ChangeNotes notes;
private final Change change;
private final PersonIdent serverIdent;
protected PatchSet.Id psId;
@@ -59,15 +62,16 @@ public abstract class AbstractChangeUpdate {
String anonymousCowardName,
ChangeNoteUtil noteUtil,
Date when) {
this(
migration,
noteUtil,
serverIdent,
anonymousCowardName,
ctl.getNotes(),
accountId(ctl.getUser()),
ident(noteUtil, serverIdent, anonymousCowardName, ctl.getUser(), when),
when);
this.migration = migration;
this.noteUtil = noteUtil;
this.serverIdent = new PersonIdent(serverIdent, when);
this.anonymousCowardName = anonymousCowardName;
this.notes = ctl.getNotes();
this.change = notes.getChange();
this.accountId = accountId(ctl.getUser());
this.authorIdent =
ident(noteUtil, serverIdent, anonymousCowardName, ctl.getUser(), when);
this.when = when;
}
protected AbstractChangeUpdate(
@@ -75,15 +79,21 @@ public abstract class AbstractChangeUpdate {
ChangeNoteUtil noteUtil,
PersonIdent serverIdent,
String anonymousCowardName,
ChangeNotes notes,
@Nullable ChangeNotes notes,
@Nullable Change change,
Account.Id accountId,
PersonIdent authorIdent,
Date when) {
checkArgument(
(notes != null && change == null)
|| (notes == null && change != null),
"exactly one of notes or change required");
this.migration = migration;
this.noteUtil = noteUtil;
this.serverIdent = new PersonIdent(serverIdent, when);
this.anonymousCowardName = anonymousCowardName;
this.notes = notes;
this.change = change != null ? change : notes.getChange();
this.accountId = accountId;
this.authorIdent = authorIdent;
this.when = when;
@@ -114,15 +124,16 @@ public abstract class AbstractChangeUpdate {
}
public Change.Id getId() {
return notes.getChangeId();
return change.getId();
}
@Nullable
public ChangeNotes getNotes() {
return notes;
}
public Change getChange() {
return notes.getChange();
return change;
}
public Date getWhen() {

View File

@@ -21,6 +21,7 @@ import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import com.google.auto.value.AutoValue;
import com.google.common.collect.Sets;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchLineComment;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
@@ -62,6 +63,8 @@ public class ChangeDraftUpdate extends AbstractChangeUpdate {
public interface Factory {
ChangeDraftUpdate create(ChangeNotes notes, Account.Id accountId,
PersonIdent authorIdent, Date when);
ChangeDraftUpdate create(Change change, Account.Id accountId,
PersonIdent authorIdent, Date when);
}
@AutoValue
@@ -76,8 +79,8 @@ public class ChangeDraftUpdate extends AbstractChangeUpdate {
private final AllUsersName draftsProject;
private List<PatchLineComment> put;
private Set<Key> delete;
private List<PatchLineComment> put = new ArrayList<>();
private Set<Key> delete = new HashSet<>();
@AssistedInject
private ChangeDraftUpdate(
@@ -90,11 +93,25 @@ public class ChangeDraftUpdate extends AbstractChangeUpdate {
@Assisted Account.Id accountId,
@Assisted PersonIdent authorIdent,
@Assisted Date when) {
super(migration, noteUtil, serverIdent, anonymousCowardName, notes,
super(migration, noteUtil, serverIdent, anonymousCowardName, notes, null,
accountId, authorIdent, when);
this.draftsProject = allUsers;
}
@AssistedInject
private ChangeDraftUpdate(
@GerritPersonIdent PersonIdent serverIdent,
@AnonymousCowardName String anonymousCowardName,
NotesMigration migration,
AllUsersName allUsers,
ChangeNoteUtil noteUtil,
@Assisted Change change,
@Assisted Account.Id accountId,
@Assisted PersonIdent authorIdent,
@Assisted Date when) {
super(migration, noteUtil, serverIdent, anonymousCowardName, null, change,
accountId, authorIdent, when);
this.draftsProject = allUsers;
this.put = new ArrayList<>();
this.delete = new HashSet<>();
}
public void putComment(PatchLineComment c) {
@@ -179,14 +196,17 @@ public class ChangeDraftUpdate extends AbstractChangeUpdate {
// If reading from changes is enabled, then the old DraftCommentNotes
// already parsed the revision notes. We can reuse them as long as the ref
// hasn't advanced.
DraftCommentNotes draftNotes =
getNotes().load().getDraftCommentNotes();
if (draftNotes != null) {
ObjectId idFromNotes =
firstNonNull(draftNotes.getRevision(), ObjectId.zeroId());
RevisionNoteMap rnm = draftNotes.getRevisionNoteMap();
if (idFromNotes.equals(curr) && rnm != null) {
return rnm;
ChangeNotes changeNotes = getNotes();
if (changeNotes != null) {
DraftCommentNotes draftNotes =
changeNotes.load().getDraftCommentNotes();
if (draftNotes != null) {
ObjectId idFromNotes =
firstNonNull(draftNotes.getRevision(), ObjectId.zeroId());
RevisionNoteMap rnm = draftNotes.getRevisionNoteMap();
if (idFromNotes.equals(curr) && rnm != null) {
return rnm;
}
}
}
}

View File

@@ -116,7 +116,6 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
private final AccountCache accountCache;
private final ChangeDraftUpdate.Factory draftUpdateFactory;
private final ChangeNotes.Factory notesFactory;
private final ChangeNoteUtil changeNoteUtil;
private final ChangeUpdate.Factory updateFactory;
private final NoteDbUpdateManager.Factory updateManagerFactory;
@@ -129,7 +128,6 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
ChangeRebuilderImpl(SchemaFactory<ReviewDb> schemaFactory,
AccountCache accountCache,
ChangeDraftUpdate.Factory draftUpdateFactory,
ChangeNotes.Factory notesFactory,
ChangeNoteUtil changeNoteUtil,
ChangeUpdate.Factory updateFactory,
NoteDbUpdateManager.Factory updateManagerFactory,
@@ -140,7 +138,6 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
super(schemaFactory);
this.accountCache = accountCache;
this.draftUpdateFactory = draftUpdateFactory;
this.notesFactory = notesFactory;
this.changeNoteUtil = changeNoteUtil;
this.updateFactory = updateFactory;
this.updateManagerFactory = updateManagerFactory;
@@ -388,8 +385,7 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
labelNameComparator = Ordering.natural();
}
ChangeUpdate update = updateFactory.create(
notesFactory.createWithAutoRebuildingDisabled(
change, manager.getChangeRepo().cmds),
change,
events.getAccountId(),
events.newAuthorIdent(),
events.getWhen(),
@@ -406,13 +402,12 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
private void flushEventsToDraftUpdate(NoteDbUpdateManager manager,
EventList<PatchLineCommentEvent> events, Change change)
throws OrmException, IOException {
throws OrmException {
if (events.isEmpty()) {
return;
}
ChangeDraftUpdate update = draftUpdateFactory.create(
notesFactory.createWithAutoRebuildingDisabled(
change, manager.getChangeRepo().cmds),
change,
events.getAccountId(),
events.newAuthorIdent(),
events.getWhen());

View File

@@ -95,7 +95,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
public interface Factory {
ChangeUpdate create(ChangeControl ctl);
ChangeUpdate create(ChangeControl ctl, Date when);
ChangeUpdate create(ChangeNotes notes, @Nullable Account.Id accountId,
ChangeUpdate create(Change change, @Nullable Account.Id accountId,
PersonIdent authorIdent, Date when,
Comparator<String> labelNameComparator);
@@ -204,12 +204,12 @@ public class ChangeUpdate extends AbstractChangeUpdate {
NoteDbUpdateManager.Factory updateManagerFactory,
ChangeDraftUpdate.Factory draftUpdateFactory,
ChangeNoteUtil noteUtil,
@Assisted ChangeNotes notes,
@Assisted Change change,
@Assisted @Nullable Account.Id accountId,
@Assisted PersonIdent authorIdent,
@Assisted Date when,
@Assisted Comparator<String> labelNameComparator) {
super(migration, noteUtil, serverIdent, anonymousCowardName, notes,
super(migration, noteUtil, serverIdent, anonymousCowardName, null, change,
accountId, authorIdent, when);
this.accountCache = accountCache;
this.draftUpdateFactory = draftUpdateFactory;
@@ -328,8 +328,14 @@ public class ChangeUpdate extends AbstractChangeUpdate {
@VisibleForTesting
ChangeDraftUpdate createDraftUpdateIfNull() {
if (draftUpdate == null) {
draftUpdate =
draftUpdateFactory.create(getNotes(), accountId, authorIdent, when);
ChangeNotes notes = getNotes();
if (notes != null) {
draftUpdate =
draftUpdateFactory.create(notes, accountId, authorIdent, when);
} else {
draftUpdate = draftUpdateFactory.create(
getChange(), accountId, authorIdent, when);
}
}
return draftUpdate;
}
@@ -428,10 +434,13 @@ public class ChangeUpdate extends AbstractChangeUpdate {
// If reading from changes is enabled, then the old ChangeNotes already
// parsed the revision notes. We can reuse them as long as the ref hasn't
// advanced.
ObjectId idFromNotes =
firstNonNull(getNotes().load().getRevision(), ObjectId.zeroId());
if (idFromNotes.equals(curr)) {
return checkNotNull(getNotes().revisionNoteMap);
ChangeNotes notes = getNotes();
if (notes != null) {
ObjectId idFromNotes =
firstNonNull(notes.load().getRevision(), ObjectId.zeroId());
if (idFromNotes.equals(curr)) {
return checkNotNull(getNotes().revisionNoteMap);
}
}
}
NoteMap noteMap = NoteMap.read(rw.getObjectReader(), rw.parseCommit(curr));