[ 
https://issues.apache.org/jira/browse/HDDS-17065?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

ASF GitHub Bot updated HDDS-17065:
----------------------------------
    Labels: pull-request-available  (was: )

> Recon ContainerSizeCountTask stops for good after one failed run, freezing 
> the container size distribution until restart
> ------------------------------------------------------------------------------------------------------------------------
>
>                 Key: HDDS-17065
>                 URL: https://issues.apache.org/jira/browse/HDDS-17065
>             Project: Apache Ozone
>          Issue Type: Bug
>            Reporter: Siyao Meng
>            Assignee: Andy Huang
>            Priority: Minor
>              Labels: pull-request-available
>         Attachments: 
> CR-5-keep-container-size-count-task-running-and-recount-after-failed-run.patch,
>  TestBugCR5ContainerSizeCountTaskStopsAfterFailedRun.java
>
>
> h3. Mechanism
> {{ContainerSizeCountTask.run}} has its {{catch (Throwable)}} outside the 
> {{while (canRun())}} loop, and {{ReconScmTask.initializeAndRunTask}} passes 
> every exception up. When one run fails, the thread logs "Exception in 
> Container Size Distribution task Thread." at DEBUG and ends. The task's 
> running flag stays true, so {{isRunning()}} keeps reporting the task as 
> running and {{start()}} would not start it again; nothing else does either.
> What fails in practice is the Recon SQL database: the {{truncate}} in 
> {{runTask}}, or {{findById}}, {{insert}} or {{update}} in 
> {{writeCountsToDB}}, all of which throw a jOOQ {{DataAccessException}} when 
> the database rejects the call. Listing the containers is in memory, and 
> errors for a single container are already caught per container.
> Letting the loop simply continue is not enough. {{processContainers}} updates 
> its in memory {{processedContainers}} before {{writeCountsToDB}}, and that 
> write is an insert followed by a separate update. After a failed write the 
> next run computes its delta from the already updated map, so the failed 
> change is never applied; if the insert went through and the update did not, 
> the table also keeps half of the change. Either way the counts stay wrong 
> until Recon restarts, which recounts from scratch.
> h3. Trigger
> Any failure of the Recon SQL database during one run of the task (default 
> every 60s, {{ozone.recon.task.containercounttask.interval}}), for example an 
> outage of an external database configured with 
> {{ozone.recon.sql.db.jdbc.url}}, or a full disk under the embedded Derby 
> database.
> h3. Impact
> * After the thread has ended, {{GET /api/v1/utilization/containerCount}} and 
> the "Container Size Distribution" chart on Recon's Insights page keep showing 
> the counts of the last successful run until Recon restarts. If the failed run 
> was the first one after startup, they show nothing, or the rows the previous 
> Recon process left, since the table persists across restarts. The task logs 
> the failure only at DEBUG. In a real database outage 
> {{ReconTaskStatusUpdater}} also logs an ERROR "Failed to update table for 
> task" without the cause, but nothing reports that the thread has ended. 
> {{/api/v1/task/status}} shows the task's last run as failed, with a timestamp 
> that no longer advances.
> * Only this statistic is affected; the endpoint is its only reader. SCM, 
> datanodes and data are not affected.
> h3. Reproduction
> [^TestBugCR5ContainerSizeCountTaskStopsAfterFailedRun.java] goes in 
> {{hadoop-ozone/recon}} (target path and Maven command in the class comment) 
> and passes on unmodified source when the bug is present. It runs the real 
> {{ContainerSizeCountTask}} on Recon's real SQL schema (embedded Derby) and 
> reads the result through the real {{UtilizationEndpoint}}; the container 
> manager is a mock and the DAO is a spy of the real DAO whose first write 
> throws {{DataAccessException}}.
> * {{oneFailedRunStopsTheTask}} (200ms interval): the first run fails, then 
> the cluster gains a second container. After 12 intervals the task had listed 
> the containers once, the endpoint returned no rows for two containers, 
> {{isRunning()}} returned true, and the task logged nothing at INFO or above 
> (the failure is injected at the size count DAO only, so the task status 
> writes succeed).
> * {{retryAfterPartialWriteKeepsWrongCounts}} shows why a plain retry is not 
> enough: runs are called directly, a container moves from the 2GB bin to the 
> 4GB bin, the new 4GB row is inserted and the update of the 2GB row fails. 
> After the next successful run the endpoint still returns one container in 
> each bin for a single 2.5GB container.
> What was not exercised: a real outage of an external database or a full disk; 
> the failure is injected at the DAO.
> h3. Patch
> [^CR-5-keep-container-size-count-task-running-and-recount-after-failed-run.patch],
>  against 916aa1d9949cbb10f2750a40c8704340893131a2. It also applies to master 
> at 14e0611fdd5.
> {{ContainerSizeCountTask.run}} now catches a failed run inside the loop, logs 
> it at ERROR, records the failed run in the task status and waits the interval 
> before the next run. {{processContainers}} clears {{processedContainers}} 
> when the run fails, so the next run truncates the table and recounts all 
> containers, as Recon does at startup. A recount is used instead of rolling 
> back the in memory state because the table may already hold part of the 
> failed write. The recount costs what Recon already does on its first run 
> after startup: a truncate and one row per non empty size bin, on top of the 
> pass over the container list that every run makes. A reader may see an empty 
> table for that moment, as after a restart.
> Covered by {{TestContainerSizeCountTask.testTaskKeepsRunningAfterFailedRun}} 
> (the first write fails, the container must still reach the table) and 
> {{TestContainerSizeCountTask.testCountsRecoverAfterFailedWrite}} (after a 
> partial write and one more run only the new bin may be counted). Without the 
> change the first times out and the second sees both bins. With it the size 
> count, endpoint, SCM sync helper and SCM facade tests pass, checkstyle is 
> clean, and the attached test no longer shows the bug.
> The same death pattern in {{PipelineSyncTask}} is a separate issue with its 
> own patch; the two patches touch different files and apply together.
> Found by code review of the datanode container report and Recon container 
> tracking paths, as part of the TLA+ verification effort under HDDS-15926, on 
> commit 916aa1d9949cbb10f2750a40c8704340893131a2. Checked against HDDS issues 
> and apache/ozone pull requests for duplicates before filing. Related but not 
> duplicates: HDDS-12227 (resolved, lowered this log line to DEBUG), HDDS-10293 
> (resolved, negative container size, caught per container), HDDS-7632 
> (resolved, added the task). The attached patch is a proposal for review. 
> Generated with Specula (Claude Opus 5.5).



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

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

Reply via email to