Siyao Meng created HDDS-17048:
---------------------------------

             Summary: Recon NSSummary task fails with NullPointerException on a 
key event whose bucket is already deleted in the Recon OM DB, forcing a full 
rebuild
                 Key: HDDS-17048
                 URL: https://issues.apache.org/jira/browse/HDDS-17048
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: MC-5-skip-key-events-of-deleted-bucket.patch, 
TestBugMC5DeletedBucketKeyEvent.java

h3. Mechanism
Recon commits each fetched OM delta to its OM DB and then buffers the events 
for asynchronous processing, so the Recon OM DB can be ahead of the batch being 
processed. {{NSSummaryTaskWithOBS.processWithOBS}} and 
{{NSSummaryTaskWithLegacy.isBucketLayoutValid}} resolve the bucket of every 
keyTable event with {{NSSummaryTaskDbEventHandler.lookupBucketCached}}, which 
falls back to that DB when the bucket is not cached, and then call 
{{getBucketLayout()}} on the result without a null check. When the bucket was 
deleted in the same delta, or in a later one applied before the batch is 
processed, the lookup returns null and both sub-tasks throw a 
NullPointerException. The reprocess path 
({{NSSummaryTaskWithOBS.processKeyTableInParallel}}) already skips a null 
bucket.

A cached bucket does not avoid it. Events of one OM double buffer flush are 
grouped per table, and bucketTable events come before keyTable events. When the 
last key delete and the bucket delete are in one flush, Recon gets the 
bucketTable DELETE first, which drops the cached entry, and the key event then 
looks the bucket up in the DB.

The NPE fails the whole batch, the controller's retry fails the same way, and 
the controller sets its task failure flag. The OM sync loop then queues a 
{{TASK_FAILURES}} reinitialization, a full rebuild of every Recon OM task. 
Until the rebuild is queued {{consumeOMEvents}} drops new event batches, and 
later batches wait behind the rebuild. The rebuild produces correct data, so 
nothing stays wrong; the cost is the full rebuild and a stale window for all 
Recon OM tasks.

h3. Trigger
A key event of an OBS or LEGACY bucket is processed after the bucket is deleted 
in the Recon OM DB, for example:
# A client deletes the last key of a bucket and then the bucket, and both are 
applied in one OM double buffer flush, which back to back requests such as 
{{ozone sh bucket delete -r}} or {{aws s3 rb --force}} can do. This fails 
whether or not the bucket was cached.
# Or the key events and the bucket delete are in different flushes, the bucket 
delete reaches the Recon OM DB before the key events are processed (the default 
fetch interval is 5s, longer when event processing lags), and the sub-tasks 
have not cached the bucket, for example a bucket created in the same window or 
one with no key activity since Recon started.

The unchecked {{getBucketLayout()}} call exists since 1.3.0 for LEGACY buckets 
(HDDS-7121) and since 1.4.1 for OBS buckets (HDDS-7810); those versions looked 
the bucket up for every event without a cache. The bucket cache added in 2.2.0 
(HDDS-15335) did not change it.

h3. Impact
* Every occurrence forces a full rebuild of all Recon OM tasks and leaves their 
data stale until it completes. The other events of the failing batch (for other 
buckets) are applied only by that rebuild. On a large namespace the rebuild is 
long, and frequent bucket cleanup can keep Recon rebuilding.
* No lasting wrong data is expected, since the rebuild recomputes the summary 
from the Recon OM DB (argued from the code; the attached test stops at the task 
failure flag).

h3. Reproduction
PASS on unmodified source, deterministic, no fault injection. 
[^TestBugMC5DeletedBucketKeyEvent.java] ({{hadoop-ozone/recon}}) writes real OM 
DB batches (a key in a live bucket, delete of the last key of a second bucket, 
delete of that bucket), serves them through {{RDBStore.getUpdatesSince}}, 
applies them to the Recon OM DB as {{OzoneManagerServiceProviderImpl}} does and 
hands the events to a real {{ReconTaskControllerImpl}} running 
{{NSSummaryTask}}. Only the task status bookkeeping is mocked. It shows 
{{hasTasksFailed() = true}}, the live bucket still at 1 file instead of 2, and 
the {{getBucketLayout()}} NPE from both sub-tasks on process and retry. A 
passing test means the defect is present.

h3. Patch
[^MC-5-skip-key-events-of-deleted-bucket.patch], against 
0665932dd35da4d05087e02748af0a64eb846ad5. It also applies to master at 
2264d4d48ac.

A key event whose bucket is no longer in the Recon OM DB is skipped in both 
sub-tasks, as the reprocess path already does. The bucket and its NSSummary 
entry are unreachable after the delete, so nothing is lost. Covered by 
{{TestNSSummaryTask.TestProcessParallelMixedBatch#testKeyEventOfDeletedBucketDoesNotFailBatch}}
 (bucket not cached) and {{#testKeyEventOfDeletedCachedBucketDoesNotFailBatch}} 
(cached bucket, bucketTable DELETE and keyTable DELETE in one batch), which 
both fail without the change. With it, the NSSummary task suites pass (54 
tests) and checkstyle is clean. With the patch the reproduction fails because 
the batch succeeds and the live bucket counts both keys.

Related: HDDS-10855 covered the same root cause for the Legacy parent directory 
lookup ({{setKeyParentID}}) and only changed its messages; that path still 
fails a batch when a parent directory key is deleted in the same delta and is 
not changed here.

Found by TLA+ model checking and code review of the Recon namespace summary and 
task sync paths under HDDS-15926, on commit 
0665932dd35da4d05087e02748af0a64eb846ad5. Checked against HDDS issues and 
apache/ozone pull requests for duplicates before filing. 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