Transaction recovery takes a transaction whose final status it reads one poll later for a failed one
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 45/100
- Tipo de issue
- Error
- Claridad
- Bastante claro
- Estado de actividad
- Activo
- Stack tecnológico
- java
- Área
- backend, databases, distributed-systems
Línea de trabajo
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.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
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.
- Lenguaje dominante
- Java
- Estrellas
- 5.8k
- Forks
- 1.2k
- Merge medio
- 20 h 36 min
- PR fusionados (30 d)
- 25
Preparar el entorno
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de JanusGraph/janusgraph
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 95/100
JanusGraph/janusgraph#4943 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 62/100
JanusGraph/janusgraph#1578 ·
Los mantenedores suelen responder en 1 día
-
Native bitemporal supportAbierto
Dificultad 5/5 Más de una semana Aptitud para principiantes 25/100
JanusGraph/janusgraph#4954 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 5/5 Más de una semana Aptitud para principiantes 25/100
JanusGraph/janusgraph#4934 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 5/5 Más de una semana Aptitud para principiantes 35/100
JanusGraph/janusgraph#4923 · 1 comentario ·
Los mantenedores suelen responder en 1 día
Todos los issues de JanusGraph/janusgraph
Issues similares
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
-
component/zeebe kind/bug
Dificultad 1/5 Menos de una hora Aptitud para principiantes 90/100
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 78/100
UniversalMediaServer/UniversalMediaServer#6356 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 88/100
refinedmods/refinedstorage2#1414 · 1 comentario ·
-
Dificultad 1/5 1-3 horas Aptitud para principiantes 88/100
yegor256/rultor-image#76 · 1 comentario ·