Precommit appears to be failing related to this series of commits

    [exec] Verify...
     [echo] Checking for missing docs...
     [exec]
     [exec]
build/docs/core/org/apache/lucene/codecs/lucene70/IndexedDISICacheFactory.html
     [exec]   missing Constructors: IndexedDISICacheFactory--
     [exec]   missing Methods: getDISIBlocksWithOffsetsCount--
     [exec]   missing Methods: getDISIBlocksWithRankCount--
     [exec]   missing Methods: getVaryingBPVCount--
     [exec]
     [exec]
build/docs/core/org/apache/lucene/codecs/lucene70/LongCompressor.html
     [exec]   missing Constructors: LongCompressor--
     [exec]
     [exec]
build/docs/core/org/apache/lucene/codecs/lucene70/IndexedDISICache.html
     [exec]   missing Fields: EMPTY
     [exec]   missing Methods: getCreationStats--
     [exec]   missing Methods: getName--
     [exec]   missing Methods: hasOffsets--
     [exec]
     [exec] Missing javadocs were found!


On Mon, Dec 3, 2018 at 8:32 AM <t...@apache.org> wrote:

> Repository: lucene-solr
> Updated Branches:
>   refs/heads/master 643ffc6f9 -> e356d793c
>
>
> LUCENE-8374 part 1/4: Reduce reads for sparse DocValues
>
> Offset and index jump-table for IndexedDISI blocks.
>
>
> Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
> Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/58a7a8ad
> Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/58a7a8ad
> Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/58a7a8ad
>
> Branch: refs/heads/master
> Commit: 58a7a8ada5cebeb261060c56cd6d0a9446478bf6
> Parents: 643ffc6
> Author: Toke Eskildsen <t...@apache.org>
> Authored: Mon Dec 3 14:23:11 2018 +0100
> Committer: Toke Eskildsen <t...@apache.org>
> Committed: Mon Dec 3 14:23:11 2018 +0100
>
> ----------------------------------------------------------------------
>  lucene/CHANGES.txt                              |   3 +
>  .../lucene/codecs/lucene70/IndexedDISI.java     |  65 +++++-
>  .../codecs/lucene70/IndexedDISICache.java       | 234 +++++++++++++++++++
>  .../lucene70/IndexedDISICacheFactory.java       | 150 ++++++++++++
>  .../lucene70/Lucene70DocValuesProducer.java     |  85 +++++--
>  .../lucene/codecs/lucene70/TestIndexedDISI.java |  91 ++++++--
>  .../org/apache/lucene/index/TestDocValues.java  | 105 +++++++++
>  7 files changed, 684 insertions(+), 49 deletions(-)
> ----------------------------------------------------------------------
>
>
>
> http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/58a7a8ad/lucene/CHANGES.txt
> ----------------------------------------------------------------------
> diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
> index 6b001b9..349d64d 100644
> --- a/lucene/CHANGES.txt
> +++ b/lucene/CHANGES.txt
> @@ -208,6 +208,9 @@ Optimizations
>    to early terminate the iterator if the minimum score is greater than
> the constant
>    score. (Christophe Bismuth via Jim Ferenczi)
>
> +* LUCENE-8374: Reduce reads for sparse DocValues and whole number numeric
> DocValues.
> +  (Toke Eskildsen)
> +
>  ======================= Lucene 7.7.0 =======================
>
>  Build
>
>
> http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/58a7a8ad/lucene/core/src/java/org/apache/lucene/codecs/lucene70/IndexedDISI.java
> ----------------------------------------------------------------------
> diff --git
> a/lucene/core/src/java/org/apache/lucene/codecs/lucene70/IndexedDISI.java
> b/lucene/core/src/java/org/apache/lucene/codecs/lucene70/IndexedDISI.java
> index 6138896..114710e 100644
> ---
> a/lucene/core/src/java/org/apache/lucene/codecs/lucene70/IndexedDISI.java
> +++
> b/lucene/core/src/java/org/apache/lucene/codecs/lucene70/IndexedDISI.java
> @@ -50,6 +50,9 @@ import org.apache.lucene.util.RoaringDocIdSet;
>  final class IndexedDISI extends DocIdSetIterator {
>
>    static final int MAX_ARRAY_LENGTH = (1 << 12) - 1;
> +  static final String NO_NAME = "n/a";
> +
> +  public final String name;
>
>    private static void flush(int block, FixedBitSet buffer, int
> cardinality, IndexOutput out) throws IOException {
>      assert block >= 0 && block < 65536;
> @@ -98,19 +101,49 @@ final class IndexedDISI extends DocIdSetIterator {
>    /** The slice that stores the {@link DocIdSetIterator}. */
>    private final IndexInput slice;
>    private final long cost;
> +  private final IndexedDISICache cache;
>
>    IndexedDISI(IndexInput in, long offset, long length, long cost) throws
> IOException {
> -    this(in.slice("docs", offset, length), cost);
> +    this(in, offset, length, cost, NO_NAME);
> +  }
> +
> +  IndexedDISI(IndexInput in, long offset, long length, long cost, String
> name) throws IOException {
> +    this(in, offset, length, cost, null, name);
> +  }
> +
> +  IndexedDISI(IndexInput in, long offset, long length, long cost,
> IndexedDISICache cache) throws IOException {
> +    this(in, offset, length, cost, cache, NO_NAME);
> +  }
> +
> +  IndexedDISI(IndexInput in, long offset, long length, long cost,
> IndexedDISICache cache, String name) throws IOException {
> +    this(in.slice("docs", offset, length), cost, cache, name);
>    }
>
> +  IndexedDISI(IndexInput slice, long cost) throws IOException {
> +    this(slice, cost, NO_NAME);
> +  }
>    // This constructor allows to pass the slice directly in case it helps
> reuse
>    // see eg. Lucene70 norms producer's merge instance
> -  IndexedDISI(IndexInput slice, long cost) throws IOException {
> +  IndexedDISI(IndexInput slice, long cost, String name) throws
> IOException {
> +    this(slice, cost, null, name);
> +//    IndexedDISICacheFactory.debug(
> +//        "Non-cached direct slice IndexedDISI with length " +
> slice.length() + ": " + slice.toString());
> +  }
> +
> +  IndexedDISI(IndexInput slice, long cost, IndexedDISICache cache) throws
> IOException {
> +    this(slice, cost, cache, NO_NAME);
> +  }
> +  // This constructor allows to pass the slice directly in case it helps
> reuse
> +  // see eg. Lucene70 norms producer's merge instance
> +  IndexedDISI(IndexInput slice, long cost, IndexedDISICache cache, String
> name) {
> +    this.name = name;
>      this.slice = slice;
>      this.cost = cost;
> +    this.cache = cache == null ? IndexedDISICache.EMPTY : cache;
>    }
>
>    private int block = -1;
> +  private long blockStart; // Used with the DENSE cache
>    private long blockEnd;
>    private int nextBlockIndex = -1;
>    Method method;
> @@ -126,6 +159,8 @@ final class IndexedDISI extends DocIdSetIterator {
>    private int wordIndex = -1;
>    // number of one bits encountered so far, including those of `word`
>    private int numberOfOnes;
> +  // Used with rank for jumps inside of DENSE
> +  private int denseOrigoIndex;
>
>    // ALL variables
>    private int gap;
> @@ -138,6 +173,7 @@ final class IndexedDISI extends DocIdSetIterator {
>    @Override
>    public int advance(int target) throws IOException {
>      final int targetBlock = target & 0xFFFF0000;
> +    // Note: The cache makes it easy to add support for random access.
> This has not been done as the API forbids it
>      if (block < targetBlock) {
>        advanceBlock(targetBlock);
>      }
> @@ -163,6 +199,20 @@ final class IndexedDISI extends DocIdSetIterator {
>    }
>
>    private void advanceBlock(int targetBlock) throws IOException {
> +    if (targetBlock >= block+2) { // 1 block skip is (slightly) faster to
> do without block jump table
> +      long offset = cache.getFilePointerForBlock(targetBlock >>
> IndexedDISICache.BLOCK_BITS);
> +      if (offset != -1 && offset > slice.getFilePointer()) {
> +        int origo = cache.getIndexForBlock(targetBlock >>
> IndexedDISICache.BLOCK_BITS);
> +        if (origo != -1) {
> +          this.nextBlockIndex = origo - 1; // -1 to compensate for the
> always-added 1 in readBlockHeader
> +          slice.seek(offset);
> +          readBlockHeader();
> +          return;
> +        }
> +      }
> +    }
> +
> +    // Fallback to non-cached
>      do {
>        slice.seek(blockEnd);
>        readBlockHeader();
> @@ -170,6 +220,7 @@ final class IndexedDISI extends DocIdSetIterator {
>    }
>
>    private void readBlockHeader() throws IOException {
> +    blockStart = slice.getFilePointer();
>      block = Short.toUnsignedInt(slice.readShort()) << 16;
>      assert block >= 0;
>      final int numValues = 1 + Short.toUnsignedInt(slice.readShort());
> @@ -187,6 +238,7 @@ final class IndexedDISI extends DocIdSetIterator {
>        blockEnd = slice.getFilePointer() + (1 << 13);
>        wordIndex = -1;
>        numberOfOnes = index + 1;
> +      denseOrigoIndex = numberOfOnes;
>      }
>    }
>
> @@ -250,6 +302,7 @@ final class IndexedDISI extends DocIdSetIterator {
>        boolean advanceWithinBlock(IndexedDISI disi, int target) throws
> IOException {
>          final int targetInBlock = target & 0xFFFF;
>          final int targetWordIndex = targetInBlock >>> 6;
> +
>          for (int i = disi.wordIndex + 1; i <= targetWordIndex; ++i) {
>            disi.word = disi.slice.readLong();
>            disi.numberOfOnes += Long.bitCount(disi.word);
> @@ -263,7 +316,10 @@ final class IndexedDISI extends DocIdSetIterator {
>            return true;
>          }
>
> +        // There were no set bits at the wanted position. Move forward
> until one is reached
>          while (++disi.wordIndex < 1024) {
> +          // This could use the rank cache to skip empty spaces >= 512
> bits, but it seems unrealistic
> +          // that such blocks would be DENSE
>            disi.word = disi.slice.readLong();
>            if (disi.word != 0) {
>              disi.index = disi.numberOfOnes;
> @@ -272,12 +328,15 @@ final class IndexedDISI extends DocIdSetIterator {
>              return true;
>            }
>          }
> +        // No set bits in the block at or after the wanted position.
>          return false;
>        }
> +
>        @Override
>        boolean advanceExactWithinBlock(IndexedDISI disi, int target)
> throws IOException {
>          final int targetInBlock = target & 0xFFFF;
>          final int targetWordIndex = targetInBlock >>> 6;
> +
>          for (int i = disi.wordIndex + 1; i <= targetWordIndex; ++i) {
>            disi.word = disi.slice.readLong();
>            disi.numberOfOnes += Long.bitCount(disi.word);
> @@ -288,6 +347,8 @@ final class IndexedDISI extends DocIdSetIterator {
>          disi.index = disi.numberOfOnes - Long.bitCount(leftBits);
>          return (leftBits & 1L) != 0;
>        }
> +
> +
>      },
>      ALL {
>        @Override
>
>
> http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/58a7a8ad/lucene/core/src/java/org/apache/lucene/codecs/lucene70/IndexedDISICache.java
> ----------------------------------------------------------------------
> diff --git
> a/lucene/core/src/java/org/apache/lucene/codecs/lucene70/IndexedDISICache.java
> b/lucene/core/src/java/org/apache/lucene/codecs/lucene70/IndexedDISICache.java
> new file mode 100644
> index 0000000..9a80689
> --- /dev/null
> +++
> b/lucene/core/src/java/org/apache/lucene/codecs/lucene70/IndexedDISICache.java
> @@ -0,0 +1,234 @@
> +/*
> + * 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.codecs.lucene70;
> +
> +import java.io.IOException;
> +import java.util.Arrays;
> +import java.util.Locale;
> +import java.util.concurrent.atomic.AtomicInteger;
> +
> +import org.apache.lucene.search.DocIdSetIterator;
> +import org.apache.lucene.store.IndexInput;
> +import org.apache.lucene.util.Accountable;
> +import org.apache.lucene.util.ArrayUtil;
> +import org.apache.lucene.util.RamUsageEstimator;
> +import org.apache.lucene.util.packed.PackedInts;
> +
> +import static
> org.apache.lucene.codecs.lucene70.IndexedDISI.MAX_ARRAY_LENGTH;
> +
> +/**
> + * Caching of IndexedDISI with two strategies:
> + *
> + * A lookup table for block blockCache and index.
> + *
> + * The lookup table is an array of {@code long}s with an entry for each
> block. It allows for
> + * direct jumping to the block, as opposed to iteration from the current
> position and forward
> + * one block at a time.
> + *
> + * Each long entry consists of 2 logical parts:
> + *
> + * The first 31 bits holds the index (number of set bits in the blocks)
> up to just before the
> + * wanted block. The next 33 bits holds the offset into the underlying
> slice.
> + * As there is a maximum of 2^16 blocks, it follows that the maximum size
> of any block must
> + * not exceed 2^17 bits to avoid overflow. This is currently the case,
> with the largest
> + * block being DENSE and using 2^16 + 32 bits, and is likely to continue
> to hold as using
> + * more than double the amount of bits is unlikely to be an efficient
> representation.
> + * The cache overhead is numDocs/1024 bytes.
> + *
> + * Note: There are 4 types of blocks: ALL, DENSE, SPARSE and non-existing
> (0 set bits).
> + * In the case of non-existing blocks, the entry in the lookup table has
> index equal to the
> + * previous entry and offset equal to the next non-empty block.
> + *
> + * The performance overhead for creating a cache instance is equivalent
> to visiting every 65536th
> + * doc value for the given field, i.e. it scales lineary to field size.
> + */
> +public class IndexedDISICache implements Accountable {
> +  private static final int BLOCK = 65536;   // The number of docIDs that
> a single block represents
> +  static final int BLOCK_BITS = 16;
> +  private static final long BLOCK_INDEX_SHIFT = 33; // Number of bits to
> shift a lookup entry to get the index
> +  private static final long BLOCK_INDEX_MASK = ~0L << BLOCK_INDEX_SHIFT;
> // The index bits in a lookup entry
> +  private static final long BLOCK_LOOKUP_MASK = ~BLOCK_INDEX_MASK; // The
> offset bits in a lookup entry
> +
> +  private long[] blockCache = null; // One every 65536 docs, contains
> index & slice position
> +  private String creationStats = "";
> +  private final String name; // Identifier for debug, log & inspection
> +
> +  // Flags for not-yet-defined-values used during building
> +  private static final long BLOCK_EMPTY_INDEX = ~0L << BLOCK_INDEX_SHIFT;
> +  private static final long BLOCK_EMPTY_LOOKUP = BLOCK_LOOKUP_MASK;
> +  private static final long BLOCK_EMPTY = BLOCK_EMPTY_INDEX |
> BLOCK_EMPTY_LOOKUP;
> +
> +  /**
> +   * Builds the stated caches for the given IndexInput.
> +   *
> +   * @param in positioned at the start of the logical underlying bitmap.
> +   */
> +  IndexedDISICache(IndexInput in, String name) throws IOException {
> +    blockCache = new long[16];    // Will be extended when needed
> +    Arrays.fill(blockCache, BLOCK_EMPTY);
> +    this.name = name;
> +    updateCaches(in);
> +  }
> +
> +  private IndexedDISICache() {
> +    this.blockCache = null;
> +    this.name = "";
> +  }
> +
> +  // Used to represent no caching.
> +  public static final IndexedDISICache EMPTY = new IndexedDISICache();
> +
> +  /**
> +   * If available, returns a position within the underlying {@link
> IndexInput} for the start of the block
> +   * containing the wanted bit (the target) or the next non-EMPTY block,
> if the block representing the bit is empty.
> +   * @param targetBlock the index for the block to resolve (docID /
> 65536).
> +   * @return the offset for the block for target or -1 if it cannot be
> resolved.
> +   */
> +  long getFilePointerForBlock(int targetBlock) {
> +    long offset = blockCache == null || blockCache.length <= targetBlock ?
> +        -1 : blockCache[targetBlock] & BLOCK_LOOKUP_MASK;
> +    return offset == BLOCK_EMPTY_LOOKUP ? -1 : offset;
> +  }
> +
> +  /**
> +   * If available, returns the index; number of set bits before the
> wanted block.
> +   * @param targetBlock the block to resolve (docID / 65536).
> +   * @return the index for the block or -1 if it cannot be resolved.
> +   */
> +  int getIndexForBlock(int targetBlock) {
> +    if (blockCache == null || blockCache.length <= targetBlock) {
> +      return -1;
> +    }
> +    return (blockCache[targetBlock] & BLOCK_INDEX_MASK) ==
> BLOCK_EMPTY_INDEX ?
> +        -1 : (int)(blockCache[targetBlock] >>> BLOCK_INDEX_SHIFT);
> +  }
> +
> +  public boolean hasOffsets() {
> +    return blockCache != null;
> +  }
> +
> +  private void updateCaches(IndexInput slice) throws IOException {
> +    final long startOffset = slice.getFilePointer();
> +
> +    final long startTime = System.nanoTime();
> +    AtomicInteger statBlockALL = new AtomicInteger(0);
> +    AtomicInteger statBlockDENSE = new AtomicInteger(0);
> +    AtomicInteger statBlockSPARSE = new AtomicInteger(0);
> +
> +    // Fill phase
> +    int largestBlock = fillCache(slice, statBlockALL, statBlockDENSE,
> statBlockSPARSE);
> +    freezeCaches(largestBlock);
> +
> +    slice.seek(startOffset); // Leave it as we found it
> +    creationStats = String.format(Locale.ENGLISH,
> +        "name=%s, blocks=%d (ALL=%d, DENSE=%d, SPARSE=%d, EMPTY=%d),
> time=%dms, block=%d bytes",
> +        name,
> +        largestBlock+1, statBlockALL.get(), statBlockDENSE.get(),
> statBlockSPARSE.get(),
> +
> (largestBlock+1-statBlockALL.get()-statBlockDENSE.get()-statBlockSPARSE.get()),
> +        (System.nanoTime()-startTime)/1000000,
> +        blockCache == null ? 0 : blockCache.length*Long.BYTES);
> +  }
> +
> +  private int fillCache(
> +      IndexInput slice, AtomicInteger statBlockALL, AtomicInteger
> statBlockDENSE, AtomicInteger statBlockSPARSE)
> +      throws IOException {
> +    int largestBlock = -1;
> +    long index = 0;
> +    int rankIndex = -1;
> +    while (slice.getFilePointer() < slice.length()) {
> +      final long startFilePointer = slice.getFilePointer();
> +
> +      final int blockIndex = Short.toUnsignedInt(slice.readShort());
> +      final int numValues = 1 + Short.toUnsignedInt(slice.readShort());
> +
> +      assert blockIndex > largestBlock;
> +      if (blockIndex == DocIdSetIterator.NO_MORE_DOCS >>> 16) { // End
> reached
> +        assert Short.toUnsignedInt(slice.readShort()) ==
> (DocIdSetIterator.NO_MORE_DOCS & 0xFFFF);
> +        break;
> +      }
> +      largestBlock = blockIndex;
> +
> +      blockCache = ArrayUtil.grow(blockCache, blockIndex+1); // No-op if
> large enough
> +      blockCache[blockIndex] = (index << BLOCK_INDEX_SHIFT) |
> startFilePointer;
> +      index += numValues;
> +
> +      if (numValues <= MAX_ARRAY_LENGTH) { // SPARSE
> +        statBlockSPARSE.incrementAndGet();
> +        slice.seek(slice.getFilePointer() + (numValues << 1));
> +        continue;
> +      }
> +      if (numValues == 65536) { // ALL
> +        statBlockALL.incrementAndGet();
> +        // Already at next block offset
> +        continue;
> +      }
> +
> +      // The block is DENSE
> +      statBlockDENSE.incrementAndGet();
> +      long nextBlockOffset = slice.getFilePointer() + (1 << 13);
> +      slice.seek(nextBlockOffset);
> +    }
> +
> +    return largestBlock;
> +  }
> +
> +  private void freezeCaches(int largestBlock) {
> +    if (largestBlock == -1) { // No set bit: Disable the caches
> +      blockCache = null;
> +      return;
> +    }
> +
> +    // Reduce size to minimum
> +    if (blockCache.length-1 > largestBlock) {
> +      long[] newBC = new long[Math.max(largestBlock - 1, 1)];
> +      System.arraycopy(blockCache, 0, newBC, 0, newBC.length);
> +      blockCache = newBC;
> +    }
> +
> +    // Set non-defined blockCache entries (caused by blocks with 0 set
> bits) to the subsequently defined one
> +    long latest = BLOCK_EMPTY;
> +    for (int i = blockCache.length-1; i >= 0 ; i--) {
> +      long current = blockCache[i];
> +      if (current == BLOCK_EMPTY) {
> +        blockCache[i] = latest;
> +      } else {
> +        latest = current;
> +      }
> +    }
> +  }
> +
> +  /**
> +   * @return Human readable details from the creation of the cache
> instance.
> +   */
> +  public String getCreationStats() {
> +    return creationStats;
> +  }
> +
> +  /**
> +   * @return Human-readable name for the cache instance.
> +   */
> +  public String getName() {
> +    return name;
> +  }
> +
> +  @Override
> +  public long ramBytesUsed() {
> +    return (blockCache == null ? 0 :
> RamUsageEstimator.sizeOf(blockCache)) +
> +        RamUsageEstimator.NUM_BYTES_OBJECT_REF*3 +
> +        RamUsageEstimator.NUM_BYTES_OBJECT_HEADER +
> creationStats.length()*2;
> +  }
> +}
> \ No newline at end of file
>
>
> http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/58a7a8ad/lucene/core/src/java/org/apache/lucene/codecs/lucene70/IndexedDISICacheFactory.java
> ----------------------------------------------------------------------
> diff --git
> a/lucene/core/src/java/org/apache/lucene/codecs/lucene70/IndexedDISICacheFactory.java
> b/lucene/core/src/java/org/apache/lucene/codecs/lucene70/IndexedDISICacheFactory.java
> new file mode 100644
> index 0000000..6cf8af1
> --- /dev/null
> +++
> b/lucene/core/src/java/org/apache/lucene/codecs/lucene70/IndexedDISICacheFactory.java
> @@ -0,0 +1,150 @@
> +/*
> + * 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.codecs.lucene70;
> +
> +import java.io.IOException;
> +import java.util.HashMap;
> +import java.util.Locale;
> +import java.util.Map;
> +
> +import org.apache.lucene.store.IndexInput;
> +import org.apache.lucene.store.RandomAccessInput;
> +import org.apache.lucene.util.Accountable;
> +import org.apache.lucene.util.ArrayUtil;
> +import org.apache.lucene.util.RamUsageEstimator;
> +
> +/**
> + * Creates and stores caches for {@link IndexedDISI} and {@link
> Lucene70DocValuesProducer}.
> + * The caches are stored in maps, where the key is made up from offset
> and length of a slice
> + * in an underlying segment. Each segment uses their own
> IndexedDISICacheFactory.
> + *
> + * See {@link IndexedDISICache} for details on the caching.
> + */
> +public class IndexedDISICacheFactory implements Accountable {
> +
> +  /**
> +   * If the slice with the DISI-data is less than this number of bytes,
> don't create a cache.
> +   * This is a very low number as the DISI-structure very efficiently
> represents EMPTY and ALL blocks.
> +   */
> +  private static int MIN_LENGTH_FOR_CACHING = 50; // Set this very low:
> Could be 9 EMPTY followed by a SPARSE
> +
> +  // jump-table and rank for DISI blocks
> +  private final Map<Long, IndexedDISICache> disiPool = new HashMap<>();
> +
> +  /**
> +   * Create a cached {@link IndexedDISI} instance.
> +   * @param data   persistent data containing the DISI-structure.
> +   * @param cost   cost as defined for IndexedDISI.
> +   * @param name   identifier for the DISI-structure for debug purposes.
> +   * @return a cached IndexedDISI or a plain IndexedDISI, if caching is
> not applicable.
> +   * @throws IOException if the DISI-structure could not be accessed.
> +   */
> +  IndexedDISI createCachedIndexedDISI(IndexInput data, long key, int
> cost, String name) throws IOException {
> +    IndexedDISICache cache = getCache(data, key, name);
> +    return new IndexedDISI(data, cost, cache, name);
> +  }
> +
> +  /**
> +   * Create a cached {@link IndexedDISI} instance.
> +   * @param data   persistent data containing the DISI-structure.
> +   * @param offset same as the offset that will also be used for creating
> an {@link IndexedDISI}.
> +   * @param length same af the length that will also be used for creating
> an {@link IndexedDISI}.
> +   * @param cost   cost as defined for IndexedDISI.
> +   * @param name   identifier for the DISI-structure for debug purposes.
> +   * @return a cached IndexedDISI or a plain IndexedDISI, if caching is
> not applicable.
> +   * @throws IOException if the DISI-structure could not be accessed.
> +   */
> +  IndexedDISI createCachedIndexedDISI(IndexInput data, long offset, long
> length, long cost, String name)
> +      throws IOException {
> +    IndexedDISICache cache = getCache(data, offset, length, name);
> +    return new IndexedDISI(data, offset, length, cost, cache, name);
> +  }
> +
> +  /**
> +   * Creates a cache (jump table) for {@link IndexedDISI}.
> +   * If the cache has previously been created, the old cache is returned.
> +   * @param data   the slice to create a cache for.
> +   * @param offset same as the offset that will also be used for creating
> an {@link IndexedDISI}.
> +   * @param length same af the length that will also be used for creating
> an {@link IndexedDISI}.
> +   * @param name human readable designation, typically a field name. Used
> for debug, log and inspection.
> +   * @return a cache for the given slice+offset+length or null if not
> suitable for caching.
> +   */
> +  public IndexedDISICache getCache(IndexInput data, long offset, long
> length, String name) throws IOException {
> +    if (length < MIN_LENGTH_FOR_CACHING) {
> +      return null;
> +    }
> +
> +    long key = offset + length;
> +    IndexedDISICache cache = disiPool.get(key);
> +    if (cache == null) {
> +      // TODO: Avoid overlapping builds of the same cache for performance
> reason
> +      cache = new IndexedDISICache(data.slice("docs", offset, length),
> name);
> +      disiPool.put(key, cache);
> +    }
> +    return cache;
> +  }
> +
> +  /**
> +   * Creates a cache (jump table) for {@link IndexedDISI}.
> +   * If the cache has previously been created, the old cache is returned.
> +   * @param slice the input slice.
> +   * @param key identifier for the cache, unique within the segment that
> originated the slice.
> +   *            Recommendation is offset+length for the slice, relative
> to the data mapping the segment.
> +   *            Warning: Do not use slice.getFilePointer and slice.length
> as they are not guaranteed
> +   *            to be unique within the segment (slice.getFilePointer is
> 0 when a sub-slice is created).
> +   * @param name human readable designation, typically a field name. Used
> for debug, log and inspection.
> +   * @return a cache for the given slice+offset+length or null if not
> suitable for caching.
> +   */
> +  public IndexedDISICache getCache(IndexInput slice, long key, String
> name) throws IOException {
> +    final long length = slice.length();
> +    if (length < MIN_LENGTH_FOR_CACHING) {
> +      return null;
> +    }
> +
> +    IndexedDISICache cache = disiPool.get(key);
> +    if (cache == null) {
> +      // TODO: Avoid overlapping builds of the same cache
> +      cache = new IndexedDISICache(slice, name);
> +      disiPool.put(key, cache);
> +    }
> +    return cache;
> +  }
> +
> +  // Statistics
> +  public long getDISIBlocksWithOffsetsCount() {
> +    return
> disiPool.values().stream().filter(IndexedDISICache::hasOffsets).count();
> +  }
> +
> +  @Override
> +  public long ramBytesUsed() {
> +    long mem = RamUsageEstimator.shallowSizeOf(this) +
> +        RamUsageEstimator.shallowSizeOf(disiPool);
> +    for (Map.Entry<Long, IndexedDISICache> cacheEntry:
> disiPool.entrySet()) {
> +      mem += RamUsageEstimator.shallowSizeOf(cacheEntry);
> +      mem += RamUsageEstimator.sizeOf(cacheEntry.getKey());
> +      mem += cacheEntry.getValue().ramBytesUsed();
> +    }
> +    return mem;
> +  }
> +
> +  /**
> +   * Releases all caches.
> +   */
> +  void releaseAll() {
> +    disiPool.clear();
> +  }
> +}
>
>
> http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/58a7a8ad/lucene/core/src/java/org/apache/lucene/codecs/lucene70/Lucene70DocValuesProducer.java
> ----------------------------------------------------------------------
> diff --git
> a/lucene/core/src/java/org/apache/lucene/codecs/lucene70/Lucene70DocValuesProducer.java
> b/lucene/core/src/java/org/apache/lucene/codecs/lucene70/Lucene70DocValuesProducer.java
> index b0f6e84..812caba 100644
> ---
> a/lucene/core/src/java/org/apache/lucene/codecs/lucene70/Lucene70DocValuesProducer.java
> +++
> b/lucene/core/src/java/org/apache/lucene/codecs/lucene70/Lucene70DocValuesProducer.java
> @@ -57,6 +57,7 @@ final class Lucene70DocValuesProducer extends
> DocValuesProducer implements Close
>    private final Map<String,SortedNumericEntry> sortedNumerics = new
> HashMap<>();
>    private long ramBytesUsed;
>    private final IndexInput data;
> +  private final IndexedDISICacheFactory disiCacheFactory = new
> IndexedDISICacheFactory();
>    private final int maxDoc;
>
>    /** expert: instantiates a new reader */
> @@ -119,23 +120,23 @@ final class Lucene70DocValuesProducer extends
> DocValuesProducer implements Close
>        }
>        byte type = meta.readByte();
>        if (type == Lucene70DocValuesFormat.NUMERIC) {
> -        numerics.put(info.name, readNumeric(meta));
> +        numerics.put(info.name, readNumeric(meta, info.name));
>        } else if (type == Lucene70DocValuesFormat.BINARY) {
> -        binaries.put(info.name, readBinary(meta));
> +        binaries.put(info.name, readBinary(meta, info.name));
>        } else if (type == Lucene70DocValuesFormat.SORTED) {
> -        sorted.put(info.name, readSorted(meta));
> +        sorted.put(info.name, readSorted(meta, info.name));
>        } else if (type == Lucene70DocValuesFormat.SORTED_SET) {
> -        sortedSets.put(info.name, readSortedSet(meta));
> +        sortedSets.put(info.name, readSortedSet(meta, info.name));
>        } else if (type == Lucene70DocValuesFormat.SORTED_NUMERIC) {
> -        sortedNumerics.put(info.name, readSortedNumeric(meta));
> +        sortedNumerics.put(info.name, readSortedNumeric(meta, info.name
> ));
>        } else {
>          throw new CorruptIndexException("invalid type: " + type, meta);
>        }
>      }
>    }
>
> -  private NumericEntry readNumeric(ChecksumIndexInput meta) throws
> IOException {
> -    NumericEntry entry = new NumericEntry();
> +  private NumericEntry readNumeric(ChecksumIndexInput meta, String name)
> throws IOException {
> +    NumericEntry entry = new NumericEntry(name);
>      readNumeric(meta, entry);
>      return entry;
>    }
> @@ -167,8 +168,8 @@ final class Lucene70DocValuesProducer extends
> DocValuesProducer implements Close
>      entry.valuesLength = meta.readLong();
>    }
>
> -  private BinaryEntry readBinary(ChecksumIndexInput meta) throws
> IOException {
> -    BinaryEntry entry = new BinaryEntry();
> +  private BinaryEntry readBinary(ChecksumIndexInput meta, String name)
> throws IOException {
> +    BinaryEntry entry = new BinaryEntry(name);
>      entry.dataOffset = meta.readLong();
>      entry.dataLength = meta.readLong();
>      entry.docsWithFieldOffset = meta.readLong();
> @@ -186,8 +187,8 @@ final class Lucene70DocValuesProducer extends
> DocValuesProducer implements Close
>      return entry;
>    }
>
> -  private SortedEntry readSorted(ChecksumIndexInput meta) throws
> IOException {
> -    SortedEntry entry = new SortedEntry();
> +  private SortedEntry readSorted(ChecksumIndexInput meta, String name)
> throws IOException {
> +    SortedEntry entry = new SortedEntry(name);
>      entry.docsWithFieldOffset = meta.readLong();
>      entry.docsWithFieldLength = meta.readLong();
>      entry.numDocsWithField = meta.readInt();
> @@ -198,12 +199,12 @@ final class Lucene70DocValuesProducer extends
> DocValuesProducer implements Close
>      return entry;
>    }
>
> -  private SortedSetEntry readSortedSet(ChecksumIndexInput meta) throws
> IOException {
> -    SortedSetEntry entry = new SortedSetEntry();
> +  private SortedSetEntry readSortedSet(ChecksumIndexInput meta, String
> name) throws IOException {
> +    SortedSetEntry entry = new SortedSetEntry(name);
>      byte multiValued = meta.readByte();
>      switch (multiValued) {
>        case 0: // singlevalued
> -        entry.singleValueEntry = readSorted(meta);
> +        entry.singleValueEntry = readSorted(meta, name);
>          return entry;
>        case 1: // multivalued
>          break;
> @@ -245,8 +246,8 @@ final class Lucene70DocValuesProducer extends
> DocValuesProducer implements Close
>      entry.termsIndexAddressesLength = meta.readLong();
>    }
>
> -  private SortedNumericEntry readSortedNumeric(ChecksumIndexInput meta)
> throws IOException {
> -    SortedNumericEntry entry = new SortedNumericEntry();
> +  private SortedNumericEntry readSortedNumeric(ChecksumIndexInput meta,
> String name) throws IOException {
> +    SortedNumericEntry entry = new SortedNumericEntry(name);
>      readNumeric(meta, entry);
>      entry.numDocsWithField = meta.readInt();
>      if (entry.numDocsWithField != entry.numValues) {
> @@ -262,9 +263,23 @@ final class Lucene70DocValuesProducer extends
> DocValuesProducer implements Close
>    @Override
>    public void close() throws IOException {
>      data.close();
> +    disiCacheFactory.releaseAll();
>    }
>
> -  private static class NumericEntry {
> +  // Highly debatable if this is a sane construct as the name is only
> used for debug/logging/inspection purposes
> +  // This was introduced in LUCENE-8374
> +  private static class EntryImpl {
> +    final String name;
> +
> +    public EntryImpl(String name) {
> +      this.name = name;
> +    }
> +  }
> +
> +  private static class NumericEntry extends EntryImpl {
> +    public NumericEntry(String name) {
> +      super(name);
> +    }
>      long[] table;
>      int blockShift;
>      byte bitsPerValue;
> @@ -277,7 +292,10 @@ final class Lucene70DocValuesProducer extends
> DocValuesProducer implements Close
>      long valuesLength;
>    }
>
> -  private static class BinaryEntry {
> +  private static class BinaryEntry extends EntryImpl {
> +    public BinaryEntry(String name) {
> +      super(name);
> +    }
>      long dataOffset;
>      long dataLength;
>      long docsWithFieldOffset;
> @@ -290,7 +308,10 @@ final class Lucene70DocValuesProducer extends
> DocValuesProducer implements Close
>      DirectMonotonicReader.Meta addressesMeta;
>    }
>
> -  private static class TermsDictEntry {
> +  private static class TermsDictEntry extends EntryImpl {
> +    public TermsDictEntry(String name) {
> +      super(name);
> +    }
>      long termsDictSize;
>      int termsDictBlockShift;
>      DirectMonotonicReader.Meta termsAddressesMeta;
> @@ -308,6 +329,9 @@ final class Lucene70DocValuesProducer extends
> DocValuesProducer implements Close
>    }
>
>    private static class SortedEntry extends TermsDictEntry {
> +    public SortedEntry(String name) {
> +      super(name);
> +    }
>      long docsWithFieldOffset;
>      long docsWithFieldLength;
>      int numDocsWithField;
> @@ -317,6 +341,9 @@ final class Lucene70DocValuesProducer extends
> DocValuesProducer implements Close
>    }
>
>    private static class SortedSetEntry extends TermsDictEntry {
> +    public SortedSetEntry(String name) {
> +      super(name);
> +    }
>      SortedEntry singleValueEntry;
>      long docsWithFieldOffset;
>      long docsWithFieldLength;
> @@ -330,6 +357,9 @@ final class Lucene70DocValuesProducer extends
> DocValuesProducer implements Close
>    }
>
>    private static class SortedNumericEntry extends NumericEntry {
> +    public SortedNumericEntry(String name) {
> +      super(name);
> +    }
>      int numDocsWithField;
>      DirectMonotonicReader.Meta addressesMeta;
>      long addressesOffset;
> @@ -338,7 +368,7 @@ final class Lucene70DocValuesProducer extends
> DocValuesProducer implements Close
>
>    @Override
>    public long ramBytesUsed() {
> -    return ramBytesUsed;
> +    return ramBytesUsed + disiCacheFactory.ramBytesUsed();
>    }
>
>    @Override
> @@ -496,7 +526,8 @@ final class Lucene70DocValuesProducer extends
> DocValuesProducer implements Close
>        }
>      } else {
>        // sparse
> -      final IndexedDISI disi = new IndexedDISI(data,
> entry.docsWithFieldOffset, entry.docsWithFieldLength, entry.numValues);
> +      final IndexedDISI disi = disiCacheFactory.createCachedIndexedDISI(
> +          data, entry.docsWithFieldOffset, entry.docsWithFieldLength,
> entry.numValues, entry.name);
>        if (entry.bitsPerValue == 0) {
>          return new SparseNumericDocValues(disi) {
>            @Override
> @@ -767,7 +798,8 @@ final class Lucene70DocValuesProducer extends
> DocValuesProducer implements Close
>        }
>      } else {
>        // sparse
> -      final IndexedDISI disi = new IndexedDISI(data,
> entry.docsWithFieldOffset, entry.docsWithFieldLength,
> entry.numDocsWithField);
> +      final IndexedDISI disi = disiCacheFactory.createCachedIndexedDISI(
> +          data, entry.docsWithFieldOffset, entry.docsWithFieldLength,
> entry.numDocsWithField, entry.name);
>        if (entry.minLength == entry.maxLength) {
>          // fixed length
>          final int length = entry.maxLength;
> @@ -868,7 +900,8 @@ final class Lucene70DocValuesProducer extends
> DocValuesProducer implements Close
>        };
>      } else {
>        // sparse
> -      final IndexedDISI disi = new IndexedDISI(data,
> entry.docsWithFieldOffset, entry.docsWithFieldLength,
> entry.numDocsWithField);
> +      final IndexedDISI disi = disiCacheFactory.createCachedIndexedDISI(
> +          data, entry.docsWithFieldOffset, entry.docsWithFieldLength,
> entry.numDocsWithField, entry.name);
>        return new BaseSortedDocValues(entry, data) {
>
>          @Override
> @@ -1236,7 +1269,8 @@ final class Lucene70DocValuesProducer extends
> DocValuesProducer implements Close
>        };
>      } else {
>        // sparse
> -      final IndexedDISI disi = new IndexedDISI(data,
> entry.docsWithFieldOffset, entry.docsWithFieldLength,
> entry.numDocsWithField);
> +      final IndexedDISI disi = disiCacheFactory.createCachedIndexedDISI(
> +          data, entry.docsWithFieldOffset, entry.docsWithFieldLength,
> entry.numDocsWithField, entry.name);
>        return new SortedNumericDocValues() {
>
>          boolean set;
> @@ -1362,7 +1396,8 @@ final class Lucene70DocValuesProducer extends
> DocValuesProducer implements Close
>        };
>      } else {
>        // sparse
> -      final IndexedDISI disi = new IndexedDISI(data,
> entry.docsWithFieldOffset, entry.docsWithFieldLength,
> entry.numDocsWithField);
> +      final IndexedDISI disi = disiCacheFactory.createCachedIndexedDISI(
> +          data, entry.docsWithFieldOffset, entry.docsWithFieldLength,
> entry.numDocsWithField, entry.name);
>        return new BaseSortedSetDocValues(entry, data) {
>
>          boolean set;
>
>
> http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/58a7a8ad/lucene/core/src/test/org/apache/lucene/codecs/lucene70/TestIndexedDISI.java
> ----------------------------------------------------------------------
> diff --git
> a/lucene/core/src/test/org/apache/lucene/codecs/lucene70/TestIndexedDISI.java
> b/lucene/core/src/test/org/apache/lucene/codecs/lucene70/TestIndexedDISI.java
> index 64bfbd5..aae3a7f 100644
> ---
> a/lucene/core/src/test/org/apache/lucene/codecs/lucene70/TestIndexedDISI.java
> +++
> b/lucene/core/src/test/org/apache/lucene/codecs/lucene70/TestIndexedDISI.java
> @@ -150,6 +150,39 @@ public class TestIndexedDISI extends LuceneTestCase {
>        }
>      }
>    }
> +  public void testDenseMultiBlock() throws IOException {
> +    try (Directory dir = newDirectory()) {
> +      int maxDoc = 10 * 65536; // 10 blocks
> +      FixedBitSet set = new FixedBitSet(maxDoc);
> +      for (int i = 0; i < maxDoc; i += 2) { // Set every other to ensure
> dense
> +        set.set(i);
> +      }
> +      doTest(set, dir);
> +    }
> +  }
> +
> +  public void testOneDocMissingFixed() throws IOException {
> +    int maxDoc = 9699;
> +    FixedBitSet set = new FixedBitSet(maxDoc);
> +    set.set(0, maxDoc);
> +    set.clear(1345);
> +    try (Directory dir = newDirectory()) {
> +
> +      final int cardinality = set.cardinality();
> +      long length;
> +      try (IndexOutput out = dir.createOutput("foo", IOContext.DEFAULT)) {
> +        IndexedDISI.writeBitSet(new BitSetIterator(set, cardinality),
> out);
> +        length = out.getFilePointer();
> +      }
> +
> +      int step = 16000;
> +      try (IndexInput in = dir.openInput("foo", IOContext.DEFAULT)) {
> +        IndexedDISI disi = new IndexedDISI(in, 0L, length, cardinality);
> +        BitSetIterator disi2 = new BitSetIterator(set, cardinality);
> +        assertAdvanceEquality(disi, disi2, step);
> +      }
> +    }
> +  }
>
>    public void testRandom() throws IOException {
>      try (Directory dir = newDirectory()) {
> @@ -188,32 +221,14 @@ public class TestIndexedDISI extends LuceneTestCase {
>      try (IndexInput in = dir.openInput("foo", IOContext.DEFAULT)) {
>        IndexedDISI disi = new IndexedDISI(in, 0L, length, cardinality);
>        BitSetIterator disi2 = new BitSetIterator(set, cardinality);
> -      int i = 0;
> -      for (int doc = disi2.nextDoc(); doc !=
> DocIdSetIterator.NO_MORE_DOCS; doc = disi2.nextDoc()) {
> -        assertEquals(doc, disi.nextDoc());
> -        assertEquals(i++, disi.index());
> -      }
> -      assertEquals(DocIdSetIterator.NO_MORE_DOCS, disi.nextDoc());
> +      assertSingleStepEquality(disi, disi2);
>      }
>
>      for (int step : new int[] {1, 10, 100, 1000, 10000, 100000}) {
>        try (IndexInput in = dir.openInput("foo", IOContext.DEFAULT)) {
>          IndexedDISI disi = new IndexedDISI(in, 0L, length, cardinality);
>          BitSetIterator disi2 = new BitSetIterator(set, cardinality);
> -        int index = -1;
> -        while (true) {
> -          int target = disi2.docID() + step;
> -          int doc;
> -          do {
> -            doc = disi2.nextDoc();
> -            index++;
> -          } while (doc < target);
> -          assertEquals(doc, disi.advance(target));
> -          if (doc == DocIdSetIterator.NO_MORE_DOCS) {
> -            break;
> -          }
> -          assertEquals(index, disi.index());
> -        }
> +        assertAdvanceEquality(disi, disi2, step);
>        }
>      }
>
> @@ -221,8 +236,18 @@ public class TestIndexedDISI extends LuceneTestCase {
>        try (IndexInput in = dir.openInput("foo", IOContext.DEFAULT)) {
>          IndexedDISI disi = new IndexedDISI(in, 0L, length, cardinality);
>          BitSetIterator disi2 = new BitSetIterator(set, cardinality);
> +        int disi2length = set.length();
> +        assertAdvanceExactRandomized(disi, disi2, disi2length, step);
> +      }
> +    }
> +
> +    dir.deleteFile("foo");
> +  }
> +
> +  private void assertAdvanceExactRandomized(IndexedDISI disi,
> BitSetIterator disi2, int disi2length, int step)
> +      throws IOException {
>          int index = -1;
> -        for (int target = 0; target < set.length(); ) {
> +    for (int target = 0; target < disi2length; ) {
>            target += TestUtil.nextInt(random(), 0, step);
>            int doc = disi2.docID();
>            while (doc < target) {
> @@ -241,9 +266,31 @@ public class TestIndexedDISI extends LuceneTestCase {
>            }
>          }
>        }
> +
> +  private void assertSingleStepEquality(IndexedDISI disi, BitSetIterator
> disi2) throws IOException {
> +    int i = 0;
> +    for (int doc = disi2.nextDoc(); doc != DocIdSetIterator.NO_MORE_DOCS;
> doc = disi2.nextDoc()) {
> +      assertEquals(doc, disi.nextDoc());
> +      assertEquals(i++, disi.index());
> +    }
> +    assertEquals(DocIdSetIterator.NO_MORE_DOCS, disi.nextDoc());
>      }
>
> -    dir.deleteFile("foo");
> +  private void assertAdvanceEquality(IndexedDISI disi, BitSetIterator
> disi2, int step) throws IOException {
> +    int index = -1;
> +    while (true) {
> +      int target = disi2.docID() + step;
> +      int doc;
> +      do {
> +        doc = disi2.nextDoc();
> +        index++;
> +      } while (doc < target);
> +      assertEquals(doc, disi.advance(target));
> +      if (doc == DocIdSetIterator.NO_MORE_DOCS) {
> +        break;
> +      }
> +      assertEquals("Expected equality using step " + step + " at docID "
> + doc, index, disi.index());
> +    }
>    }
>
>  }
>
>
> http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/58a7a8ad/lucene/core/src/test/org/apache/lucene/index/TestDocValues.java
> ----------------------------------------------------------------------
> diff --git
> a/lucene/core/src/test/org/apache/lucene/index/TestDocValues.java
> b/lucene/core/src/test/org/apache/lucene/index/TestDocValues.java
> index 0214e54..daebad9 100644
> --- a/lucene/core/src/test/org/apache/lucene/index/TestDocValues.java
> +++ b/lucene/core/src/test/org/apache/lucene/index/TestDocValues.java
> @@ -18,7 +18,13 @@ package org.apache.lucene.index;
>
>
>  import java.io.IOException;
> +import java.nio.file.Files;
> +import java.nio.file.Path;
> +import java.nio.file.Paths;
> +import java.util.ArrayList;
> +import java.util.List;
>
> +import org.apache.lucene.analysis.standard.StandardAnalyzer;
>  import org.apache.lucene.document.BinaryDocValuesField;
>  import org.apache.lucene.document.Document;
>  import org.apache.lucene.document.Field;
> @@ -27,7 +33,9 @@ import org.apache.lucene.document.SortedDocValuesField;
>  import org.apache.lucene.document.SortedNumericDocValuesField;
>  import org.apache.lucene.document.SortedSetDocValuesField;
>  import org.apache.lucene.document.StringField;
> +import org.apache.lucene.search.DocIdSetIterator;
>  import org.apache.lucene.store.Directory;
> +import org.apache.lucene.store.MMapDirectory;
>  import org.apache.lucene.util.BytesRef;
>  import org.apache.lucene.util.IOUtils;
>  import org.apache.lucene.util.LuceneTestCase;
> @@ -123,6 +131,103 @@ public class TestDocValues extends LuceneTestCase {
>      iw.close();
>      dir.close();
>    }
> +
> +  /**
> +   * Triggers varying bits per value codec representation for numeric.
> +   */
> +  public void testNumericFieldVaryingBPV() throws Exception {
> +    Directory dir = newDirectory();
> +    IndexWriter iw = new IndexWriter(dir, newIndexWriterConfig(null));
> +    long generatedSum = 0;
> +    for (int bpv = 2 ; bpv < 24 ; bpv+=3) {
> +      for (int i = 0 ; i < 66000 ; i++) {
> +        Document doc = new Document();
> +        int max = 1 << (bpv - 1);
> +        int value =  random().nextInt(max) | max;
> +        generatedSum += value;
> +        //System.out.println("--- " + value);
> +        doc.add(new NumericDocValuesField("foo", value));
> +        iw.addDocument(doc);
> +      }
> +    }
> +    iw.flush();
> +    iw.forceMerge(1, true);
> +    iw.commit();
> +    DirectoryReader dr = DirectoryReader.open(iw);
> +    LeafReader r = getOnlyLeafReader(dr);
> +
> +    // ok
> +    NumericDocValues numDV = DocValues.getNumeric(r, "foo");
> +
> +    assertNotNull(numDV);
> +    long sum = 0;
> +    while (numDV.nextDoc() != DocIdSetIterator.NO_MORE_DOCS) {
> +      sum += numDV.longValue();
> +    }
> +    assertEquals("The sum of retrieved values should match the input",
> generatedSum, sum);
> +
> +//    assertNotNull(DocValues.getSortedNumeric(r, "foo"));
> +
> +    dr.close();
> +    iw.close();
> +    dir.close();
> +  }
> +
> +  // LUCENE-8374 had a bug where a vBPV-block with BPV==0 as the very end
> of the numeric DocValues made it fail
> +  public void testNumericEntryZeroesLastBlock() throws IOException {
> +    List<Long> docValues = new ArrayList<>(2*16384);
> +    for (int id = 0 ; id < 2*16384 ; id++) { // 2 vBPV-blocks for the
> dv-field
> +      if (id < 16384) { // First vBPV-block just has semi-ramdom values
> +        docValues.add((long) (id % 1000));
> +      } else {          // Second block is all zeroes, resulting in an
> extreme "1-byte for the while block"-representation
> +        docValues.add(0L);
> +      }
> +    }
> +    assertRandomAccessDV("Last block BPV=0", docValues);
> +  }
> +
> +  private void assertRandomAccessDV(String designation, List<Long>
> docValues) throws IOException {
> +    // Create corpus
> +    Path zeroPath =
> Paths.get(System.getProperty("java.io.tmpdir"),"plain_" +
> random().nextInt());
> +    Directory zeroDir = new MMapDirectory(zeroPath);
> +    IndexWriterConfig iwc = new IndexWriterConfig(new StandardAnalyzer());
> +    //iwc.setCodec(Codec.forName("Lucene70"));
> +    IndexWriter iw = new IndexWriter(zeroDir, iwc);
> +
> +    for (int id = 0 ; id < docValues.size() ; id++) {
> +      Document doc = new Document();
> +      doc.add(new StringField("id", Integer.toString(id),
> Field.Store.YES));
> +      doc.add(new NumericDocValuesField("dv", docValues.get(id)));
> +      iw.addDocument(doc);
> +    }
> +    iw.flush();
> +    iw.commit();
> +    iw.forceMerge(1, true);
> +    iw.close();
> +
> +    DirectoryReader dr = DirectoryReader.open(zeroDir);
> +
> +    for (int id = 0 ; id < docValues.size() ; id++) {
> +      int readerIndex = dr.readerIndex(id);
> +      // We create a new reader each time as we want to test
> vBPV-skipping and not sequential iteration
> +      NumericDocValues numDV =
> dr.leaves().get(readerIndex).reader().getNumericDocValues("dv");
> +      assertTrue(designation + ": There should be a value for docID " +
> id, numDV.advanceExact(id));
> +      assertEquals(designation + ": The value for docID " + id + " should
> be as expected",
> +          docValues.get(id), Long.valueOf(numDV.longValue()));
> +    }
> +
> +    // Clean up
> +    deleteAndClose(zeroDir);
> +    Files.delete(zeroPath);
> +  }
> +
> +  private void deleteAndClose(Directory dir) throws IOException {
> +    String[] files = dir.listAll();
> +    for (String file: files) {
> +      dir.deleteFile(file);
> +    }
> +    dir.close();
> +  }
>
>    /**
>     * field with binary docvalues
>
>

-- 
http://www.the111shift.com

Reply via email to