All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bboxdb.storage.sstable.SSTableWriter Maven / Gradle / Ivy

/*******************************************************************************
 *
 *    Copyright (C) 2015-2018 the BBoxDB project
 *  
 *    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 org.bboxdb.storage.sstable;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.List;

import org.bboxdb.commons.io.DataEncoderHelper;
import org.bboxdb.storage.BloomFilterBuilder;
import org.bboxdb.storage.StorageManagerException;
import org.bboxdb.storage.entity.Tuple;
import org.bboxdb.storage.entity.TupleStoreMetaData;
import org.bboxdb.storage.entity.TupleStoreName;
import org.bboxdb.storage.sstable.spatialindex.SpatialIndexBuilder;
import org.bboxdb.storage.sstable.spatialindex.SpatialIndexBuilderFactory;
import org.bboxdb.storage.sstable.spatialindex.SpatialIndexEntry;
import org.bboxdb.storage.util.TupleHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.hash.BloomFilter;
import com.google.common.io.CountingOutputStream;

import io.prometheus.client.Counter;

public class SSTableWriter implements AutoCloseable {
	
	/**
	 * The number of the table
	 */
	private final int tablenumber;
	
	/**
	 * The name of the table
	 */
	private final TupleStoreName name;
	
	/**
	 * The directory for the SSTables
	 */
	private final String directory;
	
	/**
	 * SSTable output stream
	 */
	private CountingOutputStream sstableOutputStream;
	
	/**
	 * SSTable index stream
	 */
	private OutputStream sstableIndexOutputStream;
	
	/**
	 * The SSTable file object
	 */
	private File sstableFile;
	
	/**
	 * The SSTable index file object
	 */
	private File sstableIndexFile;
	
	/**
	 * The bloom filter file
	 */
	private File sstableBloomFilterFile;
	
	/**
	 * The spatial index file
	 */
	private File spatialIndexFile;
	
	/**
	 * The meta data file
	 */
	private File metadatafile;
	
	/**
	 * A counter for the written tuples
	 */
	private final SSTableMetadataBuilder metadataBuilder;
	
	/**
	 * The bloom filter
	 */
	private final BloomFilter bloomFilter;
	
	/**
	 * The spatial index
	 */
	private final SpatialIndexBuilder spatialIndex;
	
	/**
	 * The error flag
	 */
	private boolean exceptionDuringWrite;

	/**
	 * The amount of written tuple bytes
	 */
	private final static Counter writtenTuplesBytes = Counter.build()
			.name("bboxdb_written_tuple_bytes")
			.help("Written tuple bytes")
			.register();
	
	/**
	 * The amount of written tuples
	 */
	private final static Counter writtenTuplesTotal = Counter.build()
			.name("bboxdb_written_tuple_total")
			.help("Written tuples total")
			.register();
	
	/**
	 * The Logger
	 */
	private final static Logger logger = LoggerFactory.getLogger(SSTableWriter.class);
	
	public SSTableWriter(final String directory, final TupleStoreName name, 
			final int tablenumber, final long estimatedNumberOfTuples) {
		
		this.directory = directory;
		this.name = name;
		this.tablenumber = tablenumber;		
		this.metadataBuilder = new SSTableMetadataBuilder();
		this.exceptionDuringWrite = false;
		
		// Bloom Filter
		final String sstableBloomFilterFilename = SSTableHelper.getSSTableBloomFilterFilename(directory, name, tablenumber);
		this.sstableBloomFilterFile = new File(sstableBloomFilterFilename);
		this.bloomFilter = BloomFilterBuilder.buildBloomFilter(estimatedNumberOfTuples);
		
		// Spatial index
		final String spatialIndexFilename =  SSTableHelper.getSSTableSpatialIndexFilename(directory, name, tablenumber);
		this.spatialIndexFile = new File(spatialIndexFilename);
		this.spatialIndex = SpatialIndexBuilderFactory.getInstance();
		
		// Metadata
		final String ssTableMetadataFilename = SSTableHelper.getSSTableMetadataFilename(directory, name, tablenumber);
		this.metadatafile = new File(ssTableMetadataFilename);
	}
	
	/**
	 * Open all required files
	 * @throws StorageManagerException
	 */
	public void open() throws StorageManagerException {
		final String directoryName = SSTableHelper.getSSTableDir(directory, name);
		final File directoryHandle = new File(directoryName);
		
		if(! directoryHandle.isDirectory()) {
			final String error = "Directory for SSTable " + name + " does not exist: " + directoryName;
			logger.error(error);
			throw new StorageManagerException(error);
		}
		
		final String sstableOutputFileName = SSTableHelper.getSSTableFilename(directory, name, tablenumber);
		sstableFile = new File(sstableOutputFileName);
		
		final String outputIndexFileName = SSTableHelper.getSSTableIndexFilename(directory, name, tablenumber);
		sstableIndexFile = new File(outputIndexFileName);
		
		// Don't overwrite old data
		if(sstableFile.exists()) {
			throw new StorageManagerException("Table file already exists: " + sstableOutputFileName);
		}
		
		if(sstableIndexFile.exists()) {
			throw new StorageManagerException("Table file already exists: " + sstableIndexFile);
		}
		
		if(sstableBloomFilterFile.exists()) {
			throw new StorageManagerException("Bloom filter file already exists: " + sstableBloomFilterFile);
		}
		
		try {
			logger.info("Writing new SSTable for relation: {} file: {}", name.getFullname(), sstableOutputFileName);
			final BufferedOutputStream sstableFileOutputStream = new BufferedOutputStream(new FileOutputStream(sstableFile));
			sstableOutputStream = new CountingOutputStream(sstableFileOutputStream);
			sstableOutputStream.write(SSTableConst.MAGIC_BYTES_SSTABLE);
			
			sstableIndexOutputStream = new BufferedOutputStream(new FileOutputStream(sstableIndexFile));
			sstableIndexOutputStream.write(SSTableConst.MAGIC_BYTES_INDEX);
		} catch (FileNotFoundException e) {
			exceptionDuringWrite = true;
			throw new StorageManagerException("Unable to open output file", e);
		} catch (IOException e) {
			exceptionDuringWrite = true;
			throw new StorageManagerException("Unable to write into output file", e);
		}
	}
	
	/**
	 * Close all open file handles and write the meta data
	 */
	public void close() throws StorageManagerException {
		try {			
			logger.debug("Closing new written SSTable for relation: {} number {}. File: {} ", 
					name.getFullname(), tablenumber, sstableFile.getName());

			if(sstableOutputStream != null) {
				sstableOutputStream.close();
				sstableOutputStream = null;
			}
			
			if(sstableIndexOutputStream != null) {
				sstableIndexOutputStream.close();
				sstableIndexOutputStream = null;
			}
			
			writeSpatialIndex();
			writeBloomFilter();
			writeMetadata();
			
		} catch (IOException e) {
			exceptionDuringWrite = true;
			throw new StorageManagerException("Exception while closing streams", e);
		} finally {
			checkForWriteException();
		}
	}

	/**
	 *  Delete half written files if an exception has occurred
	 *  The variable exceptionDuringWrite is set to true in every catch block
	 */
	private void checkForWriteException() {
		if(exceptionDuringWrite == true) {
			deleteFromDisk();
		}
	}
	
	/**
	 * Delete written data from disk
	 */
	public void deleteFromDisk() {
		if(sstableFile != null && sstableFile.exists()) {
			sstableFile.delete();
		}
		
		if(sstableIndexFile != null && sstableIndexFile.exists()) {
			sstableIndexFile.delete();
		}
		
		if(sstableBloomFilterFile != null && sstableBloomFilterFile.exists()) {
			sstableBloomFilterFile.delete();
		}
		
		if(spatialIndexFile != null && spatialIndexFile.exists()) {
			spatialIndexFile.delete();
		}
		
		if(metadatafile != null && metadatafile.exists()) {
			metadatafile.delete();
		}
	}
	
	/**
	 * Write the spatial index to file
	 * @throws IOException
	 * @throws StorageManagerException 
	 */
	private void writeSpatialIndex() throws IOException, StorageManagerException {
		try (   
				final RandomAccessFile file = new RandomAccessFile(spatialIndexFile, "rw" );
			) {
			spatialIndex.writeToFile(file);
			file.close();
		}
	}
	
	/**
	 * Write the bloom filter into the filter file
	 * @throws IOException
	 */
	private void writeBloomFilter() throws IOException {
		
		try (   final FileOutputStream fos = new FileOutputStream(sstableBloomFilterFile);
				final OutputStream outputStream = new BufferedOutputStream(fos);
			) {
			
			bloomFilter.writeTo(outputStream);
			outputStream.close();
		}
	}
	
	/**
	 * Write the meta data to yaml info file
	 * @throws IOException
	 */
	private void writeMetadata() throws IOException {
		final TupleStoreMetaData metadata = metadataBuilder.getMetaData();
		metadata.exportToYamlFile(metadatafile);
	}
	
	/**
	 * Add the list of tuples to the sstable
	 * @param tuples
	 * @throws StorageManagerException
	 */
	public void addData(final List tuples) throws StorageManagerException {
		if(sstableOutputStream == null) {
			final String error = "Trying to add a memtable to a non ready SSTable writer";
			logger.error(error);
			throw new StorageManagerException(error);
		}

		try {
			for(final Tuple tuple : tuples) {
				addNextTuple(tuple);
			}
		} catch(StorageManagerException e) {
			exceptionDuringWrite = true;
			throw e;
		}
	}

	/**
	 * Set the error flag
	 */
	@VisibleForTesting
	public void setErrorFlag() {
		exceptionDuringWrite = true;
	}
	
	/**
	 * Add the next tuple into the result sstable
	 * @param tuple
	 * @throws IOException
	 * @throws StorageManagerException 
	 */
	public void addNextTuple(final Tuple tuple) throws StorageManagerException {
		try {
			// Add Tuple to the index
			final int tuplePosition = (int) sstableOutputStream.getCount();
			writeIndexEntry(tuplePosition);
			
			final int newPosition = (int) sstableOutputStream.getCount();
			final int writtenBytes = newPosition - tuplePosition;
			
			// Add Tuple to the SSTable file
			TupleHelper.writeTupleToStream(tuple, sstableOutputStream);
			metadataBuilder.addTuple(tuple);
			
			// Add tuple to the bloom filter
			bloomFilter.put(tuple.getKey());
			
			// Add tuple to the spatial index
			final SpatialIndexEntry sIndexentry 
				= new SpatialIndexEntry(tuple.getBoundingBox(), tuplePosition);
			spatialIndex.insert(sIndexentry);

			writtenTuplesTotal.inc();
			writtenTuplesBytes.inc(writtenBytes);
		} catch (IOException e) {
			exceptionDuringWrite = true;
			throw new StorageManagerException("Unable to write tuple to SSTable", e);
		}
	}

	/** 
	 * Append an entry to the index file.
	 * 
	 * Format of the index file:
	 * 
	 * -------------------------------------------------
	 * | Tuple-Position | Tuple-Position |  .........  |
 	 * |     4 Byte     |     4 Byte     |  .........  |
	 * -------------------------------------------------
	 * 
	 * @param keyLengthBytes
	 * @param keyPosition
	 * @throws IOException
	 */
	private void writeIndexEntry(final int tuplePosition) throws IOException {
		final ByteBuffer tuplePositionBytes = DataEncoderHelper.intToByteBuffer(tuplePosition);
		sstableIndexOutputStream.write(tuplePositionBytes.array());
	}

	/**
	 * Get the sstable output file
	 * @return
	 */
	public File getSstableFile() {
		return sstableFile;
	}

	/**
	 * Get the sstable index output file
	 * @return
	 */
	public File getSstableIndexFile() {
		return sstableIndexFile;
	}
	
	/**
	 * Does an exception during a write operation occurred?
	 * @return
	 */
	public boolean isErrorFlagSet() {
		return exceptionDuringWrite;
	}
	
	/**
	 * Get the already written bytes for this SSTable
	 * @return
	 */
	public long getWrittenBytes() {
		return sstableOutputStream.getCount();
	}
	
	/**
	 * Get the sstable name
	 */
	public TupleStoreName getName() {
		return name;
	}
	
	/**
	 * Get the tablenumber
	 * @return
	 */
	public int getTablenumber() {
		return tablenumber;
	}
	
	/**
	 * Get the directory
	 * @return
	 */
	public String getDirectory() {
		return directory;
	}
	
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy