package edu.brown.hstore.txns;
import org.apache.log4j.Logger;
import org.voltdb.ClientResponseImpl;
import org.voltdb.ParameterSet;
import org.voltdb.VoltTable;
import org.voltdb.VoltTableRow;
import org.voltdb.catalog.Procedure;
import org.voltdb.catalog.Table;
import com.google.protobuf.RpcCallback;
import edu.brown.catalog.CatalogUtil;
import edu.brown.hstore.HStoreSite;
import edu.brown.hstore.Hstoreservice.Status;
import edu.brown.hstore.Hstoreservice.TransactionMapResponse;
import edu.brown.hstore.Hstoreservice.TransactionReduceResponse;
import edu.brown.hstore.callbacks.SendDataCallback;
import edu.brown.hstore.callbacks.RemoteFinishCallback;
import edu.brown.hstore.callbacks.TransactionMapCallback;
import edu.brown.hstore.callbacks.TransactionMapWrapperCallback;
import edu.brown.hstore.callbacks.TransactionReduceCallback;
import edu.brown.hstore.callbacks.TransactionReduceWrapperCallback;
import edu.brown.logging.LoggerUtil;
import edu.brown.logging.LoggerUtil.LoggerBoolean;
import edu.brown.utils.PartitionSet;
/**
* Special transaction state object for MapReduce jobs
* @author pavlo
* @author xin
*/
public class MapReduceTransaction extends LocalTransaction {
private static final Logger LOG = Logger.getLogger(MapReduceTransaction.class);
private static final LoggerBoolean debug = new LoggerBoolean();
private static final LoggerBoolean trace = new LoggerBoolean();
static {
LoggerUtil.attachObserver(LOG, debug, trace);
}
private final LocalTransaction local_txns[];
public int partitions_size;
private VoltTable mapOutput[];
private VoltTable reduceInput[];
private VoltTable reduceOutput[];
public enum State {
MAP,
SHUFFLE,
REDUCE,
FINISH;
}
/**
* MapReduce Phases
*/
private State mr_state = null;
private Table mapEmit;
private Table reduceEmit;
/**
* This is for non-blocking reduce executing in MapReduceHelperThread
*/
public boolean basePartition_reduce_exec = false;
public boolean basePartition_map_exec = false;
// ----------------------------------------------------------------------------
// CALLBACKS
// ----------------------------------------------------------------------------
/**
*/
private final TransactionMapCallback map_callback;
private final TransactionMapWrapperCallback mapWrapper_callback;
private final SendDataCallback sendData_callback;
private final TransactionReduceCallback reduce_callback;
private final TransactionReduceWrapperCallback reduceWrapper_callback;
private final RemoteFinishCallback cleanup_callback;
/**
* Constructor
* @param hstore_site
*/
public MapReduceTransaction(HStoreSite hstore_site) {
super(hstore_site);
// new local_txns
this.partitions_size = this.hstore_site.getLocalPartitionIds().size();
this.local_txns = new LocalTransaction[this.partitions_size];
for (int i = 0; i < this.partitions_size; i++) {
this.local_txns[i] = new LocalTransaction(hstore_site) {
@Override
public String toStringImpl() {
return MapReduceTransaction.this.toString() + "/" + this.base_partition;
}
};
} // FOR
// new mapout and reduce output talbes for each partition it wants to touch
this.mapOutput = new VoltTable[this.partitions_size];
this.reduceInput = new VoltTable[this.partitions_size];
this.reduceOutput = new VoltTable[this.partitions_size];
this.map_callback = new TransactionMapCallback(hstore_site);
this.mapWrapper_callback = new TransactionMapWrapperCallback(hstore_site);
this.sendData_callback = new SendDataCallback(hstore_site);
//this.sendDataWrapper_callback = new SendDataWrapperCallback(hstore_site);
this.reduce_callback = new TransactionReduceCallback(hstore_site);
this.reduceWrapper_callback = new TransactionReduceWrapperCallback(hstore_site);
this.cleanup_callback = new RemoteFinishCallback(hstore_site);
}
@Override
public LocalTransaction init(Long txn_id,
long initiateTime,
long clientHandle,
int base_partition,
PartitionSet predict_touchedPartitions,
boolean predict_readOnly,
boolean predict_canAbort,
Procedure catalog_proc,
ParameterSet params,
RpcCallback<ClientResponseImpl> client_callback) {
super.init(txn_id,
initiateTime,
clientHandle,
base_partition,
predict_touchedPartitions,
predict_readOnly,
predict_canAbort,
catalog_proc,
params,
client_callback);
// Intialize MapReduce properties
this.mapEmit = hstore_site.getCatalogContext().getTableByName(catalog_proc.getMapemittable());
this.reduceEmit = hstore_site.getCatalogContext().getTableByName(catalog_proc.getReduceemittable());
if (debug.val) {
LOG.debug(" CatalogUtil.getVoltTable(thisMapEmit): -> " + catalog_proc.getMapemittable());
LOG.debug("MapReduce LocalPartitionIds: " + this.hstore_site.getLocalPartitionIds());
}
// Get the Table catalog object for the map/reduce outputs
// For each partition there should be a map/reduce output voltTable
for (int partition : this.hstore_site.getLocalPartitionIds()) {
if (debug.val) LOG.debug(String.format("Partition[%d] -> Offset[%d]", partition, partition));
this.local_txns[partition].init(this.txn_id,
initiateTime,
this.client_handle,
partition,
hstore_site.getCatalogContext().getPartitionSetSingleton(partition),
this.predict_readOnly,
this.predict_abortable,
catalog_proc,
params,
null);
this.local_txns[partition].markMapReduce();
// init map/reduce Output for each partition
assert(this.mapEmit != null): "mapEmit has not been initialized\n ";
assert(this.reduceEmit != null): "reduceEmit has not been initialized\n ";
this.mapOutput[partition] = CatalogUtil.getVoltTable(this.mapEmit);
this.reduceInput[partition] = CatalogUtil.getVoltTable(this.mapEmit);
this.reduceOutput[partition] = CatalogUtil.getVoltTable(this.reduceEmit);
} // FOR
this.setMapPhase();
this.map_callback.init(this);
assert(this.map_callback.isInitialized()) : "Unexpected error for " + this;
this.reduce_callback.init(this);
assert(this.reduce_callback.isInitialized()) : "Unexpected error for " + this;
// Initialize the TransactionCleanupCallback if this txn's base partition
// is not at this HStoreSite.
if (this.hstore_site.isLocalPartition(base_partition) == false) {
this.cleanup_callback.init(this, this.hstore_site.getLocalPartitionIds());
}
if (debug.val) LOG.info("Invoked MapReduceTransaction.init() -> " + this);
return (this);
}
public MapReduceTransaction init(Long txn_id,
long initiateTime,
long client_handle,
int base_partition,
Procedure catalog_proc,
ParameterSet params) {
this.init(txn_id,
initiateTime,
client_handle,
base_partition,
hstore_site.getCatalogContext().getAllPartitionIds(),
false,
true,
catalog_proc,
params,
null);
LOG.info("Invoked MapReduceTransaction.init() -> " + this);
assert(this.map_callback.isInitialized()) : "Unexpected error for " + this;
//assert(this.sendData_callback.isInitialized()) : "Unexpected error for " + this;
return (this);
}
@Override
public void finish() {
super.finish();
// for (int i = 0; i < this.partitions_size; i++) {
// this.local_txns[i].finish();
// } // FOR
this.mr_state = null;
this.map_callback.finish();
this.mapWrapper_callback.finish();
this.sendData_callback.finish();
this.reduce_callback.finish();
this.reduceWrapper_callback.finish();
this.basePartition_map_exec = false;
this.basePartition_reduce_exec = false;
// TODO(xin): Only call TransactionCleanupCallback.finish() if this txn's base
// partition is not at this HStoreSite.
if (!this.hstore_site.isLocalPartition(this.base_partition)) {
this.cleanup_callback.finish();
}
if(debug.val) LOG.debug("<MapReduceTransaction> this.reduceWrapper_callback.finish().......................");
this.mapEmit = null;
this.reduceEmit = null;
this.mapOutput = null;
this.reduceInput = null;
this.reduceOutput = null;
}
/**
* Store Data from MapOutput table into reduceInput table
* ReduceInput table is the result of all incoming mapOutput table from other partitions
* @see edu.brown.hstore.txns.AbstractTransaction#storeData(int, org.voltdb.VoltTable)
*/
@Override
public synchronized Status storeData(int partition, VoltTable vt) {
VoltTable input = this.getReduceInputByPartition(partition);
assert(input != null);
if (debug.val)
LOG.debug(String.format("StoreData into Partition #%d: RowCount=%d ",
partition, vt.getRowCount()));
if (debug.val)
LOG.debug(String.format("<StoreData, change to ReduceInputTable> to Partition:%d>\n %s",partition,vt));
while (vt.advanceRow()) {
VoltTableRow row = vt.fetchRow(vt.getActiveRowIndex());
assert(row != null);
input.add(row);
}
vt.resetRowPosition();
return Status.OK;
}
/**
* Get a LocalTransaction handle for a local partition
*
* @param partition
* @return
*/
public LocalTransaction getLocalTransaction(int partition) {
return (this.local_txns[partition]);
}
// ----------------------------------------------------------------------------
// ACCESS METHODS
// ----------------------------------------------------------------------------
@Override
public boolean isDeletable() {
if (this.cleanup_callback.allCallbacksFinished() == false) {
if (trace.val)
LOG.warn(String.format("%s - %s is not finished", this,
this.cleanup_callback.getClass().getSimpleName()));
return (false);
}
return (super.isDeletable());
}
public boolean isBasePartitionMapExec() {
return this.basePartition_map_exec;
}
public void markBasePartitionMapExec() {
assert(this.basePartition_map_exec == false);
this.basePartition_map_exec = true;
}
public boolean isBasePartitionReduceExec() {
return this.basePartition_reduce_exec;
}
public void markBasePartitionReduceExec() {
assert(this.basePartition_reduce_exec == false);
this.basePartition_reduce_exec = true;
}
/*
* Return the MapOutput Table schema
*/
public boolean isMapPhase() {
return (this.mr_state == State.MAP);
}
public boolean isShufflePhase() {
return (this.mr_state == State.SHUFFLE);
}
public boolean isReducePhase() {
return (this.mr_state == State.REDUCE);
}
public boolean isFinishPhase() {
return (this.mr_state == State.FINISH);
}
public void setMapPhase() {
assert (this.mr_state == null);
this.mr_state = State.MAP;
}
public void setShufflePhase() {
assert(this.isMapPhase());
this.mr_state = State.SHUFFLE;
}
public void setReducePhase() {
assert(this.isShufflePhase());
this.mr_state = State.REDUCE;
}
public void setFinishPhase() {
assert(this.isReducePhase());
this.mr_state = State.FINISH;
}
/*
* return the size of partitions that MapReduce Transaction will touch
*/
public int getSize() {
return partitions_size;
}
public Table getMapEmit() {
return mapEmit;
}
/*
* Return the ReduceOutput Table schema
*/
public Table getReduceEmit() {
return reduceEmit;
}
public State getState() {
return (this.mr_state);
}
public VoltTable[] getReduceOutput() {
return this.reduceOutput;
}
public TransactionMapCallback getTransactionMapCallback() {
return (this.map_callback);
}
public TransactionMapWrapperCallback getTransactionMapWrapperCallback() {
assert(this.mapWrapper_callback.isInitialized());
return (this.mapWrapper_callback);
}
public SendDataCallback getSendDataCallback() {
return sendData_callback;
}
public TransactionReduceCallback getTransactionReduceCallback() {
return (this.reduce_callback);
}
public TransactionReduceWrapperCallback getTransactionReduceWrapperCallback() {
assert(this.reduceWrapper_callback.isInitialized());
return (this.reduceWrapper_callback);
}
public void initTransactionMapWrapperCallback(RpcCallback<TransactionMapResponse> orig_callback) {
if (debug.val) LOG.debug("Trying to intialize TransactionMapWrapperCallback for " + this);
assert (this.mapWrapper_callback.isInitialized() == false);
this.mapWrapper_callback.init(this, orig_callback);
}
public void initTransactionReduceWrapperCallback(RpcCallback<TransactionReduceResponse> orig_callback) {
if (debug.val) LOG.debug("Trying to initialize TransactionReduceWrapperCallback for " + this);
//assert (this.reduceWrapper_callback.isInitialized() == false);
this.reduceWrapper_callback.init(this, orig_callback);
}
/**
* Get the TransactionCleanupCallback for this txn.
*/
public RemoteFinishCallback getCleanupCallback() {
// TODO(xin): This should return null if this handle is located at
// the txn's basePartition HStoreSite
if (this.hstore_site.isLocalPartition(base_partition)) return null;
assert(this.cleanup_callback.isInitialized()) :
String.format("Trying to grab the %s for %s before it has been initialized",
this.cleanup_callback.getClass().getSimpleName(), this);
return (this.cleanup_callback);
}
@Override
public String toStringImpl() {
return String.format("%s-%s #%d/%d", this.getProcedure().getName(),
(this.getState() == null ? "null" : this.getState().toString()),
this.txn_id, this.base_partition);
}
@Override
public void initRound(int partition, long undoToken) {
throw new RuntimeException("initRound should not be invoked on " + this.getClass());
}
@Override
public void startRound(int partition) {
throw new RuntimeException("startRound should not be invoked on " + this.getClass());
}
@Override
public void finishRound(int partition) {
throw new RuntimeException("finishRound should not be invoked on " + this.getClass());
}
public VoltTable getMapOutputByPartition( int partition ) {
if (debug.val) LOG.debug("Trying to getMapOutputByPartition: [ " + partition + " ]");
return this.mapOutput[partition];
}
public VoltTable getReduceInputByPartition ( int partition ) {
if (debug.val) LOG.debug("Trying to getReduceInputByPartition: [ " + partition + " ]");
return this.reduceInput[partition];
//return this.reduceInput[partition];
}
public VoltTable getReduceOutputByPartition ( int partition ) {
if (debug.val) LOG.debug("Trying to getReduceOutputByPartition: [ " + partition + " ]");
return this.reduceOutput[partition];
//return this.reduceOutput[partition];
}
/**
* Reset variables in sub transactions to allow for re-execution.
*/
public void resetTransaction() {
for (LocalTransaction local_txn : this.local_txns) {
local_txn.resetControlCodeExecuted();
local_txn.resetClientResponse();
}
}
}