Lab H1: SQL → DAG
Background
A user writes one line of SQL:
SELECT a, COUNT(*) FROM t GROUP BY a ORDER BY a;
Hive compiles it into a Tez DAG with three vertices and two edges, ships that DAG to a
Tez Application Master, and streams the result back. Every production Hive-on-Tez
incident you will ever debug lives somewhere on that compilation-and-execution path. If
you cannot walk the path deliberately — parser → semantic analyzer → logical operator
tree → physical plan (MapWork/ReduceWork inside a TezWork) → TezTask →
DagUtils.createVertex/createEdge → submitted DAG → running vertices — you cannot
attribute a failure to the right layer, and every later lab in this section
(H2 through H6) assumes you can.
This lab builds that muscle end to end. You stand up one reproducible Hive-on-Tez
environment, run the three flavours of EXPLAIN on a join-and-group-by query, map each
Hive operator to the TezWork unit it lands in, read the Hive source that turns that
plan into a Tez DAG, then inspect the resulting DAG as a Graphviz .dot file. You
finish with a labelled DAG diagram and a table that maps every operator to its runtime
home — a mapper vertex, a reducer vertex, or an edge.
The boundary you are learning is documented in the Hive integration deep-dive; read its opening before you start so you know the class names by role.
Why This Lab Matters for Contributors
When a Hive-on-Tez JIRA lands, the reporter almost always gives you a SQL query and a stack trace, nothing more. The committer's first move is to rebuild the DAG in their head from the SQL — how many vertices, which edges, where the shuffle is, which vertex runs which operator. A contributor who can do that in thirty seconds triages ten issues in the time it takes a novice to triage one. A contributor who cannot will file a Tez bug for a Hive planner problem, or a Hive bug for a Tez shuffle problem, and burn a maintainer's goodwill.
The specific skill is plan literacy: reading EXPLAIN output the way a systems
programmer reads assembly. It is the foundation for the attribution methodology in
H4 and the reproduction discipline in
H5. You are not learning to use Hive; you are learning
to see the DAG through the SQL.
Prerequisites
-
A working shell with
git,docker(or a local Hadoop+Hive),dot(Graphviz), and a JDK 8 or 11 onPATH. -
A local Tez checkout at
~/tez-src(this book uses the real tree at that path). Confirm withgit -C ~/tez-src log --oneline -1. -
A local Hive checkout at
~/hive-srcfor reading the compiler source. You do not build it in this lab; yougrepit. Any Hive 3.1.x or 4.0.x tree works. -
You have read the Hive integration deep-dive
intro and can name
TezTaskandDagUtilsby role. - Roughly 4 GB free RAM for the container.
Step 1: Stand Up One Canonical Environment
Pick one recipe and stick to it for all six labs. This book standardises on the official Apache Hive 4 container, because it is the single most reproducible Hive-on-Tez setup and Hive 4 ships Tez as its only execution engine (MapReduce was removed).
# Canonical recipe: Apache Hive 4.0.1, embedded metastore, Tez engine.
docker run -d --name hive4 -p 10000:10000 -p 10002:10002 \
--env SERVICE_NAME=hiveserver2 \
apache/hive:4.0.1
# Wait for HiveServer2, then open beeline inside the container:
docker exec -it hive4 beeline -u 'jdbc:hive2://localhost:10000/'
Note: Pin the version.
apache/hive:4.0.1is explicit and reproducible;apache/hive:latestdrifts and will silently change your operator names and edge types between runs. If you are on Hive 3.x from a release tarball instead, the labs still work — the deltas are called out where they matter, and Hive 3 keeps MapReduce as an option, so you mustSET hive.execution.engine=tez;explicitly.
Adaptation for a real pseudo-distributed cluster. The container runs Tez tasks in
Hive's default embedded fashion; to inspect a DAG as it runs inside YARN (Tez UI,
yarn logs, AM-side .dot files) you need Tez pointed at a YARN ResourceManager. That
is a heavier setup and you will do it in H2 and
H3; for H1, whatever mode the container uses is fine, because
the DAG structure is identical either way. Confirm which mode you are in — it changes
nothing about this lab but you should always know:
SET tez.local.mode;
Verify the real config key exists in the Tez tree so you trust the name:
grep -n 'TEZ_LOCAL_MODE ' \
~/tez-src/tez-api/src/main/java/org/apache/tez/dag/api/TezConfiguration.java
# public static final String TEZ_LOCAL_MODE = TEZ_PREFIX + "local.mode";
Step 2: Create the Study Tables
Use a join-and-group-by query so the DAG has more than one edge type. Two tiny tables:
CREATE TABLE t (a INT, b STRING) STORED AS ORC;
CREATE TABLE d (a INT, label STRING) STORED AS ORC; -- small dimension
INSERT INTO t VALUES (1,'x'),(1,'y'),(2,'z'),(3,'p'),(3,'q'),(3,'r');
INSERT INTO d VALUES (1,'one'),(2,'two'),(3,'three');
The query under study, exercising a join and a group-by and an order-by:
SELECT t.a, d.label, COUNT(*) AS c
FROM t JOIN d ON t.a = d.a
GROUP BY t.a, d.label
ORDER BY t.a;
Because d is tiny, Hive's cost-based optimiser will normally choose a map join
(broadcast the small side) for the join, a shuffle for the group-by, and a
single-reducer stage for the total-order ORDER BY. That gives you all three edge
shapes in one DAG — exactly what you want to learn to recognise.
Step 3: The Three EXPLAINs
Hive gives you three progressively more expensive views of the same query. Run all three and save each.
3a. EXPLAIN — the planned physical DAG
EXPLAIN
SELECT t.a, d.label, COUNT(*) AS c
FROM t JOIN d ON t.a = d.a
GROUP BY t.a, d.label
ORDER BY t.a;
Look for the Tez stage and its Edges: and Vertices: blocks. You will see vertex
names like Map 1, Map 2, Reducer 3, Reducer 4 and edge annotations like
BROADCAST_EDGE (map join) and SIMPLE_EDGE (shuffle). Write down the vertex/edge
topology; this is the plan.
3b. EXPLAIN FORMATTED — the machine-readable plan
EXPLAIN FORMATTED
SELECT t.a, d.label, COUNT(*) AS c
FROM t JOIN d ON t.a = d.a GROUP BY t.a, d.label ORDER BY t.a;
This emits JSON with the same content plus explicit edge type fields and per-vertex
operator trees with row-schema annotations. It is what you parse programmatically and
what you attach to a JIRA. You dissect this in depth in H2.
3c. EXPLAIN ANALYZE — the plan annotated with actual runtime counts
EXPLAIN ANALYZE
SELECT t.a, d.label, COUNT(*) AS c
FROM t JOIN d ON t.a = d.a GROUP BY t.a, d.label ORDER BY t.a;
EXPLAIN ANALYZE actually runs the query and annotates each operator with the rows
it really emitted (shown against the optimiser's estimate). This is your single best
tool for spotting bad cardinality estimates — the root cause of a large class of
"Hive-on-Tez is slow" reports. When the estimate says 5 rows and the runtime says 5
million, the join order the optimiser chose was wrong, and that is a Hive problem, not
a Tez one.
Tip: Save all three. The habit of capturing
EXPLAIN,EXPLAIN FORMATTED, andEXPLAIN ANALYZEfor any query you are debugging is the cheapest diagnostic you have, and the three together let you separate planning faults (bad plan) from execution faults (bad DAG runtime).
Also capture the two structural views for completeness:
EXPLAIN AST SELECT t.a, d.label, COUNT(*) FROM t JOIN d ON t.a=d.a GROUP BY t.a,d.label ORDER BY t.a;
EXPLAIN CBO SELECT t.a, d.label, COUNT(*) FROM t JOIN d ON t.a=d.a GROUP BY t.a,d.label ORDER BY t.a;
EXPLAIN AST shows the parse tree (a Lisp-style tree of TOK_* nodes); EXPLAIN CBO
shows the Calcite relational algebra after cost-based optimisation, before it is lowered
into Hive operators.
Step 4: Operator Tree → TezWork
Read the operator symbols in the EXPLAIN output. The common ones:
| Symbol | Operator | Role |
|---|---|---|
TS | TableScan | Read rows from a table |
SEL | Select | Project / evaluate expressions |
FIL | Filter | Apply a WHERE predicate |
GBY | GroupBy | Aggregate (partial on map side, final on reduce side) |
RS | ReduceSink | The shuffle boundary — emits partitioned, sorted key-value pairs |
MAPJOIN | MapJoin | Hash join with the small side broadcast in memory |
MERGEJOIN | MergeJoin | Sort-merge join across a shuffle |
FS | FileSink | Write the output |
The physical planner packs these operators into Work units — MapWork,
ReduceWork, MergeJoinWork — and the whole graph of Work units is a TezWork.
TezWork is the Hive-side representation of the DAG, one step before it becomes a Tez
DAG object. Read its shape in the Hive source (name it by role, let the grep find the
current file):
grep -rln "class TezWork" ~/hive-src/ql/src/java/
grep -rln "class MapWork" ~/hive-src/ql/src/java/
grep -rln "class ReduceWork" ~/hive-src/ql/src/java/
grep -rln "class TezCompiler" ~/hive-src/ql/src/java/
TezCompiler is the physical compiler that produces the TezWork; skim it to see the
operator-tree-to-work assignment happen. For our query, the Work units are:
| Work | Purpose | Operators inside |
|---|---|---|
MapWork "Map 1" | Read t, apply the map-join with broadcast d, partial-aggregate | TS → MAPJOIN → GBY → RS |
MapWork "Map 2" | Read d, feed the broadcast hash table | TS → RS (to the broadcast) |
ReduceWork "Reducer 3" | Final aggregate by (a, label), prepare for total sort | GBY → RS |
ReduceWork "Reducer 4" | Total-order sort by a, write output | SEL → FS |
Note: The exact vertex numbering and whether the small side gets its own
Mapvertex depends on the Hive version and the CBO decision. Record your topology from yourEXPLAIN; do not trust this table over your own output. If your build chose a shuffle join instead of a map join (e.g. stats were missing), you will see aMERGEJOINand aSIMPLE_EDGEwhere this table showsMAPJOINand a broadcast.
Step 5: TezTask — The Boundary
TezTask is the one Hive class that executes a TezWork on Tez. Find it and read the
entry point by role:
grep -rln "class TezTask" ~/hive-src/ql/src/java/
grep -n "public int execute" $(grep -rln "class TezTask" ~/hive-src/ql/src/java/)
TezTask.execute(...) does, in order:
- Acquire a
TezSessionState(a pooled Tez session = one Tez AM) viaTezSessionPoolManager. - Build a Tez
DAGfrom theTezWorkusingDagUtils. - Submit the
DAGthrough the session (TezSessionState→TezClient.submitDAG). - Monitor the returned
DAGClientto completion. - Surface counters and diagnostics back to the Hive console.
Find the DAG-building call (name varies by version — createDag, build, etc.):
grep -n "DagUtils\|dagUtils" $(grep -rln "class TezTask" ~/hive-src/ql/src/java/)
Above this call, Hive owns everything (parse, analyse, plan). Below it,
TezClient.submitDAG in tez-api takes over and Tez owns execution. That single call
site is the entire integration surface — memorise it. Verify the Tez side of the
boundary in your Tez tree:
grep -n "public synchronized DAGClient submitDAG" \
~/tez-src/tez-api/src/main/java/org/apache/tez/client/TezClient.java
Step 6: DagUtils.createVertex / createEdge
DagUtils is where a Hive Work becomes a Tez Vertex and a Hive edge becomes a Tez
Edge. Read the two methods by role:
grep -rln "class DagUtils" ~/hive-src/ql/src/java/
grep -n "createVertex\|public Vertex " $(grep -rln "class DagUtils" ~/hive-src/ql/src/java/)
grep -n "createEdge\|EdgeProperty" $(grep -rln "class DagUtils" ~/hive-src/ql/src/java/)
For our query, DagUtils produces:
Hive Work | Tez Vertex | Processor descriptor |
|---|---|---|
MapWork "Map 1" | Vertex "Map 1" | MapTezProcessor |
MapWork "Map 2" | Vertex "Map 2" | MapTezProcessor |
ReduceWork "Reducer 3" | Vertex "Reducer 3" | ReduceTezProcessor |
ReduceWork "Reducer 4" | Vertex "Reducer 4" | ReduceTezProcessor |
And the edges, whose EdgeProperty uses a Tez DataMovementType. Verify the four
movement types exist in your Tez tree — these are the vocabulary of every DAG you will
ever read:
grep -n "ONE_TO_ONE\|BROADCAST\|SCATTER_GATHER\|CUSTOM" \
~/tez-src/tez-api/src/main/java/org/apache/tez/dag/api/EdgeProperty.java
| From | To | DataMovementType | Why |
|---|---|---|---|
| Map 2 | Map 1 | BROADCAST | Map join: small side broadcast to every map task |
| Map 1 | Reducer 3 | SCATTER_GATHER | Group-by shuffle: partition on (a, label) |
| Reducer 3 | Reducer 4 | SCATTER_GATHER | Total-order sort: single downstream reducer |
The "single reducer for ORDER BY" is Hive forcing Reducer 4 to parallelism 1 (with no
LIMIT), so all rows land on one task and sort globally.
Step 7: Inspect the Resulting DAG
Reading source tells you what should be built. Now capture what was built. Tez can
dump every DAG it runs as a Graphviz .dot file, gated by one config flag. Verify the
real key and default in your Tez tree:
grep -n "TEZ_GENERATE_DEBUG_ARTIFACTS" \
~/tez-src/tez-api/src/main/java/org/apache/tez/dag/api/TezConfiguration.java
# tez.generate.debug.artifacts (default false)
Read where the AM acts on it — this is the code that writes the .dot and the
tez-dag.pb.txt plan text file:
grep -n "writeDebugArtifacts\|generateDAGVizFile\|writePBTextFile" \
~/tez-src/tez-dag/src/main/java/org/apache/tez/dag/app/DAGAppMaster.java
grep -n "TEZ_PB_PLAN_TEXT_NAME" \
~/tez-src/tez-api/src/main/java/org/apache/tez/dag/api/TezConstants.java
# TEZ_PB_PLAN_TEXT_NAME = "tez-dag.pb.txt"
Turn it on and run the query:
SET tez.generate.debug.artifacts=true;
SELECT t.a, d.label, COUNT(*) AS c
FROM t JOIN d ON t.a = d.a GROUP BY t.a, d.label ORDER BY t.a;
The AM writes <dagId>_priority.dot (and a <dagId>-tez-dag.pb.txt plan dump) into one
of its log dirs. On the container / AM node:
find / -name '*_priority.dot' 2>/dev/null
dot -Tpng <dagId>_priority.dot -o ~/tez-notes/hive-h1-dag.png
Warning: The file is written from the AM, into the AM's log directory — on a real cluster that is the AM's node, not your client. You will chase this again in H2; for now, if you are in local/embedded mode the file is on your machine.
Also turn on the runtime summary, which prints the executed topology and per-vertex counters after the query:
SET hive.exec.print.summary=true;
SELECT t.a, d.label, COUNT(*) AS c
FROM t JOIN d ON t.a = d.a GROUP BY t.a, d.label ORDER BY t.a;
Confirm the vertex names and count in the summary match the .dot and match your
EXPLAIN. Any mismatch is a runtime decision (auto-parallelism, CBO re-planning) and is
exactly the kind of thing H2 teaches you to chase.
Step 8: The Operator → Runtime-Home Table
The payoff. Fill in, for your captured DAG, where each operator actually runs. This is the mental model you carry into every later lab.
| Hive operator | Runtime home | Edge into/out of it |
|---|---|---|
TS on t | Map 1 vertex, a task per input split | — (reads ORC directly) |
TS on d | Map 2 vertex | — |
MAPJOIN | Map 1 vertex (in-memory hash table) | fed by a BROADCAST edge from Map 2 |
GBY (partial) | Map 1 vertex | out via RS |
RS (group key) | boundary at end of Map 1 | SCATTER_GATHER edge → Reducer 3 |
GBY (final) | Reducer 3 vertex | — |
RS (sort key) | boundary at end of Reducer 3 | SCATTER_GATHER edge → Reducer 4 |
SEL / FS | Reducer 4 vertex (parallelism 1) | writes result |
Save the mermaid diagram of your actual topology:
flowchart TD
M2["Map 2<br/>MapTezProcessor<br/>TS(d) → RS"]
M1["Map 1<br/>MapTezProcessor<br/>TS(t) → MAPJOIN → GBY(partial) → RS"]
R3["Reducer 3<br/>ReduceTezProcessor<br/>GBY(final) → RS"]
R4["Reducer 4<br/>ReduceTezProcessor<br/>SEL → FS (parallelism 1)"]
M2 -->|"BROADCAST"| M1
M1 -->|"SCATTER_GATHER (partition on a,label)"| R3
R3 -->|"SCATTER_GATHER (sort on a)"| R4
Deliverables
-
~/tez-notes/hive-h1-explain.txt—EXPLAIN,EXPLAIN FORMATTED,EXPLAIN ANALYZE,EXPLAIN AST,EXPLAIN CBOall saved. -
~/tez-notes/hive-h1-dag.pngrendered from the real_priority.dot. - The operator → runtime-home table above, filled with your topology.
- The mermaid DAG diagram of your actual run.
-
The
grepresults forcreateVertex/createEdgeinDagUtils, saved. -
One sentence per edge stating why Hive chose that
DataMovementType.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
EXPLAIN shows a MERGEJOIN, not MAPJOIN | Stats missing, so CBO didn't know d was small | ANALYZE TABLE d COMPUTE STATISTICS; then re-EXPLAIN |
No .dot file appears | tez.generate.debug.artifacts not set, or file is on the AM node | Re-check the SET; on a cluster look on the AM host's log dir |
SET hive.execution.engine returns mr | Hive 3 default | SET hive.execution.engine=tez; (Hive 4 is always Tez) |
| Only two vertices for the group-by+order-by | CBO merged the final-agg and sort stages | Record it; it's a valid plan, note the delta |
EXPLAIN ANALYZE errors "not supported" | Old Hive build | Use EXPLAIN + hive.exec.print.summary=true for actual counts |
| Container never becomes ready | Not enough RAM / slow pull | docker logs hive4; give the container 4 GB |
Stretch Goals
- Break the map join. Run
SET hive.auto.convert.join=false;and re-EXPLAIN. Watch theBROADCASTedge become aSCATTER_GATHERand a second reducer appear. Explain the new topology in one paragraph. - Add a
LIMIT. AppendLIMIT 2to the query and re-EXPLAIN. Does the total-order reducer stay at parallelism 1? What did Hive change about the sort? - Cross-check the plan text. Open the
<dagId>-tez-dag.pb.txtthe AM wrote and match eachVertexPlan'sprocessorDescriptorclass name to the table in Step 6. - Trace one operator into Tez. For the
RSat the end of Map 1, find in Hive'sDagUtilswhich Tez output class it becomes (an ordered-partitioned KV output). Grep the class name in~/tez-src/tez-runtime-libraryto confirm it exists.
Validation / Self-check
- Name the exact Hive class and method that is the last Hive code to run before Tez owns execution, and the Tez method it calls.
- For our query, how many vertices and edges does the DAG have, and what
DataMovementTypeis each edge? Why did Hive pick each one? - Which Hive operator becomes a Tez
Edgerather than living inside aProcessor, and why is that the natural boundary? - What does
EXPLAIN ANALYZEtell you thatEXPLAIN FORMATTEDcannot, and what class of Hive bug does that difference expose? - Where does the
.dotfile get written, which config flag enables it, and why might it not be on the machine you ran the query from? - If the same SQL produced a
MERGEJOINinstead of aMAPJOIN, what one fact about the tables changed, and how would the DAG's edges change? - Point (by
grep) to the Hive source that assigns operators toMapWorkvsReduceWork. Name the compiler class.
You can now trace any Hive query from SQL to a running Tez topology. Next, Lab H2: Inspecting the Hive-Emitted DAG turns this into a production capture-and-inspect discipline.