2525import com .google .common .cache .RemovalListener ;
2626import com .google .common .cache .RemovalNotification ;
2727import com .google .common .collect .ImmutableMap ;
28+ import gnu .trove .set .hash .THashSet ;
2829import org .elasticsearch .cluster .metadata .MetaData ;
30+ import org .elasticsearch .common .CacheRecycler ;
2931import org .elasticsearch .common .collect .MapBuilder ;
3032import org .elasticsearch .common .component .AbstractComponent ;
3133import org .elasticsearch .common .inject .Inject ;
3234import org .elasticsearch .common .lucene .docset .DocSet ;
3335import org .elasticsearch .common .settings .Settings ;
3436import org .elasticsearch .common .unit .ByteSizeValue ;
3537import org .elasticsearch .common .unit .TimeValue ;
38+ import org .elasticsearch .common .util .concurrent .ConcurrentCollections ;
3639import org .elasticsearch .index .cache .filter .weighted .WeightedFilterCache ;
3740import org .elasticsearch .monitor .jvm .JvmInfo ;
3841import org .elasticsearch .node .settings .NodeSettingsService ;
42+ import org .elasticsearch .threadpool .ThreadPool ;
3943
44+ import java .util .Iterator ;
4045import java .util .Map ;
46+ import java .util .Set ;
4147import java .util .concurrent .TimeUnit ;
4248
4349public class IndicesFilterCache extends AbstractComponent implements RemovalListener <WeightedFilterCache .FilterCacheKey , DocSet > {
4450
51+ private final ThreadPool threadPool ;
52+
4553 private Cache <WeightedFilterCache .FilterCacheKey , DocSet > cache ;
4654
4755 private volatile String size ;
4856 private volatile long sizeInBytes ;
4957 private volatile TimeValue expire ;
5058
59+ private final TimeValue cleanInterval ;
60+
61+ private final Set <Object > readersKeysToClean = ConcurrentCollections .newConcurrentSet ();
62+
63+ private volatile boolean closed ;
64+
5165 private volatile Map <String , RemovalListener <WeightedFilterCache .FilterCacheKey , DocSet >> removalListeners =
5266 ImmutableMap .of ();
5367
@@ -85,15 +99,19 @@ public void onRefreshSettings(Settings settings) {
8599 }
86100
87101 @ Inject
88- public IndicesFilterCache (Settings settings , NodeSettingsService nodeSettingsService ) {
102+ public IndicesFilterCache (Settings settings , ThreadPool threadPool , NodeSettingsService nodeSettingsService ) {
89103 super (settings );
104+ this .threadPool = threadPool ;
90105 this .size = componentSettings .get ("size" , "20%" );
91106 this .expire = componentSettings .getAsTime ("expire" , null );
107+ this .cleanInterval = componentSettings .getAsTime ("clean_interval" , TimeValue .timeValueSeconds (1 ));
92108 computeSizeInBytes ();
93109 buildCache ();
94110 logger .debug ("using [node] filter cache with size [{}], actual_size [{}]" , size , new ByteSizeValue (sizeInBytes ));
95111
96112 nodeSettingsService .addListener (new ApplySettings ());
113+
114+ threadPool .schedule (cleanInterval , ThreadPool .Names .SAME , new ReaderCleaner ());
97115 }
98116
99117 private void buildCache () {
@@ -102,7 +120,7 @@ private void buildCache() {
102120 .maximumWeight (sizeInBytes ).weigher (new WeightedFilterCache .FilterCacheValueWeigher ());
103121
104122 // defaults to 4, but this is a busy map for all indices, increase it a bit
105- cacheBuilder .concurrencyLevel (8 );
123+ cacheBuilder .concurrencyLevel (16 );
106124
107125 if (expire != null ) {
108126 cacheBuilder .expireAfterAccess (expire .millis (), TimeUnit .MILLISECONDS );
@@ -128,7 +146,12 @@ public synchronized void removeRemovalListener(String index) {
128146 removalListeners = MapBuilder .newMapBuilder (removalListeners ).remove (index ).immutableMap ();
129147 }
130148
149+ public void addReaderKeyToClean (Object readerKey ) {
150+ readersKeysToClean .add (readerKey );
151+ }
152+
131153 public void close () {
154+ closed = true ;
132155 cache .invalidateAll ();
133156 }
134157
@@ -147,4 +170,49 @@ public void onRemoval(RemovalNotification<WeightedFilterCache.FilterCacheKey, Do
147170 listener .onRemoval (removalNotification );
148171 }
149172 }
173+
174+ /**
175+ * The reason we need this class ie because we need to clean all the filters that are associated
176+ * with a reader. We don't want to do it every time a reader closes, since iterating over all the map
177+ * is expensive. There doesn't seem to be a nicer way to do it (and maintaining a list per reader
178+ * of the filters will cost more).
179+ */
180+ class ReaderCleaner implements Runnable {
181+
182+ @ Override
183+ public void run () {
184+ if (closed ) {
185+ return ;
186+ }
187+ if (readersKeysToClean .isEmpty ()) {
188+ threadPool .schedule (cleanInterval , ThreadPool .Names .SAME , this );
189+ return ;
190+ }
191+ threadPool .executor (ThreadPool .Names .GENERIC ).execute (new Runnable () {
192+ @ Override
193+ public void run () {
194+ THashSet <Object > keys = CacheRecycler .popHashSet ();
195+ try {
196+ for (Iterator <Object > it = readersKeysToClean .iterator (); it .hasNext (); ) {
197+ keys .add (it .next ());
198+ it .remove ();
199+ }
200+ cache .cleanUp ();
201+ if (!keys .isEmpty ()) {
202+ for (Iterator <WeightedFilterCache .FilterCacheKey > it = cache .asMap ().keySet ().iterator (); it .hasNext (); ) {
203+ WeightedFilterCache .FilterCacheKey filterCacheKey = it .next ();
204+ if (keys .contains (filterCacheKey .readerKey ())) {
205+ // same as invalidate
206+ it .remove ();
207+ }
208+ }
209+ }
210+ threadPool .schedule (cleanInterval , ThreadPool .Names .SAME , ReaderCleaner .this );
211+ } finally {
212+ CacheRecycler .pushHashSet (keys );
213+ }
214+ }
215+ });
216+ }
217+ }
150218}
0 commit comments