/* * 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.solr.update; import java.io.IOException; import java.lang.invoke.MethodHandles; import java.util.LinkedHashMap; import java.util.Map; import org.apache.lucene.index.LeafReaderContext; import org.apache.lucene.queries.function.FunctionValues; import org.apache.lucene.queries.function.ValueSource; import org.apache.lucene.util.Bits; import org.apache.solr.common.MapSerializable; import org.apache.solr.common.SolrException; import org.apache.solr.common.util.Hash; import org.apache.solr.common.util.NamedList; import org.apache.solr.core.SolrCore; import org.apache.solr.schema.SchemaField; import org.apache.solr.search.SolrIndexSearcher; import org.apache.solr.util.RTimer; import org.apache.solr.util.RefCounted; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** @lucene.internal */ public class IndexFingerprint implements MapSerializable { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private long maxVersionSpecified; private long maxVersionEncountered; // this actually means max versions used in computing the hash. // we cannot change this now because it changes back-compat private long maxInHash; private long versionsHash; private long numVersions; private long numDocs; private long maxDoc; public IndexFingerprint() { // default constructor } public IndexFingerprint (long maxVersionSpecified) { this.maxVersionSpecified = maxVersionSpecified; } public long getMaxVersionSpecified() { return maxVersionSpecified; } public long getMaxVersionEncountered() { return maxVersionEncountered; } public long getMaxInHash() { return maxInHash; } public long getVersionsHash() { return versionsHash; } public long getNumVersions() { return numVersions; } public long getNumDocs() { return numDocs; } public long getMaxDoc() { return maxDoc; } /** Opens a new realtime searcher and returns it's (possibly cached) fingerprint */ public static IndexFingerprint getFingerprint(SolrCore core, long maxVersion) throws IOException { RTimer timer = new RTimer(); core.getUpdateHandler().getUpdateLog().openRealtimeSearcher(); RefCounted<SolrIndexSearcher> newestSearcher = core.getUpdateHandler().getUpdateLog().uhandler.core.getRealtimeSearcher(); try { IndexFingerprint f = newestSearcher.get().getIndexFingerprint(maxVersion); final double duration = timer.stop(); log.info("IndexFingerprint millis:{} result:{}",duration, f); return f; } finally { if (newestSearcher != null) { newestSearcher.decref(); } } } public static IndexFingerprint getFingerprint(SolrIndexSearcher searcher, LeafReaderContext ctx, Long maxVersion) throws IOException { SchemaField versionField = VersionInfo.getAndCheckVersionField(searcher.getSchema()); ValueSource vs = versionField.getType().getValueSource(versionField, null); Map funcContext = ValueSource.newContext(searcher); vs.createWeight(funcContext, searcher); IndexFingerprint f = new IndexFingerprint(); f.maxVersionSpecified = maxVersion; f.maxDoc = ctx.reader().maxDoc(); f.numDocs = ctx.reader().numDocs(); int maxDoc = ctx.reader().maxDoc(); Bits liveDocs = ctx.reader().getLiveDocs(); FunctionValues fv = vs.getValues(funcContext, ctx); for (int doc = 0; doc < maxDoc; doc++) { if (liveDocs != null && !liveDocs.get(doc)) continue; long v = fv.longVal(doc); f.maxVersionEncountered = Math.max(v, f.maxVersionEncountered); if (v <= f.maxVersionSpecified) { f.maxInHash = Math.max(v, f.maxInHash); f.versionsHash += Hash.fmix64(v); f.numVersions++; } } return f; } public static IndexFingerprint reduce(IndexFingerprint acc, IndexFingerprint f2) { // acc should have maxVersionSpecified already set in it using IndexFingerprint(long maxVersionSpecified) constructor acc.maxDoc = Math.max(acc.maxDoc, f2.maxDoc); acc.numDocs += f2.numDocs; acc.maxVersionEncountered = Math.max(acc.maxVersionEncountered, f2.maxVersionEncountered); acc.maxInHash = Math.max(acc.maxInHash, f2.maxInHash); acc.versionsHash += f2.versionsHash; acc.numVersions += f2.numVersions; return acc; } /** returns 0 for equal, negative if f1 is less recent than f2, positive if more recent */ public static int compare(IndexFingerprint f1, IndexFingerprint f2) { int cmp; // NOTE: some way want number of docs in index to take precedence over highest version (add-only systems for sure) // if we're comparing all of the versions in the index, then go by the highest encountered. if (f1.maxVersionSpecified == Long.MAX_VALUE) { cmp = Long.compare(f1.maxVersionEncountered, f2.maxVersionEncountered); if (cmp != 0) return cmp; } // Go by the highest version under the requested max. cmp = Long.compare(f1.maxInHash, f2.maxInHash); if (cmp != 0) return cmp; // go by who has the most documents in the index cmp = Long.compare(f1.numVersions, f2.numVersions); if (cmp != 0) return cmp; // both have same number of documents, so go by hash cmp = Long.compare(f1.versionsHash, f2.versionsHash); return cmp; } @Override public Map<String, Object> toMap(Map<String, Object> map) { map.put("maxVersionSpecified", maxVersionSpecified); map.put("maxVersionEncountered", maxVersionEncountered); map.put("maxInHash", maxInHash); map.put("versionsHash", versionsHash); map.put("numVersions", numVersions); map.put("numDocs", numDocs); map.put("maxDoc", maxDoc); return map; } private static long getLong(Map m, String key, long def) { Object oval = m.get(key); return oval != null ? ((Number)oval).longValue() : def; } /** * Create an IndexFingerprint object from a deserialized generic object (Map or NamedList) */ public static IndexFingerprint fromObject(Object o) { if (o instanceof IndexFingerprint) return (IndexFingerprint) o; Map map = null; if (o instanceof Map) { map = (Map) o; } else if (o instanceof NamedList) { map = ((NamedList) o).asShallowMap(); } else { throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown type " + o); } IndexFingerprint f = new IndexFingerprint(); f.maxVersionSpecified = getLong(map, "maxVersionSpecified", Long.MAX_VALUE); f.maxVersionEncountered = getLong(map, "maxVersionEncountered", -1); f.maxInHash = getLong(map, "maxInHash", -1); f.versionsHash = getLong(map, "versionsHash", -1); f.numVersions = getLong(map, "numVersions", -1); f.numDocs = getLong(map, "numDocs", -1); f.maxDoc = getLong(map, "maxDoc", -1); return f; } @Override public String toString() { return toMap(new LinkedHashMap<>()).toString(); } }