From aecd37d822ae92c2806f3d30200bbd72caf7b5e8 Mon Sep 17 00:00:00 2001 From: Anthonin Bonnefoy Date: Tue, 10 Dec 2024 10:55:30 +0100 Subject: [PATCH] Report pg_class xmin (#19218) Add a postgresql.relation.xmin reporting the xmin of the relation's row in pg_class. This will allow to detect if and when a relation was modified through a DDL as most of DDL will modify the relation's pg_class. Other processes may modify pg_class, like ANALYZE to update tuple estimations. However, those updates are done in place and won't bump the row's xmin. --- postgres/changelog.d/19218.added | 1 + .../postgres/relationsmanager.py | 4 ++- postgres/metadata.csv | 1 + postgres/tests/test_relations.py | 29 +++++++++++++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 postgres/changelog.d/19218.added diff --git a/postgres/changelog.d/19218.added b/postgres/changelog.d/19218.added new file mode 100644 index 0000000000000..7ba3bd2933029 --- /dev/null +++ b/postgres/changelog.d/19218.added @@ -0,0 +1 @@ +Add postgresql.relation.xmin metric diff --git a/postgres/datadog_checks/postgres/relationsmanager.py b/postgres/datadog_checks/postgres/relationsmanager.py index b9dec77283709..1952534049e1c 100644 --- a/postgres/datadog_checks/postgres/relationsmanager.py +++ b/postgres/datadog_checks/postgres/relationsmanager.py @@ -187,7 +187,8 @@ pg_stat_get_vacuum_count(C.reltoastrelid), pg_stat_get_autovacuum_count(C.reltoastrelid), EXTRACT(EPOCH FROM age(CURRENT_TIMESTAMP, pg_stat_get_last_vacuum_time(C.reltoastrelid))), - EXTRACT(EPOCH FROM age(CURRENT_TIMESTAMP, pg_stat_get_last_autovacuum_time(C.reltoastrelid))) + EXTRACT(EPOCH FROM age(CURRENT_TIMESTAMP, pg_stat_get_last_autovacuum_time(C.reltoastrelid))), + C.xmin FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) LEFT JOIN pg_locks L ON C.oid = L.relation AND L.locktype = 'relation' @@ -234,6 +235,7 @@ {'name': 'toast.autovacuumed', 'type': 'monotonic_count'}, {'name': 'toast.last_vacuum_age', 'type': 'gauge'}, {'name': 'toast.last_autovacuum_age', 'type': 'gauge'}, + {'name': 'relation.xmin', 'type': 'gauge'}, ], } diff --git a/postgres/metadata.csv b/postgres/metadata.csv index 1cf6813327fe3..49183d67a61cc 100644 --- a/postgres/metadata.csv +++ b/postgres/metadata.csv @@ -116,6 +116,7 @@ postgresql.queries.time,count,,nanosecond,,"The total query execution time per q postgresql.relation.all_visible,gauge,,,,"Number of pages that are marked as all visible in the table's visibility map. This is only an estimation used by the planner and is updated by VACUUM or ANALYZE. This metric is tagged with db, schema, table, partition_of",0,postgres,relation all_visible, postgresql.relation.pages,gauge,,,,"Size of a table in pages (1 page == 8KB by default). This is only an estimation used by the planner and is updated by VACUUM or ANALYZE. This metric is tagged with db, schema, table, partition_of.",0,postgres,relation pages, postgresql.relation.tuples,gauge,,,,"Number of live rows in the table. This is only an estimation used by the planner and is updated by VACUUM or ANALYZE. If the table has never been vacuumed or analyze, -1 will be reported. This metric is tagged with db, schema, table, partition_of",0,postgres,relation tuples, +postgresql.relation.xmin,gauge,,,,"Transaction ID of the latest relation's modification in pg_class. This metric is tagged with db, schema, table",0,postgres,relation xmin, postgresql.relation_size,gauge,,byte,,"The disk space used by the specified table. TOAST data, indexes, free space map and visibility map are not included. This metric is tagged with db, schema, table.",0,postgres,relation size, postgresql.replication.backend_xmin_age,gauge,,,,The age of the standby server's xmin horizon (relative to latest stable xid) reported by hot_standby_feedback.,-1,postgres,repl backend xmin, postgresql.replication.wal_flush_lag,gauge,,second,,Time elapsed between flushing recent WAL locally and receiving notification that this standby server has written and flushed it (but not yet applied it). This can be used to gauge the delay that synchronous_commit level on incurred while committing if this server was configured as a synchronous standby. Only available with postgresql 10 and newer.,-1,postgres,repl flush lag, diff --git a/postgres/tests/test_relations.py b/postgres/tests/test_relations.py index e2e5799d7c128..97e9c192732bb 100644 --- a/postgres/tests/test_relations.py +++ b/postgres/tests/test_relations.py @@ -197,6 +197,35 @@ def test_relations_metrics_regex(aggregator, integration_check, pg_instance): _check_metrics_for_relation_wo_index(aggregator, expected_tags[relation]) +@pytest.mark.integration +@pytest.mark.usefixtures('dd_environment') +def test_relations_xmin(aggregator, integration_check, pg_instance): + pg_instance['relations'] = ['persons'] + + conn = _get_superconn(pg_instance) + cursor = conn.cursor() + cursor.execute("SELECT xmin FROM pg_class WHERE relname='persons'") + start_xmin = float(cursor.fetchone()[0]) + + # Check that initial xmin metric match + check = integration_check(pg_instance) + check.check(pg_instance) + expected_tags = _get_expected_tags(check, pg_instance, db=pg_instance['dbname'], table='persons', schema='public') + aggregator.assert_metric('postgresql.relation.xmin', count=1, value=start_xmin, tags=expected_tags) + aggregator.reset() + + # Run multiple DDL modifying the persons relation which will increase persons' xmin in pg_class + cursor.execute("ALTER TABLE persons REPLICA IDENTITY FULL;") + cursor.execute("ALTER TABLE persons REPLICA IDENTITY DEFAULT;") + cursor.close() + conn.close() + + check.check(pg_instance) + + # xmin metric should be greater than initial xmin + assert_metric_at_least(aggregator, 'postgresql.relation.xmin', lower_bound=start_xmin + 1, tags=expected_tags) + + @pytest.mark.integration @pytest.mark.usefixtures('dd_environment') def test_max_relations(aggregator, integration_check, pg_instance):