/** * 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.cassandra.service; import java.util.Queue; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import com.google.common.cache.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.io.util.RandomAccessReader; import org.apache.cassandra.metrics.FileCacheMetrics; public class FileCacheService { private static final Logger logger = LoggerFactory.getLogger(FileCacheService.class); private static final long MEMORY_USAGE_THRESHOLD = DatabaseDescriptor.getFileCacheSizeInMB() * 1024L * 1024L; private static final int AFTER_ACCESS_EXPIRATION = 512; // in millis public static FileCacheService instance = new FileCacheService(); private static final Callable<Queue<RandomAccessReader>> cacheForPathCreator = new Callable<Queue<RandomAccessReader>>() { @Override public Queue<RandomAccessReader> call() { return new ConcurrentLinkedQueue<RandomAccessReader>(); } }; private static final AtomicInteger memoryUsage = new AtomicInteger(); private final Cache<String, Queue<RandomAccessReader>> cache; private final FileCacheMetrics metrics = new FileCacheMetrics(); protected FileCacheService() { RemovalListener<String, Queue<RandomAccessReader>> onRemove = new RemovalListener<String, Queue<RandomAccessReader>>() { @Override public void onRemoval(RemovalNotification<String, Queue<RandomAccessReader>> notification) { Queue<RandomAccessReader> cachedInstances = notification.getValue(); if (cachedInstances == null) return; if (cachedInstances.size() > 0) logger.debug("Evicting cold readers for {}", cachedInstances.peek().getPath()); for (RandomAccessReader reader = cachedInstances.poll(); reader != null; reader = cachedInstances.poll()) { memoryUsage.addAndGet(-1 * reader.getTotalBufferSize()); reader.deallocate(); } } }; cache = CacheBuilder.newBuilder() .expireAfterAccess(AFTER_ACCESS_EXPIRATION, TimeUnit.MILLISECONDS) .concurrencyLevel(DatabaseDescriptor.getConcurrentReaders()) .removalListener(onRemove) .build(); } public RandomAccessReader get(String path) { metrics.requests.mark(); Queue<RandomAccessReader> instances = getCacheFor(path); RandomAccessReader result = instances.poll(); if (result != null) { metrics.hits.mark(); memoryUsage.addAndGet(-result.getTotalBufferSize()); } return result; } private Queue<RandomAccessReader> getCacheFor(String path) { try { return cache.get(path, cacheForPathCreator); } catch (ExecutionException e) { throw new AssertionError(e); } } public void put(RandomAccessReader instance) { int memoryUsed = memoryUsage.get(); if (logger.isDebugEnabled()) logger.debug("Estimated memory usage is {} compared to actual usage {}", memoryUsed, sizeInBytes()); if (memoryUsed >= MEMORY_USAGE_THRESHOLD) { instance.deallocate(); } else { memoryUsage.addAndGet(instance.getTotalBufferSize()); getCacheFor(instance.getPath()).add(instance); } } public void invalidate(String path) { logger.debug("Invalidating cache for {}", path); cache.invalidate(path); } public long sizeInBytes() { long n = 0; for (Queue<RandomAccessReader> queue : cache.asMap().values()) for (RandomAccessReader reader : queue) n += reader.getTotalBufferSize(); return n; } }