[Bug]: With `stem_interactive_updates = false`, replicated writes can leave a leaf with no body (500 on GET, `_changes`, views and compaction)
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 4/5
- 見積もり時間
- 3〜5日
- 初心者へのやさしさ
- 35/100
調査の方向性
Start with replicated_path, maybe_stem_full_doc_info, and couch_key_tree:merge/stem/2, then reproduce with the provided _bulk_docs sequence while stem_interactive_updates is false. Confirm the stored tree has a bodyless duplicate leaf, implement a narrowly scoped repair, and run couch_db_doc_tests, couchdb_update_conflicts_tests, and couch_key_tree_tests. Done means the expected a -> b -> c -> d tree has only deleted leaf 4-d, while GET, _changes, views, replication, and compaction no longer crash.
索引モデルが issue の本文から書いたものです。
説明
Version
unknown (main). From 9af4ae4c ("Fix revision patching for replicated changes", #6168); tested at 9af4ae4c and 5b14b7c. Not in 3.5.3.
Describe the problem you're encountering
With [couchdb] stem_interactive_updates = false, three replicated writes (_bulk_docs with new_edits: false) leave a document with a leaf whose value is ?REV_MISSING. After that:
GET /db/Xreturns 500 (function_clauseincouch_doc:rev_info/1, reached fromto_doc_info_path/1). An interactivePUTto X also returns 500.GET /db/_changesreturns 500 for any feed that reaches X's update, including the full feed fromsince=0.- Views on the database return 500, and replicating from it fails with
changes_reader_died. - Compaction crashes on the same leaf (
function_clauseincouch_bt_engine_compactor:copy_docs/4), so the database can no longer be compacted. _all_docshas no row for X, becausecouch_doc:is_deleted/1does not count the[]leaf as live.
Setting stem_interactive_updates back to true does not clear it. A later replicated write to X with stemming on does.
Tested with the same toolchain, before and after the commit:
| Commit | GET X | _changes |
Compaction |
|---|---|---|---|
| d3884102 (parent of 9af4ae4c) | 200 | 200 | completes and folds the duplicate (GET then 404, deleted) |
| 9af4ae4c | 500 | 500 | crashes |
| 5b14b7c | 500 | 500 | crashes |
It is not tied to tiny revision histories: the same three writes with a 998-revision history for the deleted branch (revisions 3 to 1000, against a full history from revision 1) give the same results.
Expected Behaviour
The same result as with the default setting: one path a -> b -> c -> d with 4-d deleted as the only leaf, so GET /db/X returns 404 (deleted), and _changes, views and compaction work.
Steps to Reproduce
Single node, plain database, no VDU. Needs curl. The flag is set on _node/_local only, so on a multi-node ./dev/run cluster set it on every node (or use ./dev/run -n 1).
COUCH=${COUCH:-http://admin:[email protected]:5984}
DB=r3
A=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; B=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
C=cccccccccccccccccccccccccccccccc; D=dddddddddddddddddddddddddddddddd
curl -sS -X PUT "$COUCH/_node/_local/_config/couchdb/stem_interactive_updates" \
-H 'X-Couch-Persist: false' -d '"false"'
curl -sS -X DELETE "$COUCH/$DB" > /dev/null
curl -sS -X PUT "$COUCH/$DB"
put() {
curl -sS -X POST "$COUCH/$DB/_bulk_docs" -H 'Content-Type: application/json' \
-d "{\"new_edits\":false,\"docs\":[$1]}"; echo
}
put '{"_id":"X","_rev":"1-'$A'","_revisions":{"start":1,"ids":["'$A'"]},"v":1}'
put '{"_id":"X","_rev":"4-'$D'","_deleted":true,"_revisions":{"start":4,"ids":["'$D'","'$C'"]}}'
put '{"_id":"X","_rev":"3-'$C'","_revisions":{"start":3,"ids":["'$C'","'$B'","'$A'"]},"v":3}'
curl -sS -o /dev/null -w '%{http_code}\n' "$COUCH/$DB/X" # 500 on main, 200 on d3884102
curl -sS -o /dev/null -w '%{http_code}\n' "$COUCH/$DB/_changes" # 500 on main, 200 on d3884102
curl -sS -X DELETE "$COUCH/_node/_local/_config/couchdb/stem_interactive_updates" \
-H 'X-Couch-Persist: false'
Your Environment
Built from source, Erlang/OTP 28.5, ./configure --dev --js-engine=quickjs, single node, macOS arm64. Reproduces at q=1, q=2 and q=8.
Additional Context
From the code and a dump of the stored tree, this is what happens. After the second write the tree is a and c -> d, where c is an internal node with ?REV_MISSING. In the third write, replicated_path calls get_full_key_paths(OldTree, [{3, c}]), finds c inside the c -> d branch, and maps the new path's leaf to that stored value. It assumes the merge lands on the node it found, but couch_key_tree:merge extends the first branch it can (a) and stops, so the value ends up on a different node: a leaf 3-c with no body, plus a duplicate c. With stemming on, stem throws dupe_keys and repair_tree folds the duplicate into a -> b -> c -> d, which hides it. With stemming off the updater never repairs it, and compaction, which does stem, crashes on the bodyless leaf before it gets there.
Stored tree after each write (couch_db:get_full_doc_info)
missing is ?REV_MISSING, body a stored #leaf{}, del a deleted one.
main (5b14b7c) d3884102
write 1 [{1, a body}] [{1, a body}]
write 2 [{1, a body}, {3, c missing -> d del}] [{1, a body}, {3, c missing -> d del}]
write 3 [{1, a body -> b missing -> c missing}, [{1, a body -> b missing -> c body},
{3, c missing -> d del}] {3, c missing -> d del}]
leaves 4-d, 3-c; duplicate key {3, c} leaves 4-d, 3-c; duplicate key {3, c}
Before 9af4ae4c the flag-off tree already holds the duplicate. The shard has two leaves, so GET /db/X returns live 3-c while open_revs=all shows only 4-d, because fabric merges the two leaf paths. That lasts until the next compaction folds it. 9af4ae4c makes the new leaf bodyless, which turns the duplicate into a crash and blocks the compaction that would have repaired it.
Through the replicator, the third write can only reach a target that already has {3, c} as a key by way of the _revs_diff skip in find_missing, the same path as in #6162. I have only reproduced this with _bulk_docs.
Two approaches I tried locally:
- Making
replicated_pathskip the copy when the revision lands as a leaf removes the 500, but with stemming on that leaf can be folded into an internal node byrepair_treeright after, so the check is not reliable insidereplicated_path. - Leaving
replicated_pathas it is and folding duplicate keys in thefalsebranch ofmaybe_stem_full_doc_info(callingcouch_key_tree:stem/2with a depth limit of16#7FFFFFFF, which only falls intorepair_treewhen there is a duplicate and trims no history) fixes it: the three writes end asa -> b -> c -> dwith4-ddeleted as the only leaf. The change is confined to thefalsebranch, so default behaviour is unchanged. With it,couch_db_doc_tests(which runs with the flag off),couchdb_update_conflicts_testsandcouch_key_tree_testspass. It does walk the tree on every write with the flag off, which the flag exists to avoid; a narrower version would repair only whenreplicated_pathfound the revision but the merge reported a new leaf rather thaninternal_node.
The flag is still read in maybe_stem_full_doc_info and set by couch_db_doc_tests, and #3193 (fixed in 2.1) was an earlier bug with the flag off. If the flag isn't worth keeping, removing it closes this too. If one of the approaches above looks right, I can test it against these cases.
- 主要言語
- Erlang
- スター
- 7k
- フォーク
- 1.1k
- 平均マージ
- 10時間 25分
- マージ済み PR(30日)
- 35
環境構築
このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
apache/couchdb のほかの issue
-
難易度 3/5 1〜2日 初心者へのやさしさ 45/100
apache/couchdb#6166 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
Fix: Warning: 'catch ...' is deprecated; please use 'try ... catch ... end' instead再び着手できるかも @devx-arjun が 13 日前に担当しましたが、オープン中のプルリクエストはありません。 オープンbeginner-friendly build chore patches-welcome
難易度 4/5 3〜5日 初心者へのやさしさ 45/100
apache/couchdb#6144 · コメント 4 件 ·
メンテナーはふだん 1 日以内に返信
-
bug needs-triage
難易度 4/5 3〜5日 初心者へのやさしさ 48/100
apache/couchdb#6132 · コメント 3 件 ·
メンテナーはふだん 1 日以内に返信
-
[ENHANCEMENT]: Add seq checkpoints to continuous feeds対応中かも @nickva が 6 日前に担当しました。 オープンenhancement
難易度 5/5 1週間以上 初心者へのやさしさ 45/100
apache/couchdb#6120 · コメント 7 件 ·
メンテナーはふだん 1 日以内に返信
-
enhancement needs-triage
難易度 3/5 1〜2日 初心者へのやさしさ 55/100
メンテナーはふだん 1 日以内に返信
似ている issue
-
wr.dynamodb.read_items returns [{}] for a single partition value that does not exist対応中かも @auraseb が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
aws/aws-sdk-pandas#3520 ·
メンテナーはふだん 2 日以内に返信
-
[DC] OneLogin sync imports a partial user list when an API request fails対応中かも @RaphaelFakhri が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 84/100
bitwarden/directory-connector#1276 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 62/100
home-assistant/installer#309 ·
メンテナーはふだん 1 日以内に返信
-
good first issue
難易度 2/5 1〜3時間 初心者へのやさしさ 88/100
メンテナーはふだん 1 日以内に返信
-
decision kits
難易度 2/5 1〜3時間 初心者へのやさしさ 79/100
firebase/extensions#3273 ·
メンテナーはふだん 1 日以内に返信