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

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


The following commit(s) were added to refs/heads/branch-4.2 by this push:
     new e6eadf2fc53 branch-4.2: [fix](regression) Fix test_ttl_lru_evict and 
test_stale_rowset in cloud file cache #69847 (#70116)
e6eadf2fc53 is described below

commit e6eadf2fc53bfd75d17679bc4b0c12591d02f5e0
Author: Xin Liao <[email protected]>
AuthorDate: Wed Nov 4 08:33:36 2026 +0800

    branch-4.2: [fix](regression) Fix test_ttl_lru_evict and test_stale_rowset 
in cloud file cache #69847 (#70116)
    
    ### What problem does this PR solve?
    
    Issue Number: None
    
    Related PR: #69847
    
    Problem Summary:
    
    Backport #69847 to `branch-4.2`.
    
    `test_ttl_lru_evict` could take longer than its TTL to fill a large
    cache, so the test now temporarily reduces cache capacity and restores
    it afterward. `test_stale_rowset` now polls for stale-rowset cache
    reclamation instead of relying on a single fixed wait.
    
    The backport includes prerequisite changes in the same TTL test from
    #69705: the metric helper, bounded loading, TTL duration, and expiry
    polling. It does not include the BE changes from #69705. Both final
    regression files match upstream #69847 exactly.
    
    ### Release note
    
    None
    
    ### Check List (For Author)
    
    - Test: Groovy 4.0.19 syntax parsing/conversion and `git diff --check`
    passed. The regression entry point could not start because the local
    Mac's Xcode license has not been accepted; no runtime regression pass is
    claimed. CI `run buildall` is requested separately after PR creation.
    - Behavior changed: No. Regression tests only.
    - Does this need documentation: No.
---
 .../cache/compaction/test_stale_rowset.groovy      |  19 ++-
 .../cloud_p0/cache/ttl/test_ttl_lru_evict.groovy   | 164 +++++++++++----------
 2 files changed, 103 insertions(+), 80 deletions(-)

diff --git 
a/regression-test/suites/cloud_p0/cache/compaction/test_stale_rowset.groovy 
b/regression-test/suites/cloud_p0/cache/compaction/test_stale_rowset.groovy
index bc65e78584a..b1ac9ff5892 100644
--- a/regression-test/suites/cloud_p0/cache/compaction/test_stale_rowset.groovy
+++ b/regression-test/suites/cloud_p0/cache/compaction/test_stale_rowset.groovy
@@ -171,13 +171,20 @@ suite("test_stale_rowset") {
     select count(*) from ${tableName};
     """
 
-    sleep(60000);
-    def backendIdToAfterCompactionCacheSize = getCurCacheSize()
-    for (String[] backend in backends) {
-        if (backend[9].equals("true") && 
backend[19].contains("regression_cluster_name1")) {
-            assertTrue(backendIdToAfterLoadCacheSize.get(backend[0]) >
-                backendIdToAfterCompactionCacheSize.get(backend[0]))
+    // The cache of the input rowsets is removed only when the BE sweeps the 
stale rowsets, and the
+    // file_cache_cache_size metric is refreshed every few seconds after that. 
So poll until it
+    // shrinks instead of reading it once after a fixed wait.
+    def backendIdToAfterCompactionCacheSize = [:]
+    def shrunk = false
+    long deadline = System.currentTimeMillis() + 180000
+    while (!shrunk && System.currentTimeMillis() < deadline) {
+        sleep(5000)
+        backendIdToAfterCompactionCacheSize = getCurCacheSize()
+        shrunk = backendIdToAfterLoadCacheSize.every { id, size ->
+            size > backendIdToAfterCompactionCacheSize.get(id)
         }
     }
+    assertTrue(shrunk, "file cache did not shrink after compaction, after 
load: " +
+            "${backendIdToAfterLoadCacheSize}, after compaction: 
${backendIdToAfterCompactionCacheSize}")
     sql new File("""${context.file.parent}/../ddl/${table}_delete.sql""").text
 }
diff --git 
a/regression-test/suites/cloud_p0/cache/ttl/test_ttl_lru_evict.groovy 
b/regression-test/suites/cloud_p0/cache/ttl/test_ttl_lru_evict.groovy
index f452d071e04..32703fb907b 100644
--- a/regression-test/suites/cloud_p0/cache/ttl/test_ttl_lru_evict.groovy
+++ b/regression-test/suites/cloud_p0/cache/ttl/test_ttl_lru_evict.groovy
@@ -58,7 +58,10 @@ suite("test_ttl_lru_evict") {
     logger.info("getS3SK:${getS3SK()}");
     logger.info("getS3Endpoint:${getS3Endpoint()}");
 
-    def ttlProperties = """ PROPERTIES("file_cache_ttl_seconds"="150") """
+    // Long enough that the first loads are still TTL when the TTL queue 
overflows. How many loads
+    // that takes depends on the cache capacity of the environment.
+    def ttlSeconds = 300
+    def ttlProperties = """ 
PROPERTIES("file_cache_ttl_seconds"="${ttlSeconds}") """
     String[][] backends = sql """ show backends """
     String backendId;
     def backendIdToBackendIP = [:]
@@ -206,65 +209,83 @@ suite("test_ttl_lru_evict") {
         }
     }
 
-    clearFileCache.call() {
-        respCode, body -> {}
-    }
-
-    long ttl_cache_evict_size_begin = 0;
-    getMetricsMethod.call() {
-        respCode, body ->
-            assertEquals("${respCode}".toString(), "200")
-            String out = "${body}".toString()
-            def strs = out.split('\n')
-            Boolean flag1 = false;
-            for (String line in strs) {
-                if (flag1) break;
-                if (line.contains("ttl_cache_evict_size")) {
-                    if (line.startsWith("#")) {
+    // Values of every metric line whose name contains the given name, one per 
cache path.
+    def getMetricValues = { String name ->
+        def values = []
+        getMetricsMethod.call() {
+            respCode, body ->
+                assertEquals("${respCode}".toString(), "200")
+                for (String line in "${body}".toString().split('\n')) {
+                    if (line.startsWith("#") || !line.contains(name)) {
                         continue
                     }
-                    def i = line.indexOf(' ')
-                    ttl_cache_evict_size_begin = line.substring(i).toLong()
-                    flag1 = true
+                    values.add(line.substring(line.indexOf(' 
')).trim().toLong())
                 }
-            }
-            assertTrue(flag1)
+        }
+        assertTrue(!values.isEmpty(), "metric ${name} not found")
+        return values
     }
 
-    // load for MANY times to this dup table to make cache full enough to evict
-    // It will takes huge amount of time, so it should be p1/p2 level case.
-    // But someone told me it is the right place. Never mind just do it!
+    clearFileCache.call() {
+        respCode, body -> {}
+    }
 
-    for (int i = 0; i < 10; i++) {
-        load_customer_once("customer_ttl")
+    long ttl_cache_evict_size_begin = 
getMetricValues("ttl_cache_evict_size")[0]
+    long ttl_cache_size_begin = getMetricValues("ttl_cache_size")[0]
+    def path_capacities = getMetricValues("file_cache_capacity")
+    // The capacity is reset for all cache paths at once, so it can only be 
restored when they
+    // have the same capacity.
+    assertEquals(1, path_capacities.unique(false).size(),
+            "cache paths have different capacities: ${path_capacities}")
+    long path_capacity = path_capacities[0]
+    long cache_capacity = path_capacity
+
+    // Sets the capacity of every cache path of the BE.
+    def resetCapacity = { long capacity ->
+        def (code, out, err) = curl("GET", ("http://"; + 
backendIdToBackendIP.get(backendId) + ":" +
+                backendIdToBackendHttpPort.get(backendId) +
+                "/api/file_cache?op=reset&capacity=${capacity}").toString())
+        assertEquals(0, code)
+        assertEquals([capacity] * path_capacities.size(), 
getMetricValues("file_cache_capacity"))
+        cache_capacity = capacity
+        logger.info("file cache capacity of each path reset to ${capacity}")
     }
-    sleep(10000) // 10s
-    // first, we test whether ttl evict actively
-    long ttl_cache_evict_size_end = 0;
-    getMetricsMethod.call() {
-        respCode, body ->
-            assertEquals("${respCode}".toString(), "200")
-            String out = "${body}".toString()
-            def strs = out.split('\n')
-            Boolean flag1 = false;
-            for (String line in strs) {
-                if (flag1) break;
-                if (line.contains("ttl_cache_evict_size")) {
-                    if (line.startsWith("#")) {
-                        continue
-                    }
-                    def i = line.indexOf(' ')
-                    ttl_cache_evict_size_end = line.substring(i).toLong()
-                    flag1 = true
-                }
+    onFinish {
+        if (cache_capacity != path_capacity) {
+            resetCapacity(path_capacity)
+        }
+    }
+
+    // first, we test whether ttl evict actively.
+    // Load to this dup table until the TTL queue overflows. TTL blocks are 
evicted only when the
+    // whole cache is full, and filling a large cache can take longer than the 
TTL. Once the TTL
+    // expires, the blocks become NORMAL and are no longer evicted as TTL. So 
the cache is shrunk
+    // to a few loads after the first one.
+    long evicted = 0
+    long loads = 0
+    long max_loads = 10
+    while (evicted <= 238860800 && loads < max_loads) {
+        load_customer_once("customer_ttl")
+        loads++
+        if (loads == 1) {
+            // ttl_cache_size is refreshed by the cache monitor, so wait for a 
refresh that
+            // includes the whole load.
+            
sleep(get_be_param("file_cache_background_monitor_interval_ms").toLong() + 1000)
+            long bytes_per_load = getMetricValues("ttl_cache_size")[0] - 
ttl_cache_size_begin
+            assertTrue(bytes_per_load > 0, "the first load did not add any TTL 
cache")
+            if (bytes_per_load * 4 < path_capacity) {
+                resetCapacity(bytes_per_load * 4)
             }
-            assertTrue(flag1)
+            // This many loads overflow the cache for sure.
+            max_loads = Math.max(max_loads, 
cache_capacity.intdiv(bytes_per_load) + 3)
+            logger.info("cache capacity: ${cache_capacity}, TTL bytes per 
load: ${bytes_per_load}, " +
+                    "max loads: ${max_loads}")
+        }
+        evicted = getMetricValues("ttl_cache_evict_size")[0] - 
ttl_cache_evict_size_begin
+        logger.info("ttl evict diff after ${loads} loads: ${evicted}")
     }
-    // Note: this only applies when the case is run
-    // sequentially, coz we don't know what other cases are
-    // doing with TTL cache
-    logger.info("ttl evict diff:" + (ttl_cache_evict_size_end - 
ttl_cache_evict_size_begin).toString())
-    assertTrue((ttl_cache_evict_size_end - ttl_cache_evict_size_begin) > 
238860800)
+    assertTrue(evicted > 238860800,
+            "TTL cache evicted only ${evicted} bytes after ${loads} loads, 
capacity ${cache_capacity}")
 
     // then we test skip_cache count when doing query when ttl cache is full
     // we expect it to be rare
@@ -322,31 +343,26 @@ suite("test_ttl_lru_evict") {
     logger.info("skip cache diff:" + (skip_cache_count_end - 
skip_cache_count_begin).toString())
     assertTrue((skip_cache_count_end - skip_cache_count_begin) < 1000000)
 
+    // The rest of the case does not need a full cache.
+    if (cache_capacity != path_capacity) {
+        resetCapacity(path_capacity)
+    }
+
     // finally, we test whether LRU queue clean itself up when all ttl
     // cache evict after expiration
-    sleep(200000)
-    getMetricsMethod.call() {
-        respCode, body ->
-            assertEquals("${respCode}".toString(), "200")
-            String out = "${body}".toString()
-            def strs = out.split('\n')
-            Boolean flag1 = false;
-            for (String line in strs) {
-                if (flag1) break;
-                if (line.contains("ttl_cache_lru_queue_size")) {
-                    if (line.startsWith("#")) {
-                        continue
-                    }
-                    def i = line.indexOf(' ')
-                    // all ttl will expire, so the ttl LRU queue set to 0
-                    // Note: this only applies when the case is run
-                    // sequentially, coz we don't know what other cases are
-                    // doing with TTL cache
-                    assertEquals(line.substring(i).toLong(), 0)
-                    flag1 = true
-                }
-            }
-            assertTrue(flag1)
+    // Note: this only applies when the case is run
+    // sequentially, coz we don't know what other cases are
+    // doing with TTL cache
+    long ttl_queue_size = -1
+    long deadline = System.currentTimeMillis() + (ttlSeconds + 120) * 1000L
+    while (System.currentTimeMillis() < deadline) {
+        // all ttl will expire, so the ttl LRU queue set to 0
+        ttl_queue_size = getMetricValues("ttl_cache_lru_queue_size")[0]
+        if (ttl_queue_size == 0) {
+            break
+        }
+        sleep(10000)
     }
+    assertEquals(0, ttl_queue_size)
     }
 }


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

Reply via email to