Copilot commented on code in PR #11262:
URL: https://github.com/apache/ozone/pull/11262#discussion_r4050087562


##########
hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/tasks/ReconTaskControllerImpl.java:
##########
@@ -473,8 +475,23 @@ private void processTasks(
             // Track task delta processing success
             taskMetrics.incrTaskDeltaProcessingSuccess(taskName);
 
-            taskStatusUpdater.setLastTaskRunStatus(0);
-            
taskStatusUpdater.setLastUpdatedSeqNumber(events.getLastSequenceNumber());
+            // Make the derived-table RocksDB writes for this batch durable 
before the task-status
+            // cursor is committed to Derby. The cursor row is fsync-durable 
while the RocksDB writes
+            // are not synced by default, so without this barrier a power loss 
can leave the durable
+            // cursor ahead of the (lost) derived data. Startup reconciliation 
then sees the derived
+            // and delta cursors at the same sequence number and never 
reprocesses, permanently
+            // dropping the applied update. If the sync fails, leave the 
cursor unadvanced so the
+            // batch is reprocessed instead of recording an un-durable success.
+            if (syncReconDbLog()) {
+              taskStatusUpdater.setLastTaskRunStatus(0);
+              
taskStatusUpdater.setLastUpdatedSeqNumber(events.getLastSequenceNumber());
+            } else {
+              failedTasks.add(new ReconOmTask.TaskResult.Builder()
+                  .setTaskName(taskName)
+                  .setSubTaskSeekPositions(result.getSubTaskSeekPositions())
+                  .build());

Review Comment:
   `failedTasks` is an `ArrayList`, but these completion callbacks run 
concurrently on `executorService`. If multiple tasks hit this new sync-failure 
branch, concurrent `add` calls can lose entries, so some failed tasks may not 
be retried. Make the collection thread-safe (and apply the same fix to the 
existing add at line 469).



##########
hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/tasks/ReconTaskControllerImpl.java:
##########
@@ -503,6 +520,27 @@ private void processTasks(
       LOG.error("Some tasks were cancelled with exception", ce);
     }
   }
+
+  /**
+   * Flushes and syncs the Recon derived-data RocksDB write-ahead log to 
stable storage so the
+   * derived writes for the processed batch are durable before the task-status 
cursor advances.
+   * Returns {@code false} if the sync fails, in which case the caller must 
not advance the cursor,
+   * so that the cursor is never persisted ahead of the derived data.
+   */
+  private boolean syncReconDbLog() {
+    DBStore dbStore = reconDBProvider.getDbStore();
+    if (dbStore == null) {
+      return true;
+    }
+    try {
+      dbStore.flushLog(true);
+      return true;
+    } catch (RocksDatabaseException e) {
+      LOG.error("Failed to sync Recon DB WAL before advancing task status 
cursor; "
+          + "leaving cursor unadvanced so the batch is reprocessed.", e);
+      return false;

Review Comment:
   The failure behavior introduced here is not covered: no test makes 
`flushLog(true)` throw and verifies that the task cursor stays at its previous 
sequence, status becomes `-1`, and the retry path is exercised. This is the 
critical failure mode of the durability barrier and should be protected by a 
unit test.



##########
hadoop-ozone/recon/src/main/java/org/apache/hadoop/ozone/recon/tasks/ReconTaskControllerImpl.java:
##########
@@ -473,8 +475,23 @@ private void processTasks(
             // Track task delta processing success
             taskMetrics.incrTaskDeltaProcessingSuccess(taskName);
 
-            taskStatusUpdater.setLastTaskRunStatus(0);
-            
taskStatusUpdater.setLastUpdatedSeqNumber(events.getLastSequenceNumber());
+            // Make the derived-table RocksDB writes for this batch durable 
before the task-status
+            // cursor is committed to Derby. The cursor row is fsync-durable 
while the RocksDB writes
+            // are not synced by default, so without this barrier a power loss 
can leave the durable
+            // cursor ahead of the (lost) derived data. Startup reconciliation 
then sees the derived
+            // and delta cursors at the same sequence number and never 
reprocesses, permanently
+            // dropping the applied update. If the sync fails, leave the 
cursor unadvanced so the
+            // batch is reprocessed instead of recording an un-durable success.
+            if (syncReconDbLog()) {
+              taskStatusUpdater.setLastTaskRunStatus(0);
+              
taskStatusUpdater.setLastUpdatedSeqNumber(events.getLastSequenceNumber());

Review Comment:
   This callback runs once per successful task, so a batch with the six 
registered Recon tasks can issue multiple `flushLog(true)` calls, each 
potentially forcing an fsync on the same WAL. That adds avoidable durable-sync 
latency and blocks task executor threads; coalesce the barrier after all task 
writes complete, then commit the successful task cursors, or otherwise share 
one sync per batch.



##########
hadoop-ozone/recon/src/test/java/org/apache/hadoop/ozone/recon/tasks/TestReconTaskControllerImpl.java:
##########
@@ -932,6 +935,46 @@ public void 
testCreateOMCheckpointThrowsWhenCheckpointNull() throws Exception {
     assertThrows(IOException.class, () -> 
controller.createOMCheckpoint(omMetadataManager));
   }
 
+  /**
+   * A successful delta batch must sync the derived-data RocksDB WAL to stable 
storage before the
+   * fsync-durable task-status cursor is advanced. Without that barrier a 
power loss can lose the
+   * un-synced derived write while the durable cursor survives, leaving the 
cursor ahead of the
+   * data; startup reconciliation then sees equal cursors and never 
reprocesses, permanently
+   * dropping the applied update.
+   */
+  @Test
+  public void testDerivedDbSyncedBeforeCursorAdvanceOnSuccess() throws 
Exception {
+    ReconOmTask reconOmTaskMock = getMockTask("SyncBarrierTask");
+    when(reconOmTaskMock.process(any(OMUpdateEventBatch.class), anyMap()))
+        .thenReturn(new ReconOmTask.TaskResult.Builder()
+            .setTaskName("SyncBarrierTask").setTaskSuccess(true).build());
+    reconTaskController.registerTask(reconOmTaskMock);
+
+    OMUpdateEventBatch batch = mock(OMUpdateEventBatch.class);
+    when(batch.getLastSequenceNumber()).thenReturn(100L);
+    when(batch.isEmpty()).thenReturn(false);
+    when(batch.getEvents()).thenReturn(new ArrayList<>());
+    
when(batch.getEventType()).thenReturn(ReconEvent.EventType.OM_UPDATE_BATCH);
+    when(batch.getEventCount()).thenReturn(1);
+
+    reconTaskController.consumeOMEvents(batch, mock(OMMetadataManager.class));
+
+    GenericTestUtils.waitFor(() -> {
+      try {
+        ReconTaskStatus status = 
reconTaskStatusDao.findById("SyncBarrierTask");
+        return status != null && status.getLastTaskRunStatus() == 0
+            && status.getLastUpdatedSeqNumber() == 100L;
+      } catch (Exception e) {
+        return false;
+      }
+    }, 100, 5000);
+
+    // The derived-data RocksDB WAL must be synced (sync == true) before the 
durable cursor advances.
+    verify(reconDbStore, times(1)).flushLog(true);
+    // A non-syncing flush would leave the write in page cache and reintroduce 
the durability gap.
+    verify(reconDbStore, never()).flushLog(false);

Review Comment:
   The new test only verifies that `flushLog(true)` was eventually called after 
observing the committed status. A regression that calls `recordRunCompletion()` 
first and then flushes could still pass if the flush wins this race, so the 
required ordering is not actually asserted. Use an ordered verification or 
record the flush and cursor-update events explicitly.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to