Milestones: M1 Through M9

Milestones are the "what does mastery look like at this stage" checkpoints. They map to the nine levels and the 16-week plan, but they are the real gate — the calendar is only a suggestion. Each milestone has:

  • Expected completion — a calendar guideline tied to the 16-week plan.
  • Skills you must demonstrate — 5–8 concrete abilities, tied to OpenSearch internals.
  • Self-check questions — answer them out loud, without notes.
  • 20-point rubric — five criteria, four points each.
  • Pass threshold — minimum total to advance.
  • Move to the next level when — the binary gate.

Pass thresholds are deliberately high. The point is competence, not throughput. A maintainer-track contributor is measured in months of sustained, high-quality work — these milestones are how you know you are on track.


M1 — Orientation and Data Model (end of Week 2)

You can run OpenSearch as a user, and you can state precisely what is OpenSearch and what is Lucene.

Skills

  1. Build OpenSearch from a clean checkout and serve :9200 via ./gradlew run.
  2. Reproduce all five warm-up scenarios with curl against localhost:9200.
  3. Draw the data model: cluster → index → shard (primary/replica) → Lucene index → segment.
  4. Classify any concept as OpenSearch-owned vs. Lucene-owned (scoring, DocValues, translog, cluster state, REST/transport).
  5. Write a mapping and explain each field type's Lucene representation.
  6. Run a single test by name: ./gradlew :server:test --tests "<Class>.<method>".
  7. Locate any org.opensearch.* class in server/ within 60 seconds via Go to Class or grep.

Self-check questions

  • Which of these is Lucene and which is OpenSearch: BM25 scoring, the translog, the inverted index, the RoutingTable, segment merges?
  • What is the difference between a text and a keyword field, and why does it matter for aggregations?
  • What does _refresh make happen, and how is it different from _flush?

Rubric

Criterion1234
Build/run fluency./gradlew run worksRuns scenariosRuns a named testDiagnoses a run failure
Data modelNames the termsSketches index→shardSketches down to segmentsPredicts shard layout
OS-vs-Lucene boundaryConfusedKnows a fewClassifies mostClassifies any concept
REST/curl fluencyCopies examplesEdits queriesWrites from memoryPredicts the response shape
CommunicationCannot explainExplains with notesExplains without notesTeaches another

Pass threshold: 14/20, with no criterion below 2.

Move to Level 2 when: from a verbal prompt, you can index sample data and run a filtered search + a date_histogram aggregation from memory, and state the OS-vs-Lucene boundary cold.


M2 — Build and Test Literacy (end of Week 4)

You can navigate the codebase, build it, run any test, and produce a contribution-ready commit.

Skills

  1. Run a single test in any module: ./gradlew :server:test --tests "Class.method".
  2. Add a new test file and have Gradle pick it up.
  3. Run and interpret ./gradlew precommit (checkstyle, forbidden APIs, license headers, loggerUsageCheck).
  4. Identify the module of a class from its FQN (org.opensearch.cluster...server; org.opensearch.core...libs/core).
  5. Produce a commit with a DCO Signed-off-by: line (git commit -s) and a CHANGELOG.md entry.
  6. Distinguish a unit test (*Tests) from an integration test (*IT, internalClusterTest).
  7. Describe the full PR lifecycle: fork → branch → sign-off → CHANGELOG → PR → DCO/precommit → review → backport label.

Self-check questions

  • Why is there no JIRA and no CLA — what replaces them in OpenSearch?
  • What does every PR have to include besides code, and which check enforces the sign-off?
  • What is the difference between ./gradlew :server:test and ./gradlew :server:internalClusterTest?

Rubric

Criterion1234
Build masteryassemble worksKnows localDistro/runKnows module depsDiagnoses build failures
Test executionRuns allRuns a classRuns a methodRuns internalClusterTest
PrecommitUnawareRuns itReads failuresFixes checkstyle/headers
Contribution hygieneNoneSigns offAdds CHANGELOGFills PR template correctly
Module mapKnows namesKnows top-level depsMaps FQN→moduleDiagnoses where a class lives

Pass threshold: 14/20.

Move to Level 3 when: on a fresh checkout you can build, run a :server:test method by name, and produce a signed, CHANGELOG-bearing commit that passes ./gradlew precommit — within 20 minutes.


M3 — The Request Path (end of Week 6)

You can trace a request from the REST handler through the transport action framework.

Skills

  1. Trace _search end to end: RestControllerRestSearchActionNodeClient.executeTransportSearchAction, with a breakpoint at each hop.
  2. Explain ActionModule registration: how a RestHandler and a TransportAction get wired.
  3. Distinguish the transport action base classes (HandledTransportAction, TransportSingleShardAction, TransportBroadcastAction, TransportReplicationAction, TransportClusterManagerNodeAction).
  4. Describe how a TransportRequest is serialized (Writeable + StreamInput/StreamOutput, NamedWriteableRegistry) and moved by TransportService/Netty4Transport.
  5. Name three thread pools (SEARCH, WRITE, GET, …) and the work each owns.
  6. Build a custom REST action plugin and serve it on :9200.

Self-check questions

  • When a request must run on the elected cluster manager, which base action routes it there?
  • How does a polymorphic transport payload deserialize on the receiving node?
  • Which thread pool does a _search run on, and why does that matter under load?

Rubric

Criterion1234
REST→action pathVagueNames the chainCites filesWalks it with breakpoints
Action frameworkConfusedKnows base classesPicks the right oneKnows routing/replication semantics
SerializationUnawareKnows WriteableKnows StreamInput/OutputKnows NamedWriteableRegistry
ThreadpoolsUnawareNames a fewMaps work→poolReasons about saturation
PluginCannotStub compilesResponds on :9200Idiomatic + tested

Pass threshold: 14/20.

Move to Level 4 when: you can answer "where does my _search request first leave the REST layer and enter server code?" with a file:method citation, and your custom REST action responds on :9200.


M4 — Coordination and Cluster State (end of Week 8)

You understand cluster-manager election, the cluster state, its publishing, and shard allocation.

Skills

  1. Name the four components of ClusterState (Metadata, RoutingTable, DiscoveryNodes, ClusterBlocks) and what each holds.
  2. Explain the two-phase publish/commit and which class publishes (PublicationTransportHandler) vs. applies (ClusterApplierService).
  3. Trace a ClusterStateUpdateTask through MasterService to a published, applied state.
  4. Read _cluster/allocation/explain and name the AllocationDecider that blocked an allocation.
  5. Describe election and failure detection (Coordinator, PreVoteCollector, FollowersChecker, LeaderChecker) and what happens to writes during a re-election.
  6. Implement a ClusterStateListener and explain exactly when it fires.

Self-check questions

  • Why is ClusterState immutable and versioned? What breaks if two nodes apply different versions?
  • Which service computes new states and which applies them — and why are they separate?
  • What turns a cluster yellow, and what sequence of events brings it back to green?

Rubric

Criterion1234
Cluster stateNames itKnows the 4 partsReads _cluster/statePredicts a state diff
Publish/applyConfusedKnows the splitKnows two-phaseWalks it in source
CoordinationAwareNames CoordinatorKnows election flowReasons about split-brain safety
AllocationBlack boxNames decidersReads allocation/explainDiagnoses + fixes a decider
Listener/updateCannotStub firesCorrect timingBatched update task understood

Pass threshold: 16/20 — this is the first hard gate.

Move to Level 5 when: given an UNASSIGNED shard, you can use _cluster/allocation/explain to name the responsible decider, and you have a working ClusterStateListener with a test.


M5 — Testing and the InternalTestCluster (end of Week 10)

You can write multi-node in-JVM tests, reproduce randomized failures, and handle flaky tests correctly.

Skills

  1. Write an OpenSearchIntegTestCase that spins up a multi-node InternalTestCluster in-JVM.
  2. Write an OpenSearchSingleNodeTestCase and a plain OpenSearchTestCase unit test.
  3. Reproduce a randomized failure from its printed -Dtests.seed=... line.
  4. Write an AbstractWireSerializingTestCase/AbstractSerializingTestCase round-trip for a Writeable/XContent type.
  5. Mute a flaky test correctly with @AwaitsFix(bugUrl="https://github.com/opensearch-project/OpenSearch/issues/NNNN") — never @Ignore.
  6. Take a flaky-test-labeled issue from reproduction toward a candidate fix.

Self-check questions

  • Why are OpenSearch tests randomized, and how do you make a failure deterministic again?
  • Why does serialization round-trip testing matter for backward compatibility?
  • What is the wrong way to silence a flaky test, and why is it wrong?

Rubric

Criterion1234
Integ testsRuns themWrites single-nodeWrites multi-nodeControls cluster scope
RandomizationConfusedKnows seedsReproduces a failureMinimizes a repro
Serialization testsUnawareKnows the base classWrites a round-tripCatches a BWC break
Flaky discipline@IgnoreKnows @AwaitsFixLinks the issueRoots out the cause
DebuggingReads stackMaps to sourceReproduces locallyWrites a regression test

Pass threshold: 15/20.

Move to Level 6 when: you can reproduce a randomized failure from a seed and write a multi-node integration test that asserts cluster behavior — both from memory.


M6 — Indexing Path and the Engine (end of Week 12)

You can read the write path from a transport action down to Lucene and the translog.

Skills

  1. Walk indexing end to end: TransportShardBulkActionIndexShard.applyIndexOperationOnPrimaryInternalEngine.index → Lucene IndexWriter + Translog.add.
  2. Explain refresh (visibility / new searcher) vs. flush (durability / Lucene commit) vs. merge (segment cleanup, MergePolicy/MergeScheduler).
  3. Explain how a write replicates: document replication (TransportReplicationAction) vs. segment replication (SegmentReplicationTargetService/SourceService).
  4. Describe sequence-number tracking: LocalCheckpointTracker, global checkpoint via ReplicationTracker.
  5. Implement an AnalysisPlugin exposing a custom token filter and prove it with _analyze.
  6. Explain how a mapping field type selects its analyzer and Lucene field.

Self-check questions

  • What guarantees does the translog provide between two Lucene commits?
  • Why is a freshly indexed document not searchable until a refresh?
  • What is the difference, on the wire, between document replication and segment replication?

Rubric

Criterion1234
Write pathNames classesWalks happy pathWalks to LuceneWalks edge cases
Refresh/flush/mergeConfusedKnows definitionsKnows triggersTunes/diagnoses
ReplicationAwareKnows doc-repKnows seg-repReasons about seqno/checkpoints
Analysis/mappingAwareReads a mapperBuilds a filterBuilds + tests an AnalysisPlugin
Engine debuggingReads stackMaps to sourceBreakpoints in engineWrites a repro test

Pass threshold: 15/20.

Move to Level 7 when: you can set a breakpoint in IndexShard.applyIndexOperationOnPrimary, step into InternalEngine.index, and explain refresh vs. flush vs. merge without notes.


M7 — Search and Aggregations (end of Week 14)

You can read the search fan-out, the per-shard phases, and the coordinating-node reduce.

Skills

  1. Walk search end to end: TransportSearchAction fan-out → per-shard SearchServiceQueryPhaseFetchPhase → reduce in SearchPhaseController.
  2. Explain the optional DfsPhase (global term statistics) and when it matters.
  3. Explain the aggregation lifecycle: AggregatorFactoryAggregatorInternalAggregation.reduce(...).
  4. Explain why aggregations read DocValues, not the inverted index, and what doc_count_error_upper_bound means.
  5. Read a BM25 _explain tree and account for each term.
  6. Build a custom aggregation and register it via SearchPlugin.

Self-check questions

  • Where does shard-local partial work become a globally correct answer in both search and aggs?
  • What does SearchContext/DefaultSearchContext hold per shard, and when is it released?
  • Why can a terms aggregation be approximate across shards?

Rubric

Criterion1234
Search pathNames phasesOrders themCites filesWalks with breakpoints
ReduceVagueKnows it existsKnows it merges shardsReasons about correctness
AggregationsAwareKnows factory→aggKnows reduceBuilds a custom agg
DocValues lensConfusedKnows aggs use themKnows whyReasons about cardinality/memory
QueryBuildersAwareReads oneKnows toQuery pathAdds/extends a query

Pass threshold: 15/20.

Move to Level 8 when: you can trace a _search from fan-out to reduce with breakpoints and you have a custom aggregation registered via SearchPlugin that returns correct results.


M8 — Production Diagnostics and Real Contribution (end of Week 15)

You can reproduce a real GitHub issue, localize the root cause, and prepare a fix.

Skills

  1. Reproduce a reported issue locally with a failing test or a curl repro.
  2. Read a stack trace and walk it into server/ to a file:method.
  3. Use cluster diagnostics: _cluster/health, _cat/shards, _cluster/allocation/explain, _nodes/stats, and the circuit-breaker stats.
  4. Distinguish a core bug from a plugin/Dashboards/Lucene bug (correct attribution).
  5. Write a minimal regression test that fails before the fix and passes after.
  6. Open a PR with a DCO sign-off, a CHANGELOG entry, and a filled-out PR template, linked to the issue.

Self-check questions

  • Given a wrong dashboard chart, how do you decide whether the bug is in Dashboards, core search, a plugin, or Lucene?
  • What is the minimum a good issue reproduction contains?
  • Why does a regression test belong in the same PR as the fix?

Rubric

Criterion1234
ReproductionNoneManual curlScriptedAdded as a failing test
Root causeSpeculativeLocalizedCited file:methodExplained in the issue
DiagnosticsGuessesReads _cat/healthUses allocation/explainCross-checks stats+logs
AttributionConfusedKnows boundariesPicks the right repoFiles/triages correctly
PR readinessNoneDraftSigned + CHANGELOGTemplate + linked issue

Pass threshold: 16/20.

Move to the capstone when: you have a reproducible failing case for a real issue, a root cause localized to a file:method, and a regression test that gates the fix.


M9 — Capstone: You Have Shipped a Patch (end of Week 16)

You have taken a real OpenSearch issue through the full contribution cycle.

Skills

  1. Selected an appropriate, unassigned, in-scope issue.
  2. Reproduced and root-caused it.
  3. Implemented a minimal, idiomatic fix with a regression test.
  4. Submitted a PR in OpenSearch's accepted format: signed commits, CHANGELOG entry, PR template, linked issue; ./gradlew precommit and module tests green.
  5. Responded to at least one round of review feedback (real or simulated against the PR quality checklist).

Self-check questions

  • Is your change minimal and focused, or does it bundle unrelated cleanup?
  • Did you consider wire/index backward compatibility and add a serialization/BWC test if needed?
  • Can you summarize the root cause and the fix in three sentences for a reviewer?

Rubric (20 points)

Criterion1234
Issue selectionRandomScopedJustifiedAligned to roadmap
ReproductionNoneManualScriptedAdded as a test
Root causeSpeculativeLocalizedCitedExplained on the issue/PR
ImplementationCompilesTests passIdiomaticMinimal, focused, BWC-aware
SubmissionNoneDraftSubmitted (signed + CHANGELOG)Reviewed, feedback addressed

Pass threshold: 16/20, and the change must pass ./gradlew precommit plus the tests on the affected module.


Global Rubric (maintainer-readiness)

Use this every quarter, regardless of level, to self-assess against where OpenSearch maintainers operate.

Dimension1 (Beginner)2 (Apprentice)3 (Practitioner)4 (Maintainer-ready)
CodeReads org.opensearch.*Modifies safelyDesigns a subsystem changeReviews others' PRs
TestingRuns testsAdds unit/integ testsWrites regression + BWC suitesDrives test-infra / flaky triage
Distributed reasoningSingle nodeShards/replicasCoordination + allocationReasons about consensus/recovery edge cases
Contribution & communityOpens issuesOpens PRs with sign-off + CHANGELOGReviews, attributes cross-repo bugsShapes RFCs, mentors, weighs release impact

A maintainer-track contributor should be at level 3 on all four dimensions and level 4 on at least one. Aim for 3/3/3/3 → 4/3/3/4 by month 12 of focused, sustained contribution. Maintainership in OpenSearch is earned through demonstrated judgment over time — not PR volume — and the per-repo MAINTAINERS.md reflects that.


Use these milestones together with the 16-week plan. When the calendar and the milestone disagree, the milestone wins: repeat the week until you pass the gate.