blob: 03c1da42bb4ec4253abe208203ee285e8dd21523 [file] [log] [blame]
package org.apache.lucene.facet;
/*
* 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.
*/
import java.io.IOException;
import java.util.Map;
import java.util.WeakHashMap;
import org.apache.lucene.codecs.DocValuesFormat;
import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.index.BinaryDocValues;
import org.apache.lucene.util.ArrayUtil;
import org.apache.lucene.util.IntsRef;
import org.apache.lucene.util.RamUsageEstimator;
/**
* A per-segment cache of documents' facet ordinals. Every
* {@link CachedOrds} holds the ordinals in a raw {@code
* int[]}, and therefore consumes as much RAM as the total
* number of ordinals found in the segment, but saves the
* CPU cost of decoding ordinals during facet counting.
*
* <p>
* <b>NOTE:</b> every {@link CachedOrds} is limited to 2.1B
* total ordinals. If that is a limitation for you then
* consider limiting the segment size to fewer documents, or
* use an alternative cache which pages through the category
* ordinals.
*
* <p>
* <b>NOTE:</b> when using this cache, it is advised to use
* a {@link DocValuesFormat} that does not cache the data in
* memory, at least for the category lists fields, or
* otherwise you'll be doing double-caching.
*
* <p>
* <b>NOTE:</b> create one instance of this and re-use it
* for all facet implementations (the cache is per-instance,
* not static).
*/
public class CachedOrdinalsReader extends OrdinalsReader {
private final OrdinalsReader source;
private final Map<Object,CachedOrds> ordsCache = new WeakHashMap<Object,CachedOrds>();
/** Sole constructor. */
public CachedOrdinalsReader(OrdinalsReader source) {
this.source = source;
}
private synchronized CachedOrds getCachedOrds(AtomicReaderContext context) throws IOException {
Object cacheKey = context.reader().getCoreCacheKey();
CachedOrds ords = ordsCache.get(cacheKey);
if (ords == null) {
ords = new CachedOrds(source.getReader(context), context.reader().maxDoc());
ordsCache.put(cacheKey, ords);
}
return ords;
}
@Override
public String getIndexFieldName() {
return source.getIndexFieldName();
}
@Override
public OrdinalsSegmentReader getReader(AtomicReaderContext context) throws IOException {
final CachedOrds cachedOrds = getCachedOrds(context);
return new OrdinalsSegmentReader() {
@Override
public void get(int docID, IntsRef ordinals) {
ordinals.ints = cachedOrds.ordinals;
ordinals.offset = cachedOrds.offsets[docID];
ordinals.length = cachedOrds.offsets[docID+1] - ordinals.offset;
}
};
}
/** Holds the cached ordinals in two paralel {@code int[]} arrays. */
public static final class CachedOrds {
/** Index into {@link #ordinals} for each document. */
public final int[] offsets;
/** Holds ords for all docs. */
public final int[] ordinals;
/**
* Creates a new {@link CachedOrds} from the {@link BinaryDocValues}.
* Assumes that the {@link BinaryDocValues} is not {@code null}.
*/
public CachedOrds(OrdinalsSegmentReader source, int maxDoc) throws IOException {
offsets = new int[maxDoc + 1];
int[] ords = new int[maxDoc]; // let's assume one ordinal per-document as an initial size
// this aggregator is limited to Integer.MAX_VALUE total ordinals.
long totOrds = 0;
final IntsRef values = new IntsRef(32);
for (int docID = 0; docID < maxDoc; docID++) {
offsets[docID] = (int) totOrds;
source.get(docID, values);
long nextLength = totOrds + values.length;
if (nextLength > ords.length) {
if (nextLength > ArrayUtil.MAX_ARRAY_LENGTH) {
throw new IllegalStateException("too many ordinals (>= " + nextLength + ") to cache");
}
ords = ArrayUtil.grow(ords, (int) nextLength);
}
System.arraycopy(values.ints, 0, ords, (int) totOrds, values.length);
totOrds = nextLength;
}
offsets[maxDoc] = (int) totOrds;
// if ords array is bigger by more than 10% of what we really need, shrink it
if ((double) totOrds / ords.length < 0.9) {
this.ordinals = new int[(int) totOrds];
System.arraycopy(ords, 0, this.ordinals, 0, (int) totOrds);
} else {
this.ordinals = ords;
}
}
}
/** How many bytes is this cache using? */
public synchronized long ramBytesUsed() {
long bytes = 0;
for(CachedOrds ords : ordsCache.values()) {
bytes += RamUsageEstimator.sizeOf(ords);
}
return bytes;
}
}