ChangeFinder: Dedup results when subindexes are used
Some secondary index implementations store changes in nonoverlapping
subindexes. Due to the non-atomic nature of these subindexes, we might
fleetingly observe a change as present twice. This is not an error that
the user can do anything about, so relax ChangeFinder a bit in this
specific case.
Change-Id: Ie434937ad6e05c063bae04fc0affe3eaef6a51e0
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
index 566e159..53e7c19 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
@@ -68,6 +68,6 @@
@Provides
@Singleton
IndexConfig getIndexConfig(@GerritServerConfig Config cfg) {
- return IndexConfig.fromConfig(cfg);
+ return IndexConfig.fromConfig(cfg).separateChangeSubIndexes(true).build();
}
}
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
index 699fd51..89fd819 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneIndexModule.java
@@ -84,7 +84,7 @@
IndexConfig getIndexConfig(@GerritServerConfig Config cfg) {
BooleanQuery.setMaxClauseCount(
cfg.getInt("index", "maxTerms", BooleanQuery.getMaxClauseCount()));
- return IndexConfig.fromConfig(cfg);
+ return IndexConfig.fromConfig(cfg).separateChangeSubIndexes(true).build();
}
private static class MultiVersionModule extends LifecycleModule {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeFinder.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeFinder.java
index 2f3a76f..2d80ceb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeFinder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeFinder.java
@@ -14,9 +14,11 @@
package com.google.gerrit.server;
+import com.google.common.collect.Sets;
import com.google.common.primitives.Ints;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.server.change.ChangeTriplet;
+import com.google.gerrit.server.index.IndexConfig;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.query.change.ChangeData;
@@ -29,13 +31,16 @@
import java.util.Collections;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
@Singleton
public class ChangeFinder {
+ private final IndexConfig indexConfig;
private final Provider<InternalChangeQuery> queryProvider;
@Inject
- ChangeFinder(Provider<InternalChangeQuery> queryProvider) {
+ ChangeFinder(IndexConfig indexConfig, Provider<InternalChangeQuery> queryProvider) {
+ this.indexConfig = indexConfig;
this.queryProvider = queryProvider;
}
@@ -93,8 +98,24 @@
private List<ChangeControl> asChangeControls(List<ChangeData> cds, CurrentUser user)
throws OrmException {
List<ChangeControl> ctls = new ArrayList<>(cds.size());
+ if (!indexConfig.separateChangeSubIndexes()) {
+ for (ChangeData cd : cds) {
+ ctls.add(cd.changeControl(user));
+ }
+ return ctls;
+ }
+
+ // If an index implementation uses separate non-atomic subindexes, it's possible to temporarily
+ // observe a change as present in both subindexes, if this search is concurrent with a write.
+ // Dedup to avoid confusing the caller. We can choose an arbitrary ChangeData instance because
+ // the index results have no stored fields, so the data is already reloaded. (It's also possible
+ // that a change might appear in zero subindexes, but there's nothing we can do here to help
+ // this case.)
+ Set<Change.Id> seen = Sets.newHashSetWithExpectedSize(cds.size());
for (ChangeData cd : cds) {
- ctls.add(cd.changeControl(user));
+ if (seen.add(cd.getId())) {
+ ctls.add(cd.changeControl(user));
+ }
}
return ctls;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexConfig.java
index 313ffc2..5c3cdf2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexConfig.java
@@ -34,12 +34,12 @@
return builder().build();
}
- public static IndexConfig fromConfig(Config cfg) {
+ public static Builder fromConfig(Config cfg) {
Builder b = builder();
setIfPresent(cfg, "maxLimit", b::maxLimit);
setIfPresent(cfg, "maxPages", b::maxPages);
setIfPresent(cfg, "maxTerms", b::maxTerms);
- return b.build();
+ return b;
}
private static void setIfPresent(Config cfg, String name, IntConsumer setter) {
@@ -53,7 +53,8 @@
return new AutoValue_IndexConfig.Builder()
.maxLimit(Integer.MAX_VALUE)
.maxPages(Integer.MAX_VALUE)
- .maxTerms(DEFAULT_MAX_TERMS);
+ .maxTerms(DEFAULT_MAX_TERMS)
+ .separateChangeSubIndexes(false);
}
@AutoValue.Builder
@@ -70,6 +71,8 @@
abstract int maxTerms();
+ public abstract Builder separateChangeSubIndexes(boolean separate);
+
abstract IndexConfig autoBuild();
public IndexConfig build() {
@@ -101,4 +104,9 @@
* for performance reasons.
*/
public abstract int maxTerms();
+
+ /**
+ * @return whether different subsets of changes may be stored in different physical sub-indexes.
+ */
+ public abstract boolean separateChangeSubIndexes();
}