blob: bc46b9f08653f6523676144fff92e6adb42d8c0a [file]
// 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.
#ifndef IMPALA_EXEC_BLOCKING_JOIN_NODE_H
#define IMPALA_EXEC_BLOCKING_JOIN_NODE_H
#include <boost/scoped_ptr.hpp>
#include <string>
#include "exec/exec-node.h"
#include "exec/join-op.h"
#include "runtime/runtime-state.h"
#include "util/promise.h"
#include "util/stopwatch.h"
namespace impala {
class JoinBuilder;
class RowBatch;
class TupleRow;
class BlockingJoinPlanNode : public PlanNode {
public:
/// Subclasses should call BlockingJoinNode::Init() and then perform any other Init()
/// work, e.g. creating expr trees.
virtual Status Init(const TPlanNode& tnode, FragmentState* state) override;
virtual Status CreateExecNode(RuntimeState* state, ExecNode** node) const override = 0;
/// Returns true if this join node will use a separate builder that is the root sink
/// of a different fragment. Otherwise the builder is owned by this node and consumes
/// input from the second child node.
/// Note: This depends on the containing subplan being initialized, and isn't accurate
/// until the whole PlanNode tree has been initialized.
bool UseSeparateBuild(const TQueryOptions& query_options) const {
return !IsInSubplan() && query_options.num_nodes != 1 && is_mt_fragment();
}
TJoinOp::type join_op() const { return join_op_; }
const RowDescriptor& probe_row_desc() const { return *children_[0]->row_descriptor_; }
const RowDescriptor& build_row_desc() const {
DCHECK(build_row_desc_ != nullptr);
return *build_row_desc_;
}
protected:
TJoinOp::type join_op_;
/// This is the same as the RowDescriptor of the build sink, if the join build is
/// separate, or the right child, if the join build is integrated into the node.
/// Owned by RuntimeState's object pool.
RowDescriptor* build_row_desc_ = nullptr;
};
/// Abstract base class for join nodes that block in Open() until all rows from the
/// right input plan tree have been processed.
///
/// BlockingJoinNode and JoinBuilder subclasses interact together to implement a blocking
/// join: the builder. Two modes are supported: an integrated join build, where the
/// JoinBuilder is owned by the BlockingJoinNode, and a separate join build, where the
/// JoinBuilder is owned by a separate build fragment co-located in the same Impala
/// daemon, and the join node synchronizes with the builder to access build-side data
/// structures.
///
/// TODO: Remove the restriction that the tuples in the join's output row have to
/// correspond to the order of its child exec nodes. See the DCHECKs in Init().
class BlockingJoinNode : public ExecNode {
public:
BlockingJoinNode(const std::string& node_name, ObjectPool* pool,
const BlockingJoinPlanNode& pnode, const DescriptorTbl& descs);
virtual ~BlockingJoinNode();
/// Subclasses should call BlockingJoinNode::Prepare() and then perform any other
/// Prepare() work, e.g. codegen.
virtual Status Prepare(RuntimeState* state);
/// Helper called by subclass's Open() implementation.
/// Calls ExecNode::Open() and initializes 'eos_' and 'probe_side_eos_'.
/// If the join build is separate, the join builder is returned in *separate_builder.
Status OpenImpl(RuntimeState* state, JoinBuilder** separate_builder);
/// Transfers resources from 'probe_batch_' to 'row_batch'.
virtual Status Reset(RuntimeState* state, RowBatch* row_batch);
/// Subclasses should close any other structures and then call
/// BlockingJoinNode::Close().
virtual void Close(RuntimeState* state);
static const char* LLVM_CLASS_NAME;
protected:
const std::string node_name_;
TJoinOp::type join_op_;
/// True if OpenImpl() was called.
bool open_called_ = false;
/// True if this join node has called WaitForInitialBuild() on the corresponding
/// separate join builder. This means that CloseFromProbe() needs to be called
/// on the builder.
bool waited_for_build_ = false;
/// True if Prepare() exited successfully. This is used to avoid deadlocks where
/// a thread calls WaitForPrepare() without having successfully finished Prepare().
bool prepare_succeeded_ = false;
/// Store in node to avoid reallocating. Cleared after build completes.
boost::scoped_ptr<RowBatch> build_batch_;
/// probe_batch_ must be cleared before calling GetNext(). The child node
/// does not initialize all tuple ptrs in the row, only the ones that it
/// is responsible for.
boost::scoped_ptr<RowBatch> probe_batch_;
bool eos_; // if true, nothing left to return in GetNext()
bool probe_side_eos_; // if true, left child has no more rows to process
int probe_batch_pos_; // current scan pos in probe_batch_
TupleRow* current_probe_row_; // The row currently being probed
bool matched_probe_; // if true, the current probe row is matched
/// Size of the TupleRow (just the Tuple ptrs) from the build (right) and probe (left)
/// sides. Set to zero if the build/probe tuples are not returned, e.g., for semi joins.
/// Cached because it is used in the hot path.
int probe_tuple_row_size_;
int build_tuple_row_size_;
/// Row assembled from all lhs and rhs tuples used for evaluating the non-equi-join
/// conjuncts for semi joins. Semi joins only return the lhs or rhs output tuples,
/// so this tuple is temporarily assembled for evaluating the conjuncts.
TupleRow* semi_join_staging_row_;
RuntimeProfile::Counter* probe_timer_; // time to process the probe (left child) batch
RuntimeProfile::Counter* probe_row_counter_; // num probe (left child) rows
/// Stopwatch that measures the build child's Open/GetNext time that overlaps
/// with the probe child Open(). Not used for separate join builds.
ConcurrentStopWatch built_probe_overlap_stop_watch_;
// True for a join node subclass if the build side can be closed before the probe
// side is opened. Should be true wherever possible to reduce resource consumption.
// E.g. this is true or PartitionedHashJoinNode because it rematerializes the build rows
// and false for NestedLoopJoinNode because it accumulates RowBatches that may reference
// memory still owned by the build-side ExecNode tree.
// Changes here must be kept in sync with the planner's resource profile computation.
// TODO: IMPALA-4179: this should always be true once resource transfer has been fixed.
virtual bool CanCloseBuildEarly() const { return false; }
/// Acquire resources for this ExecNode required for the build phase.
/// Called by BlockingJoinNode after opening child(1) succeeds and before
/// this node either waits for the separate build or calls SendBuildInputToSink().
virtual Status AcquireResourcesForBuild(RuntimeState* state) { return Status::OK(); }
/// Processes the build-side input, which should be already open, by sending it to
/// 'build_sink', and opens the probe side. Will do both concurrently if not in a
/// subplan and an extra thread token is available.
Status ProcessBuildInputAndOpenProbe(RuntimeState* state, JoinBuilder* build_sink);
/// Set up 'current_probe_row_' to point to the first input row from the left child
/// (probe side). Fills 'probe_batch_' with rows from the left child and updates
/// 'probe_batch_pos_' to the index of the row in 'probe_batch_' after
/// 'current_probe_row_'. 'probe_side_eos_' is set to true if 'probe_batch_' is the
/// last batch to be returned from the child.
/// If eos of the left child is reached and no rows are returned, 'current_probe_row_'
/// is set to NULL and 'eos_' is set to true for join modes where unmatched rows from
/// the build side do not need to be returned.
Status GetFirstProbeRow(RuntimeState* state);
/// Gives subclasses an opportunity to add debug output to the debug string printed by
/// DebugString().
virtual void AddToDebugString(int indentation_level, std::stringstream* out) const {
}
/// Subclasses should not override, use AddToDebugString() to add to the result.
virtual void DebugString(int indentation_level, std::stringstream* out) const;
/// Returns a debug string for the left child's 'row'. They have tuple ptrs that are
/// uninitialized; the left child only populates the tuple ptrs it is responsible
/// for. This function outputs just the row values and leaves the build
/// side values as NULL.
/// This is only used for debugging and outputting the left child rows before
/// doing the join.
std::string GetLeftChildRowString(TupleRow* row);
/// Write combined row, consisting of the left child's 'probe_row' and right child's
/// 'build_row' to 'out_row'.
/// This is replaced by codegen.
inline void CreateOutputRow(
TupleRow* out_row, TupleRow* probe_row, TupleRow* build_row);
/// This function calculates the "local time" spent in the join node.
///
/// The definition of "local time" is the wall clock time where this exec node is
/// processing and it is not blocked by any of its children.
///
/// The join node has two execution models:
/// 1. The entire join execution is in a single thread.
/// 2. The build(right) side is executed on a different thread while the main thread
/// opens the probe(left) side.
///
/// In case 1, the "local time" spent in this node is as simple as:
/// total_time - left child time - right child time
/// Because the entire right child time blocks the execution, the right child time is
/// the same as right_child_blocking_stop_watch_.
///
/// Case 2 is more complicated. The build thread is started first and then
/// the main thread will "open" the left child. When the left child is ready
/// (i.e. Open() returned), the main thread will wait for the build thread to finish.
/// Because the left child is always executed in the main thread, all the left child
/// time should not be counted towards the hash join "local time".
/// For the right child (the build side), the child time in the build thread up to the
/// point when the left child Open() returns should not be counted towards the hash
/// join local time. This time period completely overlaps with the left child time.
/// From the time when left child Open() returned, the right child time should be
/// removed from the total time because this is the only child that is blocking the
/// join execution.
///
/// Here's the calculation:
/// total_time - left child time - (right child time - overlapped period)
///
/// The "overlapped period" is measured by built_probe_overlap_stop_watch_. Using this
/// overlap method, both children's "Prepare" time are also excluded.
static int64_t LocalTimeCounterFn(const RuntimeProfile::Counter* total_time,
const RuntimeProfile::Counter* left_child_time,
const RuntimeProfile::Counter* right_child_time,
const ConcurrentStopWatch* child_overlap_timer);
const BlockingJoinPlanNode& plan_node() const {
return static_cast<const BlockingJoinPlanNode&>(plan_node_);
}
/// Returns true if this join node is using a separate builder that is the root sink
/// of a different fragment. Otherwise the builder is owned by this node and consumes
/// input from the second child node.
bool UseSeparateBuild(const TQueryOptions& query_options) const {
return plan_node().UseSeparateBuild(query_options);
}
/// Find the build sink for the separate builder fragment. This is only valid when
/// there is a separate builder fragment. Currently, this cannot be used during the
/// Prepare() phase, because it uses QueryState::WaitForPrepare(), which waits for
/// Prepare() to finish for all fragment instances.
Status LookupSeparateJoinBuilder(RuntimeState* state, JoinBuilder** separate_builder);
/// Shared logic to properly unregister from the builder during Close(). This should
/// be called from the Close() function for all subclasses. Calling this function is
/// crucial for avoiding delays / hangs, particularly for cases where the probe side
/// can terminate early, like the tuple cache.
///
/// This looks up the builder if needed (e.g. if this is called before starting probe),
/// then it unregisters itself from the builder. If this looked up the builder, it
/// returns it via the builder argument. This is useful for subclasses that need to
/// perform additional steps to unregister with the builder.
template<typename T>
void UnregisterFromBuilder(RuntimeState* state, T** builder) {
DCHECK(state != nullptr);
DCHECK(builder != nullptr);
bool separate_build = UseSeparateBuild(state->query_options());
if (prepare_succeeded_ && *builder == nullptr && separate_build) {
DCHECK(!waited_for_build_);
// Find the separate join builder. This can return an error status if the Prepare()
// phase failed. In that case, there is no need to notify the builder and it can
// be skipped.
JoinBuilder* separate_builder;
Status status = LookupSeparateJoinBuilder(state, &separate_builder);
if (status.ok()) {
*builder = dynamic_cast<T*>(separate_builder);
DCHECK(*builder != nullptr);
}
}
if (*builder != nullptr) {
if (separate_build && !waited_for_build_) {
// There is a separate build and we never reached the probe phase
(*builder)->CloseBeforeProbe(state);
} else {
(*builder)->CloseFromProbe(state);
waited_for_build_ = false;
}
}
}
const RowDescriptor& probe_row_desc() const {
return plan_node().probe_row_desc();
}
const RowDescriptor& build_row_desc() const {
return plan_node().build_row_desc();
}
private:
/// Helper function to process the build input by sending it to the integrated
/// JoinBuilder. The build input must already be open before calling this. ASYNC_BUILD
/// enables timers that impose some overhead but are required if the build is processed
/// concurrently with the Open() of the left child.
template <bool ASYNC_BUILD>
Status SendBuildInputToSink(RuntimeState* state, JoinBuilder* build_sink);
/// The main function for the thread that opens the build side and processes the build
/// input asynchronously. Its status is returned in the 'status' promise. If
/// 'build_sink' is non-NULL, it is used for the build. Otherwise, ProcessBuildInput()
/// is called on the subclass.
void ProcessBuildInputAsync(
RuntimeState* state, JoinBuilder* build_sink, Status* status);
};
} // namespace impala
#endif