/** * 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.btree.BTreeStore; import io.horizondb.db.btree.KeyValueIterator; import io.horizondb.model.schema.DatabaseDefinition; import io.horizondb.model.schema.TimeSeriesDefinition; import java.io.IOException; import java.util.NavigableMap; import java.util.TreeMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.annotation.concurrent.GuardedBy; import com.codahale.metrics.MetricRegistry; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import static org.apache.commons.lang.Validate.notNull; /** * Base class for the <code>TimeSeriesPartitionManager</code>. */ abstract class AbstractTimeSeriesPartitionManager extends AbstractComponent implements TimeSeriesPartitionManager { /** * The B+Tree branching factor. */ private static final int BRANCHING_FACTOR = 128; /** * The Database server configuration. */ private final Configuration configuration; /** * The B+Tree in which are stored the partition meta data. */ private BTreeStore<PartitionId, TimeSeriesPartitionMetaData> btree; /** * The created but unsaved partitions. */ @GuardedBy("rwLock") private TreeMap<PartitionId, TimeSeriesPartition> unsavedPartitions = new TreeMap<>(); /** * The lock guarding the unsavedPartitions. */ private ReadWriteLock rwLock = new ReentrantReadWriteLock(); /** * The flush manager */ private final FlushManager flushManager; /** * Creates a new <code>AbstractTimeSeriesPartitionManager</code> that will used the specified configuration. * * @param configuration the database configuration */ public AbstractTimeSeriesPartitionManager(Configuration configuration) { notNull(configuration, "the configuration parameter must not be null."); this.configuration = configuration; this.flushManager = new FlushManager(configuration); } /** * {@inheritDoc} */ @Override protected void doStart() throws IOException, InterruptedException { this.btree = createBTreeStore(this.configuration, BRANCHING_FACTOR); this.flushManager.start(); } /** * Creates the B+Tree used to store the partition definitions. * * @param configuration the database configuration * @param branchingFactor the B+Tree branching factor * @throws IOException if an I/O problem occurs while creating the B+Tree */ protected abstract BTreeStore<PartitionId, TimeSeriesPartitionMetaData> createBTreeStore(Configuration configuration, int branchingFactor) throws IOException; /** * {@inheritDoc} */ @Override public void register(MetricRegistry registry) { this.btree.register(registry); this.flushManager.register(registry); } /** * {@inheritDoc} */ @Override public void unregister(MetricRegistry registry) { this.flushManager.unregister(registry); this.btree.unregister(registry); } /** * {@inheritDoc} */ @Override public void save(PartitionId id, TimeSeriesPartitionMetaData metaData) throws IOException, InterruptedException, ExecutionException { this.logger.debug("saving partition {} with meta data: {}", id, metaData); if (this.unsavedPartitions.containsKey(id)) { this.rwLock.writeLock().lock(); try { this.btree.insert(id, metaData); this.unsavedPartitions.remove(id); } finally { this.rwLock.writeLock().unlock(); } } else { this.btree.insert(id, metaData); } } /** * {@inheritDoc} */ @Override public TimeSeriesPartition getPartitionForWrite(PartitionId partitionId, TimeSeriesDefinition definition) throws IOException { TimeSeriesPartitionMetaData metadata = this.btree.get(partitionId); if (metadata != null) { return newTimeSeriesPartition(partitionId, definition, metadata); } metadata = TimeSeriesPartitionMetaData.newBuilder(partitionId.getRange()).build(); TimeSeriesPartition partition = newTimeSeriesPartition(partitionId, definition, metadata); this.rwLock.writeLock().lock(); try { this.unsavedPartitions.put(partitionId, partition); } finally { this.rwLock.writeLock().unlock(); } return partition; } /** * {@inheritDoc} */ @Override public KeyValueIterator<PartitionId, TimeSeriesPartition> getRangeForRead(PartitionId fromId, PartitionId toId, TimeSeriesDefinition definition) throws IOException { KeyValueIterator<PartitionId, TimeSeriesPartition> unsavedPartitionsIterator; this.rwLock.readLock().lock(); try { NavigableMap<PartitionId, TimeSeriesPartition> subMap = this.unsavedPartitions.subMap(fromId, true, toId, true); unsavedPartitionsIterator = new MapKeyValueIterator<>(subMap); } finally { this.rwLock.readLock().unlock(); } return new MergingKeyValueIterator<>(unsavedPartitionsIterator, new TimeSeriesPartitionIterator(definition, this.btree.iterator(fromId, toId))); } /** * {@inheritDoc} */ @Override public void flush(TimeSeriesPartition timeSeriesPartition, FlushListener... listeners) { checkRunning(); this.flushManager.flush(timeSeriesPartition, listeners); } /** * {@inheritDoc} */ @Override public void forceFlush(TimeSeriesPartition timeSeriesPartition, FlushListener... listeners) { checkRunning(); this.flushManager.forceFlush(timeSeriesPartition, listeners); } /** * {@inheritDoc} */ @Override public void forceFlush(long id, TimeSeriesPartition timeSeriesPartition, FlushListener... listeners) { checkRunning(); this.flushManager.forceFlush(id, timeSeriesPartition, listeners); } /** * {@inheritDoc} */ @Override public ListenableFuture<Boolean> forceFlush(long id) { return Futures.immediateFuture(Boolean.TRUE); } /** * {@inheritDoc} */ @Override protected void doShutdown() throws InterruptedException { this.flushManager.shutdown(); this.btree.close(); } /** * Blocks until all the flush tasks previously submitted have been completed. * <p> * This method is implemented for testing purpose. * </p> * * @throws Exception if a problem occurs while synchronizing. */ void sync() throws Exception { this.flushManager.sync(); } /** * Creates a new <code>TimeSeriesPartition</code>. * * @param partitionId the partition ID * @param definition the time series definition * @param metadata the partition meta data * @return a new <code>TimeSeriesPartition</code> * @throws IOException if an I/O problem occurs */ private TimeSeriesPartition newTimeSeriesPartition(PartitionId partitionId, TimeSeriesDefinition definition, TimeSeriesPartitionMetaData metadata) throws IOException { DatabaseDefinition databaseDefinition = new DatabaseDefinition(partitionId.getDatabaseName(), partitionId.getDatabaseTimestamp()); return new TimeSeriesPartition(this, this.configuration, databaseDefinition, definition, metadata); } /** * <code>KeyValueIterator</code> used to iterate over a range of partitions. */ private final class TimeSeriesPartitionIterator implements KeyValueIterator<PartitionId, TimeSeriesPartition> { /** * The time series definition. */ private final TimeSeriesDefinition definition; /** * The meta data iterator. */ private final KeyValueIterator<PartitionId, TimeSeriesPartitionMetaData> iterator; /** * Creates a <code>TimeSeriesPartitionIterator</code>. * * @param definition the time series definition * @param iterator the meta data iterator */ public TimeSeriesPartitionIterator(TimeSeriesDefinition definition, KeyValueIterator<PartitionId, TimeSeriesPartitionMetaData> iterator) { this.definition = definition; 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 { return newTimeSeriesPartition(getKey(), this.definition, this.iterator.getValue()); } } }