Lucene.Net  3.0.3
Lucene.Net is a port of the Lucene search engine library, written in C# and targeted at .NET runtime users.
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Properties Pages
CachingWrapperFilter.cs
Go to the documentation of this file.
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 using System;
19 using System.Collections;
20 using System.Collections.Generic;
21 using Lucene.Net.Support;
22 using IndexReader = Lucene.Net.Index.IndexReader;
23 using OpenBitSetDISI = Lucene.Net.Util.OpenBitSetDISI;
24 using Lucene.Net.Util;
25 
26 namespace Lucene.Net.Search
27 {
28 
29  /// <summary> Wraps another filter's result and caches it. The purpose is to allow
30  /// filters to simply filter, and then wrap with this class to add caching.
31  /// </summary>
32  [Serializable]
34  {
35  protected internal Filter filter;
36 
37  ///
38  /// Expert: Specifies how new deletions against a reopened
39  /// reader should be handled.
40  ///
41  /// <para>The default is IGNORE, which means the cache entry
42  /// will be re-used for a given segment, even when that
43  /// segment has been reopened due to changes in deletions.
44  /// This is a big performance gain, especially with
45  /// near-real-timer readers, since you don't hit a cache
46  /// miss on every reopened reader for prior segments.</para>
47  ///
48  /// <para>However, in some cases this can cause invalid query
49  /// results, allowing deleted documents to be returned.
50  /// This only happens if the main query does not rule out
51  /// deleted documents on its own, such as a toplevel
52  /// ConstantScoreQuery. To fix this, use RECACHE to
53  /// re-create the cached filter (at a higher per-reopen
54  /// cost, but at faster subsequent search performance), or
55  /// use DYNAMIC to dynamically intersect deleted docs (fast
56  /// reopen time but some hit to search performance).</para>
57  ///
58  public enum DeletesMode { IGNORE, RECACHE, DYNAMIC }
59 
60  internal FilterCache<DocIdSet> cache;
61 
62  [Serializable]
63  abstract internal class FilterCache<T> where T : class
64  {
65  /*
66  * A transient Filter cache (package private because of test)
67  */
68  // NOTE: not final so that we can dynamically re-init
69  // after de-serialize
70  volatile IDictionary<Object, T> cache;
71 
72  private DeletesMode deletesMode;
73 
74  public FilterCache(DeletesMode deletesMode)
75  {
76  this.deletesMode = deletesMode;
77  }
78 
79  public T Get(IndexReader reader, object coreKey, object delCoreKey)
80  {
81  lock (this)
82  {
83  T value;
84 
85  if (cache == null)
86  {
87  cache = new WeakDictionary<object, T>();
88  }
89 
90  if (deletesMode == DeletesMode.IGNORE)
91  {
92  // key on core
93  value = cache[coreKey];
94  }
95  else if (deletesMode == DeletesMode.RECACHE)
96  {
97  // key on deletes, if any, else core
98  value = cache[delCoreKey];
99  }
100  else
101  {
102 
103  System.Diagnostics.Debug.Assert(deletesMode == DeletesMode.DYNAMIC);
104 
105  // first try for exact match
106  value = cache[delCoreKey];
107 
108  if (value == null)
109  {
110  // now for core match, but dynamically AND NOT
111  // deletions
112  value = cache[coreKey];
113  if (value != null && reader.HasDeletions)
114  {
115  value = MergeDeletes(reader, value);
116  }
117  }
118  }
119  return value;
120  }
121 
122  }
123 
124  protected abstract T MergeDeletes(IndexReader reader, T value);
125 
126  public void Put(object coreKey, object delCoreKey, T value)
127  {
128  lock (this)
129  {
130  if (deletesMode == DeletesMode.IGNORE)
131  {
132  cache[coreKey] = value;
133  }
134  else if (deletesMode == DeletesMode.RECACHE)
135  {
136  cache[delCoreKey] = value;
137  }
138  else
139  {
140  cache[coreKey] = value;
141  cache[delCoreKey] = value;
142  }
143  }
144  }
145  }
146 
147  /// <summary>
148  /// New deletes are ignored by default, which gives higher
149  /// cache hit rate on reopened readers. Most of the time
150  /// this is safe, because the filter will be AND'd with a
151  /// Query that fully enforces deletions. If instead you
152  /// need this filter to always enforce deletions, pass
153  /// either <see cref="DeletesMode.RECACHE" /> or
154  /// <see cref="DeletesMode.DYNAMIC"/>.
155  /// </summary>
156  /// <param name="filter">Filter to cache results of</param>
157  ///
158  public CachingWrapperFilter(Filter filter) : this(filter, DeletesMode.IGNORE)
159  {
160  }
161 
162  /// <summary>
163  /// Expert: by default, the cached filter will be shared
164  /// across reopened segments that only had changes to their
165  /// deletions.
166  /// </summary>
167  /// <param name="filter">Filter to cache results of</param>
168  /// <param name="deletesMode">See <see cref="DeletesMode" /></param>
169  ///
170  public CachingWrapperFilter(Filter filter, DeletesMode deletesMode)
171  {
172  this.filter = filter;
173  cache = new AnonymousFilterCache(deletesMode);
174 
175  //cache = new FilterCache(deletesMode)
176  // {
177  // public Object mergeDeletes(final IndexReader r, final Object docIdSet) {
178  // return new FilteredDocIdSet((DocIdSet) docIdSet) {
179  // protected boolean match(int docID) {
180  // return !r.isDeleted(docID);
181  // }
182  // };
183  // }
184  //};
185  }
186 
187  class AnonymousFilterCache : FilterCache<DocIdSet>
188  {
189  class AnonymousFilteredDocIdSet : FilteredDocIdSet
190  {
191  IndexReader r;
192  public AnonymousFilteredDocIdSet(DocIdSet innerSet, IndexReader r) : base(innerSet)
193  {
194  this.r = r;
195  }
196  public override bool Match(int docid)
197  {
198  return !r.IsDeleted(docid);
199  }
200  }
201 
202  public AnonymousFilterCache(DeletesMode deletesMode) : base(deletesMode)
203  { }
204 
205  protected override DocIdSet MergeDeletes(IndexReader reader, DocIdSet docIdSet)
206  {
207  return new AnonymousFilteredDocIdSet(docIdSet, reader);
208  }
209  }
210 
211  /// <summary>Provide the DocIdSet to be cached, using the DocIdSet provided
212  /// by the wrapped Filter.
213  /// This implementation returns the given DocIdSet.
214  /// </summary>
215  protected internal virtual DocIdSet DocIdSetToCache(DocIdSet docIdSet, IndexReader reader)
216  {
217  if (docIdSet == null)
218  {
219  // this is better than returning null, as the nonnull result can be cached
220  return DocIdSet.EMPTY_DOCIDSET;
221  }
222  else if (docIdSet.IsCacheable) {
223  return docIdSet;
224  }
225  else
226  {
227  DocIdSetIterator it = docIdSet.Iterator();
228  // null is allowed to be returned by iterator(),
229  // in this case we wrap with the empty set,
230  // which is cacheable.
231  return (it == null) ? DocIdSet.EMPTY_DOCIDSET : new OpenBitSetDISI(it, reader.MaxDoc);
232  }
233  }
234 
235  // for testing
236  public int hitCount, missCount;
237 
238  public override DocIdSet GetDocIdSet(IndexReader reader)
239  {
240  object coreKey = reader.FieldCacheKey;
241  object delCoreKey = reader.HasDeletions ? reader.DeletesCacheKey : coreKey;
242 
243  DocIdSet docIdSet = cache.Get(reader, coreKey, delCoreKey);
244 
245  if (docIdSet != null)
246  {
247  hitCount++;
248  return docIdSet;
249  }
250  missCount++;
251  // cache miss
252  docIdSet = DocIdSetToCache(filter.GetDocIdSet(reader), reader);
253 
254  if (docIdSet != null)
255  {
256  cache.Put(coreKey, delCoreKey, docIdSet);
257  }
258 
259  return docIdSet;
260  }
261 
262  public override System.String ToString()
263  {
264  return "CachingWrapperFilter(" + filter + ")";
265  }
266 
267  public override bool Equals(System.Object o)
268  {
269  if (!(o is CachingWrapperFilter))
270  return false;
271  return this.filter.Equals(((CachingWrapperFilter) o).filter);
272  }
273 
274  public override int GetHashCode()
275  {
276  return filter.GetHashCode() ^ 0x1117BF25;
277  }
278  }
279 }