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

stigahuang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/impala.git


The following commit(s) were added to refs/heads/master by this push:
     new 036c4fdf0 IMPALA-13863: Add metric to track number of loaded tables in 
catalogd
036c4fdf0 is described below

commit 036c4fdf04a1fb14838e2acaf5b2af2719d67a43
Author: Arnab Karmakar <[email protected]>
AuthorDate: Tue Dec 23 03:04:52 2025 -0800

    IMPALA-13863: Add metric to track number of loaded tables in catalogd
    
    Implements a real-time counter that tracks the number of loaded tables
    (non-IncompleteTable instances) in the catalog. This helps monitor
    catalog memory pressure and query performance impact from implicit table
    invalidation mechanisms.
    
    The counter uses AtomicInteger for thread-safety and is updated across
    all table state transitions:
    - Incremented when IncompleteTable is replaced with a loaded table
    - Decremented when tables are invalidated, dropped, or aged out
    - Reset to 0 on global INVALIDATE METADATA
    
    Testing:
    Manual verification and automated tests confirm correct
    behavior across load, invalidate, drop, and timeout scenarios.
    
    Change-Id: I5aa54f9f7507709b654df22e24592799811e8b6c
    Reviewed-on: http://gerrit.cloudera.org:8080/23804
    Reviewed-by: Impala Public Jenkins <[email protected]>
    Tested-by: Impala Public Jenkins <[email protected]>
---
 be/src/catalog/catalog-server.cc                   |   3 +
 be/src/catalog/catalog-server.h                    |   3 +
 common/thrift/JniCatalog.thrift                    |   3 +
 common/thrift/metrics.json                         |  10 ++
 .../java/org/apache/impala/catalog/Catalog.java    |  55 +++++++++++
 .../apache/impala/catalog/CatalogObjectCache.java  |  31 ++++++
 .../impala/catalog/CatalogServiceCatalog.java      |   6 ++
 fe/src/main/java/org/apache/impala/catalog/Db.java |  11 ++-
 .../java/org/apache/impala/service/JniCatalog.java |   1 +
 .../custom_cluster/test_automatic_invalidation.py  | 108 ++++++++++++++++++++-
 tests/webserver/test_web_pages.py                  |   1 +
 11 files changed, 228 insertions(+), 4 deletions(-)

diff --git a/be/src/catalog/catalog-server.cc b/be/src/catalog/catalog-server.cc
index 9e9ece243..88a0eb571 100644
--- a/be/src/catalog/catalog-server.cc
+++ b/be/src/catalog/catalog-server.cc
@@ -366,6 +366,7 @@ const string CATALOG_NUM_TABLES_WAITING_FOR_ASYNC_LOADING =
 const string CATALOG_NUM_DBS = "catalog.num-databases";
 const string CATALOG_NUM_TABLES = "catalog.num-tables";
 const string CATALOG_NUM_FUNCTIONS = "catalog.num-functions";
+const string CATALOG_NUM_LOADED_TABLES = "catalog.num-loaded-tables";
 const string CATALOG_NUM_HMS_CLIENTS_IDLE = "catalog.hms-client-pool.num-idle";
 const string CATALOG_NUM_HMS_CLIENTS_IN_USE = 
"catalog.hms-client-pool.num-in-use";
 
@@ -645,6 +646,7 @@ CatalogServer::CatalogServer(MetricGroup* metrics)
   num_dbs_metric_ = metrics->AddGauge(CATALOG_NUM_DBS, 0);
   num_tables_metric_ = metrics->AddGauge(CATALOG_NUM_TABLES, 0);
   num_functions_metric_ = metrics->AddGauge(CATALOG_NUM_FUNCTIONS, 0);
+  num_loaded_tables_metric_ = metrics->AddGauge(CATALOG_NUM_LOADED_TABLES, 0);
   num_hms_clients_idle_metric_ = 
metrics->AddGauge(CATALOG_NUM_HMS_CLIENTS_IDLE, 0);
   num_hms_clients_in_use_metric_ = 
metrics->AddGauge(CATALOG_NUM_HMS_CLIENTS_IN_USE, 0);
 
@@ -1125,6 +1127,7 @@ void CatalogServer::MarkPendingMetadataReset(const 
unique_lock<std::mutex>& lock
     num_dbs_metric_->SetValue(response.catalog_num_dbs);
     num_tables_metric_->SetValue(response.catalog_num_tables);
     num_functions_metric_->SetValue(response.catalog_num_functions);
+    num_loaded_tables_metric_->SetValue(response.catalog_num_loaded_tables);
     
num_hms_clients_idle_metric_->SetValue(response.catalog_num_hms_clients_idle);
     
num_hms_clients_in_use_metric_->SetValue(response.catalog_num_hms_clients_in_use);
     TEventProcessorMetrics eventProcessorMetrics = response.event_metrics;
diff --git a/be/src/catalog/catalog-server.h b/be/src/catalog/catalog-server.h
index cb6b637a8..3b574baec 100644
--- a/be/src/catalog/catalog-server.h
+++ b/be/src/catalog/catalog-server.h
@@ -206,6 +206,9 @@ class CatalogServer {
   IntGauge* num_tables_metric_;
   IntGauge* num_functions_metric_;
 
+  /// Metric that tracks the number of loaded tables.
+  IntGauge* num_loaded_tables_metric_;
+
   /// Metrics that track the number of HMS clients
   IntGauge* num_hms_clients_idle_metric_;
   IntGauge* num_hms_clients_in_use_metric_;
diff --git a/common/thrift/JniCatalog.thrift b/common/thrift/JniCatalog.thrift
index 766f8cd11..53a55bcd2 100644
--- a/common/thrift/JniCatalog.thrift
+++ b/common/thrift/JniCatalog.thrift
@@ -1093,6 +1093,9 @@ struct TGetCatalogServerMetricsResponse {
   // Metrics of HMS clients
   13: optional i32 catalog_num_hms_clients_idle
   14: optional i32 catalog_num_hms_clients_in_use
+
+  // Number of loaded tables (i.e. not IncompleteTable)
+  15: optional i32 catalog_num_loaded_tables
 }
 
 // Request to copy the generated testcase from a given input path.
diff --git a/common/thrift/metrics.json b/common/thrift/metrics.json
index 182919de8..b5954b2e3 100644
--- a/common/thrift/metrics.json
+++ b/common/thrift/metrics.json
@@ -571,6 +571,16 @@
     "kind": "GAUGE",
     "key": "catalog.num-functions"
   },
+  {
+    "description": "The number of loaded tables in the catalog.",
+    "contexts": [
+      "CATALOGSERVER"
+    ],
+    "label": "Loaded Tables",
+    "units": "NONE",
+    "kind": "GAUGE",
+    "key": "catalog.num-loaded-tables"
+  },
   {
     "description": "The number of Hive Metastore Clients that are idle.",
     "contexts": [
diff --git a/fe/src/main/java/org/apache/impala/catalog/Catalog.java 
b/fe/src/main/java/org/apache/impala/catalog/Catalog.java
index 387cd3c2f..d1da330b8 100644
--- a/fe/src/main/java/org/apache/impala/catalog/Catalog.java
+++ b/fe/src/main/java/org/apache/impala/catalog/Catalog.java
@@ -25,6 +25,9 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
 import javax.annotation.Nullable;
 
@@ -52,6 +55,8 @@ import org.apache.impala.thrift.TTableName;
 import org.apache.impala.thrift.TUniqueId;
 import org.apache.impala.util.EventSequence;
 import org.apache.impala.util.PatternMatcher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Joiner;
@@ -76,6 +81,8 @@ import com.google.common.base.Preconditions;
  * Builtins are populated on startup in initBuiltins().
  */
 public abstract class Catalog implements AutoCloseable {
+  private static final Logger LOG = LoggerFactory.getLogger(Catalog.class);
+
   // Initial catalog version and ID.
   public final static long INITIAL_CATALOG_VERSION = 0L;
   public static final TUniqueId INITIAL_CATALOG_SERVICE_ID = new TUniqueId(0L, 
0L);
@@ -111,6 +118,47 @@ public abstract class Catalog implements AutoCloseable {
   protected final CatalogObjectCache<AuthzCacheInvalidation> 
authzCacheInvalidation_ =
       new CatalogObjectCache<>();
 
+  // Number of loaded tables (i.e. not IncompleteTable) in the catalog cache.
+  // This is updated in real-time when tables are loaded or invalidated.
+  // IMPALA-13863: Track loaded tables to help monitor catalog memory pressure.
+  // This is static because it tracks loaded tables across all catalog 
instances.
+  protected static final AtomicInteger numLoadedTables_ = new AtomicInteger(0);
+
+  // Update function for tracking table additions/replacements in the loaded 
tables
+  // counter. This is passed to Db#addTable() to centralize the counter update 
logic.
+  public static final BiConsumer<Table, Table> TABLE_ADD_UPDATE_FUNC =
+      (existingTbl, newTbl) -> {
+    if (existingTbl != null) {
+      boolean wasLoaded = !(existingTbl instanceof IncompleteTable);
+      boolean isLoaded = !(newTbl instanceof IncompleteTable);
+
+      if (wasLoaded && !isLoaded) {
+        numLoadedTables_.decrementAndGet();
+        LOG.trace("Replaced loaded table {} with IncompleteTable, " +
+            "loaded tables count: {}", newTbl.getFullName(), 
numLoadedTables_.get());
+      } else if (!wasLoaded && isLoaded) {
+        numLoadedTables_.incrementAndGet();
+        LOG.trace("Replaced IncompleteTable with loaded table {}, " +
+            "loaded tables count: {}", newTbl.getFullName(), 
numLoadedTables_.get());
+      }
+    } else if (!(newTbl instanceof IncompleteTable)) {
+      // New table being added and it's loaded
+      numLoadedTables_.incrementAndGet();
+      LOG.trace("Added new loaded table {}, loaded tables count: {}",
+          newTbl.getFullName(), numLoadedTables_.get());
+    }
+  };
+
+  // Update function for tracking table removals in the loaded tables counter.
+  // This is passed to Db#removeTable() to centralize the counter update logic.
+  public static final Consumer<Table> TABLE_REMOVE_UPDATE_FUNC = (removedTbl) 
-> {
+    if (!(removedTbl instanceof IncompleteTable)) {
+      numLoadedTables_.decrementAndGet();
+      LOG.trace("Removed loaded table {}, loaded tables count: {}",
+          removedTbl.getFullName(), numLoadedTables_.get());
+    }
+  };
+
   // This member is responsible for heartbeating HMS locks and transactions.
   private TransactionKeepalive transactionKeepalive_;
 
@@ -299,6 +347,13 @@ public abstract class Catalog implements AutoCloseable {
     return dataSources_.getValues();
   }
 
+  /**
+   * Returns the number of loaded tables (not IncompleteTable) in the catalog.
+   */
+  public int getNumLoadedTables() {
+    return numLoadedTables_.get();
+  }
+
   /**
    * Returns a list of data sources names that match pattern.
    *
diff --git a/fe/src/main/java/org/apache/impala/catalog/CatalogObjectCache.java 
b/fe/src/main/java/org/apache/impala/catalog/CatalogObjectCache.java
index dd33ab3f4..7467a81f5 100644
--- a/fe/src/main/java/org/apache/impala/catalog/CatalogObjectCache.java
+++ b/fe/src/main/java/org/apache/impala/catalog/CatalogObjectCache.java
@@ -22,6 +22,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.Lists;
@@ -69,6 +71,16 @@ public class CatalogObjectCache<T extends CatalogObject> 
implements Iterable<T>
    * Returns true if this item was added or false if the existing value was 
preserved.
    */
   public synchronized boolean add(T catalogObject) {
+    return add(catalogObject, null);
+  }
+
+  /**
+   * Extends the base class method to call the update function if provided.
+   * The updateFunc is called with (existingItem, newItem) when an item is 
added or
+   * replaced, allowing the caller to track state transitions.
+   * Returns true if this item was added or false if the existing value was 
preserved.
+   */
+  public synchronized boolean add(T catalogObject, BiConsumer<T, T> 
updateFunc) {
     Preconditions.checkNotNull(catalogObject);
     String key = catalogObject.getName();
     if (caseInsensitiveKeys_) key = key.toLowerCase();
@@ -76,6 +88,9 @@ public class CatalogObjectCache<T extends CatalogObject> 
implements Iterable<T>
     if (existingItem == null) {
       CatalogObjectVersionSet.INSTANCE.addVersion(
           catalogObject.getCatalogVersion());
+      if (updateFunc != null) {
+        updateFunc.accept(null, catalogObject);
+      }
       return true;
     }
 
@@ -86,6 +101,9 @@ public class CatalogObjectCache<T extends CatalogObject> 
implements Iterable<T>
       metadataCache_.put(key, catalogObject);
       CatalogObjectVersionSet.INSTANCE.updateVersions(
           existingItem.getCatalogVersion(), catalogObject.getCatalogVersion());
+      if (updateFunc != null) {
+        updateFunc.accept(existingItem, catalogObject);
+      }
       return true;
     }
     return false;
@@ -96,11 +114,24 @@ public class CatalogObjectCache<T extends CatalogObject> 
implements Iterable<T>
    * if no item was removed.
    */
   public synchronized T remove(String name) {
+    return remove(name, null);
+  }
+
+  /**
+   * Removes an item from the metadata cache with an optional update function.
+   * The updateFunc is called with the removed item if it was successfully 
removed,
+   * allowing the caller to track the removal.
+   * Returns the removed item, or null if no item was removed.
+   */
+  public synchronized T remove(String name, Consumer<T> updateFunc) {
     if (caseInsensitiveKeys_) name = name.toLowerCase();
     T removedObject = metadataCache_.remove(name);
     if (removedObject != null) {
       CatalogObjectVersionSet.INSTANCE.removeVersion(
           removedObject.getCatalogVersion());
+      if (updateFunc != null) {
+        updateFunc.accept(removedObject);
+      }
     }
     return removedObject;
   }
diff --git 
a/fe/src/main/java/org/apache/impala/catalog/CatalogServiceCatalog.java 
b/fe/src/main/java/org/apache/impala/catalog/CatalogServiceCatalog.java
index 46c434615..6706bc7ec 100644
--- a/fe/src/main/java/org/apache/impala/catalog/CatalogServiceCatalog.java
+++ b/fe/src/main/java/org/apache/impala/catalog/CatalogServiceCatalog.java
@@ -2510,6 +2510,10 @@ public class CatalogServiceCatalog extends Catalog {
       catalogTimeline.markEvent(GOT_CATALOG_VERSION_WRITE_LOCK);
       catalogVersion_++;
 
+      // Reset loaded tables counter since all tables will be replaced with
+      // IncompleteTable during global invalidate.
+      numLoadedTables_.set(0);
+
       // Update data source, db and table metadata.
       // First, refresh DataSource objects from HMS and assign new versions.
       refreshDataSources();
@@ -2743,6 +2747,8 @@ public class CatalogServiceCatalog extends Catalog {
     Preconditions.checkState(versionLock_.isWriteLockedByCurrentThread());
     if (!db.isSystemDb()) {
       for (Table tbl: db.getTables()) {
+        // Update loaded tables counter using the centralized update function.
+        Catalog.TABLE_REMOVE_UPDATE_FUNC.accept(tbl);
         tbl.setCatalogVersion(incrementAndGetCatalogVersion());
         deleteLog_.addRemovedObject(tbl.toMinimalTCatalogObject());
       }
diff --git a/fe/src/main/java/org/apache/impala/catalog/Db.java 
b/fe/src/main/java/org/apache/impala/catalog/Db.java
index efd7efb51..904ae6ae0 100644
--- a/fe/src/main/java/org/apache/impala/catalog/Db.java
+++ b/fe/src/main/java/org/apache/impala/catalog/Db.java
@@ -211,9 +211,12 @@ public class Db extends CatalogObjectImpl implements FeDb {
   public TCatalogObjectType getCatalogObjectType() { return 
TCatalogObjectType.DATABASE; }
 
   /**
-   * Adds a table to the table cache.
+   * Adds a table to the table cache. The static Catalog.TABLE_ADD_UPDATE_FUNC 
is used
+   * to track loaded tables count changes.
    */
-  public void addTable(Table table) { tableCache_.add(table); }
+  public void addTable(Table table) {
+    tableCache_.add(table, Catalog.TABLE_ADD_UPDATE_FUNC);
+  }
 
   @Override
   public List<String> getAllTableNames() {
@@ -267,9 +270,11 @@ public class Db extends CatalogObjectImpl implements FeDb {
 
   /**
    * Removes the table name and any cached metadata from the Table cache.
+   * The static Catalog.TABLE_REMOVE_UPDATE_FUNC is used to track loaded tables
+   * count changes.
    */
   public Table removeTable(String tableName) {
-    return tableCache_.remove(tableName.toLowerCase());
+    return tableCache_.remove(tableName.toLowerCase(), 
Catalog.TABLE_REMOVE_UPDATE_FUNC);
   }
 
   @Override
diff --git a/fe/src/main/java/org/apache/impala/service/JniCatalog.java 
b/fe/src/main/java/org/apache/impala/service/JniCatalog.java
index eb1b8c561..9481761af 100644
--- a/fe/src/main/java/org/apache/impala/service/JniCatalog.java
+++ b/fe/src/main/java/org/apache/impala/service/JniCatalog.java
@@ -589,6 +589,7 @@ public class JniCatalog {
           response.setCatalog_num_functions(catalog_.getNumFunctions());
           
response.setCatalog_num_hms_clients_idle(catalog_.getNumHmsClientsIdle());
           
response.setCatalog_num_hms_clients_in_use(catalog_.getNumHmsClientsInUse());
+          response.setCatalog_num_loaded_tables(catalog_.getNumLoadedTables());
           response.setEvent_metrics(catalog_.getEventProcessorMetrics());
           return response;
         });
diff --git a/tests/custom_cluster/test_automatic_invalidation.py 
b/tests/custom_cluster/test_automatic_invalidation.py
index 6ab68066f..2203b7578 100644
--- a/tests/custom_cluster/test_automatic_invalidation.py
+++ b/tests/custom_cluster/test_automatic_invalidation.py
@@ -25,7 +25,8 @@ from tests.util.filesystem_utils import IS_HDFS, IS_LOCAL
 
 
 from tests.common.custom_cluster_test_suite import CustomClusterTestSuite
-
+from tests.common.impala_test_suite import ImpalaTestSuite
+from tests.util.event_processor_utils import EventProcessorUtils
 
 class TestAutomaticCatalogInvalidation(CustomClusterTestSuite):
   """ Test that tables are cached in the catalogd after usage for the 
configured time
@@ -101,3 +102,108 @@ class 
TestAutomaticCatalogInvalidation(CustomClusterTestSuite):
     # seconds to reduce flakiness.
     time.sleep(10)
     assert self.metadata_cache_string not in self._get_catalog_object()
+
+  @pytest.mark.execute_serially
+  @CustomClusterTestSuite.with_args(catalogd_args=timeout_flag, 
impalad_args=timeout_flag)
+  def test_loaded_tables_metric(self, unique_database):
+    """Test IMPALA-13863: catalog.num-loaded-tables metric tracks loaded tables
+       correctly across various metadata operations including loading, 
invalidation,
+       refresh, rename, and removal."""
+    metric_name = "catalog.num-loaded-tables"
+    catalogd = self.cluster.catalogd.service
+
+    # Test 1: Loading increases counter
+    self.execute_query("invalidate metadata")
+    catalogd.wait_for_metric_value(metric_name, 0)
+
+    self.execute_query(self.query)
+    catalogd.wait_for_metric_value(metric_name, 1)
+
+    # Test 2: Single table INVALIDATE METADATA decreases counter
+    self.execute_query("invalidate metadata functional.alltypes")
+    catalogd.wait_for_metric_value(metric_name, 0)
+
+    # Test 3: REFRESH loaded table (counter should stay same)
+    self.execute_query(self.query)
+    catalogd.wait_for_metric_value(metric_name, 1)
+    count_before_refresh = catalogd.get_metric_value(metric_name)
+
+    self.execute_query("refresh functional.alltypes")
+    # Wait for one metrics refresh cycle (REFRESH_METRICS_INTERVAL_MS)
+    # to ensure the metric is updated
+    time.sleep(1)
+    count_after_refresh = catalogd.get_metric_value(metric_name)
+    assert count_after_refresh == count_before_refresh, (
+        "Count should stay same after REFRESH of loaded table (was %d, now %d)"
+        % (count_before_refresh, count_after_refresh))
+
+    # Test 4: ALTER TABLE RENAME (counter decreases because old loaded table is
+    # removed and new table starts as IncompleteTable)
+    self.execute_query("create table %s.test_rename_tbl (id int)" % 
unique_database)
+    self.execute_query("select * from %s.test_rename_tbl" % unique_database)
+    catalogd.wait_for_metric_value(metric_name, 2)
+
+    self.execute_query("alter table %s.test_rename_tbl rename \
+        to %s.test_renamed_tbl" % (unique_database, unique_database))
+    catalogd.wait_for_metric_value(metric_name, 1)
+
+    # Verify that accessing the renamed table increments the counter
+    self.execute_query("select * from %s.test_renamed_tbl" % unique_database)
+    catalogd.wait_for_metric_value(metric_name, 2)
+
+    # Test 5: Load another table, then global INVALIDATE METADATA
+    self.execute_query("select count(*) from functional.alltypessmall")
+    catalogd.wait_for_metric_value(metric_name, 3)
+
+    self.execute_query("invalidate metadata")
+    catalogd.wait_for_metric_value(metric_name, 0)
+
+    # Test 6: CREATE TABLE, load it, then DROP TABLE
+    self.execute_query("create table %s.test_metric_tbl (id int)" % 
unique_database)
+    # Wait for one metrics refresh cycle (REFRESH_METRICS_INTERVAL_MS)
+    # to ensure the metric is updated
+    time.sleep(1)
+    count_after_create = catalogd.get_metric_value(metric_name)
+    assert count_after_create == 0, (
+        "Count should be 0 after creating table (got %d)" % count_after_create)
+
+    self.execute_query("select * from %s.test_metric_tbl" % unique_database)
+    catalogd.wait_for_metric_value(metric_name, 1)
+
+    self.execute_query("drop table %s.test_metric_tbl" % unique_database)
+    catalogd.wait_for_metric_value(metric_name, 0)
+
+    # Test 7: Hive-side DROP TABLE processed via events
+    self.execute_query("create table %s.hive_drop_tbl (id int, val string)"
+        % unique_database)
+    self.execute_query("select * from %s.hive_drop_tbl" % unique_database)
+    catalogd.wait_for_metric_value(metric_name, 1)
+
+    # Drop table from Hive side
+    self.run_stmt_in_hive("drop table %s.hive_drop_tbl" % unique_database)
+    EventProcessorUtils.wait_for_event_processing(self)
+    catalogd.wait_for_metric_value(metric_name, 0)
+
+    # Test 8: DROP DATABASE CASCADE with loaded table
+    test_db = ImpalaTestSuite.get_random_name("test_db_")
+    try:
+      self.execute_query("create database if not exists %s" % test_db)
+      self.execute_query("create table %s.t1 (id int, name string)" % test_db)
+      self.execute_query("insert into %s.t1 values (1, 'test')" % test_db)
+      self.execute_query("select * from %s.t1" % test_db)
+      catalogd.wait_for_metric_value(metric_name, 1)
+    finally:
+      self.execute_query("drop database %s cascade" % test_db)
+    catalogd.wait_for_metric_value(metric_name, 0)
+
+    # Test 9: Automatic timeout-based invalidation
+    self.execute_query(self.query)
+    catalogd.wait_for_metric_value(metric_name, 1)
+    assert self.metadata_cache_string in self._get_catalog_object()
+
+    # Wait for automatic timeout-based invalidation to complete and metric to 
update
+    # Timeout is 2x the invalidation timeout to account for background 
processing delays
+    catalogd.wait_for_metric_value(metric_name, 0, timeout=self.timeout * 2)
+    # Verify that the table metadata was actually invalidated
+    assert self.metadata_cache_string not in self._get_catalog_object(), \
+        "Table metadata should be invalidated after timeout"
diff --git a/tests/webserver/test_web_pages.py 
b/tests/webserver/test_web_pages.py
index 63f5d2b51..1b0658d7b 100644
--- a/tests/webserver/test_web_pages.py
+++ b/tests/webserver/test_web_pages.py
@@ -1101,6 +1101,7 @@ class TestWebPage(ImpalaTestSuite):
     assert "catalog.num-functions" in metric_keys
     assert "catalog.hms-client-pool.num-idle" in metric_keys
     assert "catalog.hms-client-pool.num-in-use" in metric_keys
+    assert "catalog.num-loaded-tables" in metric_keys
 
   def test_iceberg_table_metrics(self):
     assert '23448' == self.__get_table_metric(

Reply via email to