Note

大きめの OSS に Issue を出してみた(OpenSearch)

OpenSearch に出したバグ報告を中心に、出すまでの流れ・Issue の組み立て・出したあとに起きたこと・心構えを、図でまとめた記録。機能要望の 1 件にも触れる。

OSSOpenSearch

2026 年 10 月、検索エンジンの OpenSearch に Issue を 2 件出しました。1 件はバグ報告、1 件は機能要望です。OpenSearch は GitHub のスターが 1 万を超える大きめの OSS で、Issue の書き方にもテンプレートや仕分けの決まりがあります。この記事では主にバグ報告の 1 件について、何をどんな順番で調べ、どう書き、出したあと何が起きたかを、心構えと一緒にまとめます。

1なぜ出したか

仕事で検索まわりを触っていると、OpenSearch の挙動をコードまで追う場面がよくあります。追った結果「これは自分の使い方の問題ではなく、OpenSearch 側の問題だ」と言えるところまで来たら、Issue にして共有した方が、同じところで困る人が減ります。

とはいえ、知らない人たちが毎日たくさんの Issue をさばいている場所に書き込むのは、少し身構えます。なので「相手の時間をできるだけ使わせない Issue」を目標に、調べたことを全部まとめてから出しました。

2出した Issue

バグ報告は、OpenSearch 3.x の検索の最適化(range クエリの近似)が、本来かかってはいけない場所にもかかってしまい、条件に合うドキュメントがエラーもなく黙って結果から落ちる、というものです[1]。この記事では中身より「どう出したか」に絞るので、詳しくは Issue の本文を見てください。

GitHub · opensearch-project/OpenSearch #23303[BUG] Range query approximation is applied to post_filter, rescore_query, named queries and inner_hits, silently dropping matchesDescribe the bug A range query on a numeric or date field is wrapped in ApproximateScoreQuery, so that it can stop early when it is the main query. However, the same approximation is also applied w...github.com/opensearch-project/OpenSearch/issues/23303

3出すまでの流れ

気づいてから出すまでに、8 つのことをしました。順番は前後しますが、どれも「メンテナーが読んで、すぐ動ける」ためのものです。

  1. 1おかしいと気づく

    post_filter に range を置くと、当てはまるはずの件数より少ない。エラーは出ず、件数は「ちょうど」と報告される。

    なぜエラーにならない間違いは気づきにくく、気づいた人が報告しないと残り続ける。

  2. 2再現を小さくする

    単一ノード・1 シャード・curl だけのスクリプトに削った。データは value = (i × 7919) % N で、件数 N を変えるだけで規模を変えられる。

    なぜメンテナーが手元でそのまま動かせる形にする。前提が少ないほど、別の原因を疑われにくい。

  3. 3いつから壊れたか調べる

    公式 Docker イメージで 2.19.5 / 3.0.0 / 3.1.0 / 3.9.0 を並べ、同じスクリプトの結果を表にした。2.19.5 は正しく、3.x で壊れている。

    なぜ「いつから」が分かると、原因の変更をかなり絞り込める。

  4. 4同じ報告がないか探す

    既存の Issue と PR を探して、見つからなかったことを本文に書いた。

    なぜ重複はメンテナーの手間になる。探した、と書いておけば読む側も安心できる。

  5. 5原因をコードで追う

    近似を有効にする処理が、本命のクエリ以外(post_filter など)にもかかっていた。該当箇所はバージョンのタグに固定したリンクで示した。

    なぜmain ブランチへのリンクは、コードが変わると行がずれる。タグに固定すれば、後から読んでも同じ行を指す。

  6. 6直し方を考える

    「本命のクエリのときだけ近似する」という 1 行の条件を diff で載せた。参照の同一性に頼る弱点と、別の直し方の案も添えた。

    なぜ直し方は相手が決めるもの。案として出し、懸念も先に書いておくと議論が早い。

  7. 7手元で確かめる

    3.9.0 をローカルでビルドし、新しいテストが修正前に落ちて修正後に通ること、既存のテストが通ることを確かめた。

    なぜ「たぶん直る」ではなく「こう確かめた」と書けると、修正案の信頼度が上がる。

  8. 8テンプレートに沿って出す

    バグ報告のテンプレート(症状・コンポーネント・再現・期待する動作・環境)に沿って書き、最後に「よければ PR を出す」と添えた。

    なぜテンプレートの順はメンテナーが読みたい順。自分で直す気があることも伝わる。

中でも時間をかけたのが再現を小さくすることです。最初に気づいたときの状況には、関係のない設定やデータがたくさん混ざっています。それを 1 つずつ外し、curl だけで動く、1 ノード・1 シャードのスクリプトにしました。件数 N を変えるだけで規模を変えられるので、「ドキュメントが多いときだけ起きる」といった条件も確かめられます。

bash
OS=http://localhost:9200
N=20000   # must not be a multiple of 7919
IDX=postfilter-range-repro

curl -s -XDELETE "$OS/$IDX"
curl -s -XPUT "$OS/$IDX" -H 'Content-Type: application/json' -d '{
  "settings": { "number_of_shards": 1, "number_of_replicas": 0 },
  "mappings": { "properties": { "value": { "type": "long" }, "grp": { "type": "keyword" } } }
}'

# value = (i * 7919) % N is a permutation of 0..N-1 that is unrelated to doc order.
# Every 10th doc has grp=a, so N/10 docs match the main query.
seq 0 $((N - 1)) | awk -v n="$N" '{
  printf "{\"index\":{\"_id\":\"%d\"}}\n", $1
  if ($1 % 10 == 0) printf "{\"value\":%d,\"grp\":\"a\"}\n", ($1 * 7919) % n
  else              printf "{\"value\":%d}\n", ($1 * 7919) % n
}' | curl -s -XPOST "$OS/$IDX/_bulk?refresh=true" -H 'Content-Type: application/x-ndjson' --data-binary @- > /dev/null
curl -s -XPOST "$OS/$IDX/_forcemerge?max_num_segments=1"
curl -s -XPOST "$OS/$IDX/_refresh"

# Every document satisfies value >= 0, so hits.total should be N/10
curl -s -XPOST "$OS/$IDX/_search?filter_path=hits.total" -H 'Content-Type: application/json' -d '{
  "query": { "term": { "grp": "a" } },
  "post_filter": { "range": { "value": { "gte": 0 } } }
}'
Code 1Issue に載せた再現スクリプト。正しければ hits.total は 2,000 だが、3.1.0 以降では 1,024 になる。

もう 1 つはいつから壊れたかです。公式の Docker イメージならバージョンを指定してすぐ起動できるので、同じスクリプトを 2.19.5・3.0.0・3.1.0・3.9.0 で流し、結果を表にしました。2.19.5 は正しく、3.0.0 は大きなセグメントでだけ、3.1.0 以降は常に壊れている、という段差が見えたことで、原因になった変更をかなり絞り込めました。

4Issue の組み立て

OpenSearch には Issue のテンプレートがあり、バグ報告なら「症状・関係するコンポーネント・再現手順・期待する動作・環境」の順に書く欄が用意されています[5]。この順番は、メンテナーが知りたい順番そのものです。バグ報告では、そのあとにテンプレートにない「原因」と「修正案」を足しました。

GitHub の New issue 画面に表示された OpenSearch のバグ報告テンプレート
Fig. 1OpenSearch のバグ報告のテンプレート。タイトルは「[BUG] <title>」から始まり、必須の欄(*)が順に並ぶ。bug と untriaged のラベルは最初から付いている。
  1. タイトル

    [BUG] Range query approximation is applied to post_filter, rescore_query, named queries and inner_hits, silently dropping matches

    役割何が・どこで・どうなるかを 1 行に入れる。一覧で見たメンテナーが、自分の担当か、どれくらい深刻かを開かずに判断できる。

  2. ### Describe the bugテンプレート

    症状(エラーなしで一致が落ちる、件数が eq のまま減る)、影響する 6 か所、影響するバージョン(3.0.0〜3.9.0 と main、2.19.5 は影響なし)。

    役割最初の数行で「誰に関係あるか」が分かるようにする。影響範囲が広いことも、ここで伝わる。

  3. ### Related componentテンプレート

    Search:Performance

    役割テンプレートの選択肢から選ぶ。ラベルになり、その領域の担当者に届く。

  4. ### To Reproduceテンプレート

    curl だけで動く再現スクリプトと、4 つのバージョンでの結果の表。ほかの影響箇所の再現は折りたたみに入れた。

    役割コピーして貼れば再現できる、が一番強い。表があると「いつから」が一目で分かる。長い補足は折りたたんで、本筋を読みやすく保つ。

  5. ### Expected behaviorテンプレート

    post_filter などでは range を正確に評価してほしい(2.19 と同じ)。近似してよいのは本命のクエリだけ。

    役割「何が正しいか」を一文で。ここが曖昧だと、仕様なのかバグなのかの議論から始まってしまう。

  6. ### Additional Detailsテンプレート

    公式 Docker イメージ、単一ノード、セキュリティプラグインなし、macOS(arm64)。プラグインに依存しないこと。

    役割環境の違いで再現しない、を防ぐ。

  7. 原因追加した節

    近似を有効にする処理が、本命のクエリ以外にもかかっている。該当コードへのリンク(3.9.0 のタグに固定)と、3.0.0 では大きなセグメントでしか起きなかった理由。

    役割テンプレートにはない節。調べたことを残しておくと、直す人が同じ調査をやり直さずに済む。

  8. 修正案追加した節

    1 行の条件を足す diff、ローカルで足したテストと通した既存テスト、この直し方の弱点と別案、「よければ PR を出す」。

    役割案として出す。弱点も自分から書く。決めるのはメンテナー。

修正案は、近似を有効にする処理を「本命のクエリのときだけ」に絞る 1 行の条件です。ただし、これは参照が同じかどうかで本命のクエリを見分けるので、誰かが本命のクエリを包み直すと近似が効かなくなる、という弱点があります。結果が間違うことはなく遅くなるだけ、という点も含めて本文に書き、別の直し方の案も添えました。

ContextIndexSearcher.java
     @Override
     public Query rewrite(Query original) throws IOException {
-        if (original instanceof ApproximateScoreQuery approximateScoreQuery) {
+        // Only the main query may be approximated: ...
+        if (original instanceof ApproximateScoreQuery approximateScoreQuery && searchContext != null && original == searchContext.query()) {
             approximateScoreQuery.setContext(searchContext);
         }
Code 2Issue に載せた修正案の差分(コメントは省略)。

テストも手元で回しました。修正前に落ちて修正後に通る新しいテストを足し、関係する既存のテストが通ることを確かめて、その一覧を書いています。修正案が「たぶん直る」ではなく「こう確かめた」になると、受け取る側の安心感がまったく違うはずです。

5出したあと

出したあとの流れは、思っていたよりずっと機械的で、そして速いものでした。

  1. 10/11 21:47 JSTバグ報告 · 自分バグ報告 #23303 を出す

    テンプレートに沿って、再現・バージョン表・原因・修正案まで書いて出した。

  2. 10/11 21:48 JST出してから約 10 秒後バグ報告 · 自動ラベルとボードが自動で付く

    10 秒ほどの間に bug・untriaged・lucene・Search:Performance のラベルが付き、プロジェクトのボードに載った。untriaged は「まだ誰も仕分けていない」の印。

  3. 10/12 03:05 JST出してから約 5 時間後バグ報告 · ほかの人別の人が修正 PR #23304 を出す

    約 5 時間後。中身は Issue に書いた修正案と同じ 1 行の条件と、そのテスト。PR の本文に「Fixes #23303」とあり、Issue と結びついた。

  4. 10/12 03:41 JST出してから約 6 時間後バグ報告 · ほかの人PR の CI が承認待ちに

    CI(Gradle check)が失敗し、その後の実行はメンテナーの承認待ちになった。PR の作者が承認と再実行をお願いしている。

  5. 10/15 01:12 JST出してから約 3 日後バグ報告 · メンテナートリアージされる

    約 3 日後。メンテナーが「[Search Triage]」としてお礼を書き、レビュー担当を 2 人指名した。untriaged と lucene のラベルが外れた。

  6. 10/25 03:13 JST機能要望 · 自分機能要望 #23429 を出す

    数秒で untriaged・Search・lucene のラベルが付き、ボードに載った。

  7. 10/28バグ報告 · 現在今: どちらもオープン

    バグ報告は PR のレビュー待ち。機能要望はまだトリアージ待ちで、コメントは付いていない。

出した直後に、テンプレートと自動処理でラベルが付き、プロジェクトのボードに載りました。untriaged は「まだ誰も仕分けていない」の印で、メンテナーが中身を見て担当の領域を決めると外れます。バグ報告では、約 3 日後に検索まわりのトリアージで外れ、レビュー担当が指名されました。

GitHub 上の Issue #23303 の画面
Fig. 2出した Issue #23303。右側にラベル(Search:Performance・bug)とプロジェクトのボード(Search Project Board)、タイトルの下に結びついた PR #23304 が出ている。

意外だったのは、約 5 時間後に別の人が修正 PR を出したことです[2]。中身は Issue に書いた修正案と同じ条件でした。本文の最後に「よければ自分で PR を出す」と書いていましたが、修正案を具体的に書いておけば、ほかの人がそのまま PR にできます。Issue は「報告」であると同時に、誰かが直すための材料でもあるのだと実感しました。

GitHub 上の PR #23304 の画面
Fig. 3別の人が出した修正 PR #23304。本文の「Fixes #23303」で Issue と結びつき、右側にはレビュー候補が自動で並んでいる。

6機能要望も出してみた

2 件目は機能要望です[3]。近似 k-NN 検索(Faiss エンジン)と post_filter を組み合わせると、同じクエリなのに、無関係なフィールドを更新しただけで結果が変わる、という現象を追っていました。原因は、k 件を選ぶ処理がセグメントごとに行われることです[6]。

GitHub · opensearch-project/OpenSearch #23429[Feature Request] Expose which Lucene segment each search hit came from (opt-in, for debugging)Is your feature request related to a problem? Please describe With approximate k-NN on the Faiss (and NMSLIB) engine, k is applied per segment. When the query is combined with post_filter, whether ...github.com/opensearch-project/OpenSearch/issues/23429

これを確かめるには「どのヒットがどのセグメントから来たか」が分かればよいのですが、それを返す API はありません。ソートの値とセグメント一覧を突き合わせる回避策は使えたものの、前提が多く壊れやすい。そこで、version などと同じ形のオプトインのフラグで、ヒットごとのセグメントを返せるようにしてほしい、という提案にしました。

バグ報告との違いは、「正しい動作」が決まっていないことです。なので、困っている問題を再現できる形で示し、ほしい形を具体的な JSON で見せ、すでにある機能(_cat/segmentsや Profile API など)ではなぜ足りないかを 1 つずつ書きました。機能要望は「それなら〇〇で足りるのでは」と返されやすいので、代わりの案を先に調べて潰しておくのが大事だと考えました。こちらはまだトリアージ待ちです。

  1. タイトル

    [Feature Request] Expose which Lucene segment each search hit came from (opt-in, for debugging)

    役割何がほしいかに加えて、「opt-in」「for debugging」で影響の小ささと用途を先に伝える。

  2. ### Is your feature request related to a problem? Please describeテンプレート

    k-NN と post_filter を組み合わせると、ドキュメントがどのセグメントにいるかで結果が変わる現象。curl の再現と、今の回避策(_doc ソート + _cat/segments)が壊れやすい理由。

    役割機能要望でも、まず「困っていること」を再現できる形で示す。ほしい機能の必要性は、問題の具体さで決まる。

  3. ### Describe the solution you'd likeテンプレート

    version や seq_no_primary_term と同じ形の opt-in フラグ。返す JSON の例と、fetch の段階で読めるので小さく作れそうだという見立て。

    役割既存の API に似た形にすると、受け入れる側の想像がしやすい。完成形の例を見せると話が早い。

  4. ### Related componentテンプレート

    Search

    役割ラベルになり、担当の領域に届く。

  5. ### Describe alternatives you've consideredテンプレート

    _cat/segments、_shard_doc、k-NN の explain、Profile API、efficient filtering。それぞれで何が分かって何が分からないか。

    役割「それなら〇〇で足りるのでは」を先回りして潰す。既存の機能をよく調べたことも伝わる。

  6. ### Additional contextテンプレート

    2.19.5 と 3.9.0 で同じ結果になること、関連するドキュメントへのリンク。

    役割前提をそろえる。

7心構え

2 件出してみて、大きめの OSS に Issue を出すときに大事だと思ったことをまとめます。

まとめ
  • 再現を小さくし、いつから壊れたかを調べると、Issue の説得力が一気に上がる。
  • テンプレートの順はメンテナーが読みたい順。原因や修正案は、その後ろに足す。
  • 修正案を具体的に書くと、自分以外の人もすぐ直しに動ける。
  • 機能要望は、困っていることの再現と、既存の機能では足りない理由が要る。

8リンク

出した Issue と関連する PR、OpenSearch の資料です。Issue の本文は #23303 で全文を読めます。

  1. [1]kotta-27. [BUG] Range query approximation is applied to post_filter, rescore_query, named queries and inner_hits, silently dropping matches. opensearch-project/OpenSearch #23303
  2. [2]Shivampal157. Fix approximation on non-main queries. opensearch-project/OpenSearch #23304
  3. [3]kotta-27. [Feature Request] Expose which Lucene segment each search hit came from (opt-in, for debugging). opensearch-project/OpenSearch #23429
  4. [4]OpenSearch. CONTRIBUTING.md. opensearch-project/OpenSearch
  5. [5]OpenSearch. バグ報告のテンプレート(bug_template.yml). opensearch-project/OpenSearch
  6. [6]OpenSearch. Approximate k-NN search. OpenSearch Documentation 2.17