/**
* 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 com.facebook.infrastructure.config;
import com.facebook.infrastructure.db.ColumnFamily;
import com.facebook.infrastructure.db.FileUtils;
import com.facebook.infrastructure.utils.XMLUtils;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import java.io.File;
import java.io.IOException;
import java.util.*;
/**
* Author : Avinash Lakshman ( alakshman@facebook.com) & Prashant Malik ( pmalik@facebook.com )
*/
public class DatabaseDescriptor
{
private static int storagePort_ = 7000;
private static int controlPort_ = 7001;
private static int httpPort_ = 7002;
private static String clusterName_ = "Test";
private static int replicationFactor_ = 3;
private static long rpcTimeoutInMillis_ = 2000;
private static Set<String> seeds_ = new HashSet<String>();
private static String multicastAddr_ = "230.0.0.1";
private static String metadataDirectory_;
/* Keeps the list of Ganglia servers to contact */
private static String[] gangliaServers_ ;
/* Keeps the list of data file directories */
private static String[] dataFileDirectories_;
/* Current index into the above list of directories */
private static int currentIndex_ = 0;
private static String stagingFileDirectory_;
private static String logFileDirectory_;
private static String bootstrapFileDirectory_;
private static int logRotationThreshold_ = 128*1024*1024;
private static boolean fastSync_ = false;
private static boolean rackAware_ = false;
private static int threadsPerPool_ = 4;
private static List<String> tables_ = new ArrayList<String>();
private static Set<String> applicationColumnFamilies_ = new HashSet<String>();
private static Map<String, String> cfToColumnTypeMap_ = new HashMap<String, String>();
private static Map<String, String> cfToIndexPropertyMap_ = new HashMap<String, String>();
/* if the size of columns or super-columns are more than this, indexing will kick in */
private static int columnIndexSizeInKB_;
/* Size of touch key cache */
private static int touchKeyCacheSize_ = 1024;
/* Number of hours to keep a memtable in memory */
private static int memtableLifetime_ = 6;
/* Address of ZooKeeper cell */
private static String zkAddress_;
/* Zookeeper session timeout. */
private static int zkSessionTimeout_ = 30000;
/* Should cardinality be altered. */
private static boolean isCardinalityAltered_ = true;
/* The compaction parameter decides if we should deserialize each key and then serialize it to fix issues.
* true - we only deserialize if the key needs to be resolved
* false - we always desrialize the key.
* */
private static boolean aggressiveCompaction_ = false;
public static final Map<String, Set<String>> tableToColumnFamilyMap_ = new HashMap<String, Set<String>>();
private DatabaseDescriptor() {}
static {
try {
/* Read the configuration file to retrieve DB related properties. */
String file = System.getProperty("storage-config") + System.getProperty("file.separator") + "storage-conf.xml";
String os = System.getProperty("os.name");
XMLUtils xmlUtils = new XMLUtils(file);
Node rootNode = xmlUtils.getRequestedNode("/Storage");
/* Cluster Name */
clusterName_ = xmlUtils.getNodeValue(rootNode, "ClusterName");
/* Multicast channel */
multicastAddr_ = xmlUtils.getNodeValue(rootNode, "MulticastChannel");
/* Ganglia servers contact list */
gangliaServers_ = xmlUtils.getNodeValues(rootNode, "GangliaServers/GangliaServer");
/* ZooKeeper's address */
zkAddress_ = xmlUtils.getNodeValue(rootNode, "ZookeeperAddress");
/* Zookeeper's session timeout */
String zkSessionTimeout = xmlUtils.getNodeValue(rootNode, "ZookeeperSessionTimeout");
if ( zkSessionTimeout != null )
zkSessionTimeout_ = Integer.parseInt(zkSessionTimeout);
/* Data replication factor */
String replicationFactor = xmlUtils.getNodeValue(rootNode, "ReplicationFactor");
if ( replicationFactor != null )
replicationFactor_ = Integer.parseInt(replicationFactor);
/* RPC Timeout */
String rpcTimeoutInMillis = xmlUtils.getNodeValue(rootNode, "RpcTimeoutInMillis");
if ( rpcTimeoutInMillis != null )
rpcTimeoutInMillis_ = Integer.parseInt(rpcTimeoutInMillis);
/* Thread per pool */
String threadsPerPool = xmlUtils.getNodeValue(rootNode, "ThreadsPerPool");
if ( threadsPerPool != null )
threadsPerPool_ = Integer.parseInt(threadsPerPool);
/* TCP port on which the storage system listens */
String port = xmlUtils.getNodeValue(rootNode, "StoragePort");
if ( port != null )
storagePort_ = Integer.parseInt(port);
/* UDP port for control messages */
port = xmlUtils.getNodeValue(rootNode, "ControlPort");
if ( port != null )
controlPort_ = Integer.parseInt(port);
/* HTTP port for HTTP messages */
port = xmlUtils.getNodeValue(rootNode, "HttpPort");
if ( port != null )
httpPort_ = Integer.parseInt(port);
/* Touch Key Cache Size */
String touchKeyCacheSize = xmlUtils.getNodeValue(rootNode, "TouchKeyCacheSize");
if ( touchKeyCacheSize != null )
touchKeyCacheSize_ = Integer.parseInt(touchKeyCacheSize);
/* Number of days to keep the memtable around w/o flushing */
String lifetime = xmlUtils.getNodeValue(rootNode, "MemtableLifetimeInDays");
if ( lifetime != null )
memtableLifetime_ = Integer.parseInt(lifetime);
/* read the size at which we should do column indexes */
String columnIndexSizeInKB = xmlUtils.getNodeValue(rootNode, "ColumnIndexSizeInKB");
if(columnIndexSizeInKB == null)
{
columnIndexSizeInKB_ = 64;
}
else
{
columnIndexSizeInKB_ = Integer.parseInt(columnIndexSizeInKB);
}
/* metadata directory */
metadataDirectory_ = xmlUtils.getNodeValue(rootNode, "MetadataDirectory");
if ( metadataDirectory_ != null )
FileUtils.createDirectory(metadataDirectory_);
else
{
if ( os.equals("Linux") )
{
metadataDirectory_ = "/var/storage/system";
}
}
/* data file directory */
dataFileDirectories_ = xmlUtils.getNodeValues(rootNode, "DataFileDirectories/DataFileDirectory");
if ( dataFileDirectories_.length > 0 )
{
for ( String dataFileDirectory : dataFileDirectories_ )
FileUtils.createDirectory(dataFileDirectory);
}
else
{
if ( os.equals("Linux") )
{
dataFileDirectories_ = new String[]{"/var/storage/data"};
}
}
/* bootstrap file directory */
bootstrapFileDirectory_ = xmlUtils.getNodeValue(rootNode, "BootstrapFileDirectory");
if ( bootstrapFileDirectory_ != null )
FileUtils.createDirectory(bootstrapFileDirectory_);
else
{
if ( os.equals("Linux") )
{
bootstrapFileDirectory_ = "/var/storage/bootstrap";
}
}
/* bootstrap file directory */
stagingFileDirectory_ = xmlUtils.getNodeValue(rootNode, "StagingFileDirectory");
if ( stagingFileDirectory_ != null )
FileUtils.createDirectory(stagingFileDirectory_);
else
{
if ( os.equals("Linux") )
{
stagingFileDirectory_ = "/var/storage/staging";
}
}
/* commit log directory */
logFileDirectory_ = xmlUtils.getNodeValue(rootNode, "CommitLogDirectory");
if ( logFileDirectory_ != null )
FileUtils.createDirectory(logFileDirectory_);
else
{
if ( os.equals("Linux") )
{
logFileDirectory_ = "/var/storage/commitlog";
}
}
/* threshold after which commit log should be rotated. */
String value = xmlUtils.getNodeValue(rootNode, "CommitLogRotationThresholdInMB");
if ( value != null)
logRotationThreshold_ = Integer.parseInt(value) * 1024 * 1024;
/* fast sync option */
value = xmlUtils.getNodeValue(rootNode, "CommitLogFastSync");
if ( value != null )
fastSync_ = Boolean.parseBoolean(value);
/* Rack Aware option */
value = xmlUtils.getNodeValue(rootNode, "RackAware");
if ( value != null )
rackAware_ = Boolean.parseBoolean(value);
/* Read the table related stuff from config */
NodeList tables = xmlUtils.getRequestedNodeList(rootNode, "/Storage/Tables/Table");
int size = tables.getLength();
for ( int i = 0; i < size; ++i )
{
Node table = tables.item(i);
/* parsing out the table name */
String tName = xmlUtils.getAttributeValue(table, "Name");
tables_.add(tName);
tableToColumnFamilyMap_.put(tName, new HashSet<String>());
NodeList columnFamilies = xmlUtils.getRequestedNodeList(table, "ColumnFamily");
int size2 = columnFamilies.getLength();
for ( int j = 0; j < size2; ++j )
{
Node columnFamily = columnFamilies.item(j);
String cName = columnFamily.getChildNodes().item(0).getNodeValue();
/* squirrel away the application column families */
applicationColumnFamilies_.add(cName);
/* Parse out the column type */
String columnType = xmlUtils.getAttributeValue(columnFamily, "ColumnType");
columnType = ColumnFamily.getColumnType(columnType);
cfToColumnTypeMap_.put(cName, columnType);
/* Parse out the column family index property */
String columnIndexProperty = xmlUtils.getAttributeValue(columnFamily, "Index");
String columnIndexType = ColumnFamily.getColumnIndexProperty(columnIndexProperty);
cfToIndexPropertyMap_.put(cName, columnIndexType);
tableToColumnFamilyMap_.get(tName).add(cName);
}
}
/* Load the seeds for node contact points */
String[] seeds = xmlUtils.getNodeValues(rootNode, "Seeds/Seed");
for( int i = 0; i < seeds.length; ++i )
{
seeds_.add( seeds[i] );
}
} catch (IOException e) {
throw new RuntimeException(e);
} catch (ParserConfigurationException e) {
throw new RuntimeException(e);
} catch (SAXException e) {
throw new RuntimeException(e);
} catch (TransformerException e) {
throw new RuntimeException(e);
}
}
public static String getZkAddress()
{
return zkAddress_;
}
public static int getZkSessionTimeout()
{
return zkSessionTimeout_;
}
public static String getMulticastChannel()
{
return multicastAddr_;
}
public static int getColumnIndexSize()
{
return columnIndexSizeInKB_ * 1024;
}
public static boolean isAlterCardinality()
{
return isCardinalityAltered_;
}
public static int getMemtableLifetime()
{
return memtableLifetime_;
}
public static String getClusterName()
{
return clusterName_;
}
public static boolean isApplicationColumnFamily(String columnFamily)
{
return applicationColumnFamilies_.contains(columnFamily);
}
public static int getTouchKeyCacheSize()
{
return touchKeyCacheSize_;
}
public static String getGangliaServers()
{
StringBuilder sb = new StringBuilder();
for ( int i = 0; i < gangliaServers_.length; ++i )
{
sb.append(gangliaServers_[i]);
if ( i != (gangliaServers_.length - 1) )
sb.append(", ");
}
return sb.toString();
}
public static String getColumnType(String cfName)
{
return cfToColumnTypeMap_.get(cfName);
}
public static boolean isNameIndexEnabled(String cfName)
{
return "Name".equals(cfToIndexPropertyMap_.get(cfName));
}
public static List<String> getTables()
{
return tables_;
}
// todo ultimately we want to support multiple tables; until then, this makes code assuming only one
// a little cleaner
public static String getTableName() {
return tables_.get(0);
}
public static void setTables(String table)
{
tables_.add(table);
}
public static int getStoragePort()
{
return storagePort_;
}
public static int getControlPort()
{
return controlPort_;
}
public static int getHttpPort()
{
return httpPort_;
}
public static int getReplicationFactor()
{
return replicationFactor_;
}
public static long getRpcTimeout()
{
return rpcTimeoutInMillis_;
}
public static int getThreadsPerPool()
{
return threadsPerPool_;
}
public static String getMetadataDirectory()
{
return metadataDirectory_;
}
public static void setMetadataDirectory(String metadataDirectory)
{
metadataDirectory_ = metadataDirectory_;
}
public static String[] getAllDataFileLocations()
{
return dataFileDirectories_;
}
public static String getDataFileLocation()
{
String dataFileDirectory = dataFileDirectories_[currentIndex_];
return dataFileDirectory;
}
public static String getCompactionFileLocation()
{
String dataFileDirectory = dataFileDirectories_[currentIndex_];
currentIndex_ = (currentIndex_ + 1 )%dataFileDirectories_.length ;
return dataFileDirectory;
}
public static String getBootstrapFileLocation()
{
return bootstrapFileDirectory_;
}
public static void setBootstrapFileLocation(String bfLocation)
{
bootstrapFileDirectory_ = bfLocation;
}
public static String getStagingFileLocation()
{
return stagingFileDirectory_;
}
public static void setStagingFileLocation(String stagingLocation)
{
stagingFileDirectory_ = stagingLocation;
}
public static int getLogFileSizeThreshold()
{
return logRotationThreshold_;
}
public static String getLogFileLocation()
{
return logFileDirectory_;
}
public static void setLogFileLocation(String logLocation)
{
logFileDirectory_ = logLocation;
}
public static boolean isFastSync()
{
return fastSync_;
}
public static boolean isRackAware()
{
return rackAware_;
}
public static Set<String> getSeeds()
{
return seeds_;
}
public static String getColumnFamilyType(String cfName)
{
return cfToColumnTypeMap_.get(cfName);
}
public static void setCompactionFactor(boolean compactionFactor)
{
aggressiveCompaction_ = compactionFactor;
}
public static boolean getCompactionFactor()
{
return aggressiveCompaction_;
}
public static Map<String, Set<String>> getTableToColumnFamilyMap() {
return tableToColumnFamilyMap_;
}
/*
* Loop through all the disks to see which disk has the max free space
* return the disk with max free space for compactions. If the size of the expected
* compacted file is greater than the max disk space available return null, we cannot
* do compaction in this case.
*/
public static String getCompactionFileLocation(long expectedCompactedFileSize)
{
long maxFreeDisk = 0;
int maxDiskIndex = 0;
String dataFileDirectory = null;
for ( int i = 0 ; i < dataFileDirectories_.length ; i++ )
{
File f = new File(dataFileDirectories_[i]);
if( maxFreeDisk < f.getUsableSpace())
{
maxFreeDisk = f.getUsableSpace();
maxDiskIndex = i;
}
}
// Load factor of 0.9 we do not want to use the entire disk that is too risky.
maxFreeDisk = (long)(0.9 * maxFreeDisk);
if( expectedCompactedFileSize < maxFreeDisk )
{
dataFileDirectory = dataFileDirectories_[maxDiskIndex];
currentIndex_ = (maxDiskIndex + 1 )%dataFileDirectories_.length ;
}
else
{
currentIndex_ = maxDiskIndex;
}
return dataFileDirectory;
}
}