Skip to content

Commit 74edb1a

Browse files
branch-3.0: [fix](catalog) do cache load when cache value is not present #50188 (#50450)
Cherry-picked from #50188 Co-authored-by: Mingyu Chen (Rayner) <morningman@163.com>
1 parent 6086725 commit 74edb1a

File tree

4 files changed

+194
-4
lines changed

4 files changed

+194
-4
lines changed

fe/fe-core/src/main/java/org/apache/doris/datasource/metacache/MetaCache.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.github.benmanes.caffeine.cache.CacheLoader;
2424
import com.github.benmanes.caffeine.cache.LoadingCache;
2525
import com.github.benmanes.caffeine.cache.RemovalListener;
26+
import com.google.common.annotations.VisibleForTesting;
2627
import com.google.common.collect.Lists;
2728
import com.google.common.collect.Maps;
2829

@@ -88,8 +89,13 @@ public String getRemoteName(String localName) {
8889

8990
public Optional<T> getMetaObj(String name, long id) {
9091
Optional<T> val = metaObjCache.getIfPresent(name);
91-
if (val == null) {
92+
if (val == null || !val.isPresent()) {
9293
synchronized (metaObjCache) {
94+
val = metaObjCache.getIfPresent(name);
95+
if (val != null && val.isPresent()) {
96+
return val;
97+
}
98+
metaObjCache.invalidate(name);
9399
val = metaObjCache.get(name);
94100
idToName.put(id, name);
95101
}
@@ -133,4 +139,9 @@ public void invalidateAll() {
133139
metaObjCache.invalidateAll();
134140
idToName.clear();
135141
}
142+
143+
@VisibleForTesting
144+
public LoadingCache<String, Optional<T>> getMetaObjCache() {
145+
return metaObjCache;
146+
}
136147
}

fe/fe-core/src/test/java/org/apache/doris/datasource/MetaCacheTest.java

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import java.util.concurrent.ExecutorService;
3535
import java.util.concurrent.Executors;
3636
import java.util.concurrent.TimeUnit;
37+
import java.util.concurrent.atomic.AtomicInteger;
3738

3839
public class MetaCacheTest {
3940

@@ -216,4 +217,160 @@ public void testMetaObjCacheLoader() throws InterruptedException {
216217
latch.await();
217218

218219
}
220+
221+
@Test
222+
public void testGetMetaObjCacheLoading() throws InterruptedException {
223+
// Create a CountDownLatch to track cache loading invocations
224+
CountDownLatch loadLatch = new CountDownLatch(2);
225+
226+
// Create a custom cache loader that counts invocations
227+
CacheLoader<String, Optional<String>> metaObjCacheLoader = key -> {
228+
loadLatch.countDown();
229+
return Optional.of("loaded_" + key);
230+
};
231+
232+
// Create a new MetaCache instance with our custom loader
233+
MetaCache<String> testCache = new MetaCache<>(
234+
"testCache",
235+
Executors.newCachedThreadPool(),
236+
OptionalLong.of(1),
237+
OptionalLong.of(1),
238+
100,
239+
key -> Lists.newArrayList(),
240+
metaObjCacheLoader,
241+
(key, value, cause) -> {
242+
}
243+
);
244+
245+
// Case 1: Test when key does not exist in cache (val == null)
246+
Optional<String> result1 = testCache.getMetaObj("non_existent_key", 1L);
247+
Assert.assertTrue(result1.isPresent());
248+
Assert.assertEquals("loaded_non_existent_key", result1.get());
249+
250+
// Case 2: Test when key exists but value is empty Optional
251+
// First, manually put an empty Optional into cache
252+
testCache.getMetaObjCache().put("empty_key", Optional.empty());
253+
Optional<String> result2 = testCache.getMetaObj("empty_key", 2L);
254+
Assert.assertTrue(result2.isPresent());
255+
Assert.assertEquals("loaded_empty_key", result2.get());
256+
257+
// Verify that cache loader was invoked exactly twice
258+
Assert.assertTrue(loadLatch.await(1, TimeUnit.SECONDS));
259+
}
260+
261+
@Test
262+
public void testGetMetaObjConcurrent() throws InterruptedException {
263+
// Create a CountDownLatch to track cache loading invocations
264+
CountDownLatch loadLatch = new CountDownLatch(1);
265+
AtomicInteger loadCount = new AtomicInteger(0);
266+
267+
// Create a custom cache loader that counts invocations and simulates slow loading
268+
CacheLoader<String, Optional<String>> metaObjCacheLoader = key -> {
269+
loadCount.incrementAndGet();
270+
Thread.sleep(100); // Simulate slow loading
271+
loadLatch.countDown();
272+
return Optional.of("loaded_" + key);
273+
};
274+
275+
// Create a new MetaCache instance with our custom loader
276+
MetaCache<String> testCache = new MetaCache<>(
277+
"testCache",
278+
Executors.newCachedThreadPool(),
279+
OptionalLong.of(1),
280+
OptionalLong.of(1),
281+
100,
282+
key -> Lists.newArrayList(),
283+
metaObjCacheLoader,
284+
(key, value, cause) -> {
285+
}
286+
);
287+
288+
// Test concurrent access to non-existent key
289+
ExecutorService executor = Executors.newFixedThreadPool(10);
290+
final CountDownLatch startLatch = new CountDownLatch(1);
291+
final CountDownLatch finishLatch = new CountDownLatch(10);
292+
293+
for (int i = 0; i < 10; i++) {
294+
executor.submit(() -> {
295+
try {
296+
startLatch.await();
297+
Optional<String> result = testCache.getMetaObj("concurrent_key", 1L);
298+
Assert.assertTrue(result.isPresent());
299+
Assert.assertEquals("loaded_concurrent_key", result.get());
300+
} catch (Exception e) {
301+
Assert.fail("Exception occurred: " + e.getMessage());
302+
} finally {
303+
finishLatch.countDown();
304+
}
305+
});
306+
}
307+
308+
// Start all threads
309+
startLatch.countDown();
310+
// Wait for all threads to complete
311+
finishLatch.await(5, TimeUnit.SECONDS);
312+
// Wait for cache loading to complete
313+
loadLatch.await(5, TimeUnit.SECONDS);
314+
315+
// Verify that cache loader was invoked exactly once
316+
Assert.assertEquals(1, loadCount.get());
317+
318+
// Test concurrent access to existing but empty key
319+
loadCount.set(0);
320+
CountDownLatch loadLatch2 = new CountDownLatch(1);
321+
CacheLoader<String, Optional<String>> metaObjCacheLoader2 = key -> {
322+
loadCount.incrementAndGet();
323+
Thread.sleep(100); // Simulate slow loading
324+
loadLatch2.countDown();
325+
return Optional.of("loaded_" + key);
326+
};
327+
328+
// Create another MetaCache instance
329+
MetaCache<String> testCache2 = new MetaCache<>(
330+
"testCache2",
331+
Executors.newCachedThreadPool(),
332+
OptionalLong.of(1),
333+
OptionalLong.of(1),
334+
100,
335+
key -> Lists.newArrayList(),
336+
metaObjCacheLoader2,
337+
(key, value, cause) -> {
338+
}
339+
);
340+
341+
// Manually put an empty Optional into cache
342+
testCache2.getMetaObjCache().put("empty_concurrent_key", Optional.empty());
343+
344+
// Reset latches for second test
345+
final CountDownLatch startLatch2 = new CountDownLatch(1);
346+
final CountDownLatch finishLatch2 = new CountDownLatch(10);
347+
348+
for (int i = 0; i < 10; i++) {
349+
executor.submit(() -> {
350+
try {
351+
startLatch2.await();
352+
Optional<String> result = testCache2.getMetaObj("empty_concurrent_key", 2L);
353+
Assert.assertTrue(result.isPresent());
354+
Assert.assertEquals("loaded_empty_concurrent_key", result.get());
355+
} catch (Exception e) {
356+
Assert.fail("Exception occurred: " + e.getMessage());
357+
} finally {
358+
finishLatch2.countDown();
359+
}
360+
});
361+
}
362+
363+
// Start all threads
364+
startLatch2.countDown();
365+
// Wait for all threads to complete
366+
finishLatch2.await(5, TimeUnit.SECONDS);
367+
// Wait for cache loading to complete
368+
loadLatch2.await(5, TimeUnit.SECONDS);
369+
370+
// Verify that cache loader was invoked exactly once
371+
Assert.assertEquals(1, loadCount.get());
372+
373+
executor.shutdown();
374+
executor.awaitTermination(1, TimeUnit.SECONDS);
375+
}
219376
}

regression-test/data/external_table_p0/hive/test_hive_use_meta_cache.out

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ test_use_meta_cache_db_hive
2727
-- !sql07 --
2828
test_use_meta_cache_tbl_hive
2929

30+
-- !aother_test_sql --
31+
3032
-- !sql08 --
3133

3234
-- !sql09 --
@@ -118,6 +120,8 @@ test_use_meta_cache_db_hive
118120
-- !sql07 --
119121
test_use_meta_cache_tbl_hive
120122

123+
-- !aother_test_sql --
124+
121125
-- !sql08 --
122126

123127
-- !sql09 --

regression-test/suites/external_table_p0/hive/test_hive_use_meta_cache.groovy

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ suite("test_hive_use_meta_cache", "p0,external,hive,external_docker,external_doc
4949
String partitioned_table_hive = "test_use_meta_cache_partitioned_tbl_hive"
5050

5151
sql "switch ${catalog}"
52-
sql "drop database if exists ${database}"
53-
sql "drop database if exists ${database_hive}"
52+
sql "drop database if exists ${database} force"
53+
sql "drop database if exists ${database_hive} force"
5454
order_qt_sql01 "show databases like '%${database}%'";
55-
sql "drop database if exists ${database}"
55+
sql "drop database if exists ${database} force"
5656
sql "create database ${database}"
5757
order_qt_sql02 "show databases like '%${database}%'";
5858
sql "use ${database}"
@@ -100,6 +100,24 @@ suite("test_hive_use_meta_cache", "p0,external,hive,external_docker,external_doc
100100
}
101101
// can see
102102
order_qt_sql07 "show tables"
103+
104+
// another table creation test only for use_meta_cache=true
105+
// the main point is to select the table first before creation.
106+
if (use_meta_cache) {
107+
// 0. create env
108+
hive_docker "drop table if exists ${database_hive}.another_table_creation_test"
109+
// 1. select a non exist table
110+
test {
111+
sql "select * from another_table_creation_test";
112+
exception "does not exist in database"
113+
}
114+
// 2. use hive to create this table
115+
hive_docker "create table ${database_hive}.another_table_creation_test (k1 int)"
116+
// 3. use doris to select, can see
117+
qt_aother_test_sql "select * from another_table_creation_test";
118+
// 4. drop table
119+
sql "drop table another_table_creation_test";
120+
}
103121

104122
// test Hive Metastore table partition file listing
105123
hive_docker "create table ${database_hive}.${partitioned_table_hive} (k1 int) partitioned by (p1 string)"

0 commit comments

Comments
 (0)