package org.apache.cassandra.io;
/*
*
* 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.
*
*/
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOError;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import org.apache.log4j.Logger;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.DecoratedKey;
import org.apache.cassandra.dht.IPartitioner;
import org.apache.cassandra.io.util.BufferedRandomAccessFile;
import org.apache.cassandra.io.util.DataOutputBuffer;
import org.apache.cassandra.utils.BloomFilter;
import org.apache.cassandra.utils.FBUtilities;
public class SSTableWriter extends SSTable
{
private static Logger logger = Logger.getLogger(SSTableWriter.class);
private BufferedRandomAccessFile dataFile;
private BufferedRandomAccessFile indexFile;
private DecoratedKey lastWrittenKey;
private BloomFilter bf;
public SSTableWriter(String filename, long keyCount, IPartitioner partitioner) throws IOException
{
super(filename, partitioner);
indexSummary = new IndexSummary();
dataFile = new BufferedRandomAccessFile(path, "rw", (int)(DatabaseDescriptor.getFlushDataBufferSizeInMB() * 1024 * 1024));
indexFile = new BufferedRandomAccessFile(indexFilename(), "rw", (int)(DatabaseDescriptor.getFlushIndexBufferSizeInMB() * 1024 * 1024));
bf = BloomFilter.getFilter(keyCount, 15);
}
private long beforeAppend(DecoratedKey decoratedKey) throws IOException
{
if (decoratedKey == null)
{
throw new IOException("Keys must not be null.");
}
if (lastWrittenKey != null && lastWrittenKey.compareTo(decoratedKey) > 0)
{
logger.info("Last written key : " + lastWrittenKey);
logger.info("Current key : " + decoratedKey);
logger.info("Writing into file " + path);
throw new IOException("Keys must be written in ascending order.");
}
return (lastWrittenKey == null) ? 0 : dataFile.getFilePointer();
}
private void afterAppend(DecoratedKey decoratedKey, long dataPosition) throws IOException
{
String diskKey = partitioner.convertToDiskFormat(decoratedKey);
bf.add(diskKey);
lastWrittenKey = decoratedKey;
long indexPosition = indexFile.getFilePointer();
indexFile.writeUTF(diskKey);
indexFile.writeLong(dataPosition);
if (logger.isTraceEnabled())
logger.trace("wrote " + decoratedKey + " at " + dataPosition);
if (logger.isTraceEnabled())
logger.trace("wrote index of " + decoratedKey + " at " + indexPosition);
int rowSize = (int)(dataFile.getFilePointer() - dataPosition);
indexSummary.maybeAddEntry(decoratedKey, dataPosition, rowSize, indexPosition, indexFile.getFilePointer());
}
// TODO make this take a DataOutputStream and wrap the byte[] version to combine them
public void append(DecoratedKey decoratedKey, DataOutputBuffer buffer) throws IOException
{
long currentPosition = beforeAppend(decoratedKey);
dataFile.writeUTF(partitioner.convertToDiskFormat(decoratedKey));
int length = buffer.getLength();
assert length > 0;
dataFile.writeInt(length);
dataFile.write(buffer.getData(), 0, length);
afterAppend(decoratedKey, currentPosition);
}
public void append(DecoratedKey decoratedKey, byte[] value) throws IOException
{
long currentPosition = beforeAppend(decoratedKey);
dataFile.writeUTF(partitioner.convertToDiskFormat(decoratedKey));
assert value.length > 0;
dataFile.writeInt(value.length);
dataFile.write(value);
afterAppend(decoratedKey, currentPosition);
}
/**
* Renames temporary SSTable files to valid data, index, and bloom filter files
*/
public SSTableReader closeAndOpenReader() throws IOException
{
// bloom filter
FileOutputStream fos = new FileOutputStream(filterFilename());
DataOutputStream stream = new DataOutputStream(fos);
BloomFilter.serializer().serialize(bf, stream);
stream.flush();
fos.getFD().sync();
stream.close();
// index
indexFile.getChannel().force(true);
indexFile.close();
// main data
dataFile.close(); // calls force
rename(indexFilename());
rename(filterFilename());
path = rename(path); // important to do this last since index & filter file names are derived from it
indexSummary.complete();
return new SSTableReader(path, partitioner, indexSummary, bf);
}
static String rename(String tmpFilename)
{
String filename = tmpFilename.replace("-" + SSTable.TEMPFILE_MARKER, "");
try
{
FBUtilities.renameWithConfirm(tmpFilename, filename);
}
catch (IOException e)
{
throw new IOError(e);
}
return filename;
}
public long getFilePointer()
{
return dataFile.getFilePointer();
}
public static SSTableReader renameAndOpen(String dataFileName) throws IOException
{
SSTableWriter.rename(indexFilename(dataFileName));
SSTableWriter.rename(filterFilename(dataFileName));
dataFileName = SSTableWriter.rename(dataFileName);
return SSTableReader.open(dataFileName);
}
}