Cache parsed ChangeNotes

By instrumenting a dev server, I found that loading the change screen
for a single change plus 2 related changes and 2 conflicting changes
results in a whopping 25 loads of a ChangeNotes. On a SQL database
that's not too terrible, but git storage is enough slower and more
memory-intensive than SQL that this is noticeable. Plus, the
ChangeNotes approach is to parse EVERYTHING, which is often more than
we need.

Solve this problem in the way we solve all problems: throw a cache in
front of it. This data has the nice property that the entities parsed
from a particular SHA-1 are immutable forever, so we don't need to
worry about invalidation like we would with a cache keyed by, say,
change number.

Change-Id: I4bbb9bf82f8f228b3ae8333a288af35d163c050f
This commit is contained in:
Dave Borowitz
2016-05-10 11:25:47 -07:00
parent f3fe1a0c27
commit 6f8254b667
6 changed files with 184 additions and 15 deletions

View File

@@ -22,8 +22,10 @@ import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
import static com.google.gerrit.testutil.TestChanges.incrementPatchSet;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import static org.junit.Assert.fail;
import com.google.common.base.Function;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMultimap;
@@ -52,6 +54,7 @@ import com.google.gerrit.testutil.TestChanges;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
@@ -802,9 +805,15 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
RevCommit commit = tr.commit().message("PS1 again").create();
update.setCommit(rw, commit);
update.commit();
exception.expect(OrmException.class);
exception.expectMessage("Multiple revisions parsed for patch set");
notes = newNotes(c);
try {
notes = newNotes(c);
fail("Expected IOException");
} catch (OrmException e) {
assertCause(e, ConfigInvalidException.class,
"Multiple revisions parsed for patch set 1:"
+ " RevId{" + commit.name() + "} and " + ps.getRevision().get());
}
}
@Test
@@ -2285,4 +2294,20 @@ public class ChangeNotesTest extends AbstractChangeNotesTest {
return ref != null ? ref.getObjectId() : null;
}
}
private void assertCause(Throwable e,
Class<? extends Throwable> expectedClass, String expectedMsg) {
Throwable cause = null;
for (Throwable t : Throwables.getCausalChain(e)) {
if (expectedClass.isAssignableFrom(t.getClass())) {
cause = t;
break;
}
}
assertThat(cause)
.named(expectedClass.getSimpleName() + " in causal chain of:\n"
+ Throwables.getStackTraceAsString(e))
.isNotNull();
assertThat(cause.getMessage()).isEqualTo(expectedMsg);
}
}