Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

Transaction recovery takes a transaction whose final status it reads one poll later for a failed one

Abierto
#4,986 0 comentarios 0 reacciones 0 asignados Ver en GitHub

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

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-time back;
  • 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:

  1. 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. A LogProcessorFramework processor then receives the transaction twice. The recovery docs (#4965) describe this as a risk only for commits that outlast tx.max-commit-time, but it also happens to commits that finished well inside it.
  2. 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-time 5 s, log.tx.read-interval 6 s, log.tx.read-lag-time 2 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 the log.tx.read-interval of 250 ms and log.tx.read-lag-time of 50 ms they set earlier, because both options are MASKABLE. Recovery therefore polls every 5 s against a 5 s expiry (tx.max-commit-time is 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.testIndexReplay failed with expected: <4> but was: <5>.
    • With verbose recovery, runs show that transaction being repaired twice, the second time as "expired or unpersisted".
  • 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-time past 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 to log.tx.max-read-time, and all partitions share the read threads. KCVSLog now 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 JanusGraphTest gets 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

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de JanusGraph/janusgraph

Todos los issues de JanusGraph/janusgraph

Issues similares

Más issues de Java

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.