Lab H5: Reproducing Bugs
Background
Lab H4 attributed a bug to a layer. This lab makes that attribution executable. A JIRA without a reproducer drifts; a JIRA with a clean, minimal, deterministic reproducer gets picked up. "Clean" here has a precise meaning: the smallest schema × smallest data × smallest query × smallest config-delta that still triggers the bug, runnable in under a minute — and, when the attribution is to Tez, a pure-Tez reproducer that uses no Hive at all.
That last move is the one that separates a triaged Tez JIRA from a punted one. If you
attributed a shuffle bug to tez-runtime-library in H4, the proof is a Tez DAG built
from tez-examples/tez-tests classes that reproduces the symptom with the same edge
and processor semantics Hive used — and no Hive on the classpath. This lab teaches the
minimization discipline, the Hive→pure-Tez conversion, deterministic re-runs, environment
capture, and a full worked example end to end.
The reproduction harnesses live in the testing framework world; the pure-Tez templates are the real classes you verified in H4.
Why This Lab Matters for Contributors
Reproduction is where you convert a claim into evidence. Maintainers triage by
reproducibility: a bug they can run in a minute on MiniTezCluster (or as a JUnit test)
gets fixed; a bug that needs a 200-node cluster and a customer's data gets a "please
provide a repro" and dies. The contributor who attaches the repro is the person who
gets the fix credited.
The minimization discipline also protects you: a shrunk repro often reveals the true trigger (it was the 1024-row boundary, not the data content) and sometimes reveals that your attribution was wrong. And converting a Hive repro to a pure-Tez repro forces you to state, in code, exactly which edge/processor semantics you believe are at fault — the sharpest possible attribution.
Prerequisites
- H4 complete; you have an attributed bug (real or the worked one below).
-
~/tez-srcand~/hive-srcpresent. -
Ability to build and run a Tez example (
tez-examples) and aMiniTezCluster/MiniHS2test. -
The
SET -vconfig dump from H2.
The Four Reduction Axes
Minimise along four independent axes. Reduce one, re-test, and stop the moment reduction kills the repro.
| Axis | Reduce | Stop when |
|---|---|---|
| Schema | Drop unreferenced columns; simplify types | Removing a column hides the bug |
| Data | Fewer rows; synthetic values; controlled cardinality/skew | Fewer rows hide the bug |
| Query | Drop joins, predicates, projections, clauses | Dropping a clause hides the bug |
| Config | Remove every non-default SET | Removing a SET hides the bug |
The fourth axis is the one novices forget. A repro that carries thirty SET lines from a
production hive-site.xml is not minimized — most of those settings are irrelevant, and
the two that matter are the actual trigger. Binary-search the config deltas the same way
you binary-search the data.
Step 1: Local Harness — MiniHS2 + MiniTezCluster
MiniHS2 is a single-JVM HiveServer2 running against a MiniTezCluster (single-JVM
YARN). Together they reproduce a Hive-on-Tez bug in seconds. Find the reference classes:
find ~/hive-src/itests -name "MiniHS2.java" | head
find ~/tez-src/tez-tests -name "MiniTezCluster.java"
A reproducer skeleton (Hive 3/4 style — adapt the builder to your version):
public class TestMyBugRepro {
private MiniHS2 miniHS2;
@Before public void setUp() throws Exception {
HiveConf conf = new HiveConf();
conf.set("hive.execution.engine", "tez");
miniHS2 = new MiniHS2.Builder().withConf(conf).withMiniMR().build();
miniHS2.start(new HashMap<>());
}
@After public void tearDown() throws Exception { miniHS2.stop(); }
@Test public void reproBug() throws Exception {
try (Connection c = DriverManager.getConnection(miniHS2.getJdbcURL());
Statement s = c.createStatement()) {
s.execute("CREATE TABLE t (a INT) STORED AS ORC");
s.execute("INSERT INTO t VALUES (1),(1),(2),(3),(3),(3)");
ResultSet rs = s.executeQuery("SELECT a, COUNT(*) FROM t GROUP BY a ORDER BY a");
// assert the correct behavior, or expect the exception
}
}
}
Run with mvn test -pl itests -Dtest=TestMyBugRepro. This is your Hive-side repro. If
the attribution is to Tez, you will also build the Hive-free version in Step 5.
Step 2: Reduce the Schema and Data
Schema. From a 200-column production table, keep only columns the failing query
references, then simplify their types (DECIMAL(38,10) → DECIMAL(10,2) if precision is
not load-bearing; STRUCT<...> → STRING; drop partitioning unless the partition is the
trigger). Re-test after each cut.
Data. Binary-search the row count and, at the smallest triggering count, vary the distinct-key cardinality and the skew independently:
-- Controlled synthetic data: N rows, K distinct keys.
INSERT INTO t
SELECT CAST(pos % 8 AS INT) -- 8 distinct keys
FROM (SELECT posexplode(split(space(1023),' ')) AS (pos, x)) g; -- 1024 rows
Record the exact boundary: "reproduces at ≥ 1024 rows with ≥ 8 distinct keys; does not at 1023 rows." A sharp boundary is itself a clue (a 1024 boundary smells like a vectorized batch size of 1024).
Tip: Hive's
tez-examplesshipsJoinDataGen(joindatageninExampleDriver) for generating join inputs at controlled cardinalities, andJoinValidate(joinvalidate) for asserting join correctness. Verify and reuse them rather than hand-rolling a generator:ls ~/tez-src/tez-examples/src/main/java/org/apache/tez/examples/JoinDataGen.java
Step 3: Reduce the Query and Config
Remove clauses one at a time, re-testing:
- Drop
ORDER BY— does it still fail? (If the bug is in the total-order reducer, it won't.) - Drop the aggregate.
- Drop
WHEREpredicates one by one. - Collapse a multi-table join to two tables, then tiny-on-tiny.
- Flip
hive.auto.convert.join=falseto turn a map join into a shuffle join — does the surface change? (This also tells you which edge the bug rides on, feeding Step 5.)
Then strip config: start from your SET -v dump, remove every non-default setting, and
add back only the ones whose removal hides the bug. The minimal config-delta is usually
one or two lines. A three-line query with a two-line config beats a thirty-line query
with a whole hive-site.xml.
Step 4: Deterministic Re-Runs
A repro that fails 1-in-5 is nearly useless to a maintainer. Pin the sources of nondeterminism:
| Source | Pin it with |
|---|---|
| Split boundaries / parallelism | Fix input to a single split; single container |
| Reducer count | Disable auto-parallelism; set a fixed reducer count |
| Data | Fixed Random(42) seed or inline literal VALUES |
| Scheduling races | Single-container / local mode where the bug survives it |
For Tez, tez.local.mode=true (verified in H1) removes YARN and
runs everything in one JVM — the most deterministic mode, and it makes the repro a plain
JUnit test. Verify:
grep -n "TEZ_LOCAL_MODE " \
~/tez-src/tez-api/src/main/java/org/apache/tez/dag/api/TezConfiguration.java
If the bug only reproduces with real multi-container shuffle, say so explicitly and use
MiniTezCluster with multiple containers; some bugs genuinely need it.
Step 5: Convert the Hive Repro to a Pure-Tez Repro
This is the lab's centerpiece. When H4 attributed the bug to Tez, prove it by building a
Tez DAG with the same edge and processor semantics Hive used — and no Hive. Use the
real tez-examples/tez-tests classes as templates. Verify each name before you cite it:
ls ~/tez-src/tez-examples/src/main/java/org/apache/tez/examples/HashJoinExample.java
ls ~/tez-src/tez-examples/src/main/java/org/apache/tez/examples/SortMergeJoinExample.java
ls ~/tez-src/tez-examples/src/main/java/org/apache/tez/examples/OrderedWordCount.java
ls ~/tez-src/tez-tests/src/main/java/org/apache/tez/mapreduce/examples/MRRSleepJob.java
ls ~/tez-src/tez-tests/src/test/java/org/apache/tez/test/SimpleTestDAG.java
Map your Hive DAG to its pure-Tez analog:
| Your Hive DAG | Pure-Tez template | ExampleDriver name | Semantics reproduced |
|---|---|---|---|
Map join (BROADCAST edge) | HashJoinExample | hashjoin | Broadcast input, hash-side processor |
Shuffle join (SCATTER_GATHER) | SortMergeJoinExample | sortmergejoin | Partitioned+sorted shuffle join |
| Group-by + order-by (Map→Reduce→Reduce) | MRRSleepJob or OrderedWordCount | mrrsleep / orderedwordcount | Multi-stage MRR with a final ordered stage |
| Skew / fault on shuffle | SimpleTestDAG + fetch-failure injection | (test) | Deterministic shuffle failure |
OrderedWordCount and HashJoinExample extend TezExampleBase and build their DAG with
Edge/EdgeProperty directly — read one to see how to set a BROADCAST vs
SCATTER_GATHER edge in a handful of lines:
grep -n "EdgeProperty\|DataMovementType\|OrderedPartitionedKVEdgeConfig\|UnorderedKVEdgeConfig" \
~/tez-src/tez-examples/src/main/java/org/apache/tez/examples/HashJoinExample.java
For a deterministic shuffle-failure repro, Tez has built-in fetch-failure injection. Verify the config and the format:
grep -n "shuffle.fetch.testing.errors.enable\|TEZ_RUNTIME_SHUFFLE_FETCH_ENABLE_TESTING_ERRORS" \
~/tez-src/tez-runtime-library/src/main/java/org/apache/tez/runtime/library/api/TezRuntimeConfiguration.java
The injection string is maphost#mapvertex#probability#features (e.g. host1#*#100
means fail all fetches from host1). Read the doc comment in that file for the exact
grammar. With it, a shuffle bug you attributed in H4 becomes a deterministic pure-Tez
JUnit test — the strongest artifact you can attach to a TEZ JIRA.
Step 6: Capture the Environment
A repro without the environment triplet is not reproducible. Capture:
-
The version triplet: Tez / Hive / Hadoop (
SELECT version();,hive --version,hadoop version). -
The full Hive config:
SET -v;(from H2). -
The Tez config actually in effect (the
tez.*subset ofSET -v). - The JDK version.
-
The
EXPLAIN FORMATTEDand, if it ran,EXPLAIN ANALYZE.
Bundle it:
mkdir -p ~/tez-notes/hive-h5-repro
# ddl.sql, gen.sql, query.sql, minimal-config.sql, explain.txt,
# amlog-fragment.txt, container-log-fragment.txt, PureTezRepro.java
cat > ~/tez-notes/hive-h5-repro/README.md <<'EOF'
# Repro for HIVE-XXXXX / TEZ-XXXX
Tez: 0.10.x Hive: 4.0.x Hadoop: 3.3.x JDK: 11
Setup: hive -f ddl.sql && hive -f gen.sql
Repro: hive -f minimal-config.sql -f query.sql
Pure-Tez: mvn test -Dtest=PureTezRepro (no Hive on classpath)
Expected: <oracle>. Actual: <symptom>.
EOF
tar czf ~/tez-notes/hive-h5-repro.tar.gz -C ~/tez-notes hive-h5-repro
Step 7: Worked Example End to End
The report. "A COUNT(*) group-by returns a wrong count for one key on a large ORC
table; only with vectorization on." (Illustrative — the shape is what matters, and the
numbers below are synthesized, not a real run.)
Attribute (H4). Symptom is a wrong result, not an exception. Wrong results in aggregation are almost always Hive (operator/vectorization) or the cross-stage combine. Hypothesis: Hive vectorized group-by. Provisional owner: Hive.
Minimize schema. Only the group key and COUNT(*) matter → CREATE TABLE t (a INT) STORED AS ORC;.
Minimize data. Binary-search finds the boundary at exactly one vectorized batch:
reproduces at row count = 1024, distinct keys = 1
does not reproduce at 1023, or with keys ≥ 2
Minimize config. Strip everything; two lines survive removal:
SET hive.vectorized.execution.enabled=true;
SET hive.vectorized.execution.reduce.enabled=true;
Deterministic. Single split, single reducer, inline data — fails every run.
Convert to pure-Tez? Here the boundary matters. If the wrong count reproduces with a
plain Tez MRR job (OrderedWordCount/MRRSleepJob) that has no Hive vectorization, the
bug is in Tez's shuffle/combine — re-attribute to Tez. If it only reproduces through
Hive's vectorized reducer, Hive owns it (confirmed) and the pure-Tez job runs clean. Either
way, the pure-Tez run is the deciding experiment.
Bundle per Step 6, note "Expected 1024, Actual 1023," attach, file on the project the deciding experiment pointed to.
Production-to-Test Translation
When a real production bug arrives with no reproducer, you run the pipeline in reverse:
- Get the query — from the reporter, from
hive.log, or from the HS2 operation log. - Get the schema —
SHOW CREATE TABLEon every table involved. - Get a data sample — a few thousand rows, PII anonymised (replace strings with fixed synthetic tokens, keep the distribution since skew is often the trigger).
- Get the version triplet — Tez / Hive / Hadoop.
- Reproduce — stand up
MiniHS2, load schema + sample, run the query. - If it reproduces, reduce — the four axes.
- If it does not reproduce, expand — this is the direction novices skip. Add data until you cross the trigger, add nodes/concurrency, enable the production-only settings (speculation, LLAP). A bug that needs 40 GB or three nodes is still reproducible; you just have to find the threshold and document it.
A one-day cycle for a complex production bug is fast; a one-week cycle is normal for something subtle. Either way the deliverable is the same bundle from Step 6.
Warning: Anonymise before you attach. A JIRA is public. Strip real table names, column names that leak business meaning, and any data values. Replace them with
t,a,band synthetic values — which also shrinks the repro, so anonymisation and minimisation are the same motion.
When MiniTezCluster Won't Reproduce
| Cause | Diagnostic / workaround |
|---|---|
| Multi-node shuffle; mini is single-node | Force multiple containers; some bugs still need real nodes |
| Container OOM under production memory | Configure tight memory limits on the mini cluster |
| ORC stripe layout needs big files | Generate production-size ORC |
| Concurrency (parallel DAGs) | Run parallel tests / sessions |
| Speculative execution | Enable tez.am.speculation.enabled=true |
| Real fetch failures | Use the fetch-failure injection from Step 5 instead of hoping |
If none reduce, document that the repro requires N nodes and attach the best evidence
(logs, counters, .pb.txt) you have.
Deliverables
-
A
hive-h5-repro.tar.gzbundle with DDL, data-gen, minimal query, minimal config, EXPLAIN, log fragments, and the version triplet. -
A
TestMyBugRepro.java(Hive-side) skeleton adapted to your bug. -
A
PureTezRepro.javabuilt from a verifiedtez-examples/tez-teststemplate that reproduces (or provably does not reproduce) the symptom without Hive. - A written record of the four-axis minimization: the exact schema/data/query/config boundary at which the repro flips.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Repro flaky (fails intermittently) | Nondeterminism not pinned | Fix splits, reducers, seed; try tez.local.mode=true |
| Shrinks to nothing, then won't reproduce | Over-reduced past the trigger | Add back the last thing you removed; that's load-bearing |
| Pure-Tez version needs Hive classes | You pulled in a Hive SerDe/UDF | Replace with a Tez example's input/output; keep Hive off the classpath |
| Only reproduces on the real cluster | Cluster-only code path | Document node/concurrency needs; attach cluster evidence |
MiniHS2 won't start | Version/build mismatch | Match itests build to your Hive tree; check tez.lib.uris |
| Fetch-failure injection does nothing | Testing-errors flag off | Set tez.runtime.shuffle.fetch.testing.errors.enable=true |
Stretch Goals
- Turn a shuffle bug into a deterministic test. Use
SimpleTestDAG+ fetch-failure injection to reproduce the H3 shuffle failure with 100% reliability, no Hive. - Prove a re-attribution. Take a "wrong count" bug you called Hive and try to
reproduce it with
MRRSleepJob/OrderedWordCount. If it reproduces, re-attribute to Tez and rewrite the JIRA. - Fit the repro in a JIRA comment. Compress the worked example to a DDL + 3-line query
- 2-line config that pastes into a single comment. Test that it stands alone.
- Cardinality vs skew. For a join bug, use
JoinDataGento separate "needs high cardinality" from "needs skew," and state which the bug requires.
Validation / Self-check
- Name the four reduction axes and give the stop condition for each. Which one do novices forget?
- Why is a pure-Tez reproducer the strongest attribution artifact, and which real example classes reproduce a map join and a shuffle join without Hive?
- How do you make a shuffle-failure repro deterministic instead of hoping a node dies? Name the config flag and the injection-string grammar.
- What is the "deciding experiment" in the worked example, and how does its outcome move the attribution between Hive and Tez?
- Give the environment triplet you must always capture, and one command for each.
- A repro fails 1-in-5 runs. List three sources of nondeterminism and how you pin each.
- Your repro shrank until it stopped reproducing. What does that tell you about the last thing you removed?
You can now minimize a Hive-on-Tez bug and prove its attribution with a Hive-free repro. Next, Lab H6: Writing a Diagnostic Patch covers the case where you cannot reproduce locally and must improve the diagnostics at the boundary itself.