This is an automated email from the ASF dual-hosted git repository.

yiguolei pushed a commit to branch branch-4.0
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.0 by this push:
     new 681d0b890fc branch-4.0: [Opt](cloud) cache table version for cloud 
mode #59339 (#59442)
681d0b890fc is described below

commit 681d0b890fca54d1fd97001852fe938c748bda12
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Mon Jan 12 14:20:13 2026 +0800

    branch-4.0: [Opt](cloud) cache table version for cloud mode #59339 (#59442)
    
    Cherry-picked from #59339
    
    Co-authored-by: bobhan1 <[email protected]>
---
 .../java/org/apache/doris/catalog/OlapTable.java   |  39 +++++++
 .../java/org/apache/doris/qe/SessionVariable.java  |   4 +
 .../org/apache/doris/catalog/OlapTableTest.java    | 122 +++++++++++++++++++++
 3 files changed, 165 insertions(+)

diff --git a/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java 
b/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java
index 7607d2c8be7..0ac334fd2b3 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java
@@ -234,6 +234,11 @@ public class OlapTable extends Table implements 
MTMVRelatedTableIf, GsonPostProc
     // Ensures only one creation task runs for a given partition at a time.
     private ConcurrentHashMap<String, CompletableFuture<Void>> 
partitionCreationFutures = new ConcurrentHashMap<>();
 
+    // Cache for table version in cloud mode
+    // This value is set when get the table version from meta-service, 0 means 
version is not cached yet
+    private long lastTableVersionCachedTimeMs = 0;
+    private long cachedTableVersion = -1;
+
     public OlapTable() {
         // for persist
         super(TableType.OLAP);
@@ -3280,11 +3285,43 @@ public class OlapTable extends Table implements 
MTMVRelatedTableIf, GsonPostProc
         }
     }
 
+    public boolean isCachedTableVersionExpired() {
+        // -1 means no cache yet, need to fetch from MS
+        if (cachedTableVersion == -1) {
+            return true;
+        }
+        ConnectContext ctx = ConnectContext.get();
+        if (ctx == null) {
+            return true;
+        }
+        long cacheExpirationMs = 
ctx.getSessionVariable().cloudTableVersionCacheTtlMs;
+        if (cacheExpirationMs <= 0) { // always expired
+            return true;
+        }
+        return System.currentTimeMillis() - lastTableVersionCachedTimeMs > 
cacheExpirationMs;
+    }
+
+    public void setCachedTableVersion(long version) {
+        if (version > cachedTableVersion) {
+            cachedTableVersion = version;
+            lastTableVersionCachedTimeMs = System.currentTimeMillis();
+        }
+    }
+
+    public long getCachedTableVersion() {
+        return cachedTableVersion;
+    }
+
     public long getVisibleVersion() throws RpcException {
         if (Config.isNotCloudMode()) {
             return tableAttributes.getVisibleVersion();
         }
 
+        // check if cache is not expired
+        if (!isCachedTableVersionExpired()) {
+            return getCachedTableVersion();
+        }
+
         // get version rpc
         Cloud.GetVersionRequest request = Cloud.GetVersionRequest.newBuilder()
                 .setRequestIp(FrontendOptions.getLocalHostAddressCached())
@@ -3309,6 +3346,8 @@ public class OlapTable extends Table implements 
MTMVRelatedTableIf, GsonPostProc
             if (version == 0) {
                 version = 1;
             }
+            // update cache
+            setCachedTableVersion(version);
             return version;
         } catch (RpcException e) {
             LOG.warn("get version from meta service failed", e);
diff --git a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java 
b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
index ba96a4a739e..7c5e6cca68c 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/qe/SessionVariable.java
@@ -767,6 +767,8 @@ public class SessionVariable implements Serializable, 
Writable {
     public static final String DISABLE_EMPTY_PARTITION_PRUNE = 
"disable_empty_partition_prune";
     public static final String CLOUD_PARTITION_VERSION_CACHE_TTL_MS =
             "cloud_partition_version_cache_ttl_ms";
+    public static final String CLOUD_TABLE_VERSION_CACHE_TTL_MS =
+            "cloud_table_version_cache_ttl_ms";
     // CLOUD_VARIABLES_BEGIN
 
     public static final String ENABLE_MATCH_WITHOUT_INVERTED_INDEX = 
"enable_match_without_inverted_index";
@@ -2795,6 +2797,8 @@ public class SessionVariable implements Serializable, 
Writable {
     public boolean disableEmptyPartitionPrune = false;
     @VariableMgr.VarAttr(name = CLOUD_PARTITION_VERSION_CACHE_TTL_MS)
     public static long cloudPartitionVersionCacheTtlMs = 0;
+    @VariableMgr.VarAttr(name = CLOUD_TABLE_VERSION_CACHE_TTL_MS)
+    public long cloudTableVersionCacheTtlMs = 0;
     // CLOUD_VARIABLES_END
 
     // fetch remote schema rpc timeout
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/catalog/OlapTableTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/catalog/OlapTableTest.java
index 0c983332ee5..befa306e186 100644
--- a/fe/fe-core/src/test/java/org/apache/doris/catalog/OlapTableTest.java
+++ b/fe/fe-core/src/test/java/org/apache/doris/catalog/OlapTableTest.java
@@ -20,10 +20,14 @@ package org.apache.doris.catalog;
 import org.apache.doris.analysis.IndexDef;
 import org.apache.doris.analysis.UserIdentity;
 import org.apache.doris.catalog.TableIf.TableType;
+import org.apache.doris.cloud.proto.Cloud;
+import org.apache.doris.cloud.rpc.VersionHelper;
+import org.apache.doris.common.Config;
 import org.apache.doris.common.FeConstants;
 import org.apache.doris.common.io.FastByteArrayOutputStream;
 import org.apache.doris.common.util.UnitTestUtil;
 import org.apache.doris.qe.ConnectContext;
+import org.apache.doris.qe.SessionVariable;
 import org.apache.doris.resource.Tag;
 import org.apache.doris.resource.computegroup.ComputeGroup;
 import org.apache.doris.system.Backend;
@@ -283,4 +287,122 @@ public class OlapTableTest {
         ConnectContext.remove();
 
     }
+
+    @Test
+    public void testTableVersionCacheWithRpc() throws Exception {
+        // Mock cloud mode
+        new MockUp<Config>() {
+            @Mock
+            public boolean isNotCloudMode() {
+                return false;
+            }
+        };
+
+        // Create table and database
+        final Database db = new Database(1L, "test_db");
+
+        // Create a custom OlapTable that overrides getDatabase()
+        OlapTable table = new OlapTable() {
+            @Override
+            public Database getDatabase() {
+                return db;
+            }
+        };
+        table.id = 1000L;
+
+        // Mock VersionHelper.getVersionFromMeta()
+        final long[] versions = {100L, 200L, 300L};
+        final int[] callCount = {0};
+
+        new MockUp<VersionHelper>() {
+            @Mock
+            public Cloud.GetVersionResponse 
getVersionFromMeta(Cloud.GetVersionRequest req) {
+                Cloud.GetVersionResponse.Builder builder = 
Cloud.GetVersionResponse.newBuilder();
+                builder.setStatus(Cloud.MetaServiceResponseStatus.newBuilder()
+                        .setCode(Cloud.MetaServiceCode.OK).build());
+                builder.setVersion(versions[callCount[0]]);
+                callCount[0]++;
+                return builder.build();
+            }
+        };
+
+        // Create ConnectContext with SessionVariable
+        ConnectContext ctx = new ConnectContext();
+        ctx.setSessionVariable(new SessionVariable());
+        ctx.setThreadLocalInfo();
+
+        try {
+            // Test 1: Initial state with TTL set, should still call RPC for 
first time
+            ctx.getSessionVariable().cloudTableVersionCacheTtlMs = 100000; // 
Set long TTL
+            Assert.assertEquals(-1, table.getCachedTableVersion()); // Initial 
state
+            Assert.assertTrue(table.isCachedTableVersionExpired()); // Should 
be expired due to -1
+
+            long ver0 = table.getVisibleVersion();
+            Assert.assertEquals(100, ver0); // Should get from MS
+            Assert.assertEquals(1, callCount[0]); // First RPC call
+            Assert.assertEquals(100, table.getCachedTableVersion()); // Cache 
updated
+
+            // Second call should use cache
+            long ver0Again = table.getVisibleVersion();
+            Assert.assertEquals(100, ver0Again); // Should use cached version
+            Assert.assertEquals(1, callCount[0]); // No new RPC call
+
+            // Test 2: Disable cache (TTL = 0), should always call RPC
+            ctx.getSessionVariable().cloudTableVersionCacheTtlMs = 0;
+            long ver1 = table.getVisibleVersion();
+            Assert.assertEquals(200, ver1);
+            Assert.assertEquals(2, callCount[0]); // Second RPC call
+
+            long ver2 = table.getVisibleVersion();
+            Assert.assertEquals(300, ver2);
+            Assert.assertEquals(3, callCount[0]); // Third RPC call
+            Assert.assertEquals(300, table.getCachedTableVersion()); // Cache 
updated to 300
+
+            // Test 3: Enable cache with long TTL, should use cached version
+            ctx.getSessionVariable().cloudTableVersionCacheTtlMs = 100000; // 
100 seconds
+            table.setCachedTableVersion(350); // Set cache to a larger version
+            long ver3 = table.getVisibleVersion();
+            Assert.assertEquals(350, ver3); // Should return cached version 
(350)
+            Assert.assertEquals(3, callCount[0]); // No new RPC call
+
+            // Test 4: Test setCachedTableVersion only updates when version is 
greater
+            ctx.getSessionVariable().cloudTableVersionCacheTtlMs = 500; // 
500ms TTL
+
+            // At this point, cache is 350 from Test 3
+            // Set a larger version to 400
+            table.setCachedTableVersion(400);
+            Assert.assertEquals(400, table.getCachedTableVersion());
+            Assert.assertFalse(table.isCachedTableVersionExpired()); // Not 
expired yet
+
+            Thread.sleep(300); // Sleep 300ms
+
+            // Try to set a smaller version (380), should NOT update version 
or timestamp
+            table.setCachedTableVersion(380);
+            Assert.assertEquals(400, table.getCachedTableVersion()); // 
Version should remain 400
+
+            Thread.sleep(300); // Total 600ms since setCachedTableVersion(400)
+            // Cache should be expired (600ms > 500ms TTL)
+            // If timestamp was incorrectly reset by 
setCachedTableVersion(380), cache would not be expired
+            Assert.assertTrue(table.isCachedTableVersionExpired());
+
+            // Test 5: Setting a greater version should update both version 
and timestamp
+            ctx.getSessionVariable().cloudTableVersionCacheTtlMs = 500; // 
500ms TTL
+            table.setCachedTableVersion(500); // Set to 500
+            Assert.assertEquals(500, table.getCachedTableVersion());
+            Assert.assertFalse(table.isCachedTableVersionExpired()); // Not 
expired
+
+            Thread.sleep(300); // Sleep 300ms
+
+            // Set a greater version (550), should update both version and 
timestamp
+            table.setCachedTableVersion(550);
+            Assert.assertEquals(550, table.getCachedTableVersion()); // 
Version updated to 550
+            Assert.assertFalse(table.isCachedTableVersionExpired()); // 
Timestamp reset, not expired yet
+
+            Thread.sleep(300); // Sleep another 300ms (total 600ms from first 
setCachedTableVersion(500), but only 300ms from setCachedTableVersion(550))
+            Assert.assertFalse(table.isCachedTableVersionExpired()); // Still 
not expired (300ms < 500ms TTL)
+
+        } finally {
+            ConnectContext.remove();
+        }
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to