/**
* 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.io.sstable;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOError;
import java.io.IOException;
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.io.util.FileUtils;
import org.apache.cassandra.utils.EstimatedHistogram;
import org.apache.cassandra.utils.Pair;
/**
* This class is built on top of the SequenceFile. It stores
* data on disk in sorted fashion. However the sorting is upto
* the application. This class expects keys to be handed to it
* in sorted order.
*
* A separate index file is maintained as well, containing the
* SSTable keys and the offset into the SSTable at which they are found.
* Every 1/indexInterval key is read into memory when the SSTable is opened.
*
* Finally, a bloom filter file is also kept for the keys in each SSTable.
*/
public abstract class SSTable
{
static final Logger logger = LoggerFactory.getLogger(SSTable.class);
// TODO: replace with 'Component' objects
public static final String COMPONENT_DATA = Component.Type.DATA.repr;
public static final String COMPONENT_INDEX = Component.Type.PRIMARY_INDEX.repr;
public static final String COMPONENT_FILTER = Component.Type.FILTER.repr;
public static final String COMPONENT_STATS = Component.Type.STATS.repr;
public static final String COMPONENT_COMPACTED = Component.Type.COMPACTED_MARKER.repr;
public static final String COMPONENT_STREAMED = Component.Type.STREAMED.repr;
protected final Descriptor desc;
protected final Set<Component> components;
public final CFMetaData metadata;
protected final IPartitioner partitioner;
public static final String TEMPFILE_MARKER = "tmp";
protected EstimatedHistogram estimatedRowSize = new EstimatedHistogram(150);
protected EstimatedHistogram estimatedColumnCount = new EstimatedHistogram(114);
protected SSTable(Descriptor desc, CFMetaData metadata, IPartitioner partitioner)
{
this(desc, new HashSet<Component>(), metadata, partitioner);
}
protected SSTable(Descriptor desc, Set<Component> components, CFMetaData metadata, IPartitioner partitioner)
{
this.desc = desc;
this.components = components;
this.metadata = metadata;
this.partitioner = partitioner;
}
public EstimatedHistogram getEstimatedRowSize()
{
return estimatedRowSize;
}
public EstimatedHistogram getEstimatedColumnCount()
{
return estimatedColumnCount;
}
public IPartitioner getPartitioner()
{
return partitioner;
}
public Descriptor getDescriptor()
{
return desc;
}
public Set<Component> getComponents()
{
return components;
}
/**
* We use a ReferenceQueue to manage deleting files that have been compacted
* and for which no more SSTable references exist. But this is not guaranteed
* to run for each such file because of the semantics of the JVM gc. So,
* we write a marker to `compactedFilename` when a file is compacted;
* if such a marker exists on startup, the file should be removed.
*
* This method will also remove SSTables that are marked as temporary.
*
* @return true if the file was deleted
*/
public static boolean conditionalDelete(Descriptor desc, Set<Component> components)
{
if (!components.contains(Component.COMPACTED_MARKER) && !desc.temporary)
// not compacted or temporary
return false;
try
{
// remove the DATA component first if it exists
if (components.contains(Component.DATA))
FileUtils.deleteWithConfirm(desc.filenameFor(Component.DATA));
for (Component component : components)
{
if (component.equals(Component.DATA) || component.equals(Component.COMPACTED_MARKER))
continue;
FileUtils.deleteWithConfirm(desc.filenameFor(component));
}
// remove the COMPACTED_MARKER component last if it exists
if (components.contains(Component.COMPACTED_MARKER))
FileUtils.deleteWithConfirm(desc.filenameFor(Component.COMPACTED_MARKER));
}
catch (IOException e)
{
throw new IOError(e);
}
logger.info("Deleted " + desc);
return true;
}
public String getFilename()
{
return desc.filenameFor(COMPONENT_DATA);
}
public String getColumnFamilyName()
{
return desc.cfname;
}
public String getTableName()
{
return desc.ksname;
}
/**
* @return A Descriptor,Component pair, or null if not a valid sstable component.
*/
public static Pair<Descriptor,Component> tryComponentFromFilename(File dir, String name)
{
try
{
return Component.fromFilename(dir, name);
}
catch (Exception e)
{
logger.warn("Invalid file '{}' in data directory {}.", name, dir);
return null;
}
}
/**
* Discovers existing components for the descriptor. Slow: only intended for use outside the critical path.
*/
static Set<Component> componentsFor(final Descriptor desc) throws IOException
{
final Set<Component> components = new HashSet<Component>();
desc.directory.list(new FilenameFilter()
{
public boolean accept(File dir, String name)
{
Pair<Descriptor,Component> component = tryComponentFromFilename(dir, name);
if (component != null && component.left.equals(desc))
components.add(component.right);
return false;
}
});
return components;
}
public static long getTotalBytes(Iterable<SSTableReader> sstables)
{
long sum = 0;
for (SSTableReader sstable : sstables)
{
sum += sstable.length();
}
return sum;
}
public long bytesOnDisk()
{
long bytes = 0;
for (Component component : components)
{
bytes += new File(desc.filenameFor(component)).length();
}
return bytes;
}
@Override
public String toString()
{
return getClass().getName() + "(" +
"path='" + getFilename() + '\'' +
')';
}
}