AlexanderKM opened a new pull request, #19337:
URL: https://github.com/apache/pinot/pull/19337

   # Remove unsafe delete-before-copy in S3PinotFS#copyDir
   
   ## What
   
   `S3PinotFS#copyDir`'s single-file branch used to do:
   
   ```java
   if (!isDirectory(srcUri)) {
     delete(dstUri, true);
     return copyFile(srcUri, dstUri);
   }
   ```
   
   This deletes the destination object before copying the new one in. If the 
process (controller, server,
   minion, etc.) crashes or OOMs after the delete but before the copy 
completes, the destination object is
   gone permanently — nothing ever replaces it.
   
   The fix removes the delete and relies on S3's `CopyObject` (and `PutObject`) 
semantics: writing to an
   existing key is a single atomic operation that replaces the object in place. 
There's no need to clear the
   destination first, and doing so only introduces a window where data can be 
lost.
   
   ```java
   if (!isDirectory(srcUri)) {
     return copyFile(srcUri, dstUri);
   }
   ```
   
   ## Why this is safe
   
   - S3 object writes (`PutObject`/`CopyObject`) to an existing key are atomic 
— the destination key is
     either the old content or the new content, never neither. S3 has had 
strong read-after-write consistency
     for both new and overwritten keys since Dec 2020, so there is no 
partial-write or stale-read concern.
   - This pattern is already how `GcsPinotFS#copy` handles the same case (see 
its single-file branch) — it
     never deletes the destination first, because GCS object copy is also an 
atomic overwrite. This change
     brings S3 in line with that existing, working implementation.
   - The removed `delete()` call was unconditional and didn't check whether the 
destination was actually a
     directory — it wasn't a deliberate "convert directory to file" safeguard, 
just a leftover pre-clear that
     has been present since the very first version of the S3 plugin (predates 
`copyDir` itself, back when this
     method was just `copy()`).
   
   ## Blast radius / what this affects
   
   `copyDir`'s single-file path is also what `doMove` uses under the hood 
(`doMove` = `copyDir` + delete
   source), so every S3 `copy`/`move` call in the codebase was exposed to this 
race. Tracing call sites:
   
   - **Controller METADATA push** (`ZKOperator#copyFromSegmentURIToDeepStore`) 
— when a segment is pushed
     with `uploadType == METADATA`, the client sends only metadata plus a 
`sourceDownloadURI`, and the
     **controller itself** copies the segment bytes from that staging location 
to the table's deep-store
     location. This is the path minion tasks (purge, 
`RealtimeToOfflineSegmentsTask`, `MergeRollupTask`,
     etc.) rely on when they stage a derived/cleaned segment and use METADATA 
push to hand it to the
     controller — but it is a general METADATA-push code path, not 
minion-specific, so any client using
     METADATA push against an S3 deep store was exposed.
   - **Controller realtime (LLC) segment commit/repair** 
(`PinotLLCRealtimeSegmentManager#moveSegmentFile`) —
     moves a just-committed consuming segment file, and LLC segment 
repair/re-upload, from a temp location to
     its permanent controller-managed deep-store path.
   - **Controller segment deletion/retention** 
(`SegmentDeletionManager#moveSegmentsToDeletedDir`) — moves
     segments to a `DELETED_SEGMENTS` staging directory instead of 
hard-deleting, so `RetentionManager` can
     purge them later.
   - **Batch ingestion segment generation jobs** (`SegmentGenerationJobUtils`, 
used by the Hadoop, Spark, and
     standalone job runners) — moves locally-built segment tar files from a 
local/staging directory to the
     final output deep-store directory before push.
   
   Before this fix, a crash in any of these flows at the wrong moment could 
leave an S3 deep store missing a
   segment (or a deleted-segment/purge artifact) with no way to recover it 
short of re-running the upstream
   job. This fix closes that window across all of them, since they all bottom 
out in `S3PinotFS#copyDir`.
   
   ## Testing
   
   - Existing unit tests in `S3PinotFSCopyTest` continue to cover single-file 
copy request construction.
   - No behavior change for the happy path — `CopyObjectRequest` is issued 
exactly as before; only the
     preceding `DeleteObjectRequest` is removed.
   


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