/* * Copyright (c) 2013-2017 Cinchapi Inc. * * 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 com.cinchapi.concourse.server.storage.temp; import java.util.Iterator; import java.util.List; import java.util.concurrent.Callable; import javax.annotation.Nullable; import com.cinchapi.concourse.server.model.Value; import com.cinchapi.concourse.server.storage.Action; import com.cinchapi.concourse.server.storage.PermanentStore; import com.cinchapi.concourse.server.storage.cache.BloomFilter; import com.cinchapi.concourse.thrift.Type; import com.cinchapi.concourse.util.Producer; import com.google.common.collect.Lists; /** * A {@link Queue} is a very simple form of {@link Limbo} that represents * data as a sequence of {@link Write} objects. New data is appended to the * sequence and the returned {@link Iterator} traverses the list. * * @author Jeff Nelson */ public class Queue extends Limbo { /** * The threshold at which an internal bloom filter is dynamically created to * speed up verifies. */ private static final int BLOOM_FILTER_CREATION_THRESHOLD = 10; /** * An empty array of writes that is used to specify type conversion in the * {@link ArrayList#toArray(Object[])} method. */ private static final Write[] EMPTY_WRITES_ARRAY = new Write[0]; /** * A global producer that provides BloomFilters to instances that need them. * To some extent, this producer will queue up bloom filters so that the * overhead of creating them is not incurred directly by the caller. */ private static final Producer<BloomFilter> producer = new Producer<BloomFilter>( new Callable<BloomFilter>() { @Override public BloomFilter call() throws Exception { // TODO: at some point this size should be determined based // on some intelligent heuristic BloomFilter filter = BloomFilter.create(500000); filter.disableThreadSafety(); return filter; } }); /** * Revisions are stored as a sequential list of {@link Write} objects, which * means most reads are <em>at least</em> an O(n) scan. */ protected final List<Write> writes; /** * The bloom filter used to speed up verifies. */ private BloomFilter filter = null; /** * A cache of the {@link #getOldestWriteTimstamp() timestamp} for the oldest * write in the Queue. This value is not expected to change often, so it * makes sense to cache it for situations that frequently look for it. */ private long oldestWriteTimestampCache = 0; /** * Construct a Limbo with enough capacity for {@code initialSize}. If * necessary, the structure will grow to accommodate more data. * * @param initialSize */ public Queue(int initialSize) { writes = Lists.newArrayListWithCapacity(initialSize); } /** * Return an unmodifiable copy of the writes contained in the Queue. * * @return the writes */ public List<Write> getWrites() { return writes; } @Override public boolean insert(Write write, boolean sync) { writes.add(write); // #sync is meaningless since Queue is a memory store if(filter != null) { filter.putCached(write.getKey(), write.getValue(), write.getRecord()); } else if(writes.size() > BLOOM_FILTER_CREATION_THRESHOLD) { filter = producer.consume(); for (int i = 0; i < writes.size(); ++i) { Write stored = writes.get(i); filter.put(stored.getKey(), stored.getValue(), stored.getRecord()); } } return true; } @Override public Iterator<Write> iterator() { return writes.iterator(); } /** * Return the number of writes in this Queue. * * @return the number of writes */ public int size() { return writes.size(); } @Override public void start() { // do nothing } @Override public void stop() { // do nothing } @Override public void transport(PermanentStore destination, boolean sync) { // For transactions, this method will only be called once, so we can // optimize it by not using the services of an Iterator (e.g. hasNext(), // remove(), etc) and, if the number of writes in the Queue is large // enough, grabbing elements from the backing array directly. oldestWriteTimestampCache = 0; int length = writes.size(); Write[] elts = length > 10000 ? writes.toArray(EMPTY_WRITES_ARRAY) : null; for (int i = 0; i < length; ++i) { Write write = elts == null ? writes.get(i) : elts[i]; destination.accept(write, sync); } } @Override public boolean verify(Write write, long timestamp, boolean exists) { if(filter == null || (filter != null && filter.mightContainCached(write.getKey(), write.getValue(), write.getRecord()))) { return super.verify(write, timestamp, exists); } else { return exists; } } @Override @Nullable protected Action getLastWriteAction(Write write, long timestamp) { if(filter == null || (filter != null && filter.mightContainCached(write.getKey(), write.getValue(), write.getRecord()))) { return super.getLastWriteAction(write, timestamp); } else { return null; } } @Override protected long getOldestWriteTimestamp() { // When there is no data in the buffer return the max possible timestamp // so that no query's timestamp is less than this timestamp if(writes.size() == 0) { return Long.MAX_VALUE; } else { if(oldestWriteTimestampCache == 0) { Write oldestWrite = writes.get(0); oldestWriteTimestampCache = oldestWrite.getVersion(); } return oldestWriteTimestampCache; } } @Override protected Iterator<Write> getSearchIterator(String key) { return iterator(); } @Override protected boolean isPossibleSearchMatch(String key, Write write, Value value) { return write.getKey().toString().equals(key) && value.getType() == Type.STRING; } }