{"record":{"id":"2131aacba0d5a260","repo":"apache/hadoop","slug":"ipc-s-epoch-is-not-the-current-writer-epoch","errorCode":null,"errorMessage":"IPC's epoch {} is not the current writer epoch  {} ; journal id: {}","messagePattern":"IPC's epoch (.+?) is not the current writer epoch  (.+?) ; journal id: (.+?)","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/qjournal/server/Journal.java","lineNumber":518,"sourceCode":"        Server.getRemoteIp(), currentEpochIpcSerial, journalId);\n    currentEpochIpcSerial = reqInfo.getIpcSerialNumber();\n\n    if (reqInfo.hasCommittedTxId()) {\n      Preconditions.checkArgument(\n          reqInfo.getCommittedTxId() >= committedTxnId.get(),\n          \"Client trying to move committed txid backward from \" +\n          committedTxnId.get() + \" to \" + reqInfo.getCommittedTxId() +\n              \" ; journal id: \" + journalId);\n      \n      committedTxnId.set(reqInfo.getCommittedTxId());\n    }\n  }\n  \n  private synchronized void checkWriteRequest(RequestInfo reqInfo) throws IOException {\n    checkRequest(reqInfo);\n    \n    if (reqInfo.getEpoch() != lastWriterEpoch.get()) {\n      throw new IOException(\"IPC's epoch \" + reqInfo.getEpoch() +\n          \" is not the current writer epoch  \" +\n          lastWriterEpoch.get() + \" ; journal id: \" + journalId);\n    }\n  }\n  \n  public synchronized boolean isFormatted() {\n    return storage.isFormatted();\n  }\n\n  private void checkFormatted() throws JournalNotFormattedException {\n    if (!isFormatted()) {\n      throw new JournalNotFormattedException(\"Journal \" +\n          storage.getSingularStorageDir() + \" not formatted\" +\n          \" ; journal id: \" + journalId);\n    }\n  }\n\n  /**","sourceCodeStart":500,"sourceCodeEnd":536,"githubUrl":"https://github.com/apache/hadoop/blob/2add9630210752f88ceb1bb74eb65e37bf41da8e/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/qjournal/server/Journal.java#L500-L536","documentation":"checkWriteRequest runs after checkRequest and adds a second rule: writes (journal, startLogSegment, finalizeLogSegment) must come from the current WRITER — the epoch that actually won newEpoch and set lastWriterEpoch. An epoch that merely passes the promise check but differs from lastWriterEpoch gets this IOException. It stops a non-writer (e.g., a standby or a fenced-but-reconnected client) from mutating the log.","triggerScenarios":"A client sends a write RPC whose epoch equals or exceeds the promised epoch but is not the epoch that established writer status: a client that skipped newEpoch for the current epoch, a second NN started with '-upgrade' against the same JNs mid-session, or protocol-level tooling writing without completing the writer handshake.","commonSituations":"Direct QJournalProtocol users (test harnesses, custom tools) that reuse an old session or forget newEpoch after an epoch bump; standby NN mistakenly configured to write; an NN restarted with cached epoch state attempting writes.","solutions":["Re-establish the writer session: call getJournalState, then newEpoch with a strictly higher epoch, before any write RPC — writes are only valid from the current writer.","Verify only one NN is attempting to write to this journal id; put the other in standby.","If writing from custom tooling, follow the full QJM session order (getJournalState -> newEpoch -> writes) rather than journaling directly.","Check JN logs for the concurrent newEpoch that changed lastWriterEpoch — it identifies the writer that displaced this client."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Only send write RPCs when your epoch IS the current writer epoch;\n// establish that explicitly:\nGetJournalStateResponseProto st = jn.getJournalState(jid);\nlong epoch = Math.max(clock.nowMs(), st.getLastPromisedEpoch() + 1);\nNewEpochResponseProto resp = jn.newEpoch(jid, nsInfo, epoch);\n// only after this succeeds may journal()/startLogSegment() be sent","typeGuard":"static boolean isWrongWriterEpoch(IOException ioe) {\n  return ioe.getMessage() != null\n      && ioe.getMessage().contains(\"is not the current writer epoch\");\n}","tryCatchPattern":"try {\n  jn.startLogSegment(reqInfo, txid, epoch);\n} catch (IOException ioe) {\n  if (isWrongWriterEpoch(ioe)) {\n    // session is invalid: re-run getJournalState+newEpoch or stop writing\n    reestablishWriterSession();\n  } else {\n    throw ioe;\n  }\n}","preventionTips":["Follow the full QJM session order (getJournalState -> newEpoch -> writes) in any direct protocol client.","Never reuse a cached RequestInfo/epoch across an epoch change.","Ensure exactly one NN per nameservice attempts writes at any time."],"tags":["qjm","epoch","writer-fencing","session-handshake","edit-log"],"backgroundTag":"epoch-fencing-violation","analyzedSha":"2add9630210752f88ceb1bb74eb65e37bf41da8e","analyzedAt":"2026-08-22T19:55:07.957Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}