[Bug]: With `stem_interactive_updates = false`, replicated writes can leave a leaf with no body (500 on GET, `_changes`, views and compaction)
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 4/5
- Tempo stimato
- 3-5 giorni
- Idoneità per principianti
- 35/100
Direzione di ricerca
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.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
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.
- Lingua principale
- Erlang
- Stelle
- 7k
- Fork
- 1.1k
- Merge medio
- 14h 47m
- PR unite (30g)
- 38
Preparare l'ambiente
Avvia il container di sviluppo del progetto nel browser, con il tuo account GitHub.
- Nessun Dockerfile né file Docker Compose
- Ha un modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di apache/couchdb
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 45/100
apache/couchdb#6166 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Fix: Warning: 'catch ...' is deprecated; please use 'try ... catch ... end' insteadForse di nuovo libera @devx-arjun l’ha presa 15 giorni fa e non c’è nessuna pull request aperta. Apertabeginner-friendly build chore patches-welcome
Difficoltà 4/5 3-5 giorni Idoneità per principianti 45/100
apache/couchdb#6144 · 4 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
bug needs-triage
Difficoltà 4/5 3-5 giorni Idoneità per principianti 48/100
apache/couchdb#6132 · 3 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
enhancement needs-triage
Difficoltà 3/5 1-2 giorni Idoneità per principianti 55/100
I maintainer di solito rispondono entro 1 giorno
-
bug needs-triage
Difficoltà 4/5 3-5 giorni Idoneità per principianti 48/100
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di apache/couchdb
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 74/100
zach-blumenfeld/aip#13 ·
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
endora-commerce/endora-commerce#397 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 82/100
NVIDIA-Omniverse/usd-brep#23 ·
-
documentation good first issue hacktoberfest python
Difficoltà 2/5 1-3 ore Idoneità per principianti 86/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 69/100
pollen-robotics/reachy_mini#1493 ·
I maintainer di solito rispondono entro 1 giorno