/** * Copyright 2013 Benjamin Lerer * * Licensed 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 io.horizondb.db.series; import io.horizondb.db.AbstractComponent; import io.horizondb.db.Configuration; import io.horizondb.db.HorizonDBException; import io.horizondb.db.btree.KeyValueIterator; import io.horizondb.db.cache.ValueLoader; import io.horizondb.db.util.concurrent.CountDownFuture; import io.horizondb.model.schema.TimeSeriesDefinition; import java.io.IOException; import java.util.List; import java.util.concurrent.ExecutionException; import javax.annotation.concurrent.ThreadSafe; import com.codahale.metrics.MetricRegistry; import com.google.common.cache.CacheStats; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; /** * Decorator that add caching functionalities to a <code>TimeSeriesPartitionManager</code> * * @author Benjamin */ @ThreadSafe public final class TimeSeriesPartitionManagerCaches extends AbstractComponent implements TimeSeriesPartitionManager { /** * The decorated partition manager. */ private final TimeSeriesPartitionManager manager; /** * The cache used when performing reads. */ private final TimeSeriesPartitionReadCache readCache; /** * The cache used when performing writes. */ private final TimeSeriesPartitionWriteCache writeCache; /** * The global cache. */ private final TimeSeriesPartitionSecondLevelCache globalCache; /** * Creates a <code>TimeSeriesPartitionManagerCachesTest</code> to globalCache the time series partition returned by * the specified manager. * * @param configuration the database configuration. * @param manager the manager to decorate. */ public TimeSeriesPartitionManagerCaches(Configuration configuration, TimeSeriesPartitionManager manager) { this.manager = manager; this.globalCache = new TimeSeriesPartitionSecondLevelCache(configuration); this.readCache = new TimeSeriesPartitionReadCache(configuration, this.globalCache); this.writeCache = new TimeSeriesPartitionWriteCache(configuration, this.globalCache); } /** * {@inheritDoc} */ @Override protected void doStart() throws IOException, InterruptedException { start(this.manager, this.globalCache, this.readCache, this.writeCache); } /** * {@inheritDoc} */ @Override public void register(MetricRegistry registry) { register(registry, this.manager, this.globalCache, this.readCache, this.writeCache); } /** * {@inheritDoc} */ @Override public void unregister(MetricRegistry registry) { unregister(registry, this.writeCache, this.readCache, this.globalCache, this.manager); } /** * {@inheritDoc} */ @Override protected void doShutdown() throws InterruptedException { shutdown(this.writeCache, this.readCache, this.globalCache, this.manager); } /** * {@inheritDoc} */ @Override public void save(PartitionId id, TimeSeriesPartitionMetaData metaData) throws IOException, InterruptedException, ExecutionException { this.manager.save(id, metaData); } /** * {@inheritDoc} */ @Override public TimeSeriesPartition getPartitionForWrite(final PartitionId partitionId, final TimeSeriesDefinition seriesDefinition) throws IOException, HorizonDBException { return this.writeCache.get(partitionId, new ValueLoader<PartitionId, TimeSeriesPartition>() { @Override public TimeSeriesPartition loadValue(PartitionId key) throws IOException, HorizonDBException { // Calling get partition for write or read does not really change anything has the real // difference between the 2 methods is only in caching. return TimeSeriesPartitionManagerCaches.this.manager.getPartitionForWrite(partitionId, seriesDefinition); } }); } /** * {@inheritDoc} */ @Override public ListenableFuture<Boolean> forceFlush(long id) throws InterruptedException { this.logger.info("trying to flush all the partitions that have non persisted data within commit log segment: {}", Long.valueOf(id)); List<TimeSeriesPartition> partitions = this.writeCache.getPartitionsWithNonPersistedDataWithin(id); if (partitions.isEmpty()) { this.logger.info("no partitions have non persisted data within commit log segment: {}", Long.valueOf(id)); return Futures.immediateFuture(Boolean.TRUE); } final CountDownFuture<Boolean> countDownFuture = new CountDownFuture<>(Boolean.TRUE, partitions.size()); FlushListener listener = new FlushListener() { @Override public void afterFlush() { countDownFuture.countDown(); } }; for (TimeSeriesPartition partition : partitions) { forceFlush(id, partition, listener); } return countDownFuture; } /** * {@inheritDoc} */ @Override public void forceFlush(long id, TimeSeriesPartition timeSeriesPartition, FlushListener... listeners) { this.manager.forceFlush(id, timeSeriesPartition, listeners); } /** * {@inheritDoc} */ @Override public KeyValueIterator<PartitionId, TimeSeriesPartition> getRangeForRead(PartitionId fromId, PartitionId toId, TimeSeriesDefinition definition) throws IOException { return new TimeSeriesPartitionCacheIterator(this.readCache, this.manager.getRangeForRead(fromId, toId, definition)); } /** * Returns the global cache size. * * @return the global cache size. */ long globalCacheSize() { return this.globalCache.size(); } /** * Returns the read cache size. * * @return the read cache size. */ long readCacheSize() { return this.readCache.size(); } /** * Returns the write cache size. * * @return the write cache size. */ long writeCacheSize() { return this.writeCache.size(); } /** * {@inheritDoc} */ @Override public void flush(TimeSeriesPartition timeSeriesPartition, FlushListener... listeners) { this.manager.flush(timeSeriesPartition, listeners); } /** * {@inheritDoc} */ @Override public void forceFlush(TimeSeriesPartition timeSeriesPartition, FlushListener... listeners) { this.manager.forceFlush(timeSeriesPartition, listeners); } /** * Returns the statistics of the global cache. * * @return the statistics of the global cache. */ CacheStats globalCacheStats() { return this.globalCache.stats(); } /** * Returns the statistics of the read cache. * * @return the statistics of the read cache. */ CacheStats readCacheStats() { return this.readCache.stats(); } /** * Returns the statistics of the write cache. * * @return the statistics of the write cache. */ CacheStats writeCacheStats() { return this.writeCache.stats(); } void evictFromReadCache(PartitionId id) { this.readCache.invalidate(id); } /** * <code>KeyValueIterator</code> used to iterate over a range of partitions. */ private static final class TimeSeriesPartitionCacheIterator implements KeyValueIterator<PartitionId, TimeSeriesPartition> { /** * The meta data iterator. */ private final KeyValueIterator<PartitionId, TimeSeriesPartition> iterator; /** * The cache used when performing reads. */ private final TimeSeriesPartitionReadCache readCache; /** * Creates a <code>TimeSeriesPartitionCacheIterator</code>. * * @param readCache the read cache * @param iterator the partition iterator */ public TimeSeriesPartitionCacheIterator(TimeSeriesPartitionReadCache readCache, KeyValueIterator<PartitionId, TimeSeriesPartition> iterator) { this.readCache = readCache; this.iterator = iterator; } /** * {@inheritDoc} */ @Override public boolean next() throws IOException { return this.iterator.next(); } /** * {@inheritDoc} */ @Override public PartitionId getKey() { return this.iterator.getKey(); } /** * {@inheritDoc} */ @Override public TimeSeriesPartition getValue() throws IOException { PartitionId id = getKey(); TimeSeriesPartition partition = this.readCache.getIfPresent(id); if (partition != null) { return partition; } partition = this.iterator.getValue(); this.readCache.put(id, partition); return partition; } } }