Level 9: Advanced Committer / PMC-Level Contributor
This is the last level before the capstone, and it is the one that changes how you
read every line of a diff. Up to now you have been a contributor: you found a JIRA,
reproduced it, fixed it, wrote a test, and attached a patch. A committer carries a
different burden. A committer is the person who says no — who blocks a green,
well-reviewed, genuinely useful patch because it allocates a TezCounters object on
a path that runs once per task per heartbeat, or because it changes the wire order of
a protobuf field that a running Hive cluster deserializes during a rolling upgrade.
The contributor optimizes for "does my change work." The committer optimizes for
"does Tez still work — under Hive, at 50,000 tasks, on the branch users actually run
in production — after my change merges and the next release ships into it."
This curriculum will not hold your hand here. By Level 9 you can build Tez, trace a
DAG from TezClient.submitDAG through DAGAppMaster to a running TaskAttemptImpl,
read the state machines, debug a shuffle failure, and argue a design on a JIRA. What
you build now is judgment — the two reflexes that every Tez committer applies to
every patch they review, including their own, plus the governance literacy that turns
a trusted contributor into a committer and then a PMC member.
Learning Objectives
By the end of Level 9 you must be able to:
- Write scheduler behavior tests the way the project does — driving a real
DagAwareYarnTaskSchedulerorYarnTaskSchedulerServiceagainst a mockedAMRMClient, using the drainable-callback harness inTestTaskSchedulerHelpers, and asserting on the exact YARN RM calls (addContainerRequest,releaseAssignedContainer,updateBlacklist) a behavior should produce. - Treat performance as a correctness property on hot paths: profile before you
change, write a JMH microbenchmark against a real class, confirm with a benchmark
DAG at scale, and bisect a regression with
git bisect run— never claim a win without before/after numbers. - Reason about backward compatibility the way an embedded engine must: Tez is the
runtime under Hive and Pig, so a change to a public
tez-apitype, a protobuf record, a serializedWritable, or aTezConfigurationdefault is a compatibility event, not a cleanup. - Review another contributor's patch the way a committer does — checking test coverage, allocation on hot paths, compatibility, and whether the fix is the minimum-impact change or a clever refactor that smuggles in risk.
- Understand how Apache governance actually works: the committer path, how release votes are run and counted, and what a PMC is accountable for.
Where Tez Sits, and Why the Bar Is Higher Here
Tez is a mature, maintenance-phase Apache project. The master branch is
versioned 1.0.0-SNAPSHOT, but the releases people run are the 0.10.x line
(0.10.3 shipped 2024‑01, 0.10.4 2024‑08, 0.10.5 2025‑05). Activity is steady
rather than explosive — on the order of ~50 commits a year, concentrated among a
handful of committers (Laszlo Bodor, Raghav Aggarwal, Ayush Saxena, and a few others
carry most of it). Verify this yourself; it is the single most important fact about
contributing here:
cd ~/src/oss-repos/tez
git log --oneline --since="2024-01-01" | wc -l # ~50/yr — a maintenance cadence
git shortlog -sn --since="2024-01-01" | head # a small, stable committer set
git tag | grep "release-0.10" # the line users actually run
That maturity raises the bar, it does not lower it. In a fast-moving project a mediocre patch gets swept along by momentum. In a maintenance-phase engine that sits underneath Hive, every merge is scrutinized because there is no churn to hide behind and the downstream blast radius is enormous. The two things committers guard hardest are exactly the two skills this level drills: performance (Tez's whole reason to exist is being faster than MapReduce — a regression is an existential bug) and backward compatibility (breaking Hive-on-Tez is the worst thing you can ship).
The Two Constant Committer Concerns
Strip away everything else and a Tez committer's review reduces to defending two invariants on every change. Internalize these; they are the lens for the whole level.
Concern 1 — Performance (it stays fast while it changes)
Tez exists because MapReduce forced every computation through HDFS between stages.
The container-reuse, pipelined-shuffle, single-AM design is a performance argument.
A path that runs once per record, once per task, once per heartbeat, or inside the
AM dispatcher is hot: a small allocation or an accidental O(n²) there compounds
across every task in every DAG on every cluster. Committers do not accept "it should
be faster" — they accept "here is the JMH microbenchmark, here is the benchmark-DAG
wall-clock, here is the git bisect that fingered the commit." The discipline is
drilled in Lab 9.2 and framed in
Stage 10: Performance Improvements.
A real example you will dissect: TEZ‑4250 (Optimise TaskImpl::getCounters)
changed TaskImpl.getCounters() to stop allocating and populating a fresh
TezCounters on every call, doing the aggregation only when speculation is actually
enabled. Read the commit before you believe the description:
git show 9aeb17b4b --stat # TEZ-4250, tez-dag TaskImpl/TaskAttempt
Concern 2 — Backward compatibility (it stays working while it changes)
Tez is not an application; it is a library other projects link and a protocol other
processes speak. A change is a compatibility event if it touches: a public
@InterfaceAudience.Public type in tez-api; a protobuf message (the AM↔task and
client↔AM RPC records); a Writable's readFields/write order; recovery/history
data on disk; or a TezConfiguration default that silently changes behavior. Get it
wrong and you do not get a red unit test — you get a Hive query that fails to
recover, or a rolling upgrade where a new AM cannot read an old task's status. The
policy framing lives in
Stage 11: Backward Compatibility and
the compatibility mindset chapter; read
both before this level's labs.
These two concerns are why committer review feels slow. It is not gatekeeping for its own sake; it is the cost of invariants that cannot be un-shipped once a release goes out and Hive upgrades into it.
Required Reading
Before the labs, read (or re-read) these, in order:
- The Scheduler deep dive — the two-layer
TaskSchedulerManager/TaskSchedulerdesign,DagAwareYarnTaskSchedulervsYarnTaskSchedulerService, and howAMSchedulerEvents flow. Lab 9.1 assumes it. - Stage 10: Performance Improvements and Stage 11: Backward Compatibility — the issue-finding workflow and the policy for each concern.
- The committer mindset, release voting, and PMC responsibilities chapters — the governance you are being measured against, not just the code.
- The patch-quality and meritocracy chapters — why the path to committer is measured in years and how judgment is demonstrated.
How These Map to the Codebase
| Concern | Where it lives | What you run |
|---|---|---|
| Scheduler unit tests | tez-dag/.../app/rm/TestDagAwareYarnTaskScheduler.java, TestTaskScheduler.java, TestTaskSchedulerManager.java | mvn test -pl tez-dag -Dtest=TestDagAwareYarnTaskScheduler |
| Scheduler mock harness | tez-dag/.../app/rm/TestTaskSchedulerHelpers.java (MockAMRMClient, setupMockTaskSchedulerContext, TaskSchedulerContextDrainable) | read it end-to-end before writing a test |
| Scheduler production code | tez-dag/.../app/rm/DagAwareYarnTaskScheduler.java, YarnTaskSchedulerService.java, TaskSchedulerManager.java | rg -n "maybePreempt|blacklistNode|moveToNextMatchingLevel" |
| Perf-sensitive classes | TaskImpl.getCounters, TezTaskID.getInstance (interning), IFile.Writer.append, DefaultSorter | git show 9aeb17b4b (TEZ-4250), git show 57c857d26 (TEZ-1526) |
| Benchmark DAG | tez-examples/ — OrderedWordCount, WordCount in local mode | mvn package -DskipTests -pl tez-examples -am |
| Compatibility surface | tez-api/ public types, *.proto records, TezConfiguration defaults | rg -n "@InterfaceAudience.Public" tez-api/src/main |
| Governance | release-pmc/ chapters; real votes on dev@tez.apache.org | subscribe to the mailing lists |
Two commands to orient yourself in the repo right now:
# The scheduler package — production and test side by side.
ls tez-dag/src/main/java/org/apache/tez/dag/app/rm/
ls tez-dag/src/test/java/org/apache/tez/dag/app/rm/
# Confirm the default scheduler the AM instantiates.
rg -n "TEZ_AM_YARN_SCHEDULER_CLASS_DEFAULT" \
tez-api/src/main/java/org/apache/tez/dag/api/TezConfiguration.java
# -> "org.apache.tez.dag.app.rm.DagAwareYarnTaskScheduler"
Key Practices at the Committer Tier
| Practice | What it means in Tez | Why it gates merge |
|---|---|---|
| Test the behavior, not the code | Drive the scheduler through real events; assert on RM calls | A test that asserts "no exception" catches nothing |
| Profile before optimizing | async-profiler / JFR on a local-mode JVM, never intuition | Most "obvious" optimizations target cold code |
| Micro + macro for perf patches | JMH proves the mechanism; a benchmark DAG proves it matters | A JMH win that does not move wall-clock is noise |
| Watch allocation on hot paths | Avoid per-task/per-record/per-heartbeat object churn (cf. TEZ‑4250) | GC pressure in the AM slows the whole dispatcher |
| One change, one number | Don't bundle two optimizations or a refactor + a fix | You can't attribute the delta otherwise |
| Guard the compatibility surface | New protobuf fields are appended, never reordered; tez-api stays source- and binary-compatible | A running Hive cluster deserializes your change |
| Minimum-impact fix | Prefer the small, targeted change over the clever rewrite | Risk in a maintenance-phase engine is expensive |
| Read the whole patch, not the diff | Tests, compatibility, perf, the JIRA it closes, the review thread | A clean diff can still corrupt DAG recovery |
Reviewing Others' Patches
By Level 9 you should be reviewing patches on JIRA and GitHub PRs, not only opening them. A committer review is a checklist applied in a fixed order, fastest-to-fail first:
- Does it build and does Yetus pass? If the pre-commit build is red, stop — comment and move on.
- Are the tests real? A scheduler fix with no test that drives the exact event sequence, or a bug fix whose test passes on the unpatched code, is not done.
- Is it on a hot path? If the diff is inside
TaskImpl,VertexImpl, the AM dispatcher,DefaultSorter,IFile, or a per-record I/O path, ask for numbers. - Does it touch the compatibility surface? Any change to
tez-api, a.proto, aWritable, recovery data, or a config default is a compatibility review. - Is it the minimum-impact fix? A three-line targeted change beats a fifty-line refactor that also "cleans things up." Ask the contributor to split them.
Disagree without being a wall. The responding-to-feedback and community-interaction chapters cover the human side — how to say "this reorders a protobuf field and will break rolling upgrade" without making the contributor feel attacked.
Deliverables
You must demonstrate all of the following before attempting the capstone:
-
Completed Lab 9.1: wrote a new scheduler
behavior test on top of the real
TestTaskSchedulerHelpersharness, driving a real scheduler against a mockedAMRMClient, and it passes undermvn test -pl tez-dag. -
Completed Lab 9.2: dissected a real perf
commit, built a benchmark DAG, bisected a regression with
git bisect run, and wrote one correct JMH microbenchmark against a real Tez class with before/after numbers (no fabricated results). - A written review (JIRA-comment style) of one real Tez change that touches a hot path or the compatibility surface, applying the fixed-order checklist above.
-
From memory: explain the difference between the two schedulers
(
DagAwareYarnTaskSchedulervsYarnTaskSchedulerService), and name three scheduler behaviors that deserve dedicated tests. - A one-paragraph statement of which subsystem you would want to own as a committer and why, naming the JIRA component and one perf or compatibility invariant it carries.
Common Mistakes
| Mistake | Consequence | Fix |
|---|---|---|
| Testing the scheduler with a full mini-cluster when a mock would do | Slow, flaky, hard to assert exact RM calls | Use the TestTaskSchedulerHelpers mock harness; reserve MiniTezCluster for integration |
| Asserting on internal state instead of RM interactions | Test passes but doesn't pin the behavior | verify(mockRMClient).addContainerRequest(...) / releaseAssignedContainer(...) |
Forgetting drainableAppCallback.drain() | Async callbacks haven't run; assertions race | Drain after every event you inject |
| Claiming a perf win with no benchmark | Reviewer cannot verify; often neutral or a regression | JMH micro + benchmark-DAG macro, before/after, same config |
| Bundling two optimizations in one patch | Cannot attribute the delta; one may regress | One change, one number |
| A JMH benchmark the JIT deletes | Reports 0 ns/op; measures nothing | Blackhole.consume(...); feed inputs via @State/@Param, never constants |
| Reordering a protobuf field "to clean it up" | Positional misread on the wire → silent corruption during upgrade | Append only; never reorder or renumber |
Changing a TezConfiguration default in a bugfix | Behavior changes silently under Hive | Treat default changes as compatibility events; call them out |
| Optimizing un-profiled code | Effort on a cold path; the real hot path untouched | Profile with async-profiler/JFR first |
How to Verify
You have completed Level 9 when every one of these passes:
cd ~/src/oss-repos/tez
# Your new scheduler test compiles and runs green.
mvn test -pl tez-dag -Dtest=TestDagAwareYarnTaskScheduler 2>&1 | tail -15
# You can point at the real harness pieces without a guide.
rg -n "class MockAMRMClient|setupMockTaskSchedulerContext|class TaskSchedulerContextDrainable" \
tez-dag/src/test/java/org/apache/tez/dag/app/rm/TestTaskSchedulerHelpers.java
# You can read the real perf case study from the diff.
git show 9aeb17b4b -- tez-dag/src/main/java/org/apache/tez/dag/app/dag/impl/TaskImpl.java
# Your benchmark DAG builds.
mvn package -DskipTests -pl tez-examples -am -q
If your scheduler test drives real events against a mocked RM and asserts on the RM calls; if you can explain TEZ‑4250 as an allocation-on-a-hot-path fix straight from the diff; and if you can bisect a regression to a single commit — you are ready.
PR Profile: Level 9 Graduate
| You can now | Evidence |
|---|---|
| Write a scheduler behavior test the project would accept | It uses the real mock harness and asserts on RM calls, and passes in tez-dag |
| Prove a performance change, not eyeball it | You produced JMH before/after and a benchmark-DAG wall-clock, and bisected the regression |
| See the compatibility implication of a diff | You can point to a reordered protobuf field or a changed default and explain the failure mode |
| Review a patch like a committer | You apply the fixed-order checklist, tests and hot-path/compat first |
| Reason about the release train | You know the 0.10.x line, how a vote is run, and what the PMC signs off |
| Name an area you'd own | You can name a JIRA component and one invariant it carries |
You are now ready for the capstone — an end-to-end contribution where you select a real JIRA, reproduce it, find the root cause, implement and test the fix (with performance and compatibility discipline), prepare the patch, and write it up. Start at the Capstone Overview. The two reflexes you built here — did I prove the performance claim? and will this break Hive on upgrade? — are the ones the capstone evaluates hardest.
Note: Most engineers never reach this tier in an open-source project, not because they can't, but because they stop at "my patch is green." The distance from contributor to committer is the distance from "it works" to "it keeps working for every Hive cluster that upgrades into it." That is the entire content of Level 9.