👨‍💻

みんなでOpenSearchを育てよう!調査からOSSへのPRまでの実録ガイド

に公開

はじめに

OpenSearchはスケーラブルで多様なユースケースに対応できるオープンソースの検索エンジンです。オープンソースなので、誰でもソースコードを見ることができて、誰でも改善に貢献できます。

しかし、いざコントリビューションに挑戦するとなると、何から始めたものか悩む方も少なくないのではないでしょうか。そこで、先日私が作成したPull Request「Fix NPE in validateSearchableSnapshotRestorable when shard size is unavailable」を例に、OpenSearchへの貢献の流れをご紹介しようと思います。

https://github.com/opensearch-project/OpenSearch/pull/19684

バグの発見

ある日、Searchable snapshotsの挙動を調査していると、奇妙なエラーに遭遇しました。

Searchable Snapshotをリストアする際に、NullPointerExceptionが発生してリストアに失敗するのです。しかしながら、当初はエラーが発生する原因がわからず、一見すると同じ操作をしているにも関わらずエラーが発生する時としない時がありました。

# Searchable Snapshotを使用してスナップショットをリストア
POST _snapshot/my_repository/snapshot_all/_restore
{
  "indices": "test-index",
  "storage_type": "remote_snapshot",
  "rename_pattern": "(.+)",
  "rename_replacement": "remote5_$1"
}

Dev Tool上のレスポンス

# POST _snapshot/my_repository/snapshot_all/_restore
{
  "error": {
    "root_cause": [
      {
        "type": "null_pointer_exception",
        "reason": null
      }
    ],
    "type": "null_pointer_exception",
    "reason": null
  },
  "status": 500
}

ノードのエラーログ

java.lang.NullPointerException: null
	at java.base/java.util.stream.ReferencePipeline$5$1.accept(ReferencePipeline.java:249) ~[?:?]
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:215) ~[?:?]
	at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1716) ~[?:?]
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:570) ~[?:?]
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560) ~[?:?]
	at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921) ~[?:?]
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:265) ~[?:?]
	at java.base/java.util.stream.LongPipeline.reduce(LongPipeline.java:502) ~[?:?]
	at java.base/java.util.stream.LongPipeline.sum(LongPipeline.java:460) ~[?:?]
	at org.opensearch.snapshots.RestoreService$1.validateSearchableSnapshotRestorable(RestoreService.java:916) ~[opensearch-3.3.2.jar:3.3.2]
	at org.opensearch.snapshots.RestoreService$1.execute(RestoreService.java:588) ~[opensearch-3.3.2.jar:3.3.2]
	at org.opensearch.cluster.ClusterStateUpdateTask.execute(ClusterStateUpdateTask.java:67) ~[opensearch-3.3.2.jar:3.3.2]
	at org.opensearch.cluster.service.ClusterManagerService.executeTasks(ClusterManagerService.java:890) ~[opensearch-3.3.2.jar:3.3.2]
	at org.opensearch.cluster.service.ClusterManagerService.calculateTaskOutputs(ClusterManagerService.java:441) ~[opensearch-3.3.2.jar:3.3.2]
	at org.opensearch.cluster.service.ClusterManagerService.runTasks(ClusterManagerService.java:301) [opensearch-3.3.2.jar:3.3.2]
	at org.opensearch.cluster.service.ClusterManagerService$Batcher.run(ClusterManagerService.java:214) [opensearch-3.3.2.jar:3.3.2]
	at org.opensearch.cluster.service.TaskBatcher.runIfNotProcessed(TaskBatcher.java:206) [opensearch-3.3.2.jar:3.3.2]
	at org.opensearch.cluster.service.TaskBatcher$BatchedTask.run(TaskBatcher.java:264) [opensearch-3.3.2.jar:3.3.2]
	at org.opensearch.common.util.concurrent.ThreadContext$ContextPreservingRunnable.run(ThreadContext.java:916) [opensearch-3.3.2.jar:3.3.2]
	at org.opensearch.common.util.concurrent.PrioritizedOpenSearchThreadPoolExecutor$TieBreakingPrioritizedRunnable.runAndClean(PrioritizedOpenSearchThreadPoolExecutor.java:299) [opensearch-3.3.2.jar:3.3.2]
	at org.opensearch.common.util.concurrent.PrioritizedOpenSearchThreadPoolExecutor$TieBreakingPrioritizedRunnable.run(PrioritizedOpenSearchThreadPoolExecutor.java:262) [opensearch-3.3.2.jar:3.3.2]
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1095) [?:?]
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:619) [?:?]
	at java.base/java.lang.Thread.run(Thread.java:1447) [?:?]

調査

類似の報告を探す

エラーログで検索してみると、類似の事象を報告しているissueが見つかりました。
https://github.com/opensearch-project/OpenSearch/issues/19349

2名が同じエラーを報告しており、それぞれ状況が少し異なるものの、2人とも"storage_type": "remote_snapshot"を設定してPOST /_snapshot/<repository>/<snapshot>/_restoreした際にNullPointerExceptionが発生したということでした。

ソースコードを読んでみる

エラーログを読む限り、Exceptionが発生している箇所はorg.opensearch.snapshots.RestoreService$1.validateSearchableSnapshotRestorable(RestoreService.java:916のようです。
それではRestoreService.javaの該当箇所を読んでみましょう。どうやら、totalRestoredRemoteIndexesSizeを計算する際に問題が起きているようです。
https://github.com/opensearch-project/OpenSearch/blob/592f4c8434d60cc786c364aa4ba01a19d9488a7d/server/src/main/java/org/opensearch/snapshots/RestoreService.java#L902-L906
validateSearchableSnapshotRestorableメソッド全体を読んだところ、これはSearchable Snapshotをリストアする際に、ローカルのキャッシュ領域のサイズが十分に確保できていることを確認する処理であることが分かりました。
https://github.com/opensearch-project/OpenSearch/blob/592f4c8434d60cc786c364aa4ba01a19d9488a7d/server/src/main/java/org/opensearch/snapshots/RestoreService.java#L880-L917
リストア対象のインデックスのサイズが、totalNodeFileCacheSize(ノードのキャッシュ領域のサイズ) * remoteDataToFileCacheRatio(ノードのキャッシュ領域のサイズに対して許容するSearchable Snapshotのサイズの割合)を下回っていることを確認しているのです。
https://github.com/opensearch-project/OpenSearch/blob/592f4c8434d60cc786c364aa4ba01a19d9488a7d/server/src/main/java/org/opensearch/snapshots/RestoreService.java#L908-L916
ちなみにDATA_TO_FILE_CACHE_SIZE_RATIO_SETTINGのデフォルト値は5.0であるため、デフォルトではtotalNodeFileCacheSizeの5倍までのサイズのSearchable Snapshotのリストアを許容していることが分かります。
https://github.com/opensearch-project/OpenSearch/blob/022d594dc33a3c7276b94305dc6c4a29fe53ba70/server/src/main/java/org/opensearch/index/store/remote/filecache/FileCacheSettings.java#L27-L33

話を戻しましょう。totalRestoredRemoteIndexesSizeの計算でNullPointerExceptionが発生するということは、shardsIterator.getShardRoutings()によって得られたShardRoutingを引数とする、clusterInfo::getShardSizeが何らかの原因でnullを返しているものと推測できます。

long totalRestoredRemoteIndexesSize = shardsIterator.getShardRoutings()
    .stream()
    .map(clusterInfo::getShardSize)
    .mapToLong(Long::longValue)
    .sum();

ClusterInfoとRoutingTable

それでは、shardsIterator.getShardRoutings()によって得られたShardRoutingを引数とする、clusterInfo::getShardSizeがnullを返すのはどのような状況でしょうか。

ClusterInfo

まずはClusterInfoとは何であるかを考えます。
ClusterInfoとは、クラスタ全体のリソースに関する統計等の付加情報を管理するオブジェクトです。シャードのサイズやノードごとのディスク使用量などの情報を保持しています。
https://github.com/opensearch-project/OpenSearch/blob/022d594dc33a3c7276b94305dc6c4a29fe53ba70/server/src/main/java/org/opensearch/cluster/ClusterInfo.java#L65
ClusterInfoの情報はManager Node(Master Node)が管理しており、InternalClusterInfoServiceが生成 / 更新を行います。
https://github.com/opensearch-project/OpenSearch/blob/022d594dc33a3c7276b94305dc6c4a29fe53ba70/server/src/main/java/org/opensearch/cluster/InternalClusterInfoService.java#L269

ClusterInfoの更新頻度はcluster.info.update.intervalで管理されており、デフォルトは30秒間です。
https://github.com/opensearch-project/OpenSearch/blob/022d594dc33a3c7276b94305dc6c4a29fe53ba70/server/src/main/java/org/opensearch/cluster/InternalClusterInfoService.java#L99-L105

ShardRouting

それでは、ShardRoutingとはなんでしょうか。ShardRoutingとはRoutingTableを構成する要素の一部です。RoutingTableはどのシャードがどのノードに配置されているかや、各シャードの状態(STARTED, INITIALIZING, RELOCATINGなど)、プライマリ/レプリカの区別、インデックスごとのルーティング情報などクラスターの中核的な情報を管理します。

https://github.com/opensearch-project/OpenSearch/blob/022d594dc33a3c7276b94305dc6c4a29fe53ba70/server/src/main/java/org/opensearch/cluster/routing/RoutingTable.java#L75

ShardRoutingは個々のシャード単位の詳細情報を保持します。

https://github.com/opensearch-project/OpenSearch/blob/022d594dc33a3c7276b94305dc6c4a29fe53ba70/server/src/main/java/org/opensearch/cluster/routing/ShardRouting.java#L85

ShardRoutingShardRoutingの構成要素として以下のような入れ子の関係にあります。

  RoutingTable (クラスタ全体)
  │
  ├─ Map<String, IndexRoutingTable> indicesRouting
     │
     └─ IndexRoutingTable (インデックスごと、ex: "my-index")
        │
        └─ Map<Integer, IndexShardRoutingTable> shards
           │
           └─ IndexShardRoutingTable (シャードIDごと、ex: shard 0)
              │
              └─ List<ShardRouting> (プライマリ + レプリカ)
                 │
                 ├─ ShardRouting (primary=true)  ← 個々のシャード
                 ├─ ShardRouting (primary=false, replica 1)
                 └─ ShardRouting (primary=false, replica 2)
コンポーネント 粒度 役割
RoutingTable クラスタ全体 全インデックスのルーティング情報
IndexRoutingTable インデックス単位 1インデックスの全シャード
IndexShardRoutingTable シャードID単位 1シャードの全レプリカ(primary+replica)
ShardRouting シャード単位 1シャードの詳細

そして、RoutingTableは、cluster.info.update.interval周期で更新されるClusterInfoと異なり、クラスターの変更を即座に反映します。
つまり、RoutingTableが保持する情報がClusterInfoにない状況が発生し得るのです。

よって、以下のコードはClusterInfoが最新化されていない状態で、RoutingTableにのみ情報が存在するShard情報を処理する際に、.map(clusterInfo::getShardSize)NullPointerExceptionを起こす可能性があると言えます。

long totalRestoredRemoteIndexesSize = shardsIterator.getShardRoutings()
    .stream()
    .map(clusterInfo::getShardSize)
    .mapToLong(Long::longValue)
    .sum();

仮説を元に再現検証する

ここまでの調査から、以下の仮説を立てました。
「1つ以上のリモートインデックスがリストア済で、かつその情報がClusterInfoに反映されていない状況で、新しいリモートインデックスをリストアした場合に、.map(clusterInfo::getShardSize)NullPointerExceptionを起こすのではないか?」

この仮説を検証するためのコンテナ環境を用意しました。
ポイントは、searchable snapshotを使用するためnode.roleswarmを指定している点と、clusterInfoが古い状態を起こしやすくするため、cluster.info.update.intervalをデフォルトの30秒から60分に伸ばしていることです。

検証用のdocker-compose例
services:
  opensearch-node1:
    image: opensearchproject/opensearch:latest
    container_name: opensearch-node1
    environment:
      - cluster.name=opensearch-cluster
      - node.name=opensearch-node1
      - discovery.seed_hosts=opensearch-node1,opensearch-node2
      - cluster.initial_cluster_manager_nodes=opensearch-node1,opensearch-node2
      - bootstrap.memory_lock=true
      - "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m"
      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=${OPENSEARCH_INITIAL_ADMIN_PASSWORD}
      - path.repo=/mnt/snapshots
      - cluster.info.update.interval=60m
      - cluster.info.update.timeout=10s
      - node.roles=cluster_manager,data,ingest,warm
      - node.search.cache.size=1gb 
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - opensearch-data1:/usr/share/opensearch/data
      - ./snapshots:/mnt/snapshots
    ports:
      - 9200:9200
      - 9600:9600
    networks:
      - opensearch-net
      
  opensearch-node2:
    image: opensearchproject/opensearch:latest
    container_name: opensearch-node2
    environment:
      - cluster.name=opensearch-cluster
      - node.name=opensearch-node2
      - discovery.seed_hosts=opensearch-node1,opensearch-node2
      - cluster.initial_cluster_manager_nodes=opensearch-node1,opensearch-node2
      - bootstrap.memory_lock=true
      - "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m"
      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=${OPENSEARCH_INITIAL_ADMIN_PASSWORD}
      - path.repo=/mnt/snapshots
      - cluster.info.update.interval=60m
      - cluster.info.update.timeout=10s
      - node.roles=cluster_manager,data,ingest,warm
      - node.search.cache.size=1gb 
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - opensearch-data2:/usr/share/opensearch/data
      - ./snapshots:/mnt/snapshots
    networks:
      - opensearch-net
  opensearch-dashboards:
    image: opensearchproject/opensearch-dashboards:latest # Make sure the version of opensearch-dashboards matches the version of opensearch installed on other nodes
    container_name: opensearch-dashboards
    ports:
      - 5601:5601 # Map host port 5601 to container port 5601
    expose:
      - "5601" # Expose port 5601 for web access to OpenSearch Dashboards
    environment:
      OPENSEARCH_HOSTS: '["https://opensearch-node1:9200","https://opensearch-node2:9200"]' # Define the OpenSearch nodes that OpenSearch Dashboards will query
    networks:
      - opensearch-net

volumes:
  opensearch-data1:
  opensearch-data2:

networks:
  opensearch-net:

そして、以下のステップで操作を行ったところ、事象を再現できました。

  1. Snapshot Repositoryを作成
PUT _snapshot/my_repository
{
"type": "fs",
    "settings": {
        "location": "/mnt/snapshots/my_repository"
    }
}
  1. テスト用のインデックスを複数作成
PUT test-index-1
{"settings": {"number_of_shards": 2, "number_of_replicas": 0}}
... (repeat for test-index-2 through test-index-10)
  1. スナップショットを作成
PUT _snapshot/my_repository/snapshot_all?wait_for_completion=true
{
    "indices": "test-index-*"
}
  1. test-index-1をリストア("storage_type": "remote_snapshot"を指定)
POST _snapshot/my_repository/snapshot_all/restore
{
    "indices": "test-index-1",
    "storage_type": "remote_snapshot",
    "rename_pattern": "(.+)",
    "rename_replacement": "remote1$1"
}
  1. 即座に別のスナップショットをリストア
POST _snapshot/my_repository/snapshot_all/restore
{
    "indices": "test-index-2",
    "storage_type": "remote_snapshot",
    "rename_pattern": "(.+)",
    "rename_replacement": "remote1$1"
}
# 事象再現!
# POST _snapshot/my_repository/snapshot_all/_restore
{
  "error": {
    "root_cause": [
      {
        "type": "null_pointer_exception",
        "reason": null
      }
    ],
    "type": "null_pointer_exception",
    "reason": null
  },
  "status": 500
}

修正の実施とPR

Issueへの報告

先ずはここまでの調査をIssueで報告し、修正のPull Requestを送るつもりがある旨を宣言しました。
https://github.com/opensearch-project/OpenSearch/issues/19349#issuecomment-3419298846

開発環境の準備

実際の修正に取り掛かります。まずはOpenSearchのリポジトリをフォークして開発環境を準備します。
OpenSearch Projectは複数のリポジトリで構成されていますが、今回はopensearch-project
/OpenSearchが対象になります。

最初にContributing to OpenSearchGetting Started as an OpenSearch Project Contributorに目を通しておくと良いでしょう。

GitHubでhttps://github.com/opensearch-project/OpenSearchにアクセスし、右上の「Fork」ボタンをクリックして自分のアカウントにリポジトリをコピーします。

フォークしたリポジトリをローカルにクローンします。

git clone https://github.com/YOUR_USERNAME/OpenSearch.git
cd OpenSearch

本家のリポジトリをupstreamとして登録します。

git remote add upstream https://github.com/opensearch-project/OpenSearch.git
git fetch upstream

修正用のブランチを作成します。

git checkout -b fix-19349-npe-restore-remote-snapshot

修正の実装

RestoreService.javaの該当箇所を修正します。clusterInfo.getShardSize()がnullを返す可能性を考慮したコードに変更します。
ここで、clusterInfo.getShardSize()の結果が得られない場合に、Snapshotのリストアそのものを失敗させるべきか、そのまま通過させるべきかについて、実装の方針を悩みました。
考えた結果、このvalidation処理は運用上の補助的なものである点を考慮して、WARNログを出してそのまま処理を続行する実装を提案して、レビュアーの意見を聞くことにしました。(結果的にこの方針が採用されました)

修正前:

long totalRestoredRemoteIndexesSize = shardsIterator.getShardRoutings()
    .stream()
    .map(clusterInfo::getShardSize)
    .mapToLong(Long::longValue)
    .sum();

修正後:

long totalRestoredRemoteIndicesSize = 0;
int missingSizeCount = 0;
List<ShardRouting> routings = shardsIterator.getShardRoutings();

for (ShardRouting shardRouting : routings) {
    Long shardSize = clusterInfo.getShardSize(shardRouting);
    if (shardSize != null) {
        totalRestoredRemoteIndicesSize += shardSize;
    } else {
        missingSizeCount++;
    }
}

if (missingSizeCount > 0) {
    logger.warn(
        "Size information unavailable for {} out of {} remote snapshot shards. "
            + "File cache validation will use available data only.",
        missingSizeCount,
        routings.size()
    );
}

テストの追加

SearchableSnapshotIT.javaに統合テストを追加します。ClusterInfo更新を遅延させてshard sizeがnullになる状況を再現し、エラーが発生しないことを確認します。
https://github.com/opensearch-project/OpenSearch/blob/022d594dc33a3c7276b94305dc6c4a29fe53ba70/server/src/internalClusterTest/java/org/opensearch/snapshots/SearchableSnapshotIT.java#L1058-L1113

ビルドとテスト

修正が完了したらローカルでビルドとテストを実行します。

./gradlew build

追加したテストだけを実行する場合:

./gradlew :server:internalClusterTest --tests "*.SearchableSnapshotIT.testRestoreRemoteSnapshotWithNullShardSizes"

E2Eでのテスト

テストコードは通過しましたが、念の為にローカルビルドを使ったテストも実施します。OpenSearchをビルドすると、 OpenSearch/distribution/docker配下にコンテナ環境用のビルドファイルが作成されるので、これを利用してテストを実施します。

docker load < opensearch-arm64_test_3.4.0-SNAPSHOT.docker.tar
検証用のdocker-compose例
services:
  opensearch-node1:
    image: opensearch:test
    container_name: opensearch-local-node1
    environment:
      - cluster.name=opensearch-local-cluster
      - node.name=opensearch-local-node1
      - discovery.seed_hosts=opensearch-local-node1,opensearch-local-node2
      - cluster.initial_cluster_manager_nodes=opensearch-local-node1,opensearch-local-node2
      - bootstrap.memory_lock=true
      - "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m"
      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=${OPENSEARCH_INITIAL_ADMIN_PASSWORD}
      - path.repo=/mnt/snapshots
      - cluster.info.update.interval=60m
      - cluster.info.update.timeout=10s
      - node.roles=cluster_manager,data,ingest,warm
      - node.search.cache.size=1gb
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - opensearch-local-data1:/usr/share/opensearch/data
      - ./snapshots:/mnt/snapshots
    ports:
      - 9200:9200
      - 9600:9600
    networks:
      - opensearch-local-net

  opensearch-node2:
    image: opensearch:test
    container_name: opensearch-local-node2
    environment:
      - cluster.name=opensearch-local-cluster
      - node.name=opensearch-local-node2
      - discovery.seed_hosts=opensearch-local-node1,opensearch-local-node2
      - cluster.initial_cluster_manager_nodes=opensearch-local-node1,opensearch-local-node2
      - bootstrap.memory_lock=true
      - "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m"
      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=${OPENSEARCH_INITIAL_ADMIN_PASSWORD}
      - path.repo=/mnt/snapshots
      - cluster.info.update.interval=60m
      - cluster.info.update.timeout=10s
      - node.roles=cluster_manager,data,ingest,warm
      - node.search.cache.size=1gb
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - opensearch-local-data2:/usr/share/opensearch/data
      - ./snapshots:/mnt/snapshots
    networks:
      - opensearch-local-net

  opensearch-dashboards:
    image: opensearchproject/opensearch-dashboards:latest
    container_name: opensearch-local-dashboards
    ports:
      - 5601:5601
    expose:
      - "5601"
    environment:
      OPENSEARCH_HOSTS: '["http://opensearch-local-node1:9200","http://opensearch-local-node2:9200"]'
      DISABLE_SECURITY_DASHBOARDS_PLUGIN: "true"
    networks:
      - opensearch-local-net

volumes:
  opensearch-local-data1:
  opensearch-local-data2:

networks:

本環境で改めて再現検証を行ったところ、NullPointerExceptionが発生せず、想定通りのWARNログが出ていることが確認できました。

{"type": "server", "timestamp": "2025-11-09T09:06:44,242Z", "level": "WARN", "component": "o.o.s.RestoreService", "cluster.name": "opensearch-local-cluster", "node.name": "opensearch-local-node2", "message": "Size information unavailable for 8 out of 8 remote snapshot shards. File cache validation will use available data only.", "cluster.uuid": "dbwGOLHwSQKDy6fM624y0A", "node.id": "l6v98Z5KQXG2Qm8BwAxJPA"  }

コミット

テストが通ったら変更をコミットします。OpenSearchではDCO (Developer Certificate of Origin) への準拠が必要です。

まずGitの設定を確認します。

git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"

修正したファイルをコミットします。-sオプションで自動的にSigned-off-by行が追加されます。

git add server/src/main/java/org/opensearch/snapshots/RestoreService.java
git add server/src/internalClusterTest/java/org/opensearch/snapshots/SearchableSnapshotIT.java
git add CHANGELOG.md

git commit -s -m "Fix NPE in RestoreService when shard size is unavailable"

Pull Requestの作成

変更をGitHubにプッシュします。

git push origin fix-19349-npe-restore-remote-snapshot

GitHubの自分のフォークページにアクセスすると「Compare & pull request」ボタンが表示されるのでクリックしてPRを作成します。

https://github.com/opensearch-project/OpenSearch/pull/19684

CHANGELOG.mdの追記

OpenSearch Projectのポリシーに従って、CHANGELOG.mdを更新しましょう。

https://github.com/opensearch-project/OpenSearch/blob/022d594dc33a3c7276b94305dc6c4a29fe53ba70/CHANGELOG.md?plain=1#L64

PR作成後、マージまで

PR作成後、1日程度でレビューして貰えました。これは一般的なOSSとしては、かなり早い方である感覚です。これまでにOpenSearchに送ってきた他のPRも全て1-2日程度で最初のレスポンスを貰えており、コントリビューションにオープンな姿勢が感じられてとても良いです。

レビューでは、軽微な変数名の修正のみが指摘事項として挙がり、数日でApprove Changesを貰うことができました。

https://github.com/opensearch-project/OpenSearch/pull/19684#pullrequestreview-3365627980

PR時に少し困った点としては、CIが全体的に不安定で、Flaky Testが原因で無関係なチェックで引っ掛かる事象が多発しました。ただ、解決に向けてメンテナの方々にサポートして頂けたので、それほど深刻な課題にはなりませんでした。
これに関しては、Flaky Testを検出するBotが組み込まれているなど、改善への取り組みも進行中のようです。関心がある方は、CIの安定化へのコントリビューションを検討しても良いかもしれません。

まとめ

本記事では、OpenSearchの検証を通じて発見したバグの原因を調査し、修正のPRを作成してマージしてもらうまでの一連の流れを実体験に基づいてご紹介しました。
OpenSearchはコミュニティによって活発に開発が行われているソフトウェアであり、新しい機能の追加や、既存の挙動の改善まで、様々な形でコントリビューションできる余地があります。本記事でOpenSearchへの貢献に興味を持ち、挑戦される方が現れるのを心待ちにしております!

もし、OpenSearchへの貢献に興味が湧いたら、以下の記事もぜひ参考にしてみてください。
https://bering.hatenadiary.com/entry/2025/09/25/102522

OpenSearch Project

Discussion