edu.brown.hstore.TransactionInitializer.java Source code

Java tutorial

Introduction

Here is the source code for edu.brown.hstore.TransactionInitializer.java

Source

/***************************************************************************
 *   Copyright (C) 2012 by H-Store Project                                 *
 *   Brown University                                                      *
 *   Massachusetts Institute of Technology                                 *
 *   Yale University                                                       *
 *                                                                         *
 *   Permission is hereby granted, free of charge, to any person obtaining *
 *   a copy of this software and associated documentation files (the       *
 *   "Software"), to deal in the Software without restriction, including   *
 *   without limitation the rights to use, copy, modify, merge, publish,   *
 *   distribute, sublicense, and/or sell copies of the Software, and to    *
 *   permit persons to whom the Software is furnished to do so, subject to *
 *   the following conditions:                                             *
 *                                                                         *
 *   The above copyright notice and this permission notice shall be        *
 *   included in all copies or substantial portions of the Software.       *
 *                                                                         *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       *
 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    *
 *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*
 *   IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR     *
 *   OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
 *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
 *   OTHER DEALINGS IN THE SOFTWARE.                                       *
 ***************************************************************************/
package edu.brown.hstore;

import java.nio.ByteBuffer;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.voltdb.CatalogContext;
import org.voltdb.ClientResponseImpl;
import org.voltdb.ParameterSet;
import org.voltdb.StoredProcedureInvocation;
import org.voltdb.TransactionIdManager;
import org.voltdb.catalog.Procedure;
import org.voltdb.messaging.FastDeserializer;

import com.google.protobuf.RpcCallback;

import edu.brown.hstore.conf.HStoreConf;
import edu.brown.hstore.estimators.TransactionEstimator;
import edu.brown.hstore.estimators.Estimate;
import edu.brown.hstore.estimators.EstimatorState;
import edu.brown.hstore.estimators.markov.MarkovEstimatorState;
import edu.brown.hstore.txns.AbstractTransaction;
import edu.brown.hstore.txns.LocalTransaction;
import edu.brown.hstore.txns.MapReduceTransaction;
import edu.brown.hstore.txns.RemoteTransaction;
import edu.brown.hstore.txns.TransactionUtil;
import edu.brown.logging.LoggerUtil;
import edu.brown.logging.LoggerUtil.LoggerBoolean;
import edu.brown.markov.EstimationThresholds;
import edu.brown.profilers.ProfileMeasurement;
import edu.brown.profilers.TransactionProfiler;
import edu.brown.utils.EventObservable;
import edu.brown.utils.PartitionEstimator;
import edu.brown.utils.PartitionSet;
import edu.brown.utils.StringBoxUtil;
import edu.brown.utils.StringUtil;

/**
 * This class is responsible for figuring out everything about a txn before it 
 * starts running. It can figure out what partition to execute the txn's control
 * code (i.e., program logic) on. It can also figure out additional properties, such 
 * as what partitions the txn will need to access, whether it is read-only at a 
 * partition, and whether it is likely to abort.
 * <B>Note:</B> It is thread-safe so it can be used by all of the PartitionExecutors with locking 
 * @author pavlo
 */
public class TransactionInitializer {
    private static final Logger LOG = Logger.getLogger(TransactionInitializer.class);
    private static final LoggerBoolean debug = new LoggerBoolean();
    private static final LoggerBoolean trace = new LoggerBoolean();
    static {
        LoggerUtil.attachObserver(LOG, debug, trace);
    }

    // ----------------------------------------------------------------------------
    // INSTANCE MEMBERS
    // ----------------------------------------------------------------------------

    private final HStoreSite hstore_site;
    private final HStoreConf hstore_conf;
    private final CatalogContext catalogContext;
    private final PartitionEstimator p_estimator;
    private final PartitionSet local_partitions;
    private final TransactionEstimator t_estimators[];
    private final TransactionIdManager txnIdManagers[];
    private final Random rng = new Random();
    private EstimationThresholds thresholds;

    /**
     * HACK: This is the internal map used to keep track of TxnId->TxnHandles
     * inside of the HStoreSite.
     */
    private final Map<Long, AbstractTransaction> inflight_txns;

    /**
     * This is fired whenever we create a new txn handle is initialized.
     * It is only used for debugging+testing 
     */
    private EventObservable<LocalTransaction> newTxnObservable;

    // Local Catalog Cache
    private final boolean isMapReduce[];
    private final boolean isSysProc[];
    private final boolean isReadOnly[];
    private final int expectedParams[];

    // ----------------------------------------------------------------------------
    // INITIALIZATION
    // ----------------------------------------------------------------------------

    public TransactionInitializer(HStoreSite hstore_site) {
        this.hstore_site = hstore_site;
        this.hstore_conf = hstore_site.getHStoreConf();
        this.local_partitions = hstore_site.getLocalPartitionIds();
        this.catalogContext = hstore_site.getCatalogContext();
        this.inflight_txns = hstore_site.getInflightTxns();

        this.thresholds = hstore_site.getThresholds();
        this.p_estimator = hstore_site.getPartitionEstimator();
        this.t_estimators = new TransactionEstimator[catalogContext.numberOfPartitions];

        int num_procs = this.catalogContext.procedures.size() + 1;
        this.isMapReduce = new boolean[num_procs];
        this.isSysProc = new boolean[num_procs];
        this.isReadOnly = new boolean[num_procs];
        this.expectedParams = new int[num_procs];
        for (Procedure proc : this.catalogContext.procedures) {
            int id = proc.getId();
            this.isMapReduce[id] = proc.getMapreduce();
            this.isSysProc[id] = proc.getSystemproc();
            this.isReadOnly[id] = proc.getReadonly();
            this.expectedParams[id] = proc.getParameters().size();
            if (proc.getPartitionparameters().size() > 1) {
                this.expectedParams[id]--; // we need to account for the MultiProcParameter 
            }
        } // FOR

        this.txnIdManagers = new TransactionIdManager[this.catalogContext.numberOfPartitions];
        for (int partition : this.local_partitions.values()) {
            this.txnIdManagers[partition] = hstore_site.getTransactionIdManager(partition);
        } // FOR
    }

    public synchronized EventObservable<LocalTransaction> getNewTxnObservable() {
        if (this.newTxnObservable == null) {
            this.newTxnObservable = new EventObservable<LocalTransaction>();
        }
        return (this.newTxnObservable);
    }

    // ----------------------------------------------------------------------------
    // TRANSACTION PROCESSING METHODS
    // ----------------------------------------------------------------------------

    /**
     * Calculate what partition the txn should be executed on.
     * The provided base_partition argument is the "suggestion" that
     * was embedded in the original StoredProcedureInvocation from the client
     * @param client_handle
     * @param catalog_proc
     * @param procParams
     * @param base_partition
     * @return
     */
    public int calculateBasePartition(long client_handle, Procedure catalog_proc, ParameterSet procParams,
            int base_partition) {
        final int procId = catalog_proc.getId();

        // Simple sanity check to make sure that we're not being told a bad partition
        if (base_partition < 0 || base_partition >= this.local_partitions.size()) {
            base_partition = HStoreConstants.NULL_PARTITION_ID;
        }

        // -------------------------------
        // DB2-style Transaction Redirection
        // -------------------------------
        if (base_partition != HStoreConstants.NULL_PARTITION_ID && hstore_conf.site.exec_db2_redirects) {
            if (debug.val)
                LOG.debug(String.format("Using embedded base partition from %s request " + "[basePartition=%d]",
                        catalog_proc.getName(), base_partition));
        }
        // -------------------------------
        // System Procedure
        // -------------------------------
        else if (this.isSysProc[procId]) {
            if (catalog_proc.getSinglepartition() && catalog_proc.getEverysite() == false
                    && catalog_proc.getPartitionparameter() >= 0) {
                if (debug.val)
                    LOG.debug(String.format("Using PartitionEstimator for %s request", catalog_proc.getName()));
                try {
                    base_partition = this.p_estimator.getBasePartition(catalog_proc, procParams.toArray(), false);
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                }
            } else {
                // If it's a sysproc, then it doesn't need to go to a specific partition
                // We'll set it to NULL_PARTITION_ID so that we'll pick a random one down below
                if (debug.val)
                    LOG.debug(String.format("Using random local partition for %s request", catalog_proc.getName()));
                base_partition = HStoreConstants.NULL_PARTITION_ID;
            }
        }
        // -------------------------------
        // PartitionEstimator
        // -------------------------------
        else if (hstore_conf.site.exec_force_localexecution == false) {
            // HACK: If they don't have enough parameters, we'll just throw them to 
            // a random local partition and then let VoltProcedure give them back the proper error
            if (procParams.size() < this.expectedParams[procId]) {
                if (debug.val)
                    LOG.warn(String.format("Not enough parameters for %s. Not calculating base partition",
                            catalog_proc.getName()));
            } else {
                if (debug.val)
                    LOG.debug(String.format("Using PartitionEstimator for %s request", catalog_proc.getName()));
                try {
                    base_partition = this.p_estimator.getBasePartition(catalog_proc, procParams.toArray(), false);
                } catch (Exception ex) {
                    throw new RuntimeException(ex);
                }
            }
        }
        // If we don't have a partition to send this transaction to, then we will just pick
        // one our partitions at random. This can happen if we're forcing txns to execute locally
        // or if there are no input parameters <-- this should be in the paper!!!
        if (base_partition == HStoreConstants.NULL_PARTITION_ID) {
            if (trace.val)
                LOG.trace(String.format("Selecting a random local partition to execute %s request [force_local=%s]",
                        catalog_proc.getName(), hstore_conf.site.exec_force_localexecution));
            int idx = (int) (Math.abs(client_handle) % this.local_partitions.size());
            base_partition = this.local_partitions.values()[idx];
        }

        return (base_partition);
    }

    // ----------------------------------------------------------------------------
    // TRANSACTION HANDLE CREATION METHODS
    // ----------------------------------------------------------------------------

    /**
     * Create and initialize a LocalTransaction from a serialized StoredProcedureInvocation
     * request sent in from the client.  
     * @param serializedRequest
     * @param client_handle
     * @param base_partition
     * @param catalog_proc
     * @param procParams
     * @param clientCallback
     * @return
     */
    public LocalTransaction createLocalTransaction(ByteBuffer serializedRequest, long initiateTime,
            long client_handle, int base_partition, Procedure catalog_proc, ParameterSet procParams,
            RpcCallback<ClientResponseImpl> clientCallback) {
        final int procId = catalog_proc.getId();
        if (debug.val)
            LOG.debug(String.format("Incoming %s transaction request " + "[handle=%d, partition=%d]",
                    catalog_proc.getName(), client_handle, base_partition));

        // -------------------------------
        // TRANSACTION STATE INITIALIZATION
        // -------------------------------

        // Grab a new LocalTransactionState object from the target base partition's
        // PartitionExecutor object pool. This will be the handle that is used all
        // throughout this txn's lifespan to keep track of what it does
        LocalTransaction ts = null;
        try {
            if (this.isMapReduce[procId]) {
                ts = new MapReduceTransaction(this.hstore_site);
            } else {
                ts = new LocalTransaction(this.hstore_site);
            }
            assert (ts.isInitialized() == false);
        } catch (Throwable ex) {
            String msg = "Failed to instantiate new local transaction handle for " + catalog_proc.getName();
            throw new RuntimeException(msg, ex);
        }

        // Initialize our LocalTransaction handle
        Long txn_id = this.registerTransaction(ts, base_partition);
        this.populateProperties(ts, txn_id, initiateTime, client_handle, base_partition, catalog_proc, procParams,
                clientCallback);
        // Check whether this guy has already been restarted before
        if (serializedRequest != null) {
            int restartCounter = StoredProcedureInvocation.getRestartCounter(serializedRequest);
            if (restartCounter > 0) {
                ts.setRestartCounter(restartCounter);
            }
        }

        // Notify anybody that cares about this new txn
        if (this.newTxnObservable != null)
            this.newTxnObservable.notifyObservers(ts);

        assert (ts.isSysProc() == this.isSysProc[procId]) : "Unexpected sysproc mismatch for " + ts;
        return (ts);
    }

    /**
     * Create a new LocalTransaction handle from a restart txn
     * @param orig_ts
     * @param base_partition
     * @param predict_touchedPartitions
     * @param predict_readOnly
     * @param predict_abortable
     * @return
     */
    public LocalTransaction createLocalTransaction(LocalTransaction orig_ts, int base_partition,
            PartitionSet predict_touchedPartitions, boolean predict_readOnly, boolean predict_abortable) {

        LocalTransaction new_ts = new LocalTransaction(hstore_site);

        // Setup TransactionProfiler
        if (hstore_conf.site.txn_profiling) {
            if (this.setupTransactionProfiler(new_ts, orig_ts.isSysProc())) {
                // Since we're restarting the txn, we should probably include
                // the original profiler information the original txn.
                // new_ts.profiler.startTransaction(ProfileMeasurement.getTime());
                new_ts.profiler.setSingledPartitioned(predict_touchedPartitions.size() == 1);
            }
        } else if (new_ts.profiler != null) {
            new_ts.profiler.disableProfiling();
        }

        Long new_txn_id = this.registerTransaction(new_ts, base_partition);
        new_ts.init(new_txn_id, orig_ts.getInitiateTime(), orig_ts.getClientHandle(), base_partition,
                predict_touchedPartitions, predict_readOnly, predict_abortable, orig_ts.getProcedure(),
                orig_ts.getProcedureParameters(), orig_ts.getClientCallback());

        // Make sure that we remove the ParameterSet from the original LocalTransaction
        // so that they don't get returned back to the object pool when it is deleted
        orig_ts.removeProcedureParameters();

        // Increase the restart counter in the new transaction
        new_ts.setRestartCounter(orig_ts.getRestartCounter() + 1);

        // Notify anybody that cares about this new txn
        if (this.newTxnObservable != null)
            this.newTxnObservable.notifyObservers(new_ts);

        if (debug.val)
            LOG.debug(String.format("Restarted %s as %s [handle=%d, basePartition=%d]", orig_ts, new_ts,
                    orig_ts.getClientHandle(), base_partition));

        return (new_ts);
    }

    /**
     * Create a RemoteTransaction handle. This obviously only for a remote site.
     * @param txn_id
     * @param request
     * @return
     */
    public RemoteTransaction createRemoteTransaction(Long txn_id, PartitionSet partitions, ParameterSet procParams,
            int base_partition, int proc_id) {
        RemoteTransaction ts = null;
        Procedure catalog_proc = this.catalogContext.getProcedureById(proc_id);
        try {
            ts = new RemoteTransaction(this.hstore_site);
            assert (ts.isInitialized() == false);
            ts.init(txn_id, base_partition, procParams, catalog_proc, partitions, true);
            if (debug.val)
                LOG.debug(String.format(
                        "Creating new RemoteTransactionState %s from "
                                + "remote partition %d [partitions=%s, hashCode=%d]",
                        ts, base_partition, partitions, ts.hashCode()));
        } catch (Throwable ex) {
            String msg = "Failed to instantiate new remote transaction handle for "
                    + TransactionUtil.formatTxnName(catalog_proc, txn_id);
            throw new RuntimeException(msg, ex);
        }
        AbstractTransaction dupe = this.inflight_txns.put(txn_id, ts);
        assert (dupe == null) : "Trying to create multiple transaction handles for " + dupe;

        if (trace.val)
            LOG.trace(String.format("Stored new transaction state for %s", ts));
        return (ts);
    }

    /**
     * Create a MapReduceTransaction handle. This should only be invoked on a remote site.
     * @param txn_id
     * @param invocation
     * @param base_partition
     * @return
     */
    public MapReduceTransaction createMapReduceTransaction(Long txn_id, long initiateTime, long client_handle,
            int base_partition, int procId, ByteBuffer paramsBuffer) {
        Procedure catalog_proc = this.catalogContext.getProcedureById(procId);
        if (catalog_proc == null) {
            throw new RuntimeException("Unknown procedure id '" + procId + "'");
        }

        // Initialize the ParameterSet
        FastDeserializer incomingDeserializer = new FastDeserializer();
        ParameterSet procParams = new ParameterSet();
        try {
            incomingDeserializer.setBuffer(StoredProcedureInvocation.getParameterSet(paramsBuffer));
            procParams.readExternal(incomingDeserializer);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
        assert (procParams != null) : "The parameters object is null for new txn from client #" + client_handle;

        MapReduceTransaction ts = new MapReduceTransaction(hstore_site);

        // We should never already have a transaction handle for this txnId
        AbstractTransaction dupe = this.inflight_txns.put(txn_id, ts);
        assert (dupe == null) : "Trying to create multiple transaction handles for " + dupe;

        ts.init(txn_id, initiateTime, client_handle, base_partition, catalog_proc, procParams);
        if (debug.val)
            LOG.debug(String.format("Created new MapReduceTransaction state %s from remote partition %d", ts,
                    base_partition));
        return (ts);
    }

    // ----------------------------------------------------------------------------
    // TRANSACTION HANDLE INITIALIZATION METHODS
    // These don't normally need to be invoked from outside of this class
    // ----------------------------------------------------------------------------

    /**
     * This method allows you to reset the txnId for an already initialized LocalTransaction handle.
     * This is primarily needed for the AntiCacheManager stuff
     * @param ts
     * @param base_partition
     * @return
     */
    protected Long resetTransactionId(LocalTransaction ts, int base_partition) {
        Long oldTxnId = ts.getTransactionId();
        assert (oldTxnId != null);
        AbstractTransaction removed = this.inflight_txns.remove(oldTxnId);
        assert (ts == removed);

        Long newTxnId = this.registerTransaction(ts, base_partition);
        ts.setTransactionId(newTxnId);

        if (debug.val)
            LOG.debug(String.format("Changed txnId from %d to %d: %s", oldTxnId, newTxnId, ts));
        return (newTxnId);
    }

    /**
     * Register a new LocalTransaction handle with this HStoreSite
     * We will return a txnId that is guaranteed to be globally unique
     * @param ts
     * @param base_partition
     * @return
     */
    protected Long registerTransaction(LocalTransaction ts, int base_partition) {
        TransactionIdManager idManager = this.txnIdManagers[base_partition];
        Long txn_id = idManager.getNextUniqueTransactionId();

        // For some odd reason we sometimes get duplicate transaction ids from the VoltDB id generator
        // So we'll just double check to make sure that it's unique, and if not, we'll just ask for a new one
        LocalTransaction dupe = (LocalTransaction) this.inflight_txns.put(txn_id, ts);
        if (dupe != null) {
            // HACK!
            this.inflight_txns.put(txn_id, dupe);
            Long new_txn_id = idManager.getNextUniqueTransactionId();
            if (new_txn_id.equals(txn_id)) {
                String msg = "Duplicate transaction id #" + txn_id;
                LOG.fatal("ORIG TRANSACTION:\n" + dupe);
                LOG.fatal("NEW TRANSACTION:\n" + ts);
                Exception error = new Exception(msg);
                this.hstore_site.getCoordinator().shutdownClusterBlocking(error);
            }
            LOG.warn(String.format("Had to fix duplicate txn ids: %d -> %d", txn_id, new_txn_id));
            txn_id = new_txn_id;
            this.inflight_txns.put(txn_id, ts);
        }

        return (txn_id);
    }

    /**
     * Initialize the TransactionProfiler for the given txn handle.
     * Returns true if profiling is enabled for this txn.
     * @param ts
     * @param sysproc
     * @return
     */
    private boolean setupTransactionProfiler(LocalTransaction ts, boolean sysproc) {
        if (hstore_conf.site.txn_profiling && sysproc == false
                && this.rng.nextDouble() < hstore_conf.site.txn_profiling_sample) {
            if (ts.profiler == null) {
                ts.setProfiler(new TransactionProfiler());
            }
            ts.profiler.enableProfiling();
            ts.profiler.startTransaction(ProfileMeasurement.getTime());
            return (true);
        } else if (ts.profiler != null) {
            ts.profiler.disableProfiling();
        }
        return (false);
    }

    /**
     * Initialize the execution properties for a new transaction.
     * This is the important part where we try to figure out:
     * <ol>
     *   <li> Where should we execute the transaction (base partition).
     *   <li> What partitions the transaction will touch.
     *   <li> Whether the transaction could abort.
     *   <li> Whether the transaction is read-only.
     * </ol>
     * @param ts
     * @param client_handle
     * @param base_partition
     * @param catalog_proc
     * @param params
     * @param client_callback
     */
    private void populateProperties(LocalTransaction ts, Long txn_id, long initiateTime, long client_handle,
            int base_partition, Procedure catalog_proc, ParameterSet params,
            RpcCallback<ClientResponseImpl> client_callback) {
        final int procId = catalog_proc.getId();
        boolean predict_abortable = (hstore_conf.site.exec_no_undo_logging_all == false);
        boolean predict_readOnly = this.isReadOnly[procId];
        PartitionSet predict_partitions = null;
        EstimatorState t_state = null;

        // Setup TransactionProfiler
        if (hstore_conf.site.txn_profiling) {
            this.setupTransactionProfiler(ts, this.isSysProc[procId]);
        }

        // -------------------------------
        // SYSTEM PROCEDURES
        // -------------------------------
        if (this.isSysProc[procId]) {
            // Sysprocs can be either all partitions or single-partitioned
            // TODO: It would be nice if the client could pass us a hint when loading the tables
            // It would be just for the loading, and not regular transactions
            if (catalog_proc.getSinglepartition() && catalog_proc.getEverysite() == false) {
                predict_partitions = catalogContext.getPartitionSetSingleton(base_partition);
            } else {
                predict_partitions = catalogContext.getAllPartitionIds();
            }
        }
        // -------------------------------
        // MAPREDUCE TRANSACTIONS
        // -------------------------------
        else if (this.isMapReduce[procId]) {
            // MapReduceTransactions always need all partitions
            if (debug.val)
                LOG.debug(String.format(
                        "New request is for MapReduce %s, so it has to be " + "multi-partitioned [clientHandle=%d]",
                        catalog_proc.getName(), ts.getClientHandle()));
            predict_partitions = catalogContext.getAllPartitionIds();
        }
        // -------------------------------
        // VOLTDB @PROCINFO
        // -------------------------------
        else if (hstore_conf.site.exec_voltdb_procinfo) {
            if (debug.val)
                LOG.debug(String.format(
                        "Using the catalog information to determine whether the %s transaction "
                                + "is single-partitioned [clientHandle=%d, singleP=%s]",
                        catalog_proc.getName(), ts.getClientHandle(), catalog_proc.getSinglepartition()));
            if (catalog_proc.getSinglepartition()) {
                predict_partitions = catalogContext.getPartitionSetSingleton(base_partition);
            } else {
                predict_partitions = catalogContext.getAllPartitionIds();
            }
        }
        // -------------------------------
        // FORCE DISTRIBUTED
        // -------------------------------
        else if (hstore_conf.site.exec_force_allpartitions) {
            predict_partitions = catalogContext.getAllPartitionIds();
        }
        // -------------------------------
        // TRANSACTION ESTIMATORS
        // -------------------------------
        else if (hstore_conf.site.markov_enable || hstore_conf.site.markov_fixed) {
            // Grab the TransactionEstimator for the destination partition and figure out whether
            // this mofo is likely to be single-partition or not. Anything that we can't estimate
            // will just have to be multi-partitioned. This includes sysprocs
            TransactionEstimator t_estimator = this.t_estimators[base_partition];
            if (t_estimator == null) {
                t_estimator = this.hstore_site.getPartitionExecutor(base_partition).getTransactionEstimator();
                this.t_estimators[base_partition] = t_estimator;
            }

            try {
                if (hstore_conf.site.txn_profiling && ts.profiler != null)
                    ts.profiler.startInitEstimation();
                if (t_estimator != null) {
                    if (debug.val)
                        LOG.debug(String.format("%s - Using %s to populate txn properties [clientHandle=%d]",
                                TransactionUtil.formatTxnName(catalog_proc, txn_id),
                                t_estimator.getClass().getSimpleName(), client_handle));
                    t_state = t_estimator.startTransaction(txn_id, base_partition, catalog_proc, params.toArray());
                }

                // If there is no EstimatorState, then there is nothing we can do
                // It has to be executed as multi-partitioned
                if (t_state == null) {
                    if (debug.val) {
                        LOG.debug(String.format("%s - No %s was returned. Using default estimate.",
                                TransactionUtil.formatTxnName(catalog_proc, txn_id),
                                EstimatorState.class.getSimpleName()));
                    }
                }
                // We have a EstimatorState handle, so let's see what it says...
                else {
                    if (trace.val)
                        LOG.trace("\n" + StringBoxUtil.box(t_state.toString()));
                    Estimate t_estimate = t_state.getInitialEstimate();

                    // Bah! We didn't get back a Estimation for some reason...
                    if (t_estimate == null) {
                        if (debug.val)
                            LOG.debug(String.format("%s - No %s handle was return. Using default estimate.",
                                    TransactionUtil.formatTxnName(catalog_proc, txn_id),
                                    Estimate.class.getSimpleName()));
                    }
                    // Invalid Estimation. Stick with defaults
                    else if (t_estimate.isValid() == false) {
                        if (debug.val)
                            LOG.debug(String.format("%s - %s is marked as invalid. Using default estimate.\n%s",
                                    TransactionUtil.formatTxnName(catalog_proc, txn_id),
                                    t_estimate.getClass().getSimpleName(), t_estimate));
                    }
                    // Use Estimation to determine things
                    else {
                        if (debug.val) {
                            LOG.debug(String.format("%s - Using %s to determine if txn is single-partitioned",
                                    TransactionUtil.formatTxnName(catalog_proc, txn_id),
                                    t_estimate.getClass().getSimpleName()));
                            LOG.trace(
                                    String.format("%s %s:\n%s", TransactionUtil.formatTxnName(catalog_proc, txn_id),
                                            t_estimate.getClass().getSimpleName(), t_estimate));
                        }
                        predict_partitions = t_estimate.getTouchedPartitions(this.thresholds);
                        predict_readOnly = t_estimate.isReadOnlyAllPartitions(this.thresholds);
                        predict_abortable = (predict_partitions.size() == 1 || predict_readOnly == false
                                || t_estimate.isAbortable(this.thresholds));

                        // Check whether the TransactionEstimator *really* thinks that we should 
                        // give it updates about this txn. If the flag is false, then we'll
                        // check whether the updates are enabled in the HStoreConf parameters
                        if (t_state.shouldAllowUpdates() == false) {
                            if (predict_partitions.size() == 1) {
                                if (hstore_conf.site.markov_singlep_updates == false)
                                    t_state.disableUpdates();
                            } else if (hstore_conf.site.markov_dtxn_updates == false) {
                                t_state.disableUpdates();
                            }
                        }

                        if (debug.val && predict_partitions.isEmpty()) {
                            LOG.warn(String.format(
                                    "%s - Unexpected empty predicted %s from %s [updatesEnabled=%s]\n%s",
                                    TransactionUtil.formatTxnName(catalog_proc, txn_id),
                                    PartitionSet.class.getSimpleName(), t_estimator.getClass().getSimpleName(),
                                    t_state.isUpdatesEnabled(), t_estimate));
                            ts.setAllowEarlyPrepare(false);
                            //                            System.err.println("WROTE MARKOVGRAPH: " + ((MarkovEstimatorState)t_state).dumpMarkovGraph());
                            //                            System.err.flush();
                            //                            HStore.crashDB();
                        }
                    }
                }
            } catch (Throwable ex) {
                if (t_state != null && t_state instanceof MarkovEstimatorState) {
                    LOG.warn("WROTE MARKOVGRAPH: " + ((MarkovEstimatorState) t_state).dumpMarkovGraph());
                }
                LOG.error(String.format("Failed calculate estimate for %s request\nParameters: %s",
                        TransactionUtil.formatTxnName(catalog_proc, txn_id), params), ex);
                ex.printStackTrace();
                predict_partitions = catalogContext.getAllPartitionIds();
                predict_readOnly = false;
                predict_abortable = true;
            } finally {
                if (hstore_conf.site.txn_profiling && ts.profiler != null)
                    ts.profiler.stopInitEstimation();
            }
        }

        if (predict_partitions == null || predict_partitions.isEmpty()) {
            // -------------------------------
            // FORCE SINGLE-PARTITIONED
            // -------------------------------
            if (hstore_conf.site.exec_force_singlepartitioned) {
                if (debug.val)
                    LOG.debug(String.format("The \"Always Single-Partitioned\" flag is true. "
                            + "Marking new %s transaction as single-partitioned on partition %d [clientHandle=%d]",
                            catalog_proc.getName(), base_partition, client_handle));
                predict_partitions = catalogContext.getPartitionSetSingleton(base_partition);
            }
            // -------------------------------
            // FORCE MULTI-PARTITIONED
            // -------------------------------
            else {
                predict_partitions = catalogContext.getAllPartitionIds();
            }
        }
        assert (predict_partitions != null);
        assert (predict_partitions.isEmpty() == false);

        // -------------------------------
        // SET EXECUTION PROPERTIES
        // -------------------------------

        ts.init(txn_id, initiateTime, client_handle, base_partition, predict_partitions, predict_readOnly,
                predict_abortable, catalog_proc, params, client_callback);
        if (t_state != null)
            ts.setEstimatorState(t_state);
        if (hstore_conf.site.txn_profiling && ts.profiler != null)
            ts.profiler.setSingledPartitioned(ts.isPredictSinglePartition());

        if (debug.val) {
            Map<String, Object> m = new LinkedHashMap<String, Object>();
            m.put("ClientHandle", client_handle);
            m.put("Partitions", ts.getPredictTouchedPartitions());
            m.put("Single Partition", ts.isPredictSinglePartition());
            m.put("Read Only", ts.isPredictReadOnly());
            m.put("Abortable", ts.isPredictAbortable());
            LOG.debug(String.format("Initializing %s on partition %d\n%s", ts, base_partition,
                    StringUtil.formatMaps(m).trim()));
        }
    }

}