LUCENE-9036: ExitableDirectoryReader checks timeout on DocValues access.

This commit is contained in:
Mikhail Khludnev 2019-11-08 17:51:33 -08:00
parent 1411aaee94
commit 51b1c5a023
10 changed files with 596 additions and 8 deletions

View File

@ -87,6 +87,8 @@ Improvements
* LUCENE-9018: ConcatenateGraphFilter now has a configurable separator. (Stanislav Mikulchik, David Smiley)
* LUCENE-9036: ExitableDirectoryReader may interupt scaning over DocValues (Mikhail Khludnev)
Optimizations
* LUCENE-8928: When building a kd-tree for dimensions n > 2, compute exact bounds for an inner node every N splits

View File

@ -21,6 +21,7 @@ import java.io.IOException;
import org.apache.lucene.index.FilterLeafReader.FilterTerms;
import org.apache.lucene.index.FilterLeafReader.FilterTermsEnum;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.automaton.CompiledAutomaton;
@ -69,7 +70,9 @@ public class ExitableDirectoryReader extends FilterDirectoryReader {
*/
public static class ExitableFilterAtomicReader extends FilterLeafReader {
private QueryTimeout queryTimeout;
final private QueryTimeout queryTimeout;
final static int DOCS_BETWEEN_TIMEOUT_CHECK = 1000;
/** Constructor **/
public ExitableFilterAtomicReader(LeafReader in, QueryTimeout queryTimeout) {
@ -107,6 +110,216 @@ public class ExitableDirectoryReader extends FilterDirectoryReader {
return in.getCoreCacheHelper();
}
@Override
public NumericDocValues getNumericDocValues(String field) throws IOException {
final NumericDocValues numericDocValues = super.getNumericDocValues(field);
if (numericDocValues == null) {
return null;
}
return (queryTimeout.isTimeoutEnabled()) ? new FilterNumericDocValues(numericDocValues) {
private int docToCheck = 0;
@Override
public int advance(int target) throws IOException {
final int advance = super.advance(target);
if (advance >= docToCheck) {
checkAndThrow(in);
docToCheck = advance + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return advance;
}
@Override
public boolean advanceExact(int target) throws IOException {
final boolean advanceExact = super.advanceExact(target);
if (target >= docToCheck) {
checkAndThrow(in);
docToCheck=target + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return advanceExact;
}
@Override
public int nextDoc() throws IOException {
final int nextDoc = super.nextDoc();
if (nextDoc >= docToCheck) {
checkAndThrow(in);
docToCheck = nextDoc + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return nextDoc;
}
}: numericDocValues;
}
@Override
public BinaryDocValues getBinaryDocValues(String field) throws IOException {
final BinaryDocValues binaryDocValues = super.getBinaryDocValues(field);
if (binaryDocValues == null) {
return null;
}
return (queryTimeout.isTimeoutEnabled()) ? new FilterBinaryDocValues(binaryDocValues) {
private int docToCheck = 0;
@Override
public int advance(int target) throws IOException {
final int advance = super.advance(target);
if (target >= docToCheck) {
checkAndThrow(in);
docToCheck = target + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return advance;
}
@Override
public boolean advanceExact(int target) throws IOException {
final boolean advanceExact = super.advanceExact(target);
if (target >= docToCheck) {
checkAndThrow(in);
docToCheck = target + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return advanceExact;
}
@Override
public int nextDoc() throws IOException {
final int nextDoc = super.nextDoc();
if (nextDoc >= docToCheck) {
checkAndThrow(in);
docToCheck = nextDoc + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return nextDoc;
}
}: binaryDocValues;
}
@Override
public SortedDocValues getSortedDocValues(String field) throws IOException {
final SortedDocValues sortedDocValues = super.getSortedDocValues(field);
if (sortedDocValues == null) {
return null;
}
return (queryTimeout.isTimeoutEnabled()) ? new FilterSortedDocValues(sortedDocValues) {
private int docToCheck = 0;
@Override
public int advance(int target) throws IOException {
final int advance = super.advance(target);
if (advance >= docToCheck) {
checkAndThrow(in);
docToCheck = advance + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return advance;
}
@Override
public boolean advanceExact(int target) throws IOException {
final boolean advanceExact = super.advanceExact(target);
if (target >= docToCheck) {
checkAndThrow(in);
docToCheck = target + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return advanceExact;
}
@Override
public int nextDoc() throws IOException {
final int nextDoc = super.nextDoc();
if (nextDoc >= docToCheck) {
checkAndThrow(in);
docToCheck = nextDoc + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return nextDoc;
}
}: sortedDocValues;
}
@Override
public SortedNumericDocValues getSortedNumericDocValues(String field) throws IOException {
final SortedNumericDocValues sortedNumericDocValues = super.getSortedNumericDocValues(field);
if (sortedNumericDocValues == null) {
return null;
}
return (queryTimeout.isTimeoutEnabled()) ? new FilterSortedNumericDocValues(sortedNumericDocValues) {
private int docToCheck = 0;
@Override
public int advance(int target) throws IOException {
final int advance = super.advance(target);
if (advance >= docToCheck) {
checkAndThrow(in);
docToCheck = advance + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return advance;
}
@Override
public boolean advanceExact(int target) throws IOException {
final boolean advanceExact = super.advanceExact(target);
if (target >= docToCheck) {
checkAndThrow(in);
docToCheck = target + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return advanceExact;
}
@Override
public int nextDoc() throws IOException {
final int nextDoc = super.nextDoc();
if (nextDoc >= docToCheck) {
checkAndThrow(in);
docToCheck = nextDoc + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return nextDoc;
}
}: sortedNumericDocValues;
}
@Override
public SortedSetDocValues getSortedSetDocValues(String field) throws IOException {
final SortedSetDocValues sortedSetDocValues = super.getSortedSetDocValues(field);
if (sortedSetDocValues == null) {
return null;
}
return (queryTimeout.isTimeoutEnabled()) ? new FilterSortedSetDocValues(sortedSetDocValues) {
private int docToCheck=0;
@Override
public int advance(int target) throws IOException {
final int advance = super.advance(target);
if (advance >= docToCheck) {
checkAndThrow(in);
docToCheck = advance + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return advance;
}
@Override
public boolean advanceExact(int target) throws IOException {
final boolean advanceExact = super.advanceExact(target);
if (target >= docToCheck) {
checkAndThrow(in);
docToCheck = target + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return advanceExact;
}
@Override
public int nextDoc() throws IOException {
final int nextDoc = super.nextDoc();
if (nextDoc >= docToCheck) {
checkAndThrow(in);
docToCheck = nextDoc + DOCS_BETWEEN_TIMEOUT_CHECK;
}
return nextDoc;
}
}: sortedSetDocValues;
}
/**
* Throws {@link ExitingReaderException} if {@link QueryTimeout#shouldExit()} returns true,
* or if {@link Thread#interrupted()} returns true.
* @param in underneath docValues
*/
private void checkAndThrow(DocIdSetIterator in) {
if (queryTimeout.shouldExit()) {
throw new ExitingReaderException("The request took too long to iterate over doc values. Timeout: "
+ queryTimeout.toString() + ", DocValues=" + in
);
} else if (Thread.interrupted()) {
throw new ExitingReaderException("Interrupted while iterating over point values. PointValues=" + in);
}
}
}
/**

View File

@ -18,6 +18,7 @@
package org.apache.lucene.index;
import java.io.IOException;
import java.util.Objects;
import org.apache.lucene.util.BytesRef;
@ -31,6 +32,7 @@ public abstract class FilterBinaryDocValues extends BinaryDocValues {
/** Sole constructor */
protected FilterBinaryDocValues(BinaryDocValues in) {
Objects.requireNonNull(in);
this.in = in;
}

View File

@ -18,6 +18,7 @@
package org.apache.lucene.index;
import java.io.IOException;
import java.util.Objects;
/**
* Delegates all methods to a wrapped {@link NumericDocValues}.
@ -29,6 +30,7 @@ public abstract class FilterNumericDocValues extends NumericDocValues {
/** Sole constructor */
protected FilterNumericDocValues(NumericDocValues in) {
Objects.requireNonNull(in);
this.in = in;
}

View File

@ -0,0 +1,98 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.index;
import java.io.IOException;
import java.util.Objects;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.automaton.CompiledAutomaton;
/**
* Delegates all methods to a wrapped {@link SortedDocValues}.
*/
public abstract class FilterSortedDocValues extends SortedDocValues {
/** Wrapped values */
protected final SortedDocValues in;
/** Sole constructor */
public FilterSortedDocValues(SortedDocValues in) {
Objects.requireNonNull(in);
this.in = in;
}
@Override
public boolean advanceExact(int target) throws IOException {
return in.advanceExact(target);
}
@Override
public int ordValue() throws IOException {
return in.ordValue();
}
@Override
public BytesRef lookupOrd(int ord) throws IOException {
return in.lookupOrd(ord);
}
@Override
public BytesRef binaryValue() throws IOException {
return in.binaryValue();
}
@Override
public int getValueCount() {
return in.getValueCount();
}
@Override
public int lookupTerm(BytesRef key) throws IOException {
return in.lookupTerm(key);
}
@Override
public TermsEnum termsEnum() throws IOException {
return in.termsEnum();
}
@Override
public TermsEnum intersect(CompiledAutomaton automaton) throws IOException {
return in.intersect(automaton);
}
@Override
public int docID() {
return in.docID();
}
@Override
public int nextDoc() throws IOException {
return in.nextDoc();
}
@Override
public int advance(int target) throws IOException {
return in.advance(target);
}
@Override
public long cost() {
return in.cost();
}
}

View File

@ -0,0 +1,66 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.index;
import java.io.IOException;
import java.util.Objects;
/**
* Delegates all methods to a wrapped {@link SortedNumericDocValues}.
*/
public abstract class FilterSortedNumericDocValues extends SortedNumericDocValues {
/** Wrapped values */
protected final SortedNumericDocValues in;
/** Sole constructor */
public FilterSortedNumericDocValues(SortedNumericDocValues in) {
Objects.requireNonNull(in);
this.in = in;
}
public boolean advanceExact(int target) throws IOException {
return in.advanceExact(target);
}
public long nextValue() throws IOException {
return in.nextValue();
}
public int docValueCount() {
return in.docValueCount();
}
public int docID() {
return in.docID();
}
public int nextDoc() throws IOException {
return in.nextDoc();
}
public int advance(int target) throws IOException {
return in.advance(target);
}
public long cost() {
return in.cost();
}
}

View File

@ -0,0 +1,84 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.lucene.index;
import java.io.IOException;
import java.util.Objects;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.automaton.CompiledAutomaton;
/**
* Delegates all methods to a wrapped {@link SortedSetDocValues}.
*/
public class FilterSortedSetDocValues extends SortedSetDocValues {
/** Wrapped values */
protected final SortedSetDocValues in;
/** Initializes delegate */
public FilterSortedSetDocValues(SortedSetDocValues in) {
Objects.requireNonNull(in);
this.in = in;
}
public boolean advanceExact(int target) throws IOException {
return in.advanceExact(target);
}
public long nextOrd() throws IOException {
return in.nextOrd();
}
public BytesRef lookupOrd(long ord) throws IOException {
return in.lookupOrd(ord);
}
public long getValueCount() {
return in.getValueCount();
}
public long lookupTerm(BytesRef key) throws IOException {
return in.lookupTerm(key);
}
public TermsEnum termsEnum() throws IOException {
return in.termsEnum();
}
public TermsEnum intersect(CompiledAutomaton automaton) throws IOException {
return in.intersect(automaton);
}
public int docID() {
return in.docID();
}
public int nextDoc() throws IOException {
return in.nextDoc();
}
public int advance(int target) throws IOException {
return in.advance(target);
}
public long cost() {
return in.cost();
}
}

View File

@ -17,12 +17,19 @@
package org.apache.lucene.index;
import java.io.IOException;
import java.util.Arrays;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.document.BinaryDocValuesField;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.IntPoint;
import org.apache.lucene.document.NumericDocValuesField;
import org.apache.lucene.document.SortedDocValuesField;
import org.apache.lucene.document.SortedNumericDocValuesField;
import org.apache.lucene.document.SortedSetDocValuesField;
import org.apache.lucene.index.ExitableDirectoryReader.ExitingReaderException;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.PrefixQuery;
import org.apache.lucene.search.Query;
@ -275,5 +282,99 @@ public class TestExitableDirectoryReader extends LuceneTestCase {
}
};
}
@FunctionalInterface
interface DvFactory {
DocValuesIterator create(LeafReader leaf) throws IOException;
}
public void testDocValues() throws IOException {
Directory directory = newDirectory();
IndexWriter writer = new IndexWriter(directory, newIndexWriterConfig(new MockAnalyzer(random())));
Document d1 = new Document();
addDVs(d1, 10);
writer.addDocument(d1);
Document d2 = new Document();
addDVs(d2, 100);
writer.addDocument(d2);
Document d3 = new Document();
addDVs(d3, 1000);
writer.addDocument(d3);
writer.forceMerge(1);
writer.commit();
writer.close();
DirectoryReader directoryReader;
DirectoryReader exitableDirectoryReader;
for (DvFactory dvFactory : Arrays.<DvFactory>asList(
(r) -> r.getSortedDocValues("sorted"),
(r) -> r.getSortedSetDocValues("sortedset"),
(r) -> r.getSortedNumericDocValues("sortednumeric"),
(r) -> r.getNumericDocValues("numeric"),
(r) -> r.getBinaryDocValues("binary")
))
{
directoryReader = DirectoryReader.open(directory);
exitableDirectoryReader = new ExitableDirectoryReader(directoryReader, immediateQueryTimeout());
{
IndexReader reader = new TestReader(getOnlyLeafReader(exitableDirectoryReader));
expectThrows(ExitingReaderException.class, () -> {
LeafReader leaf = reader.leaves().get(0).reader();
DocValuesIterator iter = dvFactory.create(leaf);
scan(leaf, iter);
});
reader.close();
}
directoryReader = DirectoryReader.open(directory);
exitableDirectoryReader = new ExitableDirectoryReader(directoryReader, random().nextBoolean()?
infiniteQueryTimeout() : disabledQueryTimeout());
{
IndexReader reader = new TestReader(getOnlyLeafReader(exitableDirectoryReader));
final LeafReader leaf = reader.leaves().get(0).reader();
scan(leaf, dvFactory.create(leaf));
assertNull(leaf.getNumericDocValues("absent"));
assertNull(leaf.getBinaryDocValues("absent"));
assertNull(leaf.getSortedDocValues("absent"));
assertNull(leaf.getSortedNumericDocValues("absent"));
assertNull(leaf.getSortedSetDocValues("absent"));
reader.close();
}
}
directory.close();
}
static private void scan(LeafReader leaf, DocValuesIterator iter ) throws IOException {
for (iter.nextDoc(); iter.docID()!=DocIdSetIterator.NO_MORE_DOCS
&& iter.docID()<leaf.maxDoc();) {
final int nextDocId = iter.docID()+1;
if (random().nextBoolean() && nextDocId<leaf.maxDoc()) {
if(random().nextBoolean()) {
iter.advance(nextDocId);
} else {
iter.advanceExact(nextDocId);
}
} else {
iter.nextDoc();
}
}
}
private void addDVs(Document d1, int i) {
d1.add(new NumericDocValuesField("numeric", i));
d1.add(new BinaryDocValuesField("binary", new BytesRef(""+i)));
d1.add(new SortedDocValuesField("sorted", new BytesRef(""+i)));
d1.add(new SortedNumericDocValuesField("sortednumeric", i));
d1.add(new SortedSetDocValuesField("sortedset", new BytesRef(""+i)));
}
}

View File

@ -17,9 +17,13 @@
-->
<schema name="minimal" version="1.1">
<fieldType name="string" class="solr.StrField"/>
<fieldType name="string_dv" class="solr.StrField" docValues="true"/>
<fieldType name="string_dvs" class="solr.StrField" docValues="true" multiValued="true"/>
<fieldType name="int" class="${solr.tests.IntegerFieldType}" docValues="${solr.tests.numeric.dv}" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
<fieldType name="long" class="${solr.tests.LongFieldType}" docValues="${solr.tests.numeric.dv}" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
<dynamicField name="*" type="string" indexed="true" stored="true"/>
<dynamicField name="*_dv" type="string_dv" indexed="true" stored="true"/>
<dynamicField name="*_dvs" type="string_dvs" indexed="true" stored="true"/>
<!-- for versioning -->
<field name="_version_" type="long" indexed="true" stored="true"/>
<field name="_root_" type="string" indexed="true" stored="true" multiValued="false" required="false"/>

View File

@ -125,19 +125,32 @@ public class CloudExitableDirectoryReaderTest extends SolrCloudTestCase {
counter = 1;
UpdateRequest req = new UpdateRequest();
for(; (counter % NUM_DOCS_PER_TYPE) != 0; counter++ )
req.add(sdoc("id", Integer.toString(counter), "name", "a" + counter,
for(; (counter % NUM_DOCS_PER_TYPE) != 0; counter++ ) {
final String v = "a" + counter;
req.add(sdoc("id", Integer.toString(counter), "name", v,
"name_dv", v,
"name_dvs", v,"name_dvs", v+"1",
"num",""+counter));
}
counter++;
for(; (counter % NUM_DOCS_PER_TYPE) != 0; counter++ )
req.add(sdoc("id", Integer.toString(counter), "name", "b" + counter,
for(; (counter % NUM_DOCS_PER_TYPE) != 0; counter++ ) {
final String v = "b" + counter;
req.add(sdoc("id", Integer.toString(counter), "name", v,
"name_dv", v,
"name_dvs", v,"name_dvs", v+"1",
"num",""+counter));
}
counter++;
for(; counter % NUM_DOCS_PER_TYPE != 0; counter++ )
req.add(sdoc("id", Integer.toString(counter), "name", "dummy term doc" + counter,
for(; counter % NUM_DOCS_PER_TYPE != 0; counter++ ) {
final String v = "dummy term doc" + counter;
req.add(sdoc("id", Integer.toString(counter), "name",
v,
"name_dv", v,
"name_dvs", v,"name_dvs", v+"1",
"num",""+counter));
}
req.commit(client, COLLECTION);
}
@ -226,7 +239,10 @@ public class CloudExitableDirectoryReaderTest extends SolrCloudTestCase {
SolrParams cases[] = new SolrParams[] {
params( "sort","query($q,1) asc"),
params("rows","0", "facet","true", "facet.method", "enum", "facet.field", "name"),
params("rows","0", "json.facet","{ ids: { type: range, field : num, start : 1, end : 99, gap : 9 }}")
params("rows","0", "json.facet","{ ids: { type: range, field : num, start : 1, end : 99, gap : 9 }}"),
params("q", "*:*", "rows","0", "json.facet","{ ids: { type: field, field : num}}"),
params("q", "*:*", "rows","0", "json.facet","{ ids: { type: field, field : name_dv}}"),
params("q", "*:*", "rows","0", "json.facet","{ ids: { type: field, field : name_dvs}}")
}; // add more cases here
params.add(cases[random().nextInt(cases.length)]);