github-actions[bot] commented on code in PR #68104:
URL: https://github.com/apache/doris/pull/68104#discussion_r4032914010


##########
fe/fe-core/src/main/java/org/apache/doris/common/proc/PartitionsProcDir.java:
##########
@@ -422,7 +422,9 @@ private List<Long> getPartitionVersions(OlapTable 
olapTable, List<Long> partitio
                 for (int start = 0; start < partitions.size(); start += 
batchSize) {
                     int end = Math.min(start + batchSize, partitions.size());
                     List<CloudPartition> batch = partitions.subList(start, 
end);
-                    
partitionVersions.addAll(CloudPartition.getSnapshotVisibleVersionFromMs(batch, 
false));
+                    // Lazy commit may advance the table version before its 
partition versions become visible.
+                    
partitionVersions.addAll(CloudPartition.getSnapshotVisibleVersionFromMs(
+                            batch, true, 
Config.cloud_version_syncer_get_version_retry_times));

Review Comment:
   [P1] Do not wait for pending transactions under metadata locks
   
   This call runs while `getPartitionInfosInrernal` holds the table metadata 
read lock and, for MTMVs, all related-table read locks. Setting 
`waitForPendingTxns=true` turns a healthy pending lazy transaction into up to 
three 30-second waits per sequential batch, blocking DDL for the entire 
interval; the new unit test calls this helper outside the enclosing lock scope. 
Snapshot partition identity under the metadata lock, release it for the waiting 
RPCs, then reacquire and revalidate before publication.



##########
fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudSyncVersionDaemon.java:
##########
@@ -164,48 +174,134 @@ private void syncPartitionVersion(Map<OlapTable, Long> 
tableVersionMap) {
             OlapTable olapTable = entry.getKey();
             LOG.info("sync partition version for db: {}, table: {}, table 
cache version: {}, new version: {}",
                     olapTable.getDatabase().getId(), olapTable, 
olapTable.getCachedTableVersion(), entry.getValue());
-            for (Partition partition : olapTable.getAllPartitions()) {
-                partitions.add((CloudPartition) partition);
+            // Capture before fetching partitions; a later invalidation must 
survive this round.
+            tableCacheEpochs.put(olapTable.getId(), 
olapTable.getTableVersionCacheEpoch());
+            List<CloudPartition> currentPartitions;
+            olapTable.readLock();
+            try {
+                currentPartitions = 
olapTable.getAllPartitions().stream().map(p -> (CloudPartition) p)
+                        .collect(Collectors.toList());
+            } finally {
+                olapTable.readUnlock();
+            }
+            tablePartitions.put(olapTable.getId(), currentPartitions);
+            for (CloudPartition partition : currentPartitions) {
+                partitions.add(partition);
                 if (partitions.size() >= 
Config.cloud_get_version_task_batch_size) {
-                    Future<Void> future = 
submitGetPartitionVersionTask(failedTables, ImmutableList.copyOf(partitions));
+                    Future<Void> future = submitGetPartitionVersionTask(
+                            failedTables, versions, 
ImmutableList.copyOf(partitions));
                     futures.add(future);
                     partitions.clear();
                 }
             }
         }
         if (partitions.size() > 0) {
-            Future<Void> future = submitGetPartitionVersionTask(failedTables, 
ImmutableList.copyOf(partitions));
+            Future<Void> future = submitGetPartitionVersionTask(
+                    failedTables, versions, ImmutableList.copyOf(partitions));
             futures.add(future);
             partitions.clear();
         }
         try {
             for (Future<Void> future : futures) {
                 future.get();
             }
-        } catch (InterruptedException | ExecutionException e) {
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            LOG.error("Interrupted while waiting for get partition version 
tasks to complete", e);
+            return;
+        } catch (ExecutionException e) {
             LOG.error("Error waiting for get partition version tasks to 
complete", e);
+            return;
         }
-        // set table version for success tables
+        // Separate RPC batches can straddle a commit. Publish only if the MS 
table version stayed unchanged.
+        validateTableVersions(tableVersionMap, failedTables);
         for (Entry<OlapTable, Long> entry : tableVersionMap.entrySet()) {
-            if (!failedTables.contains(entry.getKey().getId())) {
-                OlapTable olapTable = entry.getKey();
-                olapTable.setCachedTableVersion(entry.getValue());
+            OlapTable olapTable = entry.getKey();
+            olapTable.readLock();
+            try {
+                olapTable.versionWriteLock();
+                try {
+                    long cacheEpoch = tableCacheEpochs.get(olapTable.getId());
+                    if (olapTable.getCachedTableVersion() > entry.getValue()
+                            || olapTable.getTableVersionCacheEpoch() != 
cacheEpoch) {
+                        failedTables.add(olapTable.getId());
+                    }
+                    if (!failedTables.contains(olapTable.getId())) {
+                        // MS reads can advance a partition without updating 
the table cache. Since partition
+                        // setters never decrease versions, validate the whole 
snapshot before applying any of it.
+                        for (CloudPartition partition : 
tablePartitions.get(olapTable.getId())) {
+                            long version = 
versions.get(partition.getId()).getVersion();
+                            if (partition.getCachedVisibleVersion() > version) 
{
+                                failedTables.add(olapTable.getId());
+                                LOG.info("partition cache advanced during 
sync, table: {}, partition: {}, "
+                                                + "snapshot version: {}, 
cached version: {}", olapTable.getId(),
+                                        partition.getId(), version, 
partition.getCachedVisibleVersion());
+                                break;
+                            }
+                        }
+                    }
+                    if (failedTables.contains(olapTable.getId())) {
+                        // Invalidate together so a query cannot combine a 
failed batch with a successful one.

Review Comment:
   [P2] Preserve newer pushed caches when a scan loses the race
   
   Every newer token now schedules a full scan, including normal commits whose 
partition updates were already installed coherently. If another such commit 
advances the token during the scan, this branch rejects the staged snapshot and 
invalidates all partition caches, including the newer coherent ones. A hot 
table whose write cadence is shorter than the scan can repeat this every cycle 
and force foreground MS refreshes. Discard the obsolete staged snapshot but 
retain the newer caches and dirty marker/retry state, or distinguish complete 
pushes from actual gaps.



##########
fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudSyncVersionDaemon.java:
##########
@@ -155,7 +157,15 @@ private Future<Void> 
submitGetTableVersionTask(Map<OlapTable, Long> tableVersion
     }
 
     private void syncPartitionVersion(Map<OlapTable, Long> tableVersionMap) {
+        // Keep retrying until every partition batch has completed, even if a 
concurrent
+        // commit or table-version read refreshes the cache during this sync.
+        // Preserve the table cache until publication: SQL caches must not 
label old partition data
+        // with a new table version fetched from MS while the partition 
snapshot is still staged.
+        
tableVersionMap.keySet().forEach(OlapTable::markPartitionVersionSyncNeeded);
         Set<Long> failedTables = ConcurrentHashMap.newKeySet();
+        Map<Long, CloudPartition.PartitionVersion> versions = new 
ConcurrentHashMap<>();
+        Map<Long, List<CloudPartition>> tablePartitions = new HashMap<>();

Review Comment:
   [P2] Bound staged snapshots to one table
   
   `tablePartitions`, task batches, and this catalog-wide map retain 
references, map nodes, boxed IDs, and `PartitionVersion` objects for every 
selected partition until every task and validation completes. The per-table 
partition guard is mutable and the selected table count is unbounded, so a 
round can create severe transient FE heap and GC pressure. Atomicity is 
table-local; process, validate, publish, and release one table, or another 
fixed-size bound, at a time.



##########
fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudPartition.java:
##########
@@ -138,7 +138,9 @@ public void invalidateCachedVisibleVersion() {
 
     @VisibleForTesting
     protected boolean isCachedVersionExpired() {
-        if (lastVersionCachedTimeMs == 0 || versionCacheEpoch.get() != 
refreshedVersionCacheEpoch.get()) {
+        // Without pushes or background sync, queries must refresh versions 
themselves.
+        if (!Config.cloud_enable_version_syncer || lastVersionCachedTimeMs == 0
+                || versionCacheEpoch.get() != 
refreshedVersionCacheEpoch.get()) {

Review Comment:
   [P1] Reconcile disabled-syncer behavior with finite TTLs
   
   This leading condition bypasses every positive finite TTL whenever the 
daemon is disabled. The unchanged two-FE 
`test_fe_cached_partition_version.groovy` deliberately disables the syncer, 
configures finite table/partition TTLs, and expects follower reads to remain 
cached until expiry; this patch instead fetches current MS versions on each 
read, so those assertions fail and RPC volume changes. If the safety 
requirement is only for non-expiring caches, gate `Long.MAX_VALUE`; otherwise 
update the existing regression and document the new config contract.



##########
fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudSyncVersionDaemon.java:
##########
@@ -80,6 +76,11 @@ private Map<OlapTable, Long> syncTableVersions() {
         List<Long> dbIds = new ArrayList<>();
         List<Long> tableIds = new ArrayList<>();
         List<OlapTable> tables = new ArrayList<>();
+        // Finite TTLs refresh lazily on reads, but incomplete syncs must 
still be retried
+        // after changing TTLs or re-enabling the daemon.
+        boolean syncExpiredTables = 
VariableMgr.getDefaultSessionVariable().cloudPartitionVersionCacheTtlMs
+                == Long.MAX_VALUE

Review Comment:
   [P1] Force a recovery sweep after re-enabling
   
   If the final push is suppressed while the syncer is disabled and the 
follower performs no read, its token remains old with no dirty marker. After 
re-enable, finite defaults make `syncExpiredTables` false, so this selection 
skips the table forever, while an already-open session with infinite TTLs can 
keep serving the stale table and partitions. The new re-enable test reads while 
disabled and thereby creates the marker. Track an enable generation and force 
one sweep or invalidation on the transition; add the no-read missed-push case.



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