/**
* 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.db.secindex;
import java.io.IOException;
import java.io.File;
import java.nio.ByteBuffer;
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.collections.iterators.CollatingIterator;
import com.google.common.base.Predicates;
import com.google.common.collect.Sets;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.db.*;
import org.apache.cassandra.dht.AbstractBounds;
import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.io.sstable.bitidx.BitmapIndexReader;
import org.apache.cassandra.io.sstable.*;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.thrift.IndexExpression;
import org.apache.cassandra.thrift.IndexOperator;
import org.apache.cassandra.thrift.IndexType;
import org.apache.cassandra.utils.CloseableIterator;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.FilterIterator;
import org.apache.cassandra.utils.Pair;
import org.apache.cassandra.utils.ReducingIterator;
/**
* Implements a binned bitmap secondary index, where individual sstables are indexed
* independently, and the results are joined from the memtable and each sstable at
* query time. Returns keys that match with high probability, but will often return
* false positives.
*/
public class KeysBitmapIndex extends SecondaryIndex
{
static final Logger logger = LoggerFactory.getLogger(KeysBitmapIndex.class);
// the column family store that is being indexed
private final ColumnFamilyStore cfs;
public KeysBitmapIndex(ColumnDefinition cdef, ColumnFamilyStore cfs)
{
super(cdef);
this.cfs = cfs;
}
public void purge()
{
throw new RuntimeException("Not implemented! Will need to rebuild all SSTables"); // FIXME
}
public double selectivity(IndexExpression expr)
{
if (cfs.getSSTables().isEmpty())
// no sstables: no disk io (TODO: consider races)
return 0;
// take the mean selectivity of all sstables for the expression
long totalSelected = 0;
long totalExisting = 1;
for (SSTableReader reader : cfs.getSSTables())
{
totalSelected += reader.cardinality(expr);
totalExisting += reader.estimatedKeys();
}
return ((double)totalSelected) / totalExisting;
}
public boolean certainty(IndexExpression expr)
{
// TODO: there are a tiny few cases where a binned bitmap index can be certain
return false;
}
public CloseableIterator<DecoratedKey> iterator(AbstractBounds range, IndexExpression expr, ByteBuffer startKey)
{
DecoratedKey dk = cfs.partitioner.decorateKey(startKey);
// FIXME: memtable should filter inside the bounds
CloseableIterator<DecoratedKey> memiter = new FilterIterator(cfs.filterMemtables(expr, dk), Predicates.notNull());
if (cfs.getSSTables().isEmpty())
// no active sstables
return memiter;
// merge probably matching keys from the memtables and sstables
List<CloseableIterator<DecoratedKey>> iters = new ArrayList<CloseableIterator<DecoratedKey>>();
iters.add(memiter);
for (SSTableReader sstable : cfs.getSSTables())
iters.add(sstable.scan(expr, range));
return new ProbableKeyIterator(iters);
}
public ColumnFamilyStore getIndexCFS()
{
return null;
}
/**
* Syncs the in-memory index definitions with indexes on disk for the given SSTables: modifies the sstable map.
* TODO: could be generalized into recovery for any missing components
*/
public static Map<Descriptor,Set<Component>> preload(ColumnFamilyStore cfs, Map<Descriptor,Set<Component>> sstables) throws IOException
{
Collection<ColumnDefinition> defs = cfs.metadata.getColumn_metadata().values();
// collect indexes of type KeysBitmap
Set<ColumnDefinition> keysb = new HashSet<ColumnDefinition>();
for (ColumnDefinition def : defs)
if (def.getIndexType() == IndexType.KEYS_BITMAP)
keysb.add(def);
if (keysb.isEmpty())
// nothing to sync
return sstables;
// recover the indexes for any sstables that don't have them
Map<Descriptor,Set<Component>> output = new HashMap<Descriptor,Set<Component>>();
for (Map.Entry<Descriptor,Set<Component>> sstable : sstables.entrySet())
{
// load definitions for indexes on disk
Map<ColumnDefinition,Component> ondisk = new HashMap<ColumnDefinition,Component>();
for (Component component : sstable.getValue())
if (component.type == Component.Type.BITMAP_INDEX)
ondisk.put(BitmapIndexReader.metadata(sstable.getKey(), component), component);
if (ondisk.keySet().equals(keysb))
{
// on-disk and in-memory match for this sstable
output.put(sstable.getKey(), sstable.getValue());
continue;
}
// drop indexes that shouldn't be on disk
for (Map.Entry<ColumnDefinition,Component> index : ondisk.entrySet())
{
if (defs.contains(index.getKey()))
// valid index
continue;
// old index: remove
sstable.getValue().remove(index.getValue());
FileUtils.deleteWithConfirm(sstable.getKey().filenameFor(index.getValue()));
}
// if any indexes are missing from disk, rebuild the sstable
if (Sets.difference(keysb, ondisk.keySet()).isEmpty())
continue;
// hardlink to a tmp copy (without any bitmap indexes)
Descriptor clone = cfs.getTempSSTable(sstable.getKey().directory);
Set<Component> ctoclone = Sets.difference(sstable.getValue(), new HashSet<Component>(ondisk.values()));
SSTable.clone(sstable.getKey(), clone, ctoclone);
// rebuild all bitmap indexes
logger.info("Rebuilding index(es) on " + sstable.getKey() + " in " + clone);
try
{
Pair<Descriptor,Set<Component>> rebuilt = CompactionManager.instance.submitSSTableBuild(cfs, clone, EnumSet.of(Component.Type.BITMAP_INDEX)).get();
output.put(rebuilt.left, rebuilt.right);
}
catch (Exception e)
{
throw new RuntimeException("Failed to rebuild index(es) in " + clone, e);
}
// the rebuilt clone is now valid: remove the old sstable
SSTable.markCompacted(sstable.getKey(), sstable.getValue());
SSTable.delete(sstable.getKey(), sstable.getValue());
}
return output;
}
static final class ProbableKeyIterator extends ReducingIterator<DecoratedKey,DecoratedKey> implements CloseableIterator<DecoratedKey>
{
private final List<CloseableIterator<DecoratedKey>> sources;
private DecoratedKey cur;
public ProbableKeyIterator(List<CloseableIterator<DecoratedKey>> sources)
{
super(getCollatingIterator(sources));
this.sources = sources;
}
private static CollatingIterator getCollatingIterator(List<CloseableIterator<DecoratedKey>> sources)
{
CollatingIterator citer = FBUtilities.<DecoratedKey>getCollatingIterator();
for (CloseableIterator<DecoratedKey> source : sources)
citer.addIterator(source);
return citer;
}
@Override
public void reduce(DecoratedKey inc)
{
cur = inc;
}
@Override
protected DecoratedKey getReduced()
{
return cur;
}
public void close()
{
for (CloseableIterator source : sources) source.close();
}
}
}