wangyinsheng opened a new issue, #19360:
URL: https://github.com/apache/hudi/issues/19360

   # [BUG] RemoteHoodieTableFileSystemView triggers repeated server-side 
timeline reload whenever any new instant appears after the view is constructed
   
   ## Describe the problem
   
   When a client uses `RemoteHoodieTableFileSystemView` and any new instant is 
committed on the timeline **after** the view has been constructed (for example 
an async operation such as clean, compaction, clustering, or rollback running 
in a different actor), every subsequent request from that view triggers a full 
server-side `SyncableFileSystemView#sync()` — i.e. 
`metaClient.reloadActiveTimeline() + clear() + init()`. The client's cached 
timeline never updates and the server's timeline is already up-to-date, so the 
mismatch never converges: the re-sync happens on **every single request** for 
the entire lifetime of the view instance.
   
   The impact is amplified when a client scans many partitions, e.g. calling 
`getLatestFileSlice(partition)` across N partitions. Each partition results in 
one HTTP request, and each request triggers one full timeline reload on the 
server. N partitions → N wasted syncs.
   
   ## Root cause
   
   Two pieces of code combine to produce the pathology:
   
   **(1) The client's local timeline is a constructor-time snapshot and is 
never refreshed.**
   
   `RemoteHoodieTableFileSystemView` assigns `this.timeline` exactly once in 
the constructor:
   
   ```java
   if (metaClient.getTableConfig().isLSMBasedLogFormat()) {
     this.timeline = 
metaClient.getActiveTimeline().filterCompletedAndReplaceInstants();
   } else {
     this.timeline = 
metaClient.getActiveTimeline().filterCompletedAndCompactionInstants();
   }
   ```
   
   There is no code path that refreshes `this.timeline` for the lifetime of the 
view instance. `executeRequest` then sends `LAST_INSTANT_TS` and 
`TIMELINE_HASH` derived from that frozen snapshot on every request. As soon as 
any instant is added to the timeline out-of-band after the view is constructed, 
the client's hash is permanently stale for that view instance.
   
   **(2) The server treats any hash mismatch as "needs sync".**
   
   `RequestHandler#isLocalViewBehind`:
   
   ```java
   private boolean isLocalViewBehind(Context ctx) {
       ...
       String localTimelineHash = localTimeline.getTimelineHash();
       // refresh if timeline hash mismatches
       if (!localTimelineHash.equals(timelineHashFromClient)) {
           return true;   // ← ANY mismatch is treated as "server behind"
       }
       // As a safety check, even if hash is same, ensure instant is present
       return 
!localTimeline.containsOrBeforeTimelineStarts(lastKnownInstantFromClient);
   }
   ```
   
   Judging by the method name, the check is supposed to answer "is the server's 
view behind what the client saw?". But the implementation collapses two very 
different situations into the same `true` return:
   
   - **server behind client** → `sync()` is legitimate
   - **client behind server** → `sync()` is unnecessary; the server is already 
newer
   
   The follow-up `containsOrBeforeTimelineStarts(lastKnownInstantFromClient)` 
check would actually cover the "client behind server" case correctly (the 
client's last instant is already present in the server timeline, so nothing 
needs to be re-synced), but it is short-circuited by the earlier `return true` 
on hash mismatch.
   
   Combined effect: (1) makes the client's hash permanently stale after any 
out-of-band instant, and (2) turns that stale hash into a per-request full 
timeline reload on the server.
   
   ## Concrete evidence from production logs
   
   In our environment the trigger was an async clean instant being committed 
after the client's view was constructed, but any new instant produces the same 
behavior.
   
   Timeline files on HDFS at the time of the incident (last two instants):
   
   ```
   20260721213743765.deltacommit           (completed — this is what the client 
saw)
   20260721213743765.deltacommit.inflight
   20260721213743765.deltacommit.requested
   20260721213744648.clean                 (completed — appeared AFTER the 
client opened its view)
   20260721213744648.clean.inflight
   20260721213744648.clean.requested
   ```
   
   A Spark reader then queries many partitions in parallel. The client's 
`TIMELINE_HASH` is stable (`6bbdf6f1...162bf`), the client's `LAST_INSTANT_TS` 
is stable (`20260721213743765`, the deltacommit — the client has not observed 
the newer instant), and the server's view already reflects the newer state. 
Every single request triggers a "Syncing view..." log on the server:
   
   ```
   2026-07-21 21:42:47.413 INFO  [ForkJoinPool.commonP:l-worker-3] 
o.a.hudi.common.table.view.RemoteHoodieTableFileSystemView - Sending request : 
(http://xxx/v1/hoodie/view/slices/partition/latest/?partition=dt%3D2017-02-03&basepath=hdfs%3A%2F%2Fxxxxx&lastinstantts=20260721213743765&timelinehash=6bbdf6f1ea5484010b2275be7e8f1fe993033321bbe8cff9675044b654f162bf)
   2026-07-21 21:42:47.575 INFO  [qtp2089656019-535557:19-5355571] 
org.apache.hudi.common.table.timeline.HoodieActiveTimeline - Loaded instants 
upto : Option{val=[20260721213744648__clean__COMPLETED__20260721213758517]}
   2026-07-21 21:42:47.587 INFO  [qtp2089656019-535557:19-5355571] 
org.apache.hudi.common.table.HoodieTableMetaClient - Skip backtrack instance : 
[]
   2026-07-21 21:42:47.587 INFO  [qtp2089656019-535557:19-5355571] 
o.apache.hudi.common.table.view.AbstractTableFileSystemView - Took 6 ms to read 
 0 instants, 0 replaced file groups
   2026-07-21 21:42:47.612 INFO  [qtp2089656019-535557:19-5355571] 
org.apache.hudi.common.util.ClusteringUtils - Found 0 files in pending 
clustering operations
   2026-07-21 21:42:47.612 INFO  [qtp2089656019-535557:19-5355571] 
o.apache.hudi.common.table.view.AbstractTableFileSystemView - Building file 
system view for partition (dt=2014-06-10)
   2026-07-21 21:42:47.617 INFO  [qtp2089656019-535572:19-5355722] 
org.apache.hudi.timeline.service.RequestHandler - Syncing view as client passed 
last known instant 20260721213743765 as last known instant but server has the 
following last instant on timeline 
:Option{val=[20260721213744648__clean__COMPLETED__20260721213758517]}
   2026-07-21 21:42:47.626 INFO  [qtp2089656019-535557:19-5355571] 
o.apache.hudi.common.table.view.AbstractTableFileSystemView - #files found in 
partition (dt=2014-06-10) =11, Time taken =14
   2026-07-21 21:42:47.627 INFO  [qtp2089656019-535557:19-5355571] 
o.apache.hudi.common.table.view.AbstractTableFileSystemView - addFilesToView: 
NumFiles=11, NumFileGroups=10, FileGroupsCreationTime=1, StoreTimeTaken=0
   2026-07-21 21:42:47.627 INFO  [qtp2089656019-535557:19-5355571] 
o.apache.hudi.common.table.view.AbstractTableFileSystemView - Time to load 
partition (dt=2014-06-10) =15
   2026-07-21 21:42:47.638 INFO  [ForkJoinPool.commonP:l-worker-2] 
o.a.hudi.common.table.view.RemoteHoodieTableFileSystemView - Sending request : 
(http://xxx/v1/hoodie/view/slices/partition/latest/?partition=dt%3D2014-06-11&basepath=hdfs%3A%2F%2Fxxxxx&lastinstantts=20260721213743765&timelinehash=6bbdf6f1ea5484010b2275be7e8f1fe993033321bbe8cff9675044b654f162bf)
   2026-07-21 21:42:47.789 INFO  [qtp2089656019-535572:19-5355722] 
org.apache.hudi.common.table.timeline.HoodieActiveTimeline - Loaded instants 
upto : Option{val=[20260721213744648__clean__COMPLETED__20260721213758517]}
   2026-07-21 21:42:47.804 INFO  [qtp2089656019-535572:19-5355722] 
org.apache.hudi.common.table.HoodieTableMetaClient - Skip backtrack instance : 
[]
   2026-07-21 21:42:47.804 INFO  [qtp2089656019-535572:19-5355722] 
o.apache.hudi.common.table.view.AbstractTableFileSystemView - Took 8 ms to read 
 0 instants, 0 replaced file groups
   2026-07-21 21:42:47.841 INFO  [qtp2089656019-535572:19-5355722] 
org.apache.hudi.common.util.ClusteringUtils - Found 0 files in pending 
clustering operations
   2026-07-21 21:42:47.841 INFO  [qtp2089656019-535572:19-5355722] 
o.apache.hudi.common.table.view.AbstractTableFileSystemView - Building file 
system view for partition (dt=2021-05-19)
   2026-07-21 21:42:47.846 INFO  [qtp2089656019-535586:19-5355867] 
org.apache.hudi.timeline.service.RequestHandler - Syncing view as client passed 
last known instant 20260721213743765 as last known instant but server has the 
following last instant on timeline 
:Option{val=[20260721213744648__clean__COMPLETED__20260721213758517]}
   2026-07-21 21:42:47.855 INFO  [qtp2089656019-535572:19-5355722] 
o.apache.hudi.common.table.view.AbstractTableFileSystemView - #files found in 
partition (dt=2021-05-19) =11, Time taken =14
   2026-07-21 21:42:47.855 INFO  [qtp2089656019-535572:19-5355722] 
o.apache.hudi.common.table.view.AbstractTableFileSystemView - addFilesToView: 
NumFiles=11, NumFileGroups=10, FileGroupsCreationTime=0, StoreTimeTaken=0
   2026-07-21 21:42:47.855 INFO  [qtp2089656019-535572:19-5355722] 
o.apache.hudi.common.table.view.AbstractTableFileSystemView - Time to load 
partition (dt=2021-05-19) =14
   ```
   
   ## Steps to reproduce
   
   1. Have a Hudi 0.13.1 table (COW or MOR) served by an embedded/standalone 
timeline server.
   2. Open a long-lived `RemoteHoodieTableFileSystemView` from a client (Spark 
reader, or a Flink component that reads via the remote view).
   3. From a different actor (e.g. Flink's async cleaner, an inline 
clean/compaction, `hudi-cli`, or any writer commit) let a new instant become 
the newest one on the timeline **after** step 2.
   4. From the client opened in step 2, call any partition-level API 
repeatedly, ideally across many partitions in one query (e.g. 
`getLatestFileSlice(partition, fileId)` for N partitions).
   5. Enable INFO logs on `RequestHandler` and `AbstractTableFileSystemView`. 
You will see:
      - the client's `TIMELINE_HASH` in the request URL stays constant across 
all requests, and
      - the server logs a `Syncing view ...` line and a `HoodieActiveTimeline: 
Loaded instants upto ...` reload on **every** request.
   
   
   ## Environment Description
   
   - **Hudi version**: 0.13.1 
   - **Affected engines**: both Spark and Flink (both use 
`RemoteHoodieTableFileSystemView`)
   - **Storage**: HDFS
   - **Running on Docker?**: No


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