Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.voltdb.export.ExportManager Maven / Gradle / Ivy
/* This file is part of VoltDB.
* Copyright (C) 2008-2018 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with VoltDB. If not, see .
*/
package org.voltdb.export;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import org.voltcore.logging.Level;
import org.voltcore.logging.VoltLogger;
import org.voltcore.messaging.HostMessenger;
import org.voltcore.utils.DBBPool;
import org.voltcore.utils.Pair;
import org.voltdb.CatalogContext;
import org.voltdb.ExportStatsBase;
import org.voltdb.ExportStatsBase.ExportStatsRow;
import org.voltdb.RealVoltDB;
import org.voltdb.StatsSelector;
import org.voltdb.VoltDB;
import org.voltdb.VoltTable;
import org.voltdb.catalog.CatalogMap;
import org.voltdb.catalog.Cluster;
import org.voltdb.catalog.Connector;
import org.voltdb.catalog.ConnectorProperty;
import org.voltdb.catalog.ConnectorTableInfo;
import org.voltdb.catalog.Database;
import org.voltdb.sysprocs.ExportControl.OperationMode;
import org.voltdb.utils.LogKeys;
import org.voltdb.utils.VoltFile;
import com.google_voltpatches.common.base.Preconditions;
/**
* Bridges the connection to an OLAP system and the buffers passed
* between the OLAP connection and the execution engine. Each processor
* implements ExportDataProcessor interface. The processors are passed one
* or more ExportDataSources. The sources map, currently, 1:1 with Export
* enabled tables. The ExportDataSource has poll() and ack() methods that
* processors may use to pull and acknowledge as processed, EE Export data.
* Data passed to processors is wrapped in ExportDataBlocks which in turn
* wrap a BBContainer.
*
* Processors are loaded by reflection based on configuration in project.xml.
*/
public class ExportManager
{
/**
* the only supported processor class
*/
public static final String PROCESSOR_CLASS =
"org.voltdb.export.processors.GuestProcessor";
/**
* This is property used for checking Export clients for validation only.
*/
public final static String CONFIG_CHECK_ONLY = "__voltdb_config_check_only__";
/**
* Processors also log using this facility.
*/
private static final VoltLogger exportLog = new VoltLogger("EXPORT");
private final AtomicReference m_generation = new AtomicReference<>(null);
private final HostMessenger m_messenger;
/**
* Set of partition ids for which this export manager instance is master of
*/
private final Set m_masterOfPartitions = new HashSet();
/**
* Master sends RELEASE_BUFFER to all its replicas to discard buffer.
*/
public static final byte RELEASE_BUFFER = 1;
/**
* Master sends GIVE_MASTERSHIP to one replica to transfer leadership.
*/
public static final byte GIVE_MASTERSHIP = 2;
/**
* Master sends GAP_QUERY to all nodes to know: can you cover the next sequence number?
*
* This is called when master hits gap in the stream.
*/
public static final byte GAP_QUERY = 3;
/**
* Node that receives GAP_QUERY sends back QUERY_RESPONSE with the information that whether
* it has data for the next sequence number.
*/
public static final byte QUERY_RESPONSE = 4;
/**
* Data sources under new SPI or SPI who receives failed host notification
* sends TASK_MASTERSHIP to all nodes to ask master to transfer leadership back.
* If master doesn't exist promote itself to be master.
*/
public static final byte TAKE_MASTERSHIP = 5;
/**
* Node that receives TAKE_MASTERSHIP sends back TAKE_MASTERSHIP_RESPONSE to indicate
* it's not master.
*/
public static final byte TAKE_MASTERSHIP_RESPONSE = 6;
/**
* Thrown if the initial setup of the loader fails
*/
public static class SetupException extends Exception {
private static final long serialVersionUID = 1L;
SetupException(final String msg) {
super(msg);
}
SetupException(final Throwable cause) {
super(cause);
}
}
/**
* Connections OLAP loaders. Currently at most one loader allowed.
* Supporting multiple loaders mainly involves reference counting
* the EE data blocks and bookkeeping ACKs from processors.
*/
AtomicReference m_processor = new AtomicReference();
/** Obtain the global ExportManager via its instance() method */
private static ExportManager m_self;
private ExportStats m_exportStats;
private final int m_hostId;
// this used to be flexible, but no longer - now m_loaderClass is just null or default value
public static final String DEFAULT_LOADER_CLASS = "org.voltdb.export.processors.GuestProcessor";
private final String m_loaderClass = DEFAULT_LOADER_CLASS;
private volatile Map>> m_processorConfig = new HashMap<>();
private int m_exportTablesCount = 0;
private int m_connCount = 0;
private boolean m_startPolling = false;
public class ExportStats extends ExportStatsBase {
List m_stats;
ExportStats() {
super();
}
@Override
public Iterator getStatsRowKeyIterator(boolean interval) {
m_stats = getStats(interval);
return buildIterator();
}
private Iterator buildIterator() {
return new Iterator() {
int index = 0;
@Override
public boolean hasNext() {
return index < m_stats.size();
}
@Override
public Object next() {
if (index < m_stats.size()) {
return index++;
}
throw new NoSuchElementException();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
@Override
protected void updateStatsRow(Object rowKey, Object rowValues[]) {
super.updateStatsRow(rowKey, rowValues);
int rowIndex = (Integer) rowKey;
assert (rowIndex >= 0);
assert (rowIndex < m_stats.size());
ExportStatsRow stat = m_stats.get(rowIndex);
rowValues[columnNameToIndex.get(Columns.SITE_ID)] = stat.m_siteId;
rowValues[columnNameToIndex.get(Columns.PARTITION_ID)] = stat.m_partitionId;
rowValues[columnNameToIndex.get(Columns.SOURCE_NAME)] = stat.m_sourceName;
rowValues[columnNameToIndex.get(Columns.EXPORT_TARGET)] = stat.m_exportTarget;
rowValues[columnNameToIndex.get(Columns.ACTIVE)] = stat.m_exportingRole;
rowValues[columnNameToIndex.get(Columns.TUPLE_COUNT)] = stat.m_tupleCount;
rowValues[columnNameToIndex.get(Columns.TUPLE_PENDING)] = stat.m_tuplesPending;
rowValues[columnNameToIndex.get(Columns.LAST_QUEUED_TIMESTAMP)] = stat.m_lastQueuedTimestamp;
rowValues[columnNameToIndex.get(Columns.LAST_ACKED_TIMESTAMP)] = stat.m_lastAckedTimestamp;
rowValues[columnNameToIndex.get(Columns.AVERAGE_LATENCY)] = stat.m_averageLatency;
rowValues[columnNameToIndex.get(Columns.MAX_LATENCY)] = stat.m_maxLatency;
rowValues[columnNameToIndex.get(Columns.QUEUE_GAP)] = stat.m_queueGap;
rowValues[columnNameToIndex.get(Columns.STATUS)] = stat.m_status;
}
public ExportStatsRow getStatsRow(Object rowKey) {
int rowIndex = (Integer) rowKey;
assert (rowIndex >= 0);
assert (rowIndex < m_stats.size());
ExportStatsRow stat = m_stats.get(rowIndex);
return stat;
}
}
/**
* Construct ExportManager using catalog.
* @param myHostId
* @param catalogContext
* @throws ExportManager.SetupException
*/
// FIXME - this synchronizes on the ExportManager class, but everyone else synchronizes on the instance.
public static synchronized void initialize(
int myHostId,
CatalogContext catalogContext,
boolean isRejoin,
boolean forceCreate,
HostMessenger messenger,
List> partitions)
throws ExportManager.SetupException
{
ExportManager em = new ExportManager(myHostId, catalogContext, messenger);
m_self = em;
if (forceCreate) {
em.clearOverflowData();
}
em.initialize(catalogContext, partitions, isRejoin);
RealVoltDB db=(RealVoltDB)VoltDB.instance();
db.getStatsAgent().registerStatsSource(StatsSelector.EXPORT,
myHostId, // m_siteId,
em.getExportStats());
}
private CatalogMap getConnectors(CatalogContext catalogContext) {
final Cluster cluster = catalogContext.catalog.getClusters().get("cluster");
final Database db = cluster.getDatabases().get("database");
return db.getConnectors();
}
private boolean hasEnabledConnectors(CatalogMap connectors) {
for (Connector conn : connectors) {
if (conn.getEnabled() && !conn.getTableinfo().isEmpty()) {
return true;
}
}
return false;
}
/**
* Indicate to associated {@link ExportGeneration}s to become
* masters for the given partition id
* @param partitionId
*/
synchronized public void takeMastership(int partitionId) {
m_masterOfPartitions.add(partitionId);
ExportGeneration generation = m_generation.get();
if (generation == null) {
return;
}
generation.takeMastership(partitionId);
}
/**
* Indicate local partition became the SPI Leader
* still waiting for old leader (ack) to trigger take over mastership
* @param partitionId
*/
synchronized public void prepareAcceptMastership(int partitionId) {
// can't acquire mastership twice for the same partition id
if (!m_masterOfPartitions.add(partitionId)) {
return;
}
if (exportLog.isDebugEnabled()) {
exportLog.debug("Export streams on local partition " + partitionId + " will become master.");
}
}
/**
* Indicate to associated {@link ExportGeneration}s to
* prepare give up mastership for the given partition id to hostId
* @param partitionId
*/
synchronized public void prepareTransferMastership(int partitionId, int hostId) {
// remove mastership for partition id, so when failure happen during the mastership transfer
// this node can be elected as new master again.
m_masterOfPartitions.remove(partitionId);
if (exportLog.isDebugEnabled()) {
exportLog.debug("Export stream masters on " + partitionId + " are going to migrate away");
}
ExportGeneration generation = m_generation.get();
if (generation == null) {
return;
}
generation.prepareTransferMastership(partitionId, hostId);
}
/**
* Get the global instance of the ExportManager.
* @return The global single instance of the ExportManager.
*/
public static ExportManager instance() {
return m_self;
}
public static void setInstanceForTest(ExportManager self) {
m_self = self;
}
protected ExportManager() {
m_hostId = 0;
m_messenger = null;
}
/**
* Read the catalog to setup manager and loader(s)
*/
private ExportManager(
int myHostId,
CatalogContext catalogContext,
HostMessenger messenger)
throws ExportManager.SetupException
{
m_hostId = myHostId;
m_messenger = messenger;
m_exportStats = new ExportStats();
boolean compress = !Boolean.getBoolean(StreamBlockQueue.EXPORT_DISABLE_COMPRESSION_OPTION);
exportLog.info("Export has compression "
+ (compress ? "enabled" : "disabled") + " in " + VoltDB.instance().getExportOverflowPath());
CatalogMap connectors = getConnectors(catalogContext);
if (!hasEnabledConnectors(connectors)) {
exportLog.info("System is not using any export functionality or connectors configured are disabled.");
return;
}
updateProcessorConfig(connectors);
exportLog.info(String.format("Export is enabled and can overflow to %s.", VoltDB.instance().getExportOverflowPath()));
}
public HostMessenger getHostMessenger() {
return m_messenger;
}
private void clearOverflowData() throws ExportManager.SetupException {
String overflowDir = VoltDB.instance().getExportOverflowPath();
try {
exportLog.info(
String.format("Cleaning out contents of export overflow directory %s for create with force", overflowDir));
VoltFile.recursivelyDelete(new File(overflowDir), false);
} catch(IOException e) {
String msg = String.format("Error cleaning out export overflow directory %s: %s",
overflowDir, e.getMessage());
if (exportLog.isDebugEnabled()) {
exportLog.debug(msg, e);
}
throw new ExportManager.SetupException(msg);
}
}
public synchronized void startPolling(CatalogContext catalogContext) {
m_startPolling = true;
CatalogMap connectors = getConnectors(catalogContext);
if(!hasEnabledConnectors(connectors)) {
exportLog.info("System is not using any export functionality or connectors configured are disabled.");
return;
}
ExportDataProcessor processor = m_processor.get();
Preconditions.checkState(processor != null, "guest processor is not set");
processor.startPolling();
}
private void updateProcessorConfig(final CatalogMap connectors) {
Map>> config = new HashMap<>();
// If the export source changes before the previous generation drains
// then the outstanding exports will go to the new source when export resumes.
int connCount = 0;
int tableCount = 0;
for (Connector conn : connectors) {
// skip disabled connectors
if (!conn.getEnabled() || conn.getTableinfo().isEmpty()) {
continue;
}
connCount++;
Properties properties = new Properties();
Set tables = new HashSet<>();
String targetName = conn.getTypeName();
for (ConnectorTableInfo ti : conn.getTableinfo()) {
tables.add(ti.getTable().getTypeName());
tableCount++;
}
if (conn.getConfig() != null) {
Iterator connPropIt = conn.getConfig().iterator();
while (connPropIt.hasNext()) {
ConnectorProperty prop = connPropIt.next();
properties.put(prop.getName(), prop.getValue().trim());
if (!prop.getName().toLowerCase().contains("password")) {
properties.put(prop.getName(), prop.getValue().trim());
} else {
//Dont trim passwords
properties.put(prop.getName(), prop.getValue());
}
}
}
Pair> connConfig = new Pair<>(properties, tables);
config.put(targetName, connConfig);
}
m_connCount = connCount;
m_exportTablesCount = tableCount;
m_processorConfig = config;
}
public int getExportTablesCount() {
return m_exportTablesCount;
}
public int getConnCount() {
return m_connCount;
}
/** Creates the initial export processor if export is enabled */
private void initialize(CatalogContext catalogContext, List> localPartitionsToSites,
boolean isRejoin) {
try {
CatalogMap connectors = getConnectors(catalogContext);
if (!hasEnabledConnectors(connectors)) {
return;
}
if (exportLog.isDebugEnabled()) {
exportLog.debug("Creating connector " + m_loaderClass);
}
ExportDataProcessor newProcessor = getNewProcessorWithProcessConfigSet(m_processorConfig);
m_processor.set(newProcessor);
File exportOverflowDirectory = new File(VoltDB.instance().getExportOverflowPath());
ExportGeneration generation = new ExportGeneration(exportOverflowDirectory);
generation.initialize(m_messenger, m_hostId, catalogContext,
connectors, localPartitionsToSites, exportOverflowDirectory);
m_generation.set(generation);
newProcessor.setExportGeneration(generation);
newProcessor.readyForData();
}
catch (final ClassNotFoundException e) {
exportLog.l7dlog( Level.ERROR, LogKeys.export_ExportManager_NoLoaderExtensions.name(), e);
throw new RuntimeException(e);
}
catch (final Exception e) {
exportLog.error("Initialize failed with:", e);
throw new RuntimeException(e);
}
}
public synchronized void updateCatalog(CatalogContext catalogContext, boolean requireCatalogDiffCmdsApplyToEE,
boolean requiresNewExportGeneration, List> localPartitionsToSites)
{
final Cluster cluster = catalogContext.catalog.getClusters().get("cluster");
final Database db = cluster.getDatabases().get("database");
final CatalogMap connectors = db.getConnectors();
Map>> processorConfigBeforeUpdate = m_processorConfig;
updateProcessorConfig(connectors);
if (m_processorConfig.isEmpty() && processorConfigBeforeUpdate.isEmpty()) {
return;
}
if (!requiresNewExportGeneration) {
exportLog.info("No stream related changes in update catalog.");
return;
}
/**
* This checks if the catalogUpdate was done in EE or not. If catalog update is skipped for @UpdateClasses and such
* EE does not roll to new generation and thus we need to ignore creating new generation roll with the current generation.
* If anything changes in getDiffCommandsForEE or design changes pay attention to fix this.
*/
if (requireCatalogDiffCmdsApplyToEE == false) {
exportLog.info("Skipped rolling generations as generation not created in EE.");
return;
}
if (m_generation.get() == null) {
File exportOverflowDirectory = new File(VoltDB.instance().getExportOverflowPath());
try {
ExportGeneration gen = new ExportGeneration(exportOverflowDirectory);
m_generation.set(gen);
} catch (IOException crash) {
//This means durig UAC we had a bad disk on a node or bad directory.
VoltDB.crashLocalVoltDB("Error creating export generation", true, crash);
return;
}
}
ExportGeneration generation = m_generation.get();
assert(generation != null);
/*
* If there is no existing export processor, create an initial one.
* This occurs when export is turned on/off at runtime.
*/
if (m_processor.get() == null) {
if (exportLog.isDebugEnabled()) {
exportLog.debug("First stream created processor will be initialized: " + m_loaderClass);
}
try {
generation.initializeGenerationFromCatalog(catalogContext,
connectors, m_hostId, m_messenger, localPartitionsToSites);
if (exportLog.isDebugEnabled()) {
exportLog.debug("Creating connector " + m_loaderClass);
}
ExportDataProcessor newProcessor = getNewProcessorWithProcessConfigSet(m_processorConfig);
m_processor.set(newProcessor);
newProcessor.setExportGeneration(generation);
if (m_startPolling && !m_processorConfig.isEmpty()) {
newProcessor.startPolling();
}
newProcessor.readyForData();
/*
* When it isn't startup, it is necessary to kick things off with the mastership
* settings that already exist
*
* This strategy is the one that piggy backs on
* regular partition mastership distribution to determine
* who will process export data for different partitions.
* We stashed away all the ones we have mastership of
* in m_masterOfPartitions
*/
for (Integer partitionId: m_masterOfPartitions) {
generation.acceptMastership(partitionId);
}
}
catch (final ClassNotFoundException e) {
exportLog.l7dlog( Level.ERROR, LogKeys.export_ExportManager_NoLoaderExtensions.name(), e);
throw new RuntimeException(e);
}
catch (final Exception e) {
throw new RuntimeException(e);
}
}
else {
swapWithNewProcessor(catalogContext, generation,
connectors, localPartitionsToSites, m_processorConfig);
}
}
// remove and install new processor
private void swapWithNewProcessor(
final CatalogContext catalogContext,
ExportGeneration generation,
CatalogMap connectors,
List> partitions,
Map>> config)
{
ExportDataProcessor oldProcessor = m_processor.get();
if (exportLog.isDebugEnabled()) {
exportLog.debug("Shutdown guestprocessor");
}
oldProcessor.shutdown();
if (exportLog.isDebugEnabled()) {
exportLog.debug("Processor shutdown completed, install new export processor");
}
generation.unacceptMastership();
if (exportLog.isDebugEnabled()) {
exportLog.debug("Existing export datasources unassigned.");
}
//Load any missing tables.
generation.initializeGenerationFromCatalog(catalogContext, connectors, m_hostId, m_messenger, partitions);
for (Pair partition : partitions) {
generation.updateAckMailboxes(partition.getFirst(), null);
}
//We create processor even if we dont have any streams.
try {
ExportDataProcessor newProcessor = getNewProcessorWithProcessConfigSet(config);
newProcessor.setExportGeneration(generation);
if (m_startPolling && !config.isEmpty()) {
newProcessor.startPolling();
}
m_processor.getAndSet(newProcessor);
newProcessor.readyForData();
}
catch (Exception crash) {
VoltDB.crashLocalVoltDB("Error creating next export processor", true, crash);
}
for (int partitionId : m_masterOfPartitions) {
generation.acceptMastership(partitionId);
}
}
private ExportDataProcessor getNewProcessorWithProcessConfigSet(Map>> config) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
final Class loaderClass = Class.forName(m_loaderClass);
ExportDataProcessor newProcessor = (ExportDataProcessor)loaderClass.newInstance();
newProcessor.addLogger(exportLog);
newProcessor.setProcessorConfig(config);
return newProcessor;
}
public void shutdown() {
ExportGeneration generation = m_generation.getAndSet(null);
if (generation != null) {
generation.close(m_messenger);
}
ExportDataProcessor proc = m_processor.getAndSet(null);
if (proc != null) {
proc.shutdown();
}
}
private List getStats(final boolean interval) {
try {
ExportGeneration generation = m_generation.get();
if (generation != null) {
return generation.getStats(interval);
}
} catch (Exception e) {
//Don't let anything take down the execution site thread
exportLog.error("Failed to get export queued bytes.", e);
}
return new ArrayList();
}
/*
* This method pulls double duty as a means of pushing export buffers
* and "syncing" export data to disk. Syncing doesn't imply fsync, it just means
* writing the data to a file instead of keeping it all in memory.
* End of stream indicates that no more data is coming from this source
* for this generation.
*/
public static void pushEndOfStream(
int partitionId,
String signature) {
}
/*
* This method pulls double duty as a means of pushing export buffers
* and "syncing" export data to disk. Syncing doesn't imply fsync, it just means
* writing the data to a file instead of keeping it all in memory.
* End of stream indicates that no more data is coming from this source
* for this generation.
*/
public static void pushExportBuffer(
int partitionId,
String signature,
long startSequenceNumber,
long tupleCount,
long uniqueId,
long bufferPtr,
ByteBuffer buffer,
boolean sync) {
//For validating that the memory is released
if (bufferPtr != 0) DBBPool.registerUnsafeMemory(bufferPtr);
ExportManager instance = instance();
try {
ExportGeneration generation = instance.m_generation.get();
if (generation == null) {
if (buffer != null) {
DBBPool.wrapBB(buffer).discard();
}
return;
}
generation.pushExportBuffer(partitionId, signature, startSequenceNumber,
(int)tupleCount, uniqueId, buffer, sync);
} catch (Exception e) {
//Don't let anything take down the execution site thread
exportLog.error("Error pushing export buffer", e);
}
}
public void updateInitialExportStateToSeqNo(int partitionId, String signature,
boolean isRecover, boolean isRejoin,
Map> sequenceNumberPerPartition,
boolean isLowestSite) {
//If the generation was completely drained, wait for the task to finish running
//by waiting for the permit that will be generated
ExportGeneration generation = m_generation.get();
if (generation != null) {
generation.updateInitialExportStateToSeqNo(partitionId, signature,
isRecover, isRejoin,
sequenceNumberPerPartition, isLowestSite);
}
}
public static synchronized void sync(final boolean nofsync) {
if (exportLog.isDebugEnabled()) {
exportLog.debug("Syncing export data");
}
ExportGeneration generation = instance().m_generation.get();
if (generation != null) {
generation.sync(nofsync);
}
}
public ExportStats getExportStats() {
return m_exportStats;
}
public void processStreamControl(String exportStream, List exportTargets, OperationMode operation, VoltTable results) {
if (m_generation.get() != null) {
m_generation.get().processStreamControl(exportStream, exportTargets, operation, results);
}
}
}