Transaction recovery takes a transaction whose final status it reads one poll later for a failed one
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
- 45/100
- Tipo di issue
- Bug
- Chiarezza
- Abbastanza chiara
- Stato di attività
- Attiva
- Stack tecnologico
- java
- Ambito
- backend, databases, distributed-systems
Direzione di ricerca
Start with StandardTransactionLogProcessor and KCVSLog to understand how recovery tracks processed log progress and expires transactions. Run the named JanusGraphIndexTest replay and recovery tests, plus the JanusGraph user-log recovery test, with their recovery-graph read options. Done means late final statuses are not double-counted, repairs are counted after being attempted, and the replay tests pass without fixed sleeps.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Bug
Transaction recovery (StandardTransactionLogProcessor) gives up on a transaction tx.max-commit-time after it has read one of the transaction's log entries (5 s at least). But it reads the transaction log in polls:
- one every
log.tx.read-interval(5 s by default); - each covering the log up to
log.tx.read-lag-timeback; - none going past the end of the 100 s timeslice it is in.
So recovery can read a transaction's final status up to two read intervals after its first entry, even when the commit took far less than tx.max-commit-time. The read lag cancels out: it delays every entry equally.
When the final status arrives after the expiry, recovery takes a transaction that succeeded for a failed one:
- It treats the transaction as failed at
PRIMARY_SUCCESS, restores the index documents of every element the transaction changed, and sends its user-log event again. ALogProcessorFrameworkprocessor then receives the transaction twice. The recovery docs (#4965) describe this as a risk only for commits that outlasttx.max-commit-time, but it also happens to commits that finished well inside it. - The late final status creates a second, empty entry for the same transaction. That entry expires as another transaction ("Trying to repair expired or unpersisted transaction"), so the transaction is counted twice.
How likely this is depends on how close tx.max-commit-time is to the read interval. With both at 5 s, the least recovery waits, a final status read one poll after the first entry arrives within milliseconds of the expiry, either before or after it.
Reproduction
A new JanusGraphIndexTest.testRecoveryWaitsForAFinalStatusReadOnePollLater:
- Settings:
tx.max-commit-time5 s,log.tx.read-interval6 s,log.tx.read-lag-time2 s. - The transaction writes a user log, and its index write takes 3 s and then succeeds.
- Recovery is started as the commit returns. Its first poll takes in the commit's first entries but not the final status, which was written less than 2 s earlier. The next poll comes 6 s later, after the entry has expired.
On master it fails 3 times out of 3 with expected: <1> but was: <2> user-log events.
The flaky replay tests
This is also behind the flaky JanusGraphIndexTest replay tests (testIndexReplay, testRecurringIndexReplay, testRecurringIndexReplayWithDifferentStartTime). They are BRITTLE, so of the CI jobs only the Solr jobs run them. They have three problems:
- Default read options on the recovery graph. They start recovery on a graph opened by a plain
clopen(). That drops thelog.tx.read-intervalof 250 ms andlog.tx.read-lag-timeof 50 ms they set earlier, because both options areMASKABLE. Recovery therefore polls every 5 s against a 5 s expiry (tx.max-commit-timeis 1 s, raised to the 5 s minimum). Locally the last transaction's final status is written less than 500 ms before the first poll, so only the next poll reads it, within milliseconds of the expiry.- Locally,
BerkeleyLuceneTest.testIndexReplayfailed withexpected: <4> but was: <5>. - With verbose recovery, runs show that transaction being repaired twice, the second time as "expired or unpersisted".
- Locally,
- A fixed 12 s wait. When a 100 s timeslice boundary falls between the schema transaction and the others, the others are read one poll later. They then expire just after the cleaner's second pass and are only counted at shutdown, after the test has read the statistics. This produced
CQLSolrTest.testRecurringIndexReplay:2585 expected: <4> but was: <0>in run 36059281715, where the success count of 1 had already been reached. - Counting before the repair.
getStatistics()counts a failed transaction before its repair starts. The repair runs on Caffeine's removal-listener executor, so a caller that sees the count cannot tell whether the repair has finished.
Fix
- Recovery gives a transaction up by how far it has read the log rather than after a wall-clock wait: once the log has been read, and its messages processed, up to
tx.max-commit-timepast the transaction's start. A wall-clock allowance can't cover it: polls start a read interval after the previous one ended, reads are retried for up tolog.tx.max-read-time, and all partitions share the read threads.KCVSLognow tracks how far its readers have processed what it read, and the recovery's cache uses that as its clock. - A failed transaction is counted once its repair has been attempted.
- The replay tests pass the fast read options to the graph they start recovery on, and wait for the statistics instead of sleeping a fixed 12 s, then long enough for a double count to show. The user-log recovery test in
JanusGraphTestgets the same options on its recovery graph.
A commit that really does outlast tx.max-commit-time, or whose instance fails between its user-log write and its final status, still gets its user-log event sent again. That is at-least-once delivery, and recovery cannot tell such a case from a failed user-log write. The repeat carries the transaction id of the original.
- Lingua principale
- Java
- Stelle
- 5.8k
- Fork
- 1.2k
- Merge medio
- 20h 36m
- PR unite (30g)
- 25
Preparare l'ambiente
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 JanusGraph/janusgraph
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 95/100
JanusGraph/janusgraph#4943 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 62/100
JanusGraph/janusgraph#1578 ·
I maintainer di solito rispondono entro 1 giorno
-
Native bitemporal supportAperta
Difficoltà 5/5 Più di una settimana Idoneità per principianti 25/100
JanusGraph/janusgraph#4954 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 25/100
JanusGraph/janusgraph#4934 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 35/100
JanusGraph/janusgraph#4923 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di JanusGraph/janusgraph
Issue simili
-
bug
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
-
component/zeebe kind/bug
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 90/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
UniversalMediaServer/UniversalMediaServer#6356 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 88/100
refinedmods/refinedstorage2#1414 · 1 commento ·
-
Difficoltà 1/5 1-3 ore Idoneità per principianti 88/100
yegor256/rultor-image#76 · 1 commento ·