package org.voltdb;
import java.nio.ByteBuffer;
import java.util.Iterator;
import org.apache.log4j.Logger;
import org.voltdb.catalog.Procedure;
import org.voltdb.messaging.FastSerializer;
import org.voltdb.types.SortDirectionType;
import org.voltdb.utils.Pair;
import org.voltdb.utils.ReduceInputIterator;
import org.voltdb.utils.VoltTableUtil;
import com.google.protobuf.ByteString;
import edu.brown.hstore.Hstoreservice.Status;
import edu.brown.hstore.Hstoreservice.TransactionReduceResponse.ReduceResult;
import edu.brown.hstore.PartitionExecutor;
import edu.brown.hstore.callbacks.TransactionMapWrapperCallback;
import edu.brown.hstore.callbacks.TransactionReduceWrapperCallback;
import edu.brown.hstore.txns.MapReduceTransaction;
import edu.brown.logging.LoggerUtil;
import edu.brown.logging.LoggerUtil.LoggerBoolean;
public abstract class VoltMapReduceProcedure<K> extends VoltProcedure {
public static final Logger LOG = Logger.getLogger(VoltMapReduceProcedure.class);
private static final LoggerBoolean debug = new LoggerBoolean();
private static final LoggerBoolean trace = new LoggerBoolean();
static {
LoggerUtil.attachObserver(LOG, debug, trace);
}
private SQLStmt mapInputQuery;
// This reduceInputQuery is prepared to executed REDUCE by internal system instead of Java code
private SQLStmt reduceInputQuery;
// Thread-local data
private MapReduceTransaction mr_ts;
private VoltTable map_output;
private VoltTable reduce_input;
private VoltTable reduce_output;
// -----------------------------------------------------------------
// MAP REDUCE API
// -----------------------------------------------------------------
/**
* Returns the schema of the MapOutput table
* @return
*/
public abstract VoltTable.ColumnInfo[] getMapOutputSchema();
/**
* Returns the schema of the ReduceOutput table
* @return
*/
public abstract VoltTable.ColumnInfo[] getReduceOutputSchema();
/**
* @param tuple
*/
public abstract void map(VoltTableRow tuple);
/**
* @param r
*/
public abstract void reduce(K key, Iterator<VoltTableRow> rows);
// -----------------------------------------------------------------
// INTERNAL METHODS
// -----------------------------------------------------------------
@Override
public void init(PartitionExecutor site, Procedure catalogProc, BackendTarget eeType) {
super.init(site, catalogProc, eeType);
// Get the SQLStmt handles for the input queries
this.mapInputQuery = this.getSQLStmt(catalogProc.getMapinputquery());
assert (this.mapInputQuery != null) : "Missing MapInputQuery " + catalogProc.getMapinputquery();
this.reduceInputQuery = this.getSQLStmt(catalogProc.getReduceinputquery());
}
/**
*
* @return
*/
public final VoltTable run(Object params[]) {
assert (this.hstore_site != null) : "error in VoltMapReduceProcedure...for hstore_site..........";
VoltTable result = null;
// The MapReduceTransaction handle will have all the key information we need about this txn
long txn_id = this.getTransactionId();
this.mr_ts = this.hstore_site.getTransaction(txn_id);
assert(this.mr_ts != null) :
"Unexpected null MapReduceTransaction handle for txn #" + txn_id;
// If this invocation is at the txn's base partition, then it is
// responsible for sending out the coordination messages to the other partitions
boolean is_local = (this.partitionId == mr_ts.getBasePartition());
// ----------------------------------------------------------------------------
// MAP PHASE
// ----------------------------------------------------------------------------
if (this.mr_ts.isMapPhase()) {
// If this is the base partition, then we'll send the out the MAP
// initialization requests to all of the partitions
if (is_local) {
// Send out network messages to all other partitions to tell them to
// execute the MAP phase of this job
if (debug.val)
LOG.debug("<VoltMapReduceProcedure.run> is executing ..<Map>...local!!!....\n");
hstore_site.getCoordinator().transactionMap(mr_ts, mr_ts.getTransactionMapCallback());
}
this.map_output = this.mr_ts.getMapOutputByPartition(this.partitionId);
assert(this.map_output != null);
if (debug.val)
LOG.debug("<VoltMapReduceProcedure.run> is executing ..<MAP>..\n");
// Execute the map
voltQueueSQL(mapInputQuery, params);
VoltTable mapResult[] = voltExecuteSQLForceSinglePartition();
assert (mapResult.length == 1);
// Check whether the HStoreConf flag for locking the entire cluster
// is true. If it is, then we have to tell the queue manager that we're done.
// MapReduceTransaction should finish forever...
if (this.hstore_conf.site.mr_map_blocking) {
hstore_site.getTransactionQueueManager().lockQueueFinished(this.mr_ts, Status.OK, this.partitionId);
}
if (debug.val)
LOG.debug(String.format("MAP: About to process %d records for %s on partition %d",
mapResult[0].getRowCount(), this.mr_ts, this.partitionId));
if (debug.val)
LOG.debug(String.format("<MapInputTable> Partition:%d\n %s", this.partitionId,mapResult[0]));
while (mapResult[0].advanceRow()) {
this.map(mapResult[0].getRow());
} // WHILE
if (debug.val)
LOG.debug(String.format("MAP: %s generated %d results on partition %d",
this.mr_ts, this.map_output.getRowCount(), this.partitionId));
if (debug.val)
LOG.debug(String.format("<MapOutputTable> Partition:%d\n %s", this.partitionId,this.map_output));
result = mr_ts.getMapOutputByPartition(this.partitionId);
// Always invoke the TransactionMapWrapperCallback to let somebody know that
// we finished the MAP phase at this partition
TransactionMapWrapperCallback callback = mr_ts.getTransactionMapWrapperCallback();
assert (callback != null) : "Unexpected null callback for " + mr_ts;
assert (callback.isInitialized()) : "Unexpected uninitalized callback for " + mr_ts;
callback.run(this.partitionId);
}
// ----------------------------------------------------------------------------
// REDUCE PHASE
// ----------------------------------------------------------------------------
else if (mr_ts.isReducePhase()) {
// If this is the local/base partition, send out the start REDUCE message
if (is_local) {
if (debug.val)
LOG.debug("<VoltMapReduceProcedure.run> is executing ..<Reduce>...local!!!....\n");
// Send out network messages to all other partitions to tell them to execute the Reduce phase of this job
hstore_site.getCoordinator().transactionReduce(mr_ts, mr_ts.getTransactionReduceCallback());
}
this.reduce_input = null; //
this.reduce_input = mr_ts.getReduceInputByPartition(this.partitionId);
assert(this.reduce_input != null);
if(debug.val)
LOG.debug(String.format("TXN: %s, [Stage] \n<VoltMapReduceProcedure.run> is executing <Reduce>..",mr_ts));
if (debug.val)
LOG.debug(String.format("<ReduceInputTable> Partition:%d\n %s", this.partitionId,this.reduce_input));
// Sort the the MAP_OUTPUT table
// Build an "smart" iterator that loops through the MAP_OUTPUT table key-by-key
@SuppressWarnings("unchecked")
VoltTable sorted = VoltTableUtil.sort(this.reduce_input, Pair.of(0, SortDirectionType.ASC));
//VoltTable sorted = VoltTableUtil.sort(mr_ts.getReduceInputByPartition(this.partitionId), Pair.of(0, SortDirectionType.ASC));
assert(sorted != null);
if (debug.val)
LOG.debug(String.format("<Sorted_ReduceInputTable> Partition:%d\n %s", this.partitionId,sorted));
this.reduce_output = mr_ts.getReduceOutputByPartition(this.partitionId);
assert(this.reduce_output != null);
// Make a Hstore.PartitionResult
ReduceInputIterator<K> rows = new ReduceInputIterator<K>(sorted);
// Loop over that iterator and call runReduce
if (debug.val)
LOG.debug(String.format("REDUCE: About to process %d records for %s on partition %d",
sorted.getRowCount(), this.mr_ts, this.partitionId));
while (rows.hasNext()) {
K key = rows.getKey();
this.reduce(key, rows);
}
if (debug.val)
LOG.debug(String.format("<ReduceOutputTable> Partition:%d\n %s", this.partitionId,this.reduce_output));
// Loop over that iterator and call runReduce
if (debug.val)
LOG.debug(String.format("REDUCE: %s generated %d results on partition %d",
this.mr_ts, this.reduce_output.getRowCount(), this.partitionId));
ByteString reduceOutData = null;
try {
ByteBuffer b = ByteBuffer.wrap(FastSerializer.serialize(reduce_output));
reduceOutData = ByteString.copyFrom(b.array());
} catch (Exception ex) {
throw new RuntimeException(String.format("Unexpected error when serializing %s reduceOutput data for partition %d",
mr_ts, this.partitionId), ex);
}
ReduceResult.Builder builder = ReduceResult.newBuilder()
.setData(reduceOutData)
.setPartitionId(this.partitionId)
.setStatus(Status.OK);
TransactionReduceWrapperCallback callback = mr_ts.getTransactionReduceWrapperCallback();
assert (callback != null) : "Unexpected null TransactionReduceWrapperCallback for " + mr_ts;
assert (callback.isInitialized()) : "Unexpected uninitalized TransactionReduceWrapperCallback for " + mr_ts;
callback.run(builder.build());
}
return (result);
}
/**
*
* @param key
* @param row
*/
public final void mapEmit(K key, Object row[]) {
assert(key == row[0]);
this.map_output.addRow(row);
}
/**
*
* @param row
*/
public final void reduceEmit(Object row[]) {
this.reduce_output.addRow(row);
}
@Override
public void finish() {
// for (int i = 0; i < this.mr_ts.getSize(); i++) {
// this.mr_ts.getLocalTransaction(i).finish();
// } // FOR
}
public final int getPartitionId() {
return partitionId;
}
public final void setPartitionId(int partitionId) {
this.partitionId = partitionId;
}
}