Level 1: Lucene and OpenSearch Foundation
This level establishes the technical baseline every subsequent level depends on. You will build
OpenSearch from source with Gradle, run its randomized test suite, launch a real single-node
cluster from your own checkout, index documents and query them over REST, and — critically —
write a tiny standalone Lucene program so you understand what a "shard" actually is before you
ever read IndexShard.
OpenSearch is a distributed search and analytics engine built on Apache Lucene. It was
forked from Elasticsearch 7.10.2 in 2021 (after Elastic relicensed under SSPL) and is
licensed Apache 2.0. Governance now sits with the OpenSearch Software Foundation under
the Linux Foundation, overseen by a Technical Steering Committee (TSC). The source of
truth is GitHub — github.com/opensearch-project/OpenSearch — not JIRA, and there is no CLA:
contributions are made by GitHub Pull Request with a DCO sign-off.
This curriculum will not hold your hand. It will point you at the right parts of the codebase, give you the right questions to ask, and make you run everything you read.
Learning Objectives
By the end of Level 1 you must be able to:
- Explain where OpenSearch sits in the search stack — Dashboards above it, Lucene beneath it — and which problems each layer owns.
- Build OpenSearch from source with the Gradle wrapper and its bundled JDK, with and without running tests.
- Run unit and integration tests scoped to a single project, reproduce a randomized failure from
its
-Dtests.seed, and read the HTML test reports. - Launch a single-node cluster with
./gradlew run, index documents over REST, and run amatchquery and atermsaggregation againstlocalhost:9200. - Map a REST call (
PUT /index,POST /_bulk,GET /_search) to theRestHandlerandTransportActionthat service it. - Write a standalone Lucene program that builds an index, opens a reader, and runs a query — and
explain how
IndexShard/InternalEnginewrap exactly those primitives. - Locate any class named in Levels 2–9 (
Node,ClusterService,IndexShard,SearchService, …) without a search engine.
The Search-Stack Context
Before touching a line of OpenSearch code, build an accurate mental model of the stack. OpenSearch is one layer in a three-layer system:
┌───────────────────────────────────────────────────────────┐
│ OpenSearch Dashboards (TypeScript) │ ← Visualization / UI
│ visualizations, dev tools, index management │ (separate repo)
└───────────────────────────────────────────────────────────┘
│ HTTP: _search / _msearch / _bulk (JSON)
▼
┌───────────────────────────────────────────────────────────┐
│ OpenSearch (org.opensearch.*) │ ← Distributed engine
│ REST layer → Transport/Action layer → cluster mgmt │ (this repo)
│ RestController → TransportAction → IndicesService │
│ → IndexShard → Engine │
└───────────────────────────────────────────────────────────┘
│ Engine wraps a Lucene IndexWriter / DirectoryReader
▼
┌───────────────────────────────────────────────────────────┐
│ Apache Lucene (library) │ ← Inverted index / search
│ IndexWriter, DirectoryReader, Query, Document │
└───────────────────────────────────────────────────────────┘
│ Directory abstraction
▼
┌───────────────────────────────────────────────────────────┐
│ Filesystem: segments (.cfs/.si/.dvd/…) │ ← Immutable on-disk files
└───────────────────────────────────────────────────────────┘
The boundaries matter for everything that follows, especially debugging:
- Dashboards never touches your data directly. It issues
_search/_bulkHTTP calls to OpenSearch throughopensearch-js. A "slow visualization" is almost always a slow_searchunderneath. Dashboards is a client. - OpenSearch owns the distributed problem: clusters, nodes, shards, replication, coordination, the REST API, the query DSL, aggregations. It does not implement the inverted index — it delegates to Lucene.
- Lucene owns the single-machine search problem: building inverted indexes, merging
immutable segments, executing
Queryobjects, scoring with BM25. A single OpenSearch shard is one Lucene index. You will prove this to yourself in Lab 1.4.
Note: When something is wrong, the first question a maintainer asks is which layer. Is it a Dashboards rendering bug, a coordinating-node reduce bug, a per-shard query bug, or a Lucene bug? Levels 7–9 and the plugin labs drill this attribution skill.
OpenSearch vs. Elasticsearch vs. Solr
You will be reading code whose history predates OpenSearch. Know the lineage:
| OpenSearch | Elasticsearch | Apache Solr | |
|---|---|---|---|
| License | Apache 2.0 | SSPL / Elastic License (post-7.10) | Apache 2.0 |
| Origin | Fork of Elasticsearch 7.10.2 (2021) | Original (2010) | Original (2004) |
| Governance | OpenSearch Software Foundation / Linux Foundation, TSC | Elastic N.V. (single vendor) | Apache Software Foundation |
| Package root | org.opensearch.* | org.elasticsearch.* | org.apache.solr.* |
| Build | Gradle (./gradlew) | Gradle | Gradle (was Ant/Ivy) |
| Contribution | GitHub PR + DCO (git commit -s), no CLA | GitHub PR + CLA | GitHub PR + ASF CLA / JIRA |
| Search core | Apache Lucene | Apache Lucene | Apache Lucene |
| Cluster coordination | Built-in (Zen2-style, Coordinator) | Built-in (Zen2) | Apache ZooKeeper (SolrCloud) |
The three share a Lucene heart. OpenSearch and Elasticsearch share a codebase up to 7.10.2 —
which is why many class names (IndexShard, InternalEngine, SearchService) are identical and
why Elasticsearch documentation from the 7.x era is often still accurate for OpenSearch internals.
The most visible OpenSearch-specific change is the rename of "master" → "cluster manager" for
inclusive language (the master role and cluster.initial_master_nodes are deprecated aliases of
cluster.initial_cluster_manager_nodes).
Required Reading
Read these in order, in your own checkout, before starting the labs. In a mature codebase the best documentation is often in-repo Markdown and class-level Javadoc — read it seriously.
| # | Resource | What to extract |
|---|---|---|
| 1 | README.md (repo root) | The project's scope, where to file issues, the high-level layout. |
| 2 | DEVELOPER_GUIDE.md | Gradle tasks, JDK requirements, IDE setup, the run task, debugging. The single most important file in this level. |
| 3 | TESTING.md | Test types, randomized testing, -Dtests.seed, --tests filtering, integration vs unit. |
| 4 | CONTRIBUTING.md | The GitHub/DCO/CHANGELOG workflow (you will live this in Level 2). |
| 5 | server/src/main/java/org/opensearch/node/Node.java | Class-level Javadoc + the constructor. The node is the object graph root — every service is wired here. |
| 6 | server/src/main/java/org/opensearch/index/shard/IndexShard.java | Class-level Javadoc only. The bridge from OpenSearch to Lucene. |
Run this to confirm the files exist on your branch (names occasionally move between major lines):
ls README.md DEVELOPER_GUIDE.md TESTING.md CONTRIBUTING.md CHANGELOG.md MAINTAINERS.md
find server/src/main/java/org/opensearch/node/Node.java \
server/src/main/java/org/opensearch/index/shard/IndexShard.java
Source Code Areas to Inspect
You are not modifying anything yet — you are building a map. Skim these before and after the labs.
server/ — the core engine
This is the bulk of what you will read across the whole curriculum.
| Path | Why |
|---|---|
org/opensearch/node/Node.java | Root of the object graph. All services are constructed and wired here. |
org/opensearch/cluster/service/ClusterService.java | Access point for cluster state and update tasks. |
org/opensearch/cluster/ClusterState.java | The immutable cluster-metadata snapshot (covered in Level 4). |
org/opensearch/rest/RestController.java | Dispatches HTTP requests to RestHandlers. |
org/opensearch/action/search/TransportSearchAction.java | Entry point of the distributed search path. |
org/opensearch/indices/IndicesService.java | Owns the IndexService/IndexShard instances on a node. |
org/opensearch/index/shard/IndexShard.java | One shard. Wraps a Lucene index via an Engine. |
org/opensearch/index/engine/InternalEngine.java | The default Engine: holds the Lucene IndexWriter, translog, refresh/flush. |
org/opensearch/search/SearchService.java | Per-shard query and fetch phase execution. |
libs/ — shared low-level libraries
| Path | Why |
|---|---|
libs/core | org.opensearch.core: StreamInput/StreamOutput, Writeable — the wire serialization primitives (BWC lives here; see Level 9). |
libs/common | Common utilities used everywhere. |
libs/x-content | XContent: pluggable JSON/YAML/CBOR/SMILE parsing for request/response bodies. |
libs/geo, libs/secure-sm | Geo primitives; the security manager. |
modules/ — bundled-by-default modules
| Path | Why |
|---|---|
modules/transport-netty4 | The default network transport (Netty4Transport) and HTTP server. |
modules/lang-painless | The Painless scripting language. |
modules/analysis-common | The standard analyzers, tokenizers, and token filters. |
modules/reindex, modules/ingest-common, modules/percolator | Reindex/update-by-query, ingest pipelines, percolation. |
test/framework/ — the test harness
| Path | Why |
|---|---|
OpenSearchTestCase | Base unit test: randomized seed, randomAlphaOfLength, assertBusy, leak detection. |
OpenSearchSingleNodeTestCase | One in-JVM node for tests that need a real index. |
OpenSearchIntegTestCase | Multi-node InternalTestCluster integration tests. |
InternalTestCluster | Spins up real nodes in-JVM; the backbone of *IT tests. |
Key Classes Quick Reference
Memorize the role of each. By the end of Level 1 you should be able to name the file path of any of these from memory.
| Class | Project / package | Role |
|---|---|---|
Node | server · org.opensearch.node | Object-graph root; constructs and wires every service. |
ClusterService | server · org.opensearch.cluster.service | Access to cluster state; submits cluster-state update tasks. |
ClusterState | server · org.opensearch.cluster | Immutable snapshot of cluster metadata, routing, nodes, blocks. |
RestController | server · org.opensearch.rest | Routes HTTP requests to RestHandlers. |
TransportAction | server · org.opensearch.action.support | Base for node-to-node actions; the execution unit behind every REST call. |
IndicesService | server · org.opensearch.indices | Owns per-node IndexService and IndexShard instances. |
IndexShard | server · org.opensearch.index.shard | One shard = one Lucene index, wrapped via an Engine. |
InternalEngine | server · org.opensearch.index.engine | Default Engine: Lucene IndexWriter + translog + refresh/flush/merge. |
SearchService | server · org.opensearch.search | Executes the query and fetch phases on a single shard. |
Build the muscle memory now:
# You should be able to predict each path before running this.
for c in Node ClusterService ClusterState RestController IndicesService \
IndexShard InternalEngine SearchService; do
echo "== $c =="
find server -name "$c.java" -path "*/main/*"
done
GitHub Issue Categories for Level 1 Contributors
OpenSearch tracks everything on GitHub. At this stage, restrict yourself to issues that exercise the workflow, not the engine:
good first issue— curated, scoped, beginner-appropriate. Always start here.- Documentation — incorrect Javadoc, stale REST examples, broken links, outdated version references in in-repo docs.
flaky-test— tests that fail intermittently. At Level 1 you observe these and learn to read the-Dtests.seedreproduction line; you fix them in Level 5.
How to find them:
# The label-filtered issue lists (open in a browser):
# https://github.com/opensearch-project/OpenSearch/issues?q=is:open+label:%22good+first+issue%22
# https://github.com/opensearch-project/OpenSearch/issues?q=is:open+label:flaky-test
# Or with the gh CLI:
gh issue list --repo opensearch-project/OpenSearch \
--label "good first issue" --state open --limit 30
Warning: Do not start coding on an issue without reading the full comment thread. If it has an assignee or an open linked PR, move on. Leave a comment saying you intend to work on it before you do. Etiquette is covered in depth in Community Interaction.
Deliverables
Demonstrate all of the following before advancing to Level 2:
-
A successful
./gradlew assemblerun — no build failures (Lab 1.1). -
At least one unit-test class run green via
./gradlew :server:test --tests ...(Lab 1.2). -
A running single-node cluster:
GET _cat/healthreturnsgreenand you have indexed and queried documents (Lab 1.3). -
A standalone Lucene program that builds an index and runs a
TermQuery, plus a written explanation of howInternalEnginewraps the same primitives (Lab 1.4). -
Ability to name the file path of
Node,IndexShard,InternalEngine, andSearchServicefrom memory. - A written explanation (2–3 sentences) of why an OpenSearch shard is a Lucene index.
Common Mistakes
| Mistake | Consequence | Fix |
|---|---|---|
| Using a system JDK instead of the bundled runtime | Cryptic compile/toolchain errors | The wrapper provisions its own JDK; let Gradle manage toolchains (see Lab 1.1). |
Running ./gradlew check on the whole repo first | Hours-long run; you give up | Scope: ./gradlew :server:test --tests "...". Save check for pre-PR. |
| Treating a randomized test failure as flaky | Real bug ignored | Re-run with the printed -Dtests.seed=... to reproduce deterministically. |
Editing files but skipping spotlessApply | precommit fails in CI | Run ./gradlew spotlessApply before committing. |
| Confusing Dashboards with the engine | Misattributed bugs | Dashboards is a separate TS repo; it is a client of OpenSearch. |
| Calling a shard a "node" or "index" | Wrong mental model that breaks at scale | A shard is one Lucene index; an OpenSearch index is N shards. |
| Reading code without running it | Abstract understanding that fails under debugging | Always ./gradlew run and exercise the path you just read. |
How to Verify Success
# 1. Build artifacts without tests.
./gradlew assemble -q && echo "ASSEMBLE OK"
# 2. One unit-test class, green.
./gradlew :server:test --tests "org.opensearch.common.UUIDTests"
# 3. Launch a cluster in one terminal...
./gradlew run
# ...and in another, confirm it is alive:
curl -s "localhost:9200/_cat/health?v"
# expected: a row whose 'status' column is 'green'
# 4. The Lucene project (Lab 1.4) prints its hits, e.g.:
# Found 2 hit(s) for term body:lucene
PR Profile: Level 1 Graduate
A Level 1 graduate can credibly open these kinds of PRs. Scope is everything.
| PR type | Example | Test requirement |
|---|---|---|
| Documentation fix | Correct a wrong parameter description in a REST handler's Javadoc | None — docs only |
| Stale example fix | Fix an outdated curl example or version string in an in-repo .md | Manual verification |
| Test naming / assertion clarity | Rename a confusing test method; add a missing assertEquals message | Re-run the test class |
flaky-test observation | Comment a reliable -Dtests.seed reproduction on an existing issue | Reproduce locally |
You are not yet ready to submit: validation-logic changes, new REST actions, engine or allocation changes, or anything touching the wire protocol. Those start in Level 2 (workflow) and ramp through Levels 3–9.