From Beginner to Advanced Issues
This roadmap is a deliberately ordered ladder of OpenSearch contributions. Each rung
trains one skill, depends on the rung below it, and ends at a concrete, review-ready
Pull Request on github.com/opensearch-project/OpenSearch. Skipping rungs is the
most common reason contributors stall: a shard-allocation fix without cluster-state
fluency turns into a six-month PR thread, and a release-blocker triage call without
backward-compatibility reflexes turns into a reverted commit on a release branch.
OpenSearch is not Apache. There is no JIRA, no patch attachments, no "Patch
Available" state. The source of truth is GitHub: issues and Pull Requests.
Contributions require a DCO sign-off (git commit -s), not a CLA. Every PR adds a
one-line CHANGELOG.md entry. CI runs through GitHub Actions and Jenkins; the
local pre-flight is ./gradlew precommit. If you are arriving from the Apache Tez
roadmap, unlearn the JIRA muscle memory now — the rest of this section assumes the
GitHub flow.
The stages are calibrated to the OpenSearch main / 3.x codebase. The maintenance
line is 2.x; 1.x is legacy. Where a stage references real modules it uses the exact
top-level paths you will see in a checkout:
server/ the core engine. org.opensearch.* — the bulk of what you read
libs/ low-level shared libs: libs/core (StreamInput/Output, Writeable),
libs/x-content (XContent parsing), libs/common, libs/geo
modules/ bundled-by-default modules: transport-netty4, reindex,
lang-painless, analysis-common, ingest-common, percolator
plugins/ in-repo optional plugins: analysis-icu, repository-s3, store-smb
client/ Java clients: client/rest, client/sniffer
distribution/ packaging: archives, docker, deb/rpm, distribution/tools
test/framework/ OpenSearchTestCase, OpenSearchIntegTestCase, InternalTestCluster
qa/ cross-version BWC, rolling-upgrade, mixed-cluster, packaging QA
rest-api-spec/ REST API JSON specs + shared REST-YAML tests (yamlRestTest)
benchmarks/ JMH microbenchmarks (:benchmarks)
buildSrc/, build-tools*/ Gradle build logic and custom plugins
Note: OpenSearch renamed the master node role and many APIs to cluster manager for inclusive language. The old terms survive as deprecated aliases (
masterrole,cluster.initial_master_nodes). This roadmap writes "cluster manager (formerly master)" on first use in each stage and prefers the new term thereafter.
The Twelve Stages
| # | Stage | Target skill | Prereq level | Primary subsystem | Typical PR size |
|---|---|---|---|---|---|
| 1 | Docs & tests | GitHub flow, DCO, CHANGELOG, precommit | none | docs, test/framework | 1–40 lines |
| 2 | Build, dependency & logging | version catalog, Spotless, forbidden-APIs, Log4j2 | 1 | buildSrc, server | 5–80 lines |
| 3 | Error messages & diagnostics | validate(), exception text, REST rendering | 2 | server/.../action, rest | 20–200 lines + test |
| 4 | Cluster state & coordination | update tasks, appliers, listeners | 3 | org.opensearch.cluster | 30–300 lines + test |
| 5 | Shard allocation | AllocationDecider, RoutingAllocation | 4 | cluster.routing.allocation | 40–400 lines + test |
| 6 | Indexing & engine | IndexShard, InternalEngine, Translog, seqno | 4 | index.engine, index.translog | 50–500 lines + test |
| 7 | Search & aggregations | QueryPhase, FetchPhase, InternalAggregation.reduce | 4 | search, search.aggregations | 50–500 lines + test |
| 8 | Plugin & extension compat | extension points, SPI, Plugin evolution | 4–7 | org.opensearch.plugins + plugin repos | varies; often two repos |
| 9 | Flaky tests | tests.seed, assertBusy, @AwaitsFix, races | 4 | test/framework, *IT | 20–150 lines |
| 10 | Performance | JMH, OpenSearch Benchmark, GC/allocation, BigArrays | 6 or 7 | hot paths in server | 30–300 lines + bench |
| 11 | Backward compatibility | Version-gated streams, Lucene/index BWC, qa/ | 4 | libs/core, qa/ | small code, long thread |
| 12 | Release-blocking | triage, milestones, reverts, backports | maintainer | whole-project | varies |
Stages 4–7 share a prerequisite level rather than a strict order: once you have cluster- state fluency (Stage 4) you can branch into allocation, engine, or search depending on the bug in front of you. Stage 8 sits across that band because the core↔plugin boundary touches whichever subsystem the extension point belongs to.
How OpenSearch labels map to stages
OpenSearch issues are labelled on GitHub. There is no JIRA component tree; instead a combination of type labels, status labels, and component/area labels does the same job. Learn these — every stage's issue search is built from them.
| Label | Meaning | Which stages use it |
|---|---|---|
good first issue | curated, small, mentor-friendly | 1, 2 |
help wanted | maintainers want community to take it | all stages |
untriaged | not yet labelled by a maintainer; do not start blind | filter these out until triaged |
bug | incorrect behaviour | 3–8, 11, 12 |
enhancement | new behaviour / improvement | 2, 3, 7, 10 |
flaky-test | a test that fails nondeterministically | 9 |
discuss / RFC / proposal / meta | design-level, needs a thread first | not bug-fix work; read, don't patch |
backport 2.x, backport 1.x | triggers the backport bot when merged | 11, 12 |
v3.x.0 (milestone, not a label) | targeted release | 12 |
| Component/area labels | Cluster Manager, Search:Aggregations, Storage:Durability, Indexing, Search:Performance, Plugins, distributed framework | every stage scopes by these |
The component labels drift and get renamed; do not memorise the exact strings. Instead,
open https://github.com/opensearch-project/OpenSearch/labels once and skim the current
set, then use the label:"…" filter that the relevant stage shows. Every stage also
gives at least one fallback grep to find a candidate when labels return nothing.
A canonical issue search, reused (with different labels) in every stage:
is:issue is:open label:"good first issue" label:"help wanted" no:assignee sort:updated-desc
Paste it into the GitHub issue search box on the OpenSearch repo. no:assignee is the
single most useful filter: it skips issues someone is already on.
How to use this roadmap
Pick a stage honestly
Find your rung by asking what is the largest change you have shipped to OpenSearch:
- Never landed an OpenSearch PR: start at Stage 1.
- Landed a docs PR but never touched Java in
server/: Stage 2. - Comfortable with
server/Java but never read a cluster-state update task: Stage 3. - Read
ClusterApplierServiceonce and were confused: Stage 4. - Read it twice and could draw the publish/apply flow: Stages 5–7.
- Already a maintainer (in a
MAINTAINERS.md): jump to Stages 10–12 for sharpening.
Do not jump rungs to chase a "cool" bug. A wrong Decision in DiskThresholdDecider
looks self-contained and isn't — the fix lands on RoutingAllocation plumbing and a
ClusterInfo you have never built in a test (Stage 4 prerequisite work).
One stage per PR
Resist the urge to fix two things in one PR. OpenSearch reviewers reject mixed-concern PRs almost reflexively, and the CHANGELOG entry forces you to name the single change in one line — if you cannot, the PR is doing too much. If you find a logging issue while fixing an error message, open a follow-up issue and move on. The roadmap rewards small surface area.
Always start with git log and git blame
Before touching a file, find who cares about it:
git log --oneline -n 5 -- server/src/main/java/org/opensearch/cluster/service/ClusterApplierService.java
git blame -L 200,260 server/src/main/java/org/opensearch/cluster/service/ClusterApplierService.java
The blame output tells you which maintainer last touched that region. Mention them with
@handle in your PR description (politely, once) — that is how OpenSearch routes review,
in place of CC-ing a mailing list.
Read the workflow once, then never re-explain it
Every stage from 2 onward assumes the Stage 1 mechanics: fork → branch → DCO sign-off →
CHANGELOG entry → ./gradlew precommit → PR → read CI → respond to review.
Stage 1 drills all of it. The later stages spend their words on
code, not process.
Time investment per stage
Calibrated against a contributor who has the repo checked out, can run
./gradlew localDistro and ./gradlew :server:test, and has opened at least one PR:
| Stage | First PR | Becoming fluent (≈5 PRs merged) |
|---|---|---|
| 1 | half a day | 1 week |
| 2 | 1 day | 2 weeks |
| 3 | 1–2 days | 1 month |
| 4 | 3–5 days | 2–3 months |
| 5 | 1–2 weeks | 4–6 months |
| 6 | 2–4 weeks | 6 months |
| 7 | 2–4 weeks | 6 months |
| 8 | 1–3 weeks per boundary | a year of cross-repo work |
| 9 | 1–3 days per flake | ongoing |
| 10 | weeks (perf is bench-bound) | maintainer-level skill |
| 11 | weeks (BWC review cycle) | maintainer-level skill |
| 12 | maintainer responsibility | n/a |
Success criterion per stage
Each stage is "complete" for you when:
- Stage 1: one docs/javadoc PR and one test-only PR are merged.
- Stage 2: two logging or build/dependency PRs merged without
precommitre-asks. - Stage 3: one error-message PR merged with a unit test asserting the exact text.
- Stage 4: one cluster-state fix merged with a
ClusterServiceUtils-based test. - Stage 5: one allocation fix merged, reproduced via a
RoutingAllocationunit test. - Stage 6: one engine/translog fix merged with an
InternalEngineTests-style test. - Stage 7: one search/agg fix merged with an
AggregatorTestCase/AbstractQueryTestCasetest. - Stage 8: one core change merged that you verified against a real plugin build.
- Stage 9: at least three flaky tests de-flaked (un-muted and fixed).
- Stage 10: one perf PR merged with before/after JMH or OSB numbers.
- Stage 11: one BWC-sensitive PR merged with a
Versionguard and aqa/test. - Stage 12: you have helped triage at least one release-blocking issue.
When to open a discuss issue first
For Stages 4 and above, before writing code, open (or comment on) the GitHub issue with a three-sentence plan:
I see <symptom> at <file> (grep below). My read is <cause>. I plan to <fix>, with a
regression test in <TestClass>. Anything I'm missing before I open a PR?
The maintainers will tell you within a day or two whether you are about to collide with
in-flight work or a design decision. This is the GitHub equivalent of the Apache
[DISCUSS] dev@ ping — same discipline, different venue.
What to read alongside this roadmap
What this roadmap is not
This roadmap is not a tutorial on OpenSearch itself. The deep dives
cover the architecture; the labs from Level 1 onward cover hands-on
code reading. The roadmap assumes you can already build from source, run
./gradlew :server:test, and stand up a node with ./gradlew run. If you cannot, the
prerequisite is Level 1.
It is also not a generic open-source guide. CONTRIBUTING.md, DEVELOPER_GUIDE.md, and
TESTING.md in the repo cover account setup, the DCO mechanics, and the test commands;
the roadmap assumes you have read them once.
Finally, it is not a roadmap to maintainership. Becoming a maintainer is a separate path the TSC and each repo's existing maintainers manage (see the maintainer mindset). The roadmap teaches the skills that, applied consistently, make maintainership a reasonable outcome — landing PRs is necessary, not sufficient.
How the stages interlock
Each stage builds vocabulary the next stage uses without re-explaining:
- Stage 1 teaches the PR artifact: fork, DCO, CHANGELOG,
precommit. Every later stage assumes it. - Stage 2 teaches Log4j2 idioms and the build gates. Stage 3 builds on them with the rule that every thrown exception carries actionable context.
- Stage 3 teaches you to navigate the
action/restlayer. Stage 4 follows the request into the cluster-manager service and its update tasks. - Stage 4 teaches
ClusterState, update tasks, and appliers. Stages 5–7 all read cluster state: allocation routes shards, the engine reacts to index metadata, search resolves shards from the routing table. - Stage 5 teaches
RoutingAllocationunit tests. Stage 6 teachesInternalEngineTests. Stage 7 teachesAggregatorTestCase. These three test harnesses are the workhorses of core contribution. - Stage 8 teaches the plugin boundary, attributing a break to core vs a plugin — the OpenSearch analog of the Tez "Hive-on-Tez" attribution skill.
- Stage 9 teaches deterministic testing (
tests.seed,assertBusy). Stage 10 uses that determinism as the baseline for stable benchmarks. - Stage 10 teaches measurement. Stage 11 uses measurement as evidence in BWC
decisions. Stage 11 teaches
Versiongating, which Stage 12 weighs when deciding whether an issue blocks a release or merely a backport.
Skipping a stage means skipping a vocabulary. Reviewers will notice.
Now turn to Stage 1.