In PostgreSQL-compatible databases like YugabyteDB, the system catalogs are packed with metadata about tables, functions, privileges, and more. But not all system objects are created equal, some are core catalog tables, while others are views installed by extensions like pg_stat_statements.
Understanding this distinction can help you better inspect, monitor, and manage your distributed database.
In this tip, we’ll explore how to programmatically identify:
• Built-in catalog tables (like
pg_class,pg_type,pg_trigger)• Views added by extensions (like
pg_stat_statements)• System views from
information_schema• User-defined tables
And more importantly, we’ll discuss how these behave in a distributed YugabyteDB cluster, because not all objects behave the same way across nodes.
System Catalog Overview in YugabyteDB
YugabyteDB uses a PostgreSQL-compatible YSQL layer, which includes many of the same catalog tables and system views you’d find in PostgreSQL, plus some Yugabyte-specific enhancements under the hood.
Key schemas:
•
pg_catalog: Core system catalogs and functions•
information_schema: Standard SQL views on metadata•
public(and others): Where user-created tables typically live
Global vs. Local Behavior in a Distributed Cluster
This is where things get interesting.
Catalog tables (e.g., pg_class, pg_type, pg_trigger)
• These are global across the cluster.
• No matter which node you query, these system catalogs will return consistent and identical metadata.
• Examples: definitions of tables, columns, types, indexes, triggers, etc.
Extension views (e.g., pg_stat_statements)
• These are local to each tserver node.
•
pg_stat_statementstracks query statistics per-node, and the data is not automatically aggregated across the cluster.- • If you want cluster-wide stats, you need to query each node individually and aggregate manually.
This distinction matters when monitoring performance, diagnosing query behavior, or analyzing activity across your distributed YugabyteDB environment.
One Query to Rule Them All
Use this query to list all user tables, catalog tables, information schema views, and extension views, and see which extension (if any) owns them:
SELECT
c.relname AS object_name,
n.nspname AS schema_name,
CASE
WHEN c.relkind = 'r' AND n.nspname = 'pg_catalog' THEN 'catalog_table'
WHEN c.relkind = 'v' AND n.nspname = 'pg_catalog' AND e.extname IS NULL THEN 'system_view'
WHEN c.relkind = 'v' AND e.extname IS NOT NULL THEN 'extension_view'
WHEN c.relkind = 'v' AND n.nspname = 'information_schema' THEN 'information_schema_view'
WHEN c.relkind = 'r' AND n.nspname NOT IN ('pg_catalog', 'information_schema') THEN 'user_table'
WHEN c.relkind = 'v' AND n.nspname NOT IN ('pg_catalog', 'information_schema') THEN 'user_view'
ELSE 'uncategorized'
END AS object_type,
e.extname AS extension_name
FROM pg_class c
JOIN pg_namespace n ON c.relnamespace = n.oid
LEFT JOIN pg_depend d ON d.objid = c.oid AND d.deptype = 'e'
LEFT JOIN pg_extension e ON e.oid = d.refobjid
WHERE c.relkind IN ('r', 'v') -- tables and views only
ORDER BY object_type, schema_name, object_name;
Example: Top 5 from each object_type:
yugabyte=# WITH objects AS (
yugabyte(# SELECT
yugabyte(# c.relname AS object_name,
yugabyte(# n.nspname AS schema_name,
yugabyte(# CASE
yugabyte(# WHEN c.relkind = 'r' AND n.nspname = 'pg_catalog' THEN 'catalog_table'
yugabyte(# WHEN c.relkind = 'v' AND n.nspname = 'pg_catalog' AND e.extname IS NULL THEN 'system_view'
yugabyte(# WHEN c.relkind = 'v' AND e.extname IS NOT NULL THEN 'extension_view'
yugabyte(# WHEN c.relkind = 'v' AND n.nspname = 'information_schema' THEN 'information_schema_view'
yugabyte(# WHEN c.relkind = 'r' AND n.nspname NOT IN ('pg_catalog', 'information_schema') THEN 'user_table'
yugabyte(# WHEN c.relkind = 'v' AND n.nspname NOT IN ('pg_catalog', 'information_schema') THEN 'user_view'
yugabyte(# ELSE 'uncategorized'
yugabyte(# END AS object_type,
yugabyte(# e.extname AS extension_name
yugabyte(# FROM pg_class c
yugabyte(# JOIN pg_namespace n ON c.relnamespace = n.oid
yugabyte(# LEFT JOIN pg_depend d ON d.objid = c.oid AND d.deptype = 'e'
yugabyte(# LEFT JOIN pg_extension e ON e.oid = d.refobjid
yugabyte(# WHERE c.relkind IN ('r', 'v')
yugabyte(# ),
yugabyte-# ranked AS (
yugabyte(# SELECT *,
yugabyte(# ROW_NUMBER() OVER (PARTITION BY object_type ORDER BY object_name) AS rn
yugabyte(# FROM objects
yugabyte(# )
yugabyte-# SELECT object_name, schema_name, object_type, extension_name
yugabyte-# FROM ranked
yugabyte-# WHERE rn <= 5
yugabyte-# ORDER BY object_type, object_name;
object_name | schema_name | object_type | extension_name
---------------------------------+--------------------+-------------------------+--------------------
pg_aggregate | pg_catalog | catalog_table |
pg_am | pg_catalog | catalog_table |
pg_amop | pg_catalog | catalog_table |
pg_amproc | pg_catalog | catalog_table |
pg_attrdef | pg_catalog | catalog_table |
pg_stat_statements | pg_catalog | extension_view | pg_stat_statements
pg_stat_statements_info | pg_catalog | extension_view | pg_stat_statements
_pg_foreign_data_wrappers | information_schema | information_schema_view |
_pg_foreign_servers | information_schema | information_schema_view |
_pg_foreign_table_columns | information_schema | information_schema_view |
_pg_foreign_tables | information_schema | information_schema_view |
_pg_user_mappings | information_schema | information_schema_view |
pg_available_extension_versions | pg_catalog | system_view |
pg_available_extensions | pg_catalog | system_view |
pg_backend_memory_contexts | pg_catalog | system_view |
pg_config | pg_catalog | system_view |
pg_cursors | pg_catalog | system_view |
sql_features | information_schema | uncategorized |
sql_implementation_info | information_schema | uncategorized |
sql_parts | information_schema | uncategorized |
sql_sizing | information_schema | uncategorized |
(21 rows)
Wrapping Up
Understanding the difference between catalog tables, extension views, and their behavior in a distributed YugabyteDB cluster is key to accurate schema introspection, performance monitoring, and debugging.
📘 Catalog tables give you consistent metadata across all nodes.
📊 Extension views like pg_stat_statements are local and must be queried per node for full visibility.
Whether you’re writing tools, building backup scripts, or optimizing queries, knowing which is which will help you do it right.
Have Fun!
