16-Week Plan: From Curious Reader to OpenSearch Maintainer Candidate

This is a 16-week, ~10-hour-per-week plan that maps the curriculum (Levels 1–9 plus a 2-week capstone) onto a calendar. Each week states:

  • Reading — concrete OpenSearch source files. Open them; do not just skim diagrams.
  • Hands-on — what you must build/run on your machine (./gradlew, curl, git).
  • GitHub issue practice — real search queries against opensearch-project/OpenSearch that surface beginner-appropriate issues. OpenSearch tracks everything on GitHub, not JIRA.
  • Labs — the curriculum labs you must complete.
  • Exit checkpoint — concrete deliverables. If you cannot produce them, repeat the week.

The plan assumes you have ~/src/OpenSearch checked out, ./gradlew run serving :9200, a passing ./gradlew :server:test --tests "...ClusterStateTests", and a working JDK 21 / Git environment from the prerequisites and the warm-up.

How to run a GitHub issue query: paste it into the search box at https://github.com/opensearch-project/OpenSearch/issues, or build a URL: https://github.com/opensearch-project/OpenSearch/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22. Labels with spaces must be quoted: label:"good first issue".


Weeks 1–2: Level 1 — Lucene & OpenSearch Foundation

Week 1 — Build, run, and the data model

Reading

  • README.md, CONTRIBUTING.md, DEVELOPER_GUIDE.md, TESTING.md in the repo root.
  • server/src/main/java/org/opensearch/node/Node.java — how a node wires up every service (read Node(...) constructor top-to-bottom; do not memorize, just map the territory).
  • server/src/main/java/org/opensearch/index/shard/IndexShard.java — read the class javadoc and the field declarations only this week.

Hands-on

  • Build: ./gradlew localDistro then ./gradlew run.
  • Reproduce all five warm-up scenarios against :9200 (bulk ingest, match, date_histogram, multi-node, snapshot).
  • Inspect a real shard on disk: find the nodes/0/indices data dir under the ./gradlew run working tree and list the Lucene segment files.

GitHub issue practice

is:issue is:open label:"good first issue"
is:issue is:open label:"good first issue" label:"untriaged"
is:issue is:open label:documentation

Labs

Exit checkpoint

  • You can build from a clean checkout and serve :9200.
  • You can state the OpenSearch-vs-Lucene boundary (segments, DocValues, translog, cluster state).
  • You have one good first issue open in a browser tab and have read it end-to-end (description + every comment).

Week 2 — First cluster, first index, Lucene underneath

Reading

  • server/src/main/java/org/opensearch/index/engine/InternalEngine.java — class javadoc + the index(...) method signature.
  • server/src/main/java/org/opensearch/index/mapper/ — skim MapperService, DocumentMapper, KeywordFieldMapper, TextFieldMapper.

Hands-on

  • Create an index with explicit mappings; index docs; run _search, _explain, and _analyze.
  • ./gradlew :server:test --tests "org.opensearch.index.engine.InternalEngineTests" and read 5 of the test methods it ran.

GitHub issue practice

is:issue is:open label:"good first issue" label:"Indexing"
is:issue is:open "analyzer" label:enhancement

Labs

Exit checkpoint

  • You can write a mapping and explain each field type's Lucene representation.
  • You can run a single test method by name without consulting docs.
  • You can describe what _analyze returns and why.

Weeks 3–4: Level 2 — Contributor Onboarding (GitHub, PRs, DCO)

Week 3 — Repository structure and the contribution flow

Reading

  • MAINTAINERS.md, CHANGELOG.md, .github/pull_request_template.md.
  • Top-level build.gradle and settings.gradle — how modules are declared.
  • The SPDX header on any file in server/src/main/java/org/opensearch/.

Hands-on

  • Fork the repo on GitHub; add your fork as a remote; create a branch.
  • Configure DCO: git config user.name/user.email, then git commit -s and verify the Signed-off-by: trailer.
  • Run ./gradlew precommit and read what it checks (checkstyle, forbidden APIs, license headers).

GitHub issue practice

is:pr is:merged label:"good first issue"
is:issue is:open label:"help wanted" label:documentation
is:pr is:open review:required

Labs

Exit checkpoint

  • You can explain the full PR lifecycle: fork → branch → commit -s → CHANGELOG entry → PR → DCO/precommit checks → review → backport label.
  • You can run ./gradlew precommit and interpret a failure.
  • You can name what every PR must include (test, CHANGELOG entry, sign-off).

Week 4 — Tests as documentation; your first fix

Reading

  • test/framework/src/main/java/org/opensearch/test/OpenSearchTestCase.java (skim).
  • One real merged "good first issue" PR diff (open it on GitHub; read the conversation too).

Hands-on

  • Add a trivial no-op test to server and run it via ./gradlew :server:test --tests "...".
  • Reproduce a documentation or error-message issue locally.

GitHub issue practice

is:issue is:open label:"good first issue" -label:"untriaged"
is:issue is:open label:bug label:"good first issue" sort:created-desc

Labs

Exit checkpoint

  • You can produce a signed, CHANGELOG-bearing commit that passes ./gradlew precommit.
  • You can review a real PR and list three things its author should improve.
  • You have drafted (not necessarily submitted) a comment on a real good first issue.

Weeks 5–6: Level 3 — Architecture (REST → Transport → Action)

Week 5 — The request path

Reading

  • server/src/main/java/org/opensearch/rest/RestController.java.
  • server/src/main/java/org/opensearch/rest/action/search/RestSearchAction.java.
  • server/src/main/java/org/opensearch/action/ActionModule.java — how actions are registered.

Hands-on

  • Start ./gradlew run; send a _search and trace it: set a breakpoint in RestSearchAction and in TransportSearchAction, step from HTTP to action.
  • grep -rn "registerHandler\|registerAction" server/src/main/java/org/opensearch/action/ActionModule.java.

GitHub issue practice

is:issue is:open label:"good first issue" label:">enhancement"
is:issue is:open "RestController" in:title,body
is:pr is:merged path:server/src/main/java/org/opensearch/rest

Labs

Exit checkpoint

  • You can name the chain RestControllerRestHandlerNodeClient.executeTransportAction and cite each file.
  • You can explain the difference between HandledTransportAction, TransportSingleShardAction, TransportBroadcastAction, TransportReplicationAction, and TransportClusterManagerNodeAction.

Week 6 — Transport, threadpools, and a custom REST action

Reading

  • server/src/main/java/org/opensearch/transport/TransportService.java.
  • libs/core/src/main/java/org/opensearch/core/common/io/stream/StreamInput.java / StreamOutput.java and Writeable.
  • server/src/main/java/org/opensearch/threadpool/ThreadPool.java (the Names constants).

Hands-on

  • grep the ThreadPool.Names constants and find where SEARCH and WRITE pools are used.
  • Build the custom REST action plugin in Lab 3.3 and load it into ./gradlew run.

GitHub issue practice

is:issue is:open "transport" label:bug
is:issue is:open label:"good first issue" "thread pool" in:title,body

Labs

Exit checkpoint

  • You can explain how a TransportRequest is serialized (Writeable/StreamOutput, NamedWriteableRegistry).
  • You have a working custom REST action plugin that responds on :9200.
  • You can name three thread pools and the work each handles.

Weeks 7–8: Level 4 — Cluster Coordination and State

Week 7 — The Coordinator and cluster-manager election

Reading

  • server/src/main/java/org/opensearch/cluster/coordination/Coordinator.java.
  • server/src/main/java/org/opensearch/cluster/coordination/CoordinationState.java, JoinHelper.java, PreVoteCollector.java.
  • server/src/main/java/org/opensearch/cluster/ClusterState.javaMetadata, RoutingTable, DiscoveryNodes, ClusterBlocks.

Hands-on

  • Run ./gradlew run -PnumNodes=3. Inspect _cluster/state, _cat/nodes, _cat/shards.
  • Kill the cluster-manager node; watch a re-election; confirm via _cluster/health.

GitHub issue practice

is:issue is:open "cluster manager" in:title,body
is:issue is:open label:"good first issue" "cluster state" in:title,body
is:issue is:open "election" label:bug

Labs

Exit checkpoint

  • You can explain the two-phase publish/commit and which class publishes (PublicationTransportHandler) vs. applies (ClusterApplierService).
  • You can name the four components of ClusterState and what each holds.
  • You can describe what happens to writes during a cluster-manager re-election.

Week 8 — Cluster state updates, applier model, and allocation

Reading

  • server/src/main/java/org/opensearch/cluster/service/MasterService.java (cluster-manager service), ClusterApplierService.java, ClusterService.java.
  • server/src/main/java/org/opensearch/cluster/routing/allocation/AllocationService.java.
  • server/src/main/java/org/opensearch/cluster/routing/allocation/decider/ — pick 4 deciders.

Hands-on

  • Force an UNASSIGNED shard (set number_of_replicas higher than node count); inspect with _cluster/allocation/explain.
  • Implement the custom ClusterStateListener (Lab 4.3); log every state version change.

GitHub issue practice

is:issue is:open "allocation" label:bug
is:issue is:open "AllocationDecider" in:title,body
is:issue is:open label:"good first issue" "unassigned shard" in:title,body

Labs

Exit checkpoint

  • You can read _cluster/allocation/explain output and name the decider that blocked an allocation.
  • You can write a ClusterStateListener and explain when it fires.
  • You can trace a ClusterStateUpdateTask through MasterService to a published state.

Weeks 9–10: Level 5 — Testing and Debugging

Week 9 — The test framework and InternalTestCluster

Reading

  • test/framework/src/main/java/org/opensearch/test/OpenSearchIntegTestCase.java.
  • test/framework/src/main/java/org/opensearch/test/InternalTestCluster.java.
  • test/framework/src/main/java/org/opensearch/test/OpenSearchSingleNodeTestCase.java.

Hands-on

  • Run ./gradlew :server:internalClusterTest --tests "org.opensearch.cluster.*".
  • Reproduce a randomized failure deliberately: run a test, capture the -Dtests.seed=... line, re-run with that seed.

GitHub issue practice

is:issue is:open label:flaky-test
is:issue is:open label:flaky-test label:"untriaged"
is:issue is:open label:flaky-test sort:created-desc

Labs

Exit checkpoint

  • You can write an OpenSearchIntegTestCase test that spins up a multi-node cluster in-JVM.
  • You can reproduce a randomized failure from its printed seed.
  • You can distinguish a unit test from an *IT integration test by what it extends.

Week 10 — Flaky tests and serialization round-trips

Reading

  • test/framework/src/main/java/org/opensearch/test/AbstractWireSerializingTestCase.java.
  • An existing *Tests class using @AwaitsFix (grep for it).

Hands-on

  • Pick a flaky-test-labeled issue; reproduce the failure by running its test on a loop with random seeds.
  • Write an AbstractWireSerializingTestCase round-trip for a Writeable of your choice.

GitHub issue practice

is:issue is:open label:flaky-test no:assignee
is:pr is:merged "AwaitsFix" in:title,body
is:issue is:open "reproduce" label:flaky-test

Labs

Exit checkpoint

  • You can mute a flaky test correctly (@AwaitsFix(bugUrl=...), never @Ignore).
  • You can write a wire-serialization round-trip test for BWC confidence.
  • You can take a flaky-test issue from repro to a candidate fix.

Weeks 11–12: Level 6 — Indexing Path and Storage Engine

Week 11 — The write path to Lucene

Reading

  • server/src/main/java/org/opensearch/action/bulk/TransportShardBulkAction.java.
  • server/src/main/java/org/opensearch/index/shard/IndexShard.javaapplyIndexOperationOnPrimary(...).
  • server/src/main/java/org/opensearch/index/engine/InternalEngine.javaindex(...).

Hands-on

  • Index a doc with a breakpoint in IndexShard.applyIndexOperationOnPrimary and step into InternalEngine.index → Lucene IndexWriter.
  • Force a _refresh and a _flush; observe segment file changes on disk.

GitHub issue practice

is:issue is:open "InternalEngine" in:title,body
is:issue is:open label:bug "translog" in:title,body
is:issue is:open label:"good first issue" "indexing" in:title,body

Labs

Exit checkpoint

  • You can walk indexing end-to-end: action → IndexShardInternalEngineIndexWriter + Translog.
  • You can explain refresh (visibility) vs. flush (durability) vs. merge (cleanup).
  • You can describe how a write replicates (document vs. segment replication).

Week 12 — Mapping, analysis, and a custom analyzer

Reading

  • server/src/main/java/org/opensearch/index/mapper/MapperService.java, DocumentMapper.java, TextFieldMapper.java.
  • modules/analysis-common/src/main/java/org/opensearch/analysis/common/ — pick two filters.

Hands-on

  • Build the custom analyzer plugin (Lab 6.3); register it; verify with _analyze.
  • grep for an existing TokenFilterFactory and mirror its structure.

GitHub issue practice

is:issue is:open "analyzer" label:enhancement
is:issue is:open label:"good first issue" "tokenizer" in:title,body
is:pr is:merged path:modules/analysis-common

Labs

Exit checkpoint

  • You can implement an AnalysisPlugin exposing a custom filter and prove it via _analyze.
  • You can explain how a mapping field type chooses its analyzer and Lucene field.

Weeks 13–14: Level 7 — Search Path and Aggregations

Week 13 — Query and fetch phases

Reading

  • server/src/main/java/org/opensearch/action/search/TransportSearchAction.java.
  • server/src/main/java/org/opensearch/search/SearchService.java, search/query/QueryPhase.java, search/fetch/FetchPhase.java.
  • server/src/main/java/org/opensearch/action/search/SearchPhaseController.java (the reduce).

Hands-on

  • Trace a _search from TransportSearchAction fan-out to per-shard QueryPhase to the coordinating-node reduce with breakpoints.
  • Run the same query with ?explain=true and read the BM25 explanation.

GitHub issue practice

is:issue is:open "QueryPhase" in:title,body
is:issue is:open label:bug "search" label:"good first issue"
is:pr is:merged path:server/src/main/java/org/opensearch/search

Labs

Exit checkpoint

  • You can name and order the phases: (DFS) → query → fetch, and where the reduce happens.
  • You can explain what SearchContext/DefaultSearchContext hold per shard.

Week 14 — Aggregations and a custom aggregation

Reading

  • server/src/main/java/org/opensearch/search/aggregations/AggregatorFactory.java, AggregatorBase.java.
  • search/aggregations/bucket/histogram/DateHistogramAggregator.java, bucket/terms/TermsAggregator.java.
  • Any InternalAggregation.reduce(...) implementation.

Hands-on

  • Run date_histogram + terms sub-agg (warm-up Scenario 3); break in the aggregator and the reduce.
  • Build the custom aggregation (Lab 7.3); wire it via SearchPlugin.

GitHub issue practice

is:issue is:open "aggregation" label:bug
is:issue is:open label:"good first issue" "aggregation" in:title,body
is:issue is:open "doc_count_error_upper_bound" in:title,body

Labs

Exit checkpoint

  • You can explain the AggregatorFactoryAggregatorInternalAggregation.reduce lifecycle.
  • You can explain why aggregations read DocValues, not the inverted index.
  • You have a working custom aggregation registered via SearchPlugin.

Week 15: Level 8 — Real Issue Contribution (and into the capstone)

Weeks 15–16 overlap with the capstone. Use Week 15 for Level 8's labs and to select and reproduce your capstone issue; use Week 16 to implement, test, and submit.

Reading

Hands-on

  • Reproduce a real GitHub issue locally with a failing test or a curl repro.
  • Localize the root cause to a file:method; draft the fix and a regression test.

GitHub issue practice

is:issue is:open label:bug label:"good first issue" no:assignee
is:issue is:open label:"help wanted" -label:"untriaged" sort:reactions-desc
is:issue is:open label:bug "stack trace" in:body sort:created-desc

Labs

Exit checkpoint

  • You have a reproducible failing case for a real issue (test or curl).
  • You have a root cause localized to a file:method with a one-paragraph explanation.
  • You have selected your capstone issue and confirmed it is unassigned and in scope.

Week 16: Level 9 + Capstone — Maintainer-Level Concerns and Shipping

Reading

Hands-on

  • Run ./gradlew :server:test + ./gradlew precommit on your capstone branch; fix every failure.
  • Write/extend a BWC or wire-serialization test where your change touches serialization.
  • Open the PR: signed commits, CHANGELOG entry, filled-out PR template, linked issue.

GitHub issue practice

is:issue is:open label:"backport 2.x"
is:pr is:open label:"backport 2.x"
is:issue is:open label:v3.0.0

Labs

Capstone — follow capstone/index.md start to finish:

  1. Issue selection (done in Week 15) → reproduction → execution-path analysis.
  2. Root cause → implementation → testing → validation.
  3. PR preparation (DCO sign-off, CHANGELOG, PR template) → GitHub documentation → write-up.

Exit checkpoint

  • A real PR opened against opensearch-project/OpenSearch (or a complete, review-ready patch on a branch), with passing ./gradlew precommit and tests on the affected module.
  • A 1500–3000 word public write-up of the experience (see capstone step 10).
  • At least one round of self-review against the PR quality checklist.

How to Use This Plan When You Fall Behind

  • If you finish a week's reading but cannot pass the exit checkpoint, repeat the week. Do not advance. The milestones are the real gate; the calendar is a suggestion.
  • If a GitHub issue query returns nothing useful, change the query. Labels and triage state shift constantly — drop a label, widen the date sort, or try is:issue is:open no:assignee plus a keyword. The community moves; your queries should too.
  • Treat ./gradlew precommit and a green :server:test on your touched module as non-negotiable before you call a week done.
  • Skip a Level only if you can pass all exit checkpoints from the previous Levels in one sitting, and only if a milestone explicitly permits it.
  • The two highest-leverage habits, sustained every week: (1) keep one real GitHub issue open and read it fully, comments included; (2) keep ./gradlew run alive and run at least one breakpoint-driven trace of the week's subsystem.