Siyao Meng created HDDS-17049:
---------------------------------

             Summary: Recon counts keys of a recreated bucket under the deleted 
bucket after a delete and recreate in one OM flush or behind a rebuild
                 Key: HDDS-17049
                 URL: https://issues.apache.org/jira/browse/HDDS-17049
             Project: Apache Ozone
          Issue Type: Bug
            Reporter: Siyao Meng
         Attachments: 
CR-7-keep-bucket-cache-in-line-with-recreate-and-rebuild.patch, 
TestBugCR7BucketCacheAfterRecreate.java

h3. Mechanism
Since HDDS-15335 (2.2.0) the OBS and Legacy NSSummary sub-tasks cache each 
bucket's {{OmBucketInfo}} ({{NSSummaryTaskDbEventHandler.lookupBucketCached}}) 
and drop the entry only on a bucketTable DELETE event, on the assumption that a 
recreate is always preceded by a delete event. Two paths break that assumption:
* The OM double buffer writes a flush through one {{RDBBatchOperation}}, which 
keeps only the last write per key. A DeleteBucket and a CreateBucket of the 
same name in one flush reach Recon as a single bucketTable UPDATE, so the cache 
keeps the deleted bucket.
* {{ReconTaskControllerImpl.reInitializeTasks}} rebuilds on a staged task 
instance, swaps the DB in and calls {{init()}} on the live tasks. 
{{NSSummaryTask}} does not override {{init()}}, so the live sub-tasks keep 
their caches. A delete and recreate whose events were never delivered (the 
reason Recon fell back to a rebuild) stays invisible to them.

In both cases every later key of the recreated bucket is counted under the 
deleted bucket's objectID, while the namespace API ({{BucketEntityHandler}}) 
reads the summary of the current objectID.

h3. Trigger
* A client deletes a bucket and creates a bucket with the same name, both 
applied in one OM double buffer flush, then writes keys to it.
* Or a bucket is deleted and recreated while Recon misses the events and 
rebuilds (for example after a full snapshot fallback or an event buffer 
overflow), then keys are written to it.

Affects releases since 2.2.0, which added the cache.

h3. Impact
The namespace summary and disk usage endpoints ({{/namespace/summary}}, 
{{/namespace/du}}) under report the recreated bucket, silently. New keys are 
counted under the deleted bucket until Recon restarts or the bucket is deleted 
again, and the keys already miscounted stay missing from the bucket until a 
rebuild recomputes the summaries; a restart does not rebuild NSSummary unless a 
task lags behind the last OM delta. After a restart, deleting one of those keys 
even decrements the new bucket. A rebuild alone does not help either, as the 
second path shows. The reproduction checks the summary response; disk usage 
reads the same NSSummary entry of the bucket's current objectID 
({{BucketEntityHandler.getDuResponse}}).

h3. Reproduction
PASS on unmodified source, deterministic, no fault injection. 
[^TestBugCR7BucketCacheAfterRecreate.java] ({{hadoop-ozone/recon}}) writes real 
OM batches (the bucket changes through {{OMBucketDeleteResponse}} and 
{{OMBucketCreateResponse}}), serves them through {{getUpdatesSince}} and 
applies them to the Recon OM DB as {{OzoneManagerServiceProviderImpl}} does.
* One flush: Recon receives {{[bucketTable UPDATE /vol/bucket, keyTable PUT 
/vol/bucket/k2]}}. After one more key, the old objectID holds 2 files, the new 
one 0, and the namespace summary reports 0 keys (2 expected).
* Rebuild: the delete and recreate are applied to the Recon OM DB without 
delivering the events, then the real {{reInitializeTasks}} with the real 
{{ReconDBProvider}} rebuilds (new bucket at 1 file, correct). The next key goes 
to the old objectID; the namespace summary reports 1 key (2 expected).

A passing test means the defect is present.

h3. Patch
[^CR-7-keep-bucket-cache-in-line-with-recreate-and-rebuild.patch], against 
0665932dd35da4d05087e02748af0a64eb846ad5. It also applies to master at 
2264d4d48ac.

Both sub-tasks now keep the cache in line with every bucketTable event: a PUT 
or UPDATE refreshes a cached entry with the event's {{OmBucketInfo}}, a DELETE 
removes it. Buckets that are not cached stay uncached, so the cache does not 
grow, and no extra DB read is needed. Invalidating on every bucketTable event 
was not chosen: every key commit and delete rewrites the bucket row, so the 
cache would be cold in almost every batch, and a missing cache entry is one way 
to reach the NullPointerException of HDDS-17048 (a key event of a bucket 
already deleted in the Recon OM DB). That issue needs its own fix and should 
land with or before this one, since clearing the cache on rebuild adds a cold 
cache window. {{NSSummaryTask.init()}} now clears the caches; Recon calls it at 
startup ({{OzoneManagerServiceProviderImpl.start()}}), and the controller after 
each successful rebuild on the event processing thread, between {{process()}} 
calls.

Covered by {{testBucketCacheInvalidatedOnRecreateInOneFlush}} and 
{{testBucketCacheClearedOnInit}} in the existing 
{{TestNSSummaryTask.TestProcessBucketRecreate}}, which fail without the change. 
With it, the NSSummary task suites, {{TestNSSummaryUnifiedControl}}, 
{{TestReconTaskControllerImpl}} and {{TestOMDBUpdatesHandler}} pass (90 tests, 
1 skipped as before) and checkstyle is clean. With the patch both reproduction 
tests fail because the new bucket counts both keys.

Not fixed: events of one OM flush are grouped per table, and bucketTable events 
come before keyTable events. A key of the old bucket deleted in the same flush 
that recreates the bucket is therefore subtracted from the new bucket, which 
leaves it one key short when a key of the new bucket from that flush was 
counted first (otherwise the delete finds no summary and only logs an error). 
In a run with one flush that deletes the old bucket's last key, deletes and 
recreates the bucket and writes one new key, followed by one more key, the new 
bucket ends at 1 file instead of 2 for three of four key names tried, and at 2 
for the fourth; before the patch it ends at 0 for all four. Before the patch 
that flush and every later one charged new keys to the old bucket instead.

Found by code review of the Recon namespace summary and task sync paths, as 
part of the TLA+ verification effort 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