Try it free

PostgreSQL extension

  • Latest Dynatrace
  • Extension

Collect PostgreSQL database performance metrics and query logs via remote ActiveGate monitoring to detect anomalies, blocking queries, and performance issues.

Get started

Overview

The PostgreSQL monitoring solution is based on a remote monitoring approach implemented as a Dynatrace ActiveGate extension. The extension queries PostgreSQL databases for performance and health metrics. Dynatrace Intelligence analyzes these metrics for anomalies and problems.

Use cases

  • Monitor PostgreSQL health and performance.
  • Detect and alert on anomalies.
  • Prevent performance issues.
  • View query performance metrics, execution plans, blocking queries, operational alerts, current configuration, and recent changes.

Requirements

  • Install an ActiveGate in your environment.

  • Designate one or more ActiveGate groups to remotely connect to your PostgreSQL database server and pull data. All ActiveGates in each group must be able to reach the server.

  • For self-hosted Postgres: Postgres additional supplied modules must be installed.

  • For cloud-managed Postgres services: Ensure that specific extensions and settings are enabled.

  • Create a dedicated database user in your database instance. Dynatrace uses this user to run monitoring queries against your PostgreSQL database. Choose the authentication method based on your setup.

    • Standard user:

      CREATE USER dynatrace WITH PASSWORD '<PASSWORD>' INHERIT;
    • IAM (AWS RDS): Use this method to authenticate using AWS IAM instead of a database password. Requires an IAM role attached to your ActiveGate host. For IAM policy and role setup, see AWS IAM.

      1. Enable IAM database authentication on your RDS instance. For details, see Enabling and disabling IAM database authentication.

      2. Create the monitoring user.

        CREATE USER dynatrace WITH LOGIN;
        GRANT rds_iam TO dynatrace;

Compatibility information

  • Compatible with all currently maintained Postgres versions.
  • Available on Dynatrace version 1.255+.

Activation and setup

Install the Postgres database extension

  1. Go to Hub Dynatrace Hub.
  2. Search for and install the PostgreSQL extension.

To monitor a new Postgres database, do one of the following:

  • Onboard the database via Databases Databases (recommended for first-time users).
  • Create a monitoring configuration in Extensions Extensions.

Create a monitoring configuration with Extensions

Required permission: Change monitoring settings

  1. Go to Extensions Extensions.
  2. Search and select the PostgreSQL extension.

1. Add configuration

Go to the PostgreSQL extension and select + Add configuration.

2. Choose where to run the extension

In Choose where to run the extension, select ActiveGate.

3. Select an ActiveGate group

In Select ActiveGate group to activate extension, select the ActiveGate group, and then select Next.

4. Create endpoints

Create one or more endpoints that point to your database instances. Each configuration can have up to 20,000 endpoints.

  1. In the Database connection section, provide:

    • Host: Hostname or IP address of your database.
    • Port: Port that the database listens on. The default is 5432.
    • Database name: Database to monitor. For each instance, configure only a single database (usually the main postgres database).
  2. In the Authentication section:

    1. Select an Authentication scheme. The default is Basic authentication. Provide the credentials for the dynatrace monitoring user you created:
      • To enter the credentials directly, provide the Username and Password.
      • To use stored credentials, enable Use credential vault, and then select an entry in Select Vault credentials or select Create new credentials. For details, see vault credentials.
    2. Optional Enable Use connection string (requires ActiveGate version 1.307 or later) to connect using a connection string instead of a datasource object, and then enter the Connection string. Don't enter any sensitive data in the connection string.
    3. Optional Enable Enable SSL (requires ActiveGate version 1.269 or later) to establish a secure connection. Use an SSL port and add the server certificate to the truststore. For details, see enable SSL.
  3. Optional Configure database discovery to automatically monitor the databases running on an instance.

  4. Optional Add Additional configuration parameters if required.

  5. Optional Add Primary fields and Primary tags to attach metadata to the entities created for the endpoint.

  6. To monitor more instances, select Add PostgreSQL endpoint and repeat the steps.

  7. After you create all endpoints, select Next.

For further configuration options, see Configure PostgreSQL for monitoring section.

5. Define the monitoring configuration variables

  1. Adjust the polling frequency and other variables for this monitoring configuration:

    • Endpoint metadata: Adds a custom metadata field to all the entities created by this configuration.
    • Query interval: Controls the polling frequency for most of the metrics collected by the extension. Defaults to 1 minute.
    • Long query interval: Controls the polling frequency of top query monitoring. Defaults to 5 minutes.
    • Table query interval: Controls the polling frequency of table and index monitoring queries. Available from major version 3 onwards.
    • Configuration query interval: Controls the polling frequency of database configuration parameters and schema.

    The default Long query interval is 5 minutes, which aligns with the Database Monitoring billing interval. Setting a shorter interval may result in additional Database Monitoring consumption.

  2. Select Next.

6. Define the metadata

  1. Add a Description to your monitoring configuration.
  1. Optional Add Primary fields and Primary tags to attach metadata to the entities created for the endpoint.
  1. Optional Add Security context, Product, and Cost center field values to enrich all collected data.
  2. Select your desired feature sets.
  3. Select Save.

Congratulations! You have successfully configured the PostgresSQL extension via Extensions Extensions for remote monitoring.

Configure PostgreSQL for monitoring

To monitor your database instance, specific settings must be configured on the PostgreSQL database. If you're onboarding through Databases Databases, scripts are provided that create the necessary database objects. Download the script to your host and run it as an admin user with sufficient permissions. Otherwise, run the following actions manually.

Once per instance

The following actions are required once per instance:

  1. Add pg_stat_statements to shared_preload_libraries to enable query-level statistics collection. This must be set before creating the extension in step 4.

    In postgresql.conf, add or update:

    shared_preload_libraries = 'pg_stat_statements'

    Restart the PostgreSQL server to apply the change.

    Update the shared_preload_libraries parameter through your cloud provider's configuration interface:

    • AWS RDS / Aurora: Modify the DB parameter group.
    • Google Cloud SQL: Update the database flag.
    • Azure Database for PostgreSQL: Update the server parameter.
  2. Grant the monitoring user membership in the pg_monitor role for read-only monitoring access.

    GRANT pg_monitor TO dynatrace;
  1. For self-hosted PostgreSQL, install the hypopg binaries on your host. See the hypopg documentation for installation instructions.

    Cloud-managed services such as AWS RDS and Aurora already ship the hypopg binaries. No additional steps are needed.

For each database

The following actions must be performed in each database you want to monitor.

The following actions are required in each database:

  1. Create the dynatrace schema.

    CREATE SCHEMA IF NOT EXISTS dynatrace;
  2. Grant the monitoring user access to the dynatrace schema.

    GRANT USAGE ON SCHEMA dynatrace TO dynatrace;
  3. Create a helper function that generates execution plans from SQL commands for deeper query insights.

    CREATE OR REPLACE FUNCTION dynatrace.dynatrace_execution_plan(
    query text,
    OUT explain JSON
    ) RETURNS SETOF JSON
    LANGUAGE plpgsql
    VOLATILE
    RETURNS NULL ON NULL INPUT
    SECURITY DEFINER
    ROWS 1
    SET plan_cache_mode = force_generic_plan
    SET statement_timeout = '10s'
    SET lock_timeout = '200ms'
    AS
    $$DECLARE
    sanitized text;
    nparams integer;
    explain_cmd text;
    json_result json;
    BEGIN
    /* remove literals, comments and quoted identifiers */
    sanitized := dynatrace_execution_plan.query;
    sanitized := pg_catalog.regexp_replace(sanitized, '\$([A-Za-z_]\w*)?\$.*?\$\1\$', '', 'gs');
    sanitized := pg_catalog.regexp_replace(sanitized, '''(''''|\\.|[^''\\])*''', '', 'g');
    sanitized := pg_catalog.regexp_replace(sanitized, '"(""|[^"])*"', '', 'g');
    sanitized := pg_catalog.regexp_replace(sanitized, '/\*.*?\*/', '', 'gs');
    sanitized := pg_catalog.regexp_replace(sanitized, '--[^\n]*', '', 'g');
    /* reject statements containing a semicolon in the middle */
    IF pg_catalog.strpos(
    pg_catalog.regexp_replace(sanitized, '[;[:space:]]+$', ''),
    ';'
    ) OPERATOR(pg_catalog.>) 0 THEN
    RAISE EXCEPTION 'query string must not contain a semicolon';
    END IF;
    /* drop a statement left behind by an earlier failure */
    IF EXISTS (
    SELECT 1
    FROM pg_catalog.pg_prepared_statements ps
    WHERE ps.name OPERATOR(pg_catalog.=) '_dt_plan_stmt_'
    ) THEN
    DEALLOCATE _dt_plan_stmt_;
    END IF;
    /* construct a prepared statement, letting the planner infer the parameter types */
    EXECUTE
    pg_catalog.concat(
    'PREPARE _dt_plan_stmt_ AS ',
    dynatrace_execution_plan.query
    );
    BEGIN
    /* get the parameter count */
    SELECT COALESCE(pg_catalog.array_length(ps.parameter_types, 1), 0)
    INTO nparams
    FROM pg_catalog.pg_prepared_statements ps
    WHERE ps.name OPERATOR(pg_catalog.=) '_dt_plan_stmt_';
    /* construct an EXPLAIN statement */
    explain_cmd :=
    pg_catalog.concat(
    'EXPLAIN (FORMAT JSON, ANALYZE FALSE) EXECUTE _dt_plan_stmt_',
    CASE
    WHEN nparams OPERATOR(pg_catalog.=) 0 THEN ''
    ELSE pg_catalog.concat(
    '(',
    pg_catalog.rtrim(
    pg_catalog.repeat('NULL,', nparams),
    ','
    ),
    ')'
    )
    END
    );
    /* get the plan */
    EXECUTE explain_cmd INTO json_result;
    EXCEPTION
    WHEN OTHERS THEN
    /* delete the prepared statement, then report the original error */
    DEALLOCATE _dt_plan_stmt_;
    RAISE;
    END;
    /* delete the prepared statement */
    DEALLOCATE _dt_plan_stmt_;
    /* return the plan */
    RETURN QUERY SELECT json_result;
    END;$$;
    • SECURITY DEFINER: Executes with the privileges of the user who defined the function, not the caller.

    • EXPLAIN: Requires the same permissions needed to run the query. The user running this function needs sufficient privileges to run PREPARE and EXPLAIN on the queries it explains.

    • In some PostgreSQL configurations you might also need to set search_path for the monitoring user:

    ALTER USER dynatrace SET search_path to dynatrace, public;
  4. Create the pg_stat_statements extension for query-level statistics collection.

    CREATE EXTENSION IF NOT EXISTS pg_stat_statements SCHEMA public;
  1. Create the hypopg extension, required for the Index Recommendations feature set.

    CREATE EXTENSION IF NOT EXISTS hypopg SCHEMA public;

For self-hosted PostgreSQL, if the hypopg binaries were not installed (see step 3), this command will fail. Index Recommendations are unavailable until you install hypopg and re-run this step.

  1. Create the dynatrace_index_advisor function for index recommendation analysis.

    CREATE OR REPLACE FUNCTION dynatrace.dynatrace_index_advisor(query text, OUT result json)
    RETURNS SETOF json
    LANGUAGE plpgsql
    STRICT SECURITY DEFINER ROWS 1
    SET search_path TO 'pg_catalog', 'dynatrace', 'public'
    AS $function$
    DECLARE
    n_args int;
    prepared_statement_name text = 'dynatrace_ia_stmt';
    hypopg_schema_name text;
    explain_plan_statement text;
    error_message text;
    rec record;
    plan_initial jsonb;
    plan_final jsonb;
    statements text[] = '{}';
    referenced_oids oid[];
    initial_cost numeric;
    final_cost numeric;
    initial_startup numeric;
    final_startup numeric;
    cost_reduction_pct numeric;
    cost_reduction numeric;
    min_cost_threshold numeric := 200;
    BEGIN
    hypopg_schema_name := (SELECT extnamespace::regnamespace::text FROM pg_extension WHERE extname = 'hypopg');
    IF hypopg_schema_name IS NULL THEN
    RETURN QUERY SELECT json_build_object(
    'database', current_database(),
    'query', query,
    'errors', array['hypopg extension is required but not installed']::text[]
    );
    RETURN;
    END IF;
    query := trim(
    regexp_replace(
    regexp_replace(
    regexp_replace(query, '\/\*[\s\S]+?\*\/', '', 'g'),
    '--[^\r\n]*', ' ', 'g'),
    '\s+', ' ', 'g')
    );
    query := regexp_replace(query, ';\s*$', '');
    BEGIN
    IF query ILIKE '%;%' THEN
    RAISE EXCEPTION 'Query must not contain a semicolon';
    END IF;
    query := replace(query, 'WITH pgrst_payload AS (SELECT $1 AS json_data)', 'WITH pgrst_payload AS (SELECT $1::json AS json_data)');
    query := regexp_replace(
    query,
    '\m(INTERVAL|TIMESTAMPTZ|TIMESTAMP|DATE|TIME)\s+(\$\d+)',
    '\2::\1',
    'gi'
    );
    query := regexp_replace(
    query,
    '\mEXTRACT\s*\(\s*(\$\d+)\s+FROM\y',
    'date_part(\1::text,',
    'gi'
    );
    BEGIN
    EXECUTE format('DEALLOCATE %I', prepared_statement_name);
    EXCEPTION WHEN OTHERS THEN
    NULL;
    END;
    EXECUTE format('PREPARE %I AS %s', prepared_statement_name, query);
    n_args := (
    SELECT COALESCE(array_length(parameter_types, 1), 0)
    FROM pg_prepared_statements
    WHERE name = prepared_statement_name
    LIMIT 1
    );
    explain_plan_statement := format(
    'SET LOCAL plan_cache_mode = force_generic_plan; EXPLAIN (FORMAT JSON, VERBOSE) EXECUTE %I%s',
    prepared_statement_name,
    CASE
    WHEN n_args = 0 THEN ''
    ELSE format('(%s)', array_to_string(array_fill('null'::text, array[n_args]), ','))
    END
    );
    EXECUTE explain_plan_statement INTO plan_initial;
    initial_cost := (plan_initial -> 0 -> 'Plan' ->> 'Total Cost')::numeric;
    initial_startup := (plan_initial -> 0 -> 'Plan' ->> 'Startup Cost')::numeric;
    IF initial_cost IS NULL OR initial_cost < min_cost_threshold THEN
    EXECUTE format('DEALLOCATE %I', prepared_statement_name);
    RETURN;
    END IF;
    BEGIN
    WITH RECURSIVE nodes AS (
    SELECT (plan_initial -> 0 -> 'Plan') AS node
    UNION ALL
    SELECT child.value
    FROM nodes n
    CROSS JOIN LATERAL jsonb_array_elements(n.node -> 'Plans') AS child(value)
    WHERE n.node ? 'Plans'
    )
    SELECT array_agg(DISTINCT rel.oid)
    INTO referenced_oids
    FROM nodes n
    CROSS JOIN LATERAL (
    SELECT format('%I.%I', n.node ->> 'Schema', n.node ->> 'Relation Name')::regclass::oid AS oid
    WHERE (n.node ->> 'Relation Name') IS NOT NULL
    AND (n.node ->> 'Schema') IS NOT NULL
    ) rel;
    EXCEPTION WHEN OTHERS THEN
    referenced_oids := NULL;
    END;
    IF referenced_oids IS NULL OR array_length(referenced_oids, 1) IS NULL THEN
    EXECUTE format('DEALLOCATE %I', prepared_statement_name);
    RETURN QUERY SELECT json_build_object(
    'database', current_database(),
    'query', query,
    'startup_cost_before', initial_startup,
    'startup_cost_after', initial_startup,
    'total_cost_before', initial_cost,
    'total_cost_after', initial_cost,
    'index_statements', array[]::text[]
    );
    RETURN;
    END IF;
    FOR rec IN (
    WITH extension_regclass AS (
    SELECT DISTINCT objid AS oid
    FROM pg_catalog.pg_depend
    WHERE deptype = 'e'
    )
    SELECT
    format(
    'SELECT %I.hypopg_create_index(''CREATE INDEX ON %I.%I(%I)'')',
    hypopg_schema_name,
    pc.relnamespace::regnamespace::text,
    pc.relname,
    pa.attname
    ) AS hypopg_statement
    FROM pg_catalog.pg_class pc
    JOIN pg_catalog.pg_attribute pa ON pc.oid = pa.attrelid
    LEFT JOIN extension_regclass er ON pc.oid = er.oid
    LEFT JOIN pg_catalog.pg_index pi
    ON pc.oid = pi.indrelid
    AND pi.indkey[0] = pa.attnum
    AND pi.indisvalid
    AND pi.indexprs IS NULL
    AND pi.indpred IS NULL
    AND (SELECT am.amname FROM pg_catalog.pg_class ic
    JOIN pg_catalog.pg_am am ON am.oid = ic.relam
    WHERE ic.oid = pi.indexrelid) = 'btree'
    WHERE pc.relnamespace::regnamespace::text NOT IN ('pg_catalog', 'pg_toast', 'information_schema')
    AND er.oid IS NULL
    AND pc.relkind IN ('r', 'm')
    AND pc.relpersistence = 'p'
    AND pa.attnum > 0
    AND NOT pa.attisdropped
    AND pi.indrelid IS NULL
    AND pa.atttypid IN (20,16,1082,1184,1114,701,23,21,700,1083,2950,1700,25,18,1042,1043,774,790,829,869,1186,1266)
    AND pc.oid = ANY(referenced_oids)
    )
    LOOP
    EXECUTE rec.hypopg_statement;
    END LOOP;
    EXECUTE format('DEALLOCATE %I', prepared_statement_name);
    EXECUTE format('PREPARE %I AS %s', prepared_statement_name, query);
    EXECUTE explain_plan_statement INTO plan_final;
    final_cost := (plan_final -> 0 -> 'Plan' ->> 'Total Cost')::numeric;
    final_startup := (plan_final -> 0 -> 'Plan' ->> 'Startup Cost')::numeric;
    EXECUTE format(
    'SELECT COALESCE(array_agg(%I.hypopg_get_indexdef(indexrelid) ORDER BY indrelid, indkey::text), ''{}''::text[])
    FROM %I.hypopg()
    WHERE position(indexname IN %s) > 0',
    hypopg_schema_name,
    hypopg_schema_name,
    quote_literal(plan_final)::text
    ) INTO statements;
    EXECUTE format('SELECT %I.hypopg_reset()', hypopg_schema_name);
    BEGIN
    EXECUTE format('DEALLOCATE %I', prepared_statement_name);
    EXCEPTION WHEN OTHERS THEN
    NULL;
    END;
    IF final_cost IS NULL OR initial_startup IS NULL OR final_startup IS NULL THEN
    RETURN;
    END IF;
    IF final_cost > initial_cost THEN
    RETURN;
    END IF;
    IF initial_cost > 0 THEN
    cost_reduction_pct := round(
    (initial_cost - final_cost) / initial_cost * 100,
    2
    );
    ELSE
    cost_reduction_pct := 0;
    END IF;
    cost_reduction := GREATEST(initial_cost - final_cost, 0);
    RETURN QUERY SELECT json_build_object(
    'database', current_database(),
    'query', query,
    'startup_cost_before', initial_startup,
    'startup_cost_after', final_startup,
    'total_cost_before', initial_cost,
    'total_cost_after', final_cost,
    'cost_reduction_pct', cost_reduction_pct,
    'cost_reduction', cost_reduction,
    'index_statements', statements
    );
    EXCEPTION WHEN OTHERS THEN
    GET STACKED DIAGNOSTICS error_message = MESSAGE_TEXT;
    BEGIN EXECUTE format('SELECT %I.hypopg_reset()', hypopg_schema_name); EXCEPTION WHEN OTHERS THEN NULL; END;
    BEGIN EXECUTE format('DEALLOCATE %I', prepared_statement_name); EXCEPTION WHEN OTHERS THEN NULL; END;
    RETURN QUERY SELECT json_build_object(
    'database', current_database(),
    'query', query,
    'index_statements', array[]::text[],
    'errors', array[error_message]::text[]
    );
    END;
    END;
    $function$;

After running the creation script, run the validation script to confirm all required objects are created. This ensures the monitoring setup works as expected.

After completing these steps, metrics for the monitored PostgreSQL instance are visible in Databases Databases within 2–3 minutes. Select any instance to explore detailed metrics and performance insights.

Details

Hosting type to monitor Postgres

Dynatrace supports both self-hosted and cloud-managed monitoring for Postgres databases.

  • Choose self-hosted Postgres for complete observability, execution plan analysis, automated onboarding, and advanced diagnostics.

  • Choose cloud-managed Postgres for reduced operational overhead with some monitoring limitations and manual configuration.

Monitor self-hosted Postgres

With self-hosted Postgres, you manage the database instance and infrastructure. The extension collects data directly from the database using a read-only user. Complete the following setup to enable monitoring:

  1. Configure database parameters.

    Configure the following Postgres parameters in the postgresql.conf file and restart the server to apply the settings. For more details, see the Postgres documentation.

  2. Grant the Dynatrace proxy access to the database.

  3. Select and configure the ActiveGate group.

Monitor Postgres cloud databases

Cloud-managed PostgreSQL is provided as a service by cloud platforms. Services like AWS RDS, AWS Aurora, and Google Cloud SQL prevent direct database configuration. Enable the following features and settings to ensure full monitoring capability.

Steps to set up monitoring capabilities

  1. Add the pg_stat_statements extension.

    This extension collects query-level statistics.

  2. Configure the following settings:

    • track_activity_query_size = 4096 Required

      Enables the collection of larger queries by increasing the size of SQL text in pg_stat_activity. If left at the default value (1024), queries longer than 1024 characters aren't collected.

    • pg_stat_statements.max = 10000 Optional

      Increases the number of normalized queries tracked in pg_stat_statements.

    • pg_stat_statements.track_utility = off Optional

      Disables tracking of utility commands like PREPARE and EXPLAIN.

    • track_io_timing = on Optional

      Collects timing information for block read and write operations in queries.

Database discovery

Starting with version Dynatrace version 1.338 and extension major version 3, the following features require an individual connection to each database:

  • Database schema collection
  • Execution plan monitoring
  • Tables and indexes monitoring

When creating an endpoint, configure database discovery to automatically monitor databases running on an instance without configuring a separate endpoint for each one.

The following discovery modes are available:

  • Manual selection: Manually provide a list of databases to be monitored by the extension.
  • Automatic discovery: All databases running on the instance are discovered and monitored.

When database discovery is enabled, the following connection management settings control how connections to each discovered database are opened and maintained:

  • On-demand (default)

    Connections open only when a query needs to run and close afterwards. A global limit caps the total number of concurrent open connections across all discovered databases.

    ParameterDescriptionDefaultMinimumMaximum

    limit

    Maximum number of concurrent connections across all discovered databases

    200

    15

    500

  • Pooled

    Each discovered database has a dedicated connection pool. Connections are established eagerly and kept alive, which minimizes overhead during query execution. No additional configuration is required.

Use on-demand mode when the number of discovered databases is large and maintaining a permanent pool per database would exhaust available database connections. Use pooled mode when low query latency is a priority and the number of discovered databases is small.

Monitoring top queries

If Dynatrace log monitoring is enabled and the pg_stat_statements view is available, Dynatrace ingests the top 100 queries (sorted by total execution time) every 5 minutes and stores them as logs. These logs are available either from the database instance screen or on Databases Databases, under Top queries by total execution time.

To filter for these queries on a dashboard or notebook, filter by dt.extension.name = com.dynatrace.extension.postgres and event.group = top_queries. Example DQL query:

fetch logs
| filter dt.extension.name=="com.dynatrace.extension.postgres" and event.group=="top_queries"
| sort total_exec_time desc

Whether or not pg_stat_statements is available, Dynatrace collects queries from pg_stat_activity as part of the Queries feature set. These are ingested as logs with event.group = longest_queries.

Fetching execution plan details

If you have configured top query monitoring (see Monitoring top queries), you can fetch execution plans for these queries in Databases Databases by selecting Statement performance > Request on the execution plan for a specific query.

For that query, the extension attempts to execute the following:

SELECT * from dynatrace.dynatrace_execution_plan({query})

and then ingest into Dynatrace the first row of the column named explain. These execution plans are ingested as logs with event.group = execution_plans.

Connection pools and load balancers

The ActiveGate must establish a direct connection to the host being monitored. It should avoid connecting through a load balancer, proxy, or connection pooler (such as pg-pool).

If the ActiveGate switches between different hosts while running (for example, during failover), it may compute differences in statistics across the DB instances. This can result in inaccurate metrics and misleading data.

Licensing and costs

There is no charge to use the extension. You are only charged for the data that the extension ingests.

The PostgreSQL extension ingests custom metrics, which consume Davis Data Units (DDUs) (Dynatrace classic license) or Metrics powered by Grail (DPS), according to your license model.

Use the following formula to estimate the number of metric data points ingested per minute:

(68 * number of instances) + (40 * number of databases) + (1 * number of tablespaces)

Dynatrace Platform Subscription

In the Dynatrace Platform Subscription, metric ingestion consumes Metrics powered by Grail according to the number of ingested metric data points.

To calculate the approximate yearly consumption, apply the following calculation: <metric data points per minute> * 60 minutes * 24 hours * 365 days.

For logs, regular consumption applies. See Log Analytics.

Dynatrace classic license

In the classic licensing model, metric ingestion consumes Davis Data Units (DDUs) at the rate of .001 DDUs per metric data point. Multiply the above formula for annual data points by .001 to estimate annual DDU usage.

For logs, regular DDU consumption applies. See DDU consumption for Log Management and Analytics or DDUs for Log Monitoring Classic.

The DDU cost above does not include any possible log events or custom events that are triggered by the extension. For more information, see DDU events.

Feature sets

When activating your extension using a monitoring configuration, you can limit monitoring to one of the feature sets. To work properly, the extension has to collect at least one metric after the activation.

In highly segmented networks, feature sets can reflect the segments of your environment. Then, when you create a monitoring configuration, you can select a feature set and a corresponding ActiveGate group that can connect to this particular segment.

All metrics that aren't categorized into any feature set are considered to be the default and are always reported.

A metric inherits the feature set of a subgroup, which in turn inherits the feature set of a group. Also, the feature set defined on the metric level overrides the feature set defined on the subgroup level, which in turn overrides the feature set defined on the group level.

Background Writer
Metric nameMetric keyDescription
Buffers written by background writerpostgres.buffers_clean.countNumber of buffers written by the background writer
Cleaning scan stopspostgres.maxwritten_clean.countNumber of times the background writer stopped a cleaning scan because it had written too many buffers
Buffers allocatedpostgres.buffers_alloc.countNumber of buffers allocated
Scheduled checkpoints performedpostgres.checkpoints_timed.countNumber of scheduled checkpoints that have been performed
Requested checkpoints performedpostgres.checkpoints_req.countNumber of requested checkpoints that have been performed
Checkpoints write timepostgres.checkpoint_write_time.countTotal amount of time that has been spent in the portion of checkpoint processing where files are written to disk
Checkpoint sync timepostgres.checkpoint_sync_time.countTotal amount of time that has been spent in the portion of checkpoint processing where files are synchronized to disk
Buffers written during checkpointspostgres.buffers_checkpoint.countNumber of buffers written during checkpoints
Buffers written by backendpostgres.buffers_backend.countNumber of buffers written directly by a backend
Backend fsync executionspostgres.buffers_backend_fsync.countNumber of times a backend had to execute its own fsync call
Tablespaces
Metric nameMetric keyDescription
Tablespace sizepostgres.tablespace.sizeTablespace size in bytes
Activity
Metric nameMetric keyDescription
Active backend processespostgres.activity.activeNumber of server processes executing a query
Active waiting backend processespostgres.activity.active_waitingNumber of server processes actively waiting
Idle backend processespostgres.activity.idleNumber of server processes waiting for a new client command
Idle in transaction backends processespostgres.activity.idle_in_transactionNumber of server processes in transaction not currently executing a query
Idle in transaction aborted backends processespostgres.activity.idle_in_transaction_abortedNumber of server processes in transaction not currently executing a query where one of the statements caused an error
Fast-path function backend processespostgres.activity.fastpath_function_callNumber of server processes executing a fast-path function call
I/O
Metric nameMetric keyDescription
I/O readspostgres.io.reads.countNumber of read operations as reported by pg_stat_io.
I/O read timepostgres.io.read_time.countTime spent waiting for read operations as reported by pg_stat_io.
I/O writespostgres.io.writes.countNumber of write operations as reported by pg_stat_io.
I/O write timepostgres.io.write_time.countTime spent waiting for write operations as reported by pg_stat_io.
I/O writebackspostgres.io.writebacks.countNumber of units of size BLCKSZ (typically 8kB) which the process requested the kernel write out to permanent storage as reported by pg_stat_io.
I/O writeback timepostgres.io.writeback_time.countTime spent waiting for writeback operations as reported by pg_stat_io.
I/O extendspostgres.io.extends.countNumber of relation extend operations as reported by pg_stat_io.
I/O extend timepostgres.io.extend_time.countTime spent waiting for extend operations as reported by pg_stat_io.
I/O hitspostgres.io.hits.countThe number of times a desired block was found in a shared buffer as reported by pg_stat_io.
I/O evictionspostgres.io.evictions.countNumber of times a block has been written out from a shared or local buffer in order to make it available for another use as reported by pg_stat_io.
I/O reusespostgres.io.reuses.countThe number of times an existing buffer in a size-limited ring buffer outside of shared buffers was reused as part of an I/O operation as reported by pg_stat_io.
I/O fsyncspostgres.io.fsyncs.countNumber of fsync calls as reported by pg_stat_io.
I/O fsync timepostgres.io.fsync_time.countTime spent waiting for fsync operations as reported by pg_stat_io.
I/O read bytespostgres.io.read_bytes.countThe total size of read operations as reported by pg_stat_io.
I/O write bytespostgres.io.write_bytes.countThe total size of write operations as reported by pg_stat_io.
I/O extend bytespostgres.io.extend_bytes.countThe total size of relation extend operations as reported by pg_stat_io.
WAL
Metric nameMetric keyDescription
WAL files sizepostgres.wal_files_sizeTotal size of all WAL files in the WAL directory
WAL files countpostgres.wal_filesTotal number of WAL files in the WAL directory
WAL files agepostgres.wal_files_ageTime elapsed since the oldest WAL file was modified
WAL writepostgres.wal_write.countNumber of times WAL buffers were written out to disk via XLogWrite request
WAL syncpostgres.wal_sync.countNumber of times WAL files were synced to disk via issue_xlog_fsync request
WAL write timepostgres.wal_write_time.countTotal amount of time spent writing WAL buffers to disk via XLogWrite request, in milliseconds
WAL sync timepostgres.wal_sync_time.countTotal amount of time spent syncing WAL files to disk via issue_xlog_fsync request, in milliseconds
WAL diff sizepostgres.wal_diff_sizeSize of difference between current WAL and last WAL replay
WAL records per minutepostgres.wal_records.countNumber of WAL records generated per minute
WAL fpi per minutepostgres.wal_fpi.countNumber of WAL full page images generated per minute
WAL bytespostgres.wal_bytes.countTotal amount of WAL generated in bytes
WAL buffers fullpostgres.wal_buffers_full.countNumber of times WAL data was written to disk because WAL buffers became full
Tables
Metric nameMetric keyDescription
Index sizepostgres.indexes.index_sizeDisk space used by the index, as reported by pg_relation_size().
Index scanspostgres.indexes.scans.countNumber of index scans initiated on this index as reported by pg_stat_user_indexes (idx_scan).
Index tuples readpostgres.indexes.tuples_read.countNumber of index entries returned by scans on this index as reported by pg_stat_user_indexes (idx_tup_read).
Index tuples fetchedpostgres.indexes.tuples_fetched.countNumber of live table rows fetched by simple index scans using this index as reported by pg_stat_user_indexes (idx_tup_fetch).
Index disk block readspostgres.indexes.index_disk_reads.countNumber of disk blocks read from this index as reported by pg_statio_user_indexes (idx_blks_read).
Index buffer hitspostgres.indexes.index_buffer_hits.countNumber of buffer hits for this index as reported by pg_statio_user_indexes (idx_blks_hit).
Estimated row countpostgres.tables.rowsEstimated number of live rows in the table as reported by pg_stat_user_tables (n_live_tup).
Dead rowspostgres.tables.dead_rowsEstimated number of dead rows in the table as reported by pg_stat_user_tables (n_dead_tup).
Rows modified since analyzepostgres.tables.rows_modified_since_analyzeEstimated number of rows modified since this table was last analyzed as reported by pg_stat_user_tables (n_mod_since_analyze).
Rows inserted since vacuumpostgres.tables.rows_inserted_since_vacuumEstimated number of rows inserted since this table was last vacuumed as reported by pg_stat_user_tables (n_ins_since_vacuum).
Total table sizepostgres.tables.total_sizeTotal disk space used by the table, including indexes and TOAST data, as returned by pg_total_relation_size().
Table sizepostgres.tables.table_sizeDisk space used by the main fork of the table, as returned by pg_relation_size().
TOAST sizepostgres.tables.toast_sizeDisk space used by the TOAST table associated with this table, as returned by pg_relation_size().
Indexes sizepostgres.tables.indexes_sizeTotal disk space used by all indexes on the table, as returned by pg_indexes_size().
Sequential scanspostgres.tables.sequential_scans.countNumber of sequential scans initiated on this table as reported by pg_stat_user_tables (seq_scan).
Index scanspostgres.tables.index_scans.countNumber of index scans initiated on this table as reported by pg_stat_user_tables (idx_scan).
Rows read by sequential scanspostgres.tables.rows_read_seq.countNumber of live rows fetched by sequential scans as reported by pg_stat_user_tables (seq_tup_read).
Rows read by index scanspostgres.tables.rows_read_idx.countNumber of live rows fetched by index scans as reported by pg_stat_user_tables (idx_tup_fetch).
Rows insertedpostgres.tables.rows_inserted.countNumber of rows inserted into this table as reported by pg_stat_user_tables (n_tup_ins).
Rows updatedpostgres.tables.rows_updated.countNumber of rows updated in this table as reported by pg_stat_user_tables (n_tup_upd).
Rows deletedpostgres.tables.rows_deleted.countNumber of rows deleted from this table as reported by pg_stat_user_tables (n_tup_del).
HOT updatespostgres.tables.hot_updates.countNumber of rows HOT updated in this table, where no separate index update was needed, as reported by pg_stat_user_tables (n_tup_hot_upd).
Table disk block readspostgres.tables.table_disk_reads.countNumber of disk blocks read from this table as reported by pg_statio_user_tables (heap_blks_read).
Table buffer hitspostgres.tables.table_buffer_hits.countNumber of buffer hits for this table as reported by pg_statio_user_tables (heap_blks_hit).
Index disk block readspostgres.tables.index_disk_reads.countNumber of disk blocks read from all indexes on this table as reported by pg_statio_user_tables (idx_blks_read).
Index buffer hitspostgres.tables.index_buffer_hits.countNumber of buffer hits for all indexes on this table as reported by pg_statio_user_tables (idx_blks_hit).
TOAST disk block readspostgres.tables.toast_disk_reads.countNumber of disk blocks read from this table's TOAST table as reported by pg_statio_user_tables (toast_blks_read).
TOAST buffer hitspostgres.tables.toast_buffer_hits.countNumber of buffer hits for this table's TOAST table as reported by pg_statio_user_tables (toast_blks_hit).
TOAST index disk block readspostgres.tables.toast_index_disk_reads.countNumber of disk blocks read from this table's TOAST table indexes as reported by pg_statio_user_tables (tidx_blks_read).
TOAST index buffer hitspostgres.tables.toast_index_buffer_hits.countNumber of buffer hits for this table's TOAST table indexes as reported by pg_statio_user_tables (tidx_blks_hit).
Manual vacuumspostgres.tables.vacuum.countNumber of times this table has been manually vacuumed (not counting VACUUM FULL) as reported by pg_stat_user_tables (vacuum_count).
Autovacuumspostgres.tables.autovacuum.countNumber of times this table has been vacuumed by the autovacuum daemon as reported by pg_stat_user_tables (autovacuum_count).
Manual analyzespostgres.tables.analyze.countNumber of times this table has been manually analyzed as reported by pg_stat_user_tables (analyze_count).
Autoanalyzespostgres.tables.autoanalyze.countNumber of times this table has been analyzed by the autovacuum daemon as reported by pg_stat_user_tables (autoanalyze_count).
Recovery
Metric nameMetric keyDescription
Instance recovery modepostgres.recovery.stateIndicate that the instance is in recovery mode. 1 if in recovery, 0 otherwise.
default
Metric nameMetric keyDescription
Instance uptimepostgres.uptimeTime since the instance has been started
Replication
Metric nameMetric keyDescription
Replication WAL restart delaypostgres.replication.restart_delayDifference between current WAL LSN and the restart_lsn as reported by pg_replication_slots.
Replication WAL confirmed flush lagpostgres.replication.confirmed_flush_lagDifference between current WAL LSN and the confirmed_flush_lsn as reported by pg_replication_slots.
Replication slot transaction agepostgres.replication.slot_xmin_ageAge of the oldest transaction that this logical replication slot needs the database to retain, as reported by pg_replication_slots.
Database conflicts tablespacepostgres.database_conflicts.tablespace.countNumber of queries in this database that have been canceled due to dropped tablespaces, as reported by pg_stat_database_conflicts. Standby only.
Database conflicts lockpostgres.database_conflicts.lock.countNumber of queries in this database that have been canceled due to lock timeouts, as reported by pg_stat_database_conflicts. Standby only.
Database conflicts snapshotpostgres.database_conflicts.snapshot.countNumber of queries in this database that have been canceled due to old snapshots, as reported by pg_stat_database_conflicts. Standby only.
Database conflicts buffer pinpostgres.database_conflicts.bufferpin.countNumber of queries in this database that have been canceled due to pinned buffers, as reported by pg_stat_database_conflicts. Standby only.
Database conflicts deadlockpostgres.database_conflicts.deadlock.countNumber of queries in this database that have been canceled due to deadlocks, as reported by pg_stat_database_conflicts. Standby only.
Replication WAL write lagpostgres.replication.write_lagTime elapsed between flushing recent WAL locally and receiving notification that this standby server has written it, as reported by pg_stat_replication.
Replication WAL flush lagpostgres.replication.flush_lagTime elapsed between flushing recent WAL locally and receiving notification that this standby server has written and flushed it, as reported by pg_stat_replication.
Replication WAL replay lagpostgres.replication.replay_lagTime elapsed between flushing recent WAL locally and receiving notification that this standby server has written, flushed and applied it, as reported by pg_stat_replication.
Replication backend transaction agepostgres.replication.backend_xmin_ageAge of the backend's xmin horizon, as reported by pg_stat_replication.
Replication replay delaypostgres.replication.replay_delayDifference in bytes between the current WAL position and the last WAL position replayed on the standby server, as reported by pg_stat_replication.
WAL receiver received timeline IDpostgres.wal_receiver.received_tliTimeline number of the last WAL received and flushed to disk, as reported by pg_stat_wal_receiver. Standby only.
WAL receiver last message send agepostgres.wal_receiver.last_msg_send_ageTime elapsed since last message sent to origin WAL sender, as reported by pg_stat_wal_receiver. Standby only.
WAL receiver last message receipt agepostgres.wal_receiver.last_msg_receipt_ageTime elapsed since last message received from origin WAL sender, as reported by pg_stat_wal_receiver. Standby only.
WAL receiver latest end agepostgres.wal_receiver.latest_end_ageTime elapsed since last write-ahead log location reported to origin WAL sender, as reported by pg_stat_wal_receiver. Standby only.
User Functions
Metric nameMetric keyDescription
User function callspostgres.user_functions.calls.countNumber of times this function has been called, as reported by pg_stat_user_functions.
User functions total timepostgres.user_functions.total_time.countTotal time spent in this function and all other functions called by it, as reported by pg_stat_user_functions.
User functions self timepostgres.user_functions.self_time.countTotal time spent in this function itself, not including other functions called by it, as reported by pg_stat_user_functions.
Databases
Metric nameMetric keyDescription
Latest transaction XID agepostgres.xid_ageDifference between the current transaction's XID and datfrozenxid. If this value exceeds 2^31, this can cause a database crash due to transaction ID wraparound.
Number of backendspostgres.numbackendsNumber of backends currently connected to this database
Committed transactionspostgres.xact_commit.countNumber of transactions in this database that have been committed
Rolled back transactionspostgres.xact_rollback.countNumber of transactions in this database that have been rolled back
Block read from diskpostgres.blks_read.countNumber of disk blocks read in this database
Blocks found in buffer cachepostgres.blks_hit.countNumber of times disk blocks were found already in the buffer cache, so that a read was not necessary
Live rows returnedpostgres.tup_returned.countNumber of live rows fetched by sequential scans and index entries returned by index scans in this database
Live rows fetched by index scanspostgres.tup_fetched.countNumber of live rows fetched by index scans in this database
Rows insertedpostgres.tup_inserted.countNumber of rows inserted by queries in this database
Rows updatedpostgres.tup_updated.countNumber of rows updated by queries in this database
Rows deletedpostgres.tup_deleted.countNumber of rows deleted by queries in this database
Queries canceled due to conflictpostgres.conflicts.countNumber of queries canceled due to conflicts with recovery in this database
Temporary files createdpostgres.temp_files.countNumber of temporary files created by queries in this database
Data written to temporary filespostgres.temp_bytes.countTotal amount of data written to temporary files by queries in this database
Deadlockspostgres.deadlocks.countNumber of deadlocks detected in this database
Data file blocks reading timepostgres.blk_read_time.countTime spent reading data file blocks by backends in this database
Data file blocks writing timepostgres.blk_write_time.countTime spent writing data file blocks by backends in this database
Database Sizepostgres.db_sizeSize of the database in bytes
Time spent by sessionspostgres.session_time.countTime spent by database sessions in this database
Time spent executing SQL statementspostgres.active_time.countTime spent executing SQL statements in this database
Time spent idlingpostgres.idle_in_transaction_time.countTime spent idling while in a transaction in this database
Data page checksum failurespostgres.checksum_failures.countNumber of data page checksum failures detected in this database. Only available if data checksums are enabled.
Established sessionspostgres.sessions.countTotal number of sessions established
Abandoned sessionspostgres.sessions_abandoned.countNumber of database sessions to this database that were terminated because connection to the client was lost
Fatal error terminated sessionspostgres.sessions_fatal.countNumber of database sessions to this database that were terminated by fatal errors
Killed sessionspostgres.sessions_killed.countNumber of database sessions to this database that were terminated by operator intervention
Subscription
Metric nameMetric keyDescription
Subscription stats - apply errorpostgres.subscription_stats.apply_error.countNumber of times an error occurred while applying changes, as reported by pg_stat_subscription_stats.
Subscription stats - sync errorpostgres.subscription_stats.sync_error.countNumber of times an error occurred during the initial table synchronization, as reported by pg_stat_subscription_stats.
Subscription stats conflict - insert existspostgres.subscription_stats.confl_insert_exists.countNumber of times a row insertion violated a NOT DEFERRABLE unique constraint during the application of changes, as reported by pg_stat_subscription_stats.
Subscription stats conflict - update origin differspostgres.subscription_stats.confl_update_origin_differs.countNumber of times an update operation was applied to a row that had been previously modified by another source during the application of changes, as reported by pg_stat_subscription_stats.
Subscription stats conflict - update existspostgres.subscription_stats.confl_update_exists.countNumber of times that an updated row value violated a NOT DEFERRABLE unique constraint during the application of changes, as reported by pg_stat_subscription_stats.
Subscription stats conflict - update missingpostgres.subscription_stats.confl_update_missing.countNumber of times the tuple to be updated was not found during the application of changes, as reported by pg_stat_subscription_stats.
Subscription stats conflict - delete origin differspostgres.subscription_stats.confl_delete_origin_differs.countNumber of times a delete operation was applied to row that had been previously modified by another source during the application of changes, as reported by pg_stat_subscription_stats.
Subscription stats conflict - missing deletepostgres.subscription_stats.confl_delete_missing.countNumber of times the tuple to be deleted was not found during the application of changes, as reported by pg_stat_subscription_stats.
Subscription stats conflict - multiple unique conflictspostgres.subscription_stats.confl_multiple_unique_conflicts.countNumber of times a row insertion or an updated row values violated multiple NOT DEFERRABLE unique constraints during the application of changes, as reported by pg_stat_subscription_stats.
Locks
Metric nameMetric keyDescription
Number of lockspostgres.locksNumber of locks as reported by pg_locks
Queries
Metric nameMetric keyDescription
SLRU
Metric nameMetric keyDescription
SLRU blocks zeroedpostgres.slru.blks_zeroed.countNumber of blocks zeroed during initializations, as reported by pg_stat_slru.
SLRU blocks hitpostgres.slru.blks_hit.countNumber of times disk blocks were found already in the SLRU cache, so that a read was not necessary, as reported by pg_stat_slru.
SLRU blocks readpostgres.slru.blks_read.countNumber of disk blocks read, as reported by pg_stat_slru.
SLRU blocks writtenpostgres.slru.blks_written.countNumber of disk blocks written, as reported by pg_stat_slru.
SLRU blocks existspostgres.slru.blks_exists.countNumber of blocks checked for existence, as reported by pg_stat_slru.
SLRU flushespostgres.slru.flushes.countNumber of SLRU flushes of dirty data, as reported by pg_stat_slru.
SLRU truncatespostgres.slru.truncates.countNumber of SLRU truncates, as reported by pg_stat_slru.

Limitations

Top queries: For security reasons, ALTER statements are excluded from top query collection.

Execution plan details:

  • The extensions:configuration.actions:write permission is required to trigger the execution plan fetching.
  • An execution plan can only be fetched from the database where the dynatrace.dynatrace_execution_plan function has been created.

FAQ

How does this Dynatrace extension collect data from my databases?

This extension runs from your Dynatrace ActiveGates and connects to the configured databases. Once the connection is established, the extension regularly runs queries on the database to gather performance and health metrics, reporting the results back to Dynatrace.

The extension executes only SELECT queries to collect data. To see exactly which queries run, download the extension yaml artifact: open the Release notes, select a release, and select Download version.

How often are these monitoring queries executed?

From version 2.3.0 onwards, the configuration variables query-interval and heavy-query-interval control query execution frequency. Most of the queries executed by the extension run every query-interval minutes (with a default of 1 minute), while the queries under the Queries feature set run every heavy-query-interval minutes (with a default of 5 minutes).

For older versions, most queries run every minute. The heavy queries mentioned above run every 5 minutes.

Why are some of the extension's queries failing?

Supporting a wide range of Postgres versions requires running several versions of the same queries in parallel, since Postgres has changed column names for several tables over time. Some query failures are expected. As long as no data is missing, there is no cause for concern.

PostgreSQL

Explore in Dynatrace Hub

Collect PostgreSQL database performance metrics and query logs via remote ActiveGate monitoring to detect anomalies, blocking queries, and performance issues.

Related tags
DatabaseSQLSQLPostgresApplication Observability