d-c-manning commented on PR #7552:
URL: https://github.com/apache/hbase/pull/7552#issuecomment-6430627590

   Thanks for sticking with this, Swarali. The updated root-cause section is 
much clearer than the earlier explanations, and I agree with most of it. I do 
think the patch as it stands narrows the NPE but doesn't fix the reported 
problem, which is compaction staying dead until the region is reopened.
   
   ### What I agree with
   
   - A compacted file can sit in `compactedfiles` with a null reader. 
`CompactedHFilesDischarger` → `removeCompactedfiles` → 
`HFileArchiver.moveAndClose` calls `closeStoreFile` before the rename. The file 
only leaves `compactedfiles` in `clearCompactedfiles`, after the whole batch is 
archived. All of this runs under `archiveLock`, not the store write lock.
   - Only date-tiered hits it, because `SEQ_ID_MAX_TIMESTAMP` is the only 
comparator that reads the timestamp from the reader.
   - One more detail for the description, since the test relies on it: 
`GetMaxTimestamp` is only reached when `getMaxSequenceId()` ties. That is 
routine for date-tiered, because `AbstractMultiFileWriter.commitWriters` stamps 
every output window of one compaction with the same maxSeqId.
   
   ### Why the wedge isn't fixed
   
   In `HStore.removeUnneededFiles`:
   
   1. `addToCompactingFiles(delSfs)` puts the expired files in 
`filesCompacting`.
   2. `replaceStoreFiles(...)` is called.
   3. The only thing that removes them from `filesCompacting` is the 
`actionUnderLock` lambda, which runs *after* `addCompactionResults` in 
`StoreEngine.replaceStoreFiles`.
   
   There is no `finally`. Compare `HStore.compact()`, which has `finally { 
finishCompactionRequest(cr); }`.
   
   `addCompactionResults` also isn't atomic. By the time the `compactedfiles` 
sort throws, `storeFiles` has already been reassigned and the files are already 
`markCompactedAway`. So on any exception there:
   
   - The expired files are in neither `storeFiles` nor `compactedfiles`, so 
they are never archived.
   - They are permanently stuck in `filesCompacting`.
   - Every later `getCurrentEligibleFiles` hits 
`Preconditions.checkArgument(idx != -1)`, which is the 
`IllegalArgumentException` in the JIRA.
   
   Reopening is the only thing that clears it.
   
   The comparator guard removes one way to get there. Any other exception 
between `addToCompactingFiles` and `actionUnderLock` produces the same 
permanent wedge and the same orphaned files. For example, an `IOException` from 
`getLiveFiles`, or a comparator contract violation (see below). HBASE-29875 is 
another report of the same "compaction dead until reopen" symptom, from a null 
reader on a different path (`getUnneededFiles`). So the failure mode isn't 
specific to date-tiered.
   
   ### Problems in the comparator change
   
   1. **It's still racy.** `sf.getReader() == null` and then 
`sf.getMaximumTimestamp()` are two separate reads of a volatile field that the 
discharger nulls without the write lock. The window is tiny, but it's the same 
NPE. Reading once avoids it:
      ```java
      StoreFileReader r = sf.getReader();
      return r == null ? Long.MAX_VALUE : r.getMaxTimestamp();
      ```
      `StoreFileReader.getMaxTimestamp()` already defaults to `Long.MAX_VALUE` 
when `timeRange` is null.
   
   2. **The sort key can change mid-sort.** A file's key jumps to 
`Long.MAX_VALUE` when its reader closes concurrently. If that happens during a 
sort, the comparator violates its contract. TimSort can throw "Comparison 
method violates its general contract" on larger lists, and that is another 
exception on the same unprotected path. It's unlikely, but a key that doesn't 
depend on reader lifecycle is the principled fix. One option is to cache the 
max timestamp in `HStoreFile` at open time, like `sequenceid`, `firstKey` and 
`lastKey`. The other is to stop sorting `compactedfiles` at all. I couldn't 
find a consumer that depends on its order, and `removeCompactedFiles` re-sorts 
it anyway.
   
   3. **The try/catch/log/rethrow in `addCompactionResults` doesn't change 
behavior.** It just logs the error twice.
   
   ### Description and test don't match the diff
   
   - The description says `HFileArchiver.moveAndClose` was reordered to rename 
first and close only on success, with tests for it. Neither is in the diff, 
which touches only `DefaultStoreFileManager`, `StoreFileComparators` and 
`TestHStoreFile`. Either add that change, which I think is worth doing (it 
removes the root cause of the null-reader window), or update the description.
   - The new test simulates a stranded file after a failed archive. It doesn't 
exercise the race, and it doesn't check recovery. It asserts that the sort 
doesn't throw, but not that compaction can still be selected afterwards.
   
   ### Suggested direction
   
   1. In `HStore.removeUnneededFiles`, wrap `replaceStoreFiles` so that on 
failure `delSfs` is removed from `filesCompacting`, mirroring 
`finishCompactionRequest`. This is the fix for the "never recovers" symptom.
   2. Make `DefaultStoreFileManager.addCompactionResults` build both new lists 
before assigning either, and only then assign them and call 
`markCompactedAway`. A failure then leaves state untouched instead of orphaning 
files.
   3. Keep a null-safe comparator, but with a single read, or a cached 
timestamp.
   4. Add a regression test that forces the TTL-path replace to fail, then 
asserts `filesCompacting` is clean and `requestCompaction()` still selects 
files.
   5. Optionally, do the `moveAndClose` reorder the description mentions.
   
   Happy to help test any of this.
   


-- 
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]

Reply via email to