{
  "leansignal_demand_export": "1.0",
  "exported_at": "2026-07-17T00:00:00Z",
  "source_tenant": "leansignal-catalog",
  "catalog": {
    "slug": "postgresql-otel-demand-standard",
    "integration": "postgresql",
    "variant": "standard",
    "title": "PostgreSQL — Standard",
    "category": "Database",
    "dashboards": 1,
    "panels": 8,
    "alerts": 6,
    "docs_url": "https://docs.leansignal.io/integrations/postgresql/"
  },
  "demand": {
    "ref": "demand",
    "name": "PostgreSQL — Standard (OpenTelemetry)",
    "description": "A rounded operational view: adds rollback rate, tuple operations, database size, and live/dead rows, with warning/critical alerting. The recommended default for PostgreSQL.",
    "dashboards": [
      {
        "ref": "dash-postgresql",
        "name": "PostgreSQL — Standard",
        "description": "PostgreSQL metrics from the OpenTelemetry postgresqlreceiver — backends, commits/rollbacks, cache hits, tuple ops, sizes, replication, and (opt-in) deadlocks and temp files (standard variant).",
        "data": {
          "display": {
            "name": "PostgreSQL — Standard"
          },
          "panels": {
            "active_backends": {
              "kind": "Panel",
              "spec": {
                "display": {
                  "name": "Active backends",
                  "description": "Server backends per database."
                },
                "plugin": {
                  "kind": "TimeSeriesChart",
                  "spec": {
                    "legend": {
                      "position": "bottom",
                      "mode": "table",
                      "values": [
                        "last-number",
                        "mean",
                        "max"
                      ]
                    },
                    "yAxis": {
                      "format": {
                        "unit": "decimal"
                      },
                      "min": 0
                    }
                  }
                },
                "queries": [
                  {
                    "kind": "TimeSeriesQuery",
                    "spec": {
                      "plugin": {
                        "kind": "PrometheusTimeSeriesQuery",
                        "spec": {
                          "query": "sum by (instance, postgresql_database_name) (postgresql_backends{instance=~\"$instance\"})",
                          "seriesNameFormat": "{{instance}} · {{postgresql_database_name}}"
                        }
                      }
                    }
                  }
                ]
              }
            },
            "connection_utilization": {
              "kind": "Panel",
              "spec": {
                "display": {
                  "name": "Connection utilization",
                  "description": "Backends as a share of max_connections — the headroom before clients get refused."
                },
                "plugin": {
                  "kind": "TimeSeriesChart",
                  "spec": {
                    "legend": {
                      "position": "bottom",
                      "mode": "table",
                      "values": [
                        "last-number",
                        "mean",
                        "max"
                      ]
                    },
                    "yAxis": {
                      "format": {
                        "unit": "percent-decimal"
                      },
                      "min": 0,
                      "max": 1
                    }
                  }
                },
                "queries": [
                  {
                    "kind": "TimeSeriesQuery",
                    "spec": {
                      "plugin": {
                        "kind": "PrometheusTimeSeriesQuery",
                        "spec": {
                          "query": "sum by (instance) (postgresql_backends{instance=~\"$instance\"}) / max by (instance) (postgresql_connection_max{instance=~\"$instance\"})",
                          "seriesNameFormat": "{{instance}}"
                        }
                      }
                    }
                  }
                ]
              }
            },
            "transaction_rate": {
              "kind": "Panel",
              "spec": {
                "display": {
                  "name": "Transaction rate",
                  "description": "Commits plus rollbacks per database."
                },
                "plugin": {
                  "kind": "TimeSeriesChart",
                  "spec": {
                    "legend": {
                      "position": "bottom",
                      "mode": "table",
                      "values": [
                        "last-number",
                        "mean",
                        "max"
                      ]
                    },
                    "yAxis": {
                      "format": {
                        "unit": "ops/sec"
                      },
                      "min": 0
                    }
                  }
                },
                "queries": [
                  {
                    "kind": "TimeSeriesQuery",
                    "spec": {
                      "plugin": {
                        "kind": "PrometheusTimeSeriesQuery",
                        "spec": {
                          "query": "sum by (instance, postgresql_database_name) (rate(postgresql_commits_total{instance=~\"$instance\"}[5m]) + rate(postgresql_rollbacks_total{instance=~\"$instance\"}[5m]))",
                          "seriesNameFormat": "{{instance}} · {{postgresql_database_name}}"
                        }
                      }
                    }
                  }
                ]
              }
            },
            "rollback_rate": {
              "kind": "Panel",
              "spec": {
                "display": {
                  "name": "Rollback rate",
                  "description": "A rollback spike usually means application errors or deadlocks."
                },
                "plugin": {
                  "kind": "TimeSeriesChart",
                  "spec": {
                    "legend": {
                      "position": "bottom",
                      "mode": "table",
                      "values": [
                        "last-number",
                        "mean",
                        "max"
                      ]
                    },
                    "yAxis": {
                      "format": {
                        "unit": "ops/sec"
                      },
                      "min": 0
                    }
                  }
                },
                "queries": [
                  {
                    "kind": "TimeSeriesQuery",
                    "spec": {
                      "plugin": {
                        "kind": "PrometheusTimeSeriesQuery",
                        "spec": {
                          "query": "sum by (instance, postgresql_database_name) (rate(postgresql_rollbacks_total{instance=~\"$instance\"}[5m]))",
                          "seriesNameFormat": "{{instance}} · {{postgresql_database_name}}"
                        }
                      }
                    }
                  }
                ]
              }
            },
            "tuple_operations": {
              "kind": "Panel",
              "spec": {
                "display": {
                  "name": "Tuple operations",
                  "description": "Rows inserted/updated/deleted/hot-updated per second."
                },
                "plugin": {
                  "kind": "TimeSeriesChart",
                  "spec": {
                    "legend": {
                      "position": "bottom",
                      "mode": "table",
                      "values": [
                        "last-number",
                        "mean",
                        "max"
                      ]
                    },
                    "yAxis": {
                      "format": {
                        "unit": "ops/sec"
                      },
                      "min": 0
                    }
                  }
                },
                "queries": [
                  {
                    "kind": "TimeSeriesQuery",
                    "spec": {
                      "plugin": {
                        "kind": "PrometheusTimeSeriesQuery",
                        "spec": {
                          "query": "sum by (instance, operation) (rate(postgresql_operations_total{instance=~\"$instance\"}[5m]))",
                          "seriesNameFormat": "{{instance}} · {{operation}}"
                        }
                      }
                    }
                  }
                ]
              }
            },
            "cache_hit_ratio": {
              "kind": "Panel",
              "spec": {
                "display": {
                  "name": "Cache hit ratio",
                  "description": "Block reads served from shared buffers — below ~95% means disk pressure."
                },
                "plugin": {
                  "kind": "TimeSeriesChart",
                  "spec": {
                    "legend": {
                      "position": "bottom",
                      "mode": "table",
                      "values": [
                        "last-number",
                        "mean",
                        "max"
                      ]
                    },
                    "yAxis": {
                      "format": {
                        "unit": "percent-decimal"
                      },
                      "min": 0,
                      "max": 1
                    }
                  }
                },
                "queries": [
                  {
                    "kind": "TimeSeriesQuery",
                    "spec": {
                      "plugin": {
                        "kind": "PrometheusTimeSeriesQuery",
                        "spec": {
                          "query": "sum by (instance, postgresql_database_name) (rate(postgresql_blocks_read_total{instance=~\"$instance\",source=~\".+_hit\"}[5m])) / sum by (instance, postgresql_database_name) (rate(postgresql_blocks_read_total{instance=~\"$instance\"}[5m]))",
                          "seriesNameFormat": "{{instance}} · {{postgresql_database_name}}"
                        }
                      }
                    }
                  }
                ]
              }
            },
            "database_size": {
              "kind": "Panel",
              "spec": {
                "display": {
                  "name": "Database size",
                  "description": "On-disk size per database — watch the growth trend, not the absolute number."
                },
                "plugin": {
                  "kind": "TimeSeriesChart",
                  "spec": {
                    "legend": {
                      "position": "bottom",
                      "mode": "table",
                      "values": [
                        "last-number",
                        "mean",
                        "max"
                      ]
                    },
                    "yAxis": {
                      "format": {
                        "unit": "bytes"
                      },
                      "min": 0
                    }
                  }
                },
                "queries": [
                  {
                    "kind": "TimeSeriesQuery",
                    "spec": {
                      "plugin": {
                        "kind": "PrometheusTimeSeriesQuery",
                        "spec": {
                          "query": "sum by (instance, postgresql_database_name) (postgresql_db_size_bytes{instance=~\"$instance\"})",
                          "seriesNameFormat": "{{instance}} · {{postgresql_database_name}}"
                        }
                      }
                    }
                  }
                ]
              }
            },
            "live_dead_rows": {
              "kind": "Panel",
              "spec": {
                "display": {
                  "name": "Live vs dead rows",
                  "description": "Dead-row growth means autovacuum is falling behind (table bloat)."
                },
                "plugin": {
                  "kind": "TimeSeriesChart",
                  "spec": {
                    "legend": {
                      "position": "bottom",
                      "mode": "table",
                      "values": [
                        "last-number",
                        "mean",
                        "max"
                      ]
                    },
                    "yAxis": {
                      "format": {
                        "unit": "decimal"
                      },
                      "min": 0
                    }
                  }
                },
                "queries": [
                  {
                    "kind": "TimeSeriesQuery",
                    "spec": {
                      "plugin": {
                        "kind": "PrometheusTimeSeriesQuery",
                        "spec": {
                          "query": "sum by (instance, state) (postgresql_rows{instance=~\"$instance\"})",
                          "seriesNameFormat": "{{instance}} · {{state}}"
                        }
                      }
                    }
                  }
                ]
              }
            }
          },
          "layouts": [
            {
              "kind": "Grid",
              "spec": {
                "display": {
                  "title": "Connections",
                  "collapse": {
                    "open": true
                  }
                },
                "items": [
                  {
                    "x": 0,
                    "y": 0,
                    "width": 12,
                    "height": 8,
                    "content": {
                      "$ref": "#/spec/panels/active_backends"
                    }
                  },
                  {
                    "x": 12,
                    "y": 0,
                    "width": 12,
                    "height": 8,
                    "content": {
                      "$ref": "#/spec/panels/connection_utilization"
                    }
                  }
                ]
              }
            },
            {
              "kind": "Grid",
              "spec": {
                "display": {
                  "title": "Throughput",
                  "collapse": {
                    "open": true
                  }
                },
                "items": [
                  {
                    "x": 0,
                    "y": 0,
                    "width": 12,
                    "height": 8,
                    "content": {
                      "$ref": "#/spec/panels/transaction_rate"
                    }
                  },
                  {
                    "x": 12,
                    "y": 0,
                    "width": 12,
                    "height": 8,
                    "content": {
                      "$ref": "#/spec/panels/rollback_rate"
                    }
                  },
                  {
                    "x": 0,
                    "y": 8,
                    "width": 12,
                    "height": 8,
                    "content": {
                      "$ref": "#/spec/panels/tuple_operations"
                    }
                  }
                ]
              }
            },
            {
              "kind": "Grid",
              "spec": {
                "display": {
                  "title": "Cache",
                  "collapse": {
                    "open": true
                  }
                },
                "items": [
                  {
                    "x": 0,
                    "y": 0,
                    "width": 12,
                    "height": 8,
                    "content": {
                      "$ref": "#/spec/panels/cache_hit_ratio"
                    }
                  }
                ]
              }
            },
            {
              "kind": "Grid",
              "spec": {
                "display": {
                  "title": "Storage",
                  "collapse": {
                    "open": true
                  }
                },
                "items": [
                  {
                    "x": 0,
                    "y": 0,
                    "width": 12,
                    "height": 8,
                    "content": {
                      "$ref": "#/spec/panels/database_size"
                    }
                  },
                  {
                    "x": 12,
                    "y": 0,
                    "width": 12,
                    "height": 8,
                    "content": {
                      "$ref": "#/spec/panels/live_dead_rows"
                    }
                  }
                ]
              }
            }
          ],
          "variables": [
            {
              "kind": "ListVariable",
              "spec": {
                "name": "instance",
                "display": {
                  "name": "Instance"
                },
                "allowAllValue": true,
                "allowMultiple": true,
                "customAllValue": ".*",
                "defaultValue": ".*",
                "plugin": {
                  "kind": "PrometheusLabelValuesVariable",
                  "spec": {
                    "labelName": "instance"
                  }
                }
              }
            }
          ],
          "duration": "1h",
          "refreshInterval": "30s",
          "datasources": {}
        }
      }
    ],
    "alert_rules": [
      {
        "ref": "alert-postgresql-connection-saturation",
        "name": "PostgreSQL connection saturation",
        "description": null,
        "query": "sum(postgresql_backends) / max(postgresql_connection_max)",
        "condition_op": "gt",
        "threshold": 0.9,
        "interval": "1m",
        "for": "5m",
        "severity": "critical",
        "message": "PostgreSQL is using {{value | percent:1}} of max_connections — approaching the connection limit.",
        "notify_on_resolve": true,
        "paused": false,
        "channels": []
      },
      {
        "ref": "alert-postgresql-low-cache-hit-ratio",
        "name": "PostgreSQL low cache hit ratio",
        "description": null,
        "query": "sum by (postgresql_database_name) (rate(postgresql_blocks_read_total{source=~\".+_hit\"}[5m])) / sum by (postgresql_database_name) (rate(postgresql_blocks_read_total[5m]))",
        "condition_op": "lt",
        "threshold": 0.95,
        "interval": "1m",
        "for": "15m",
        "severity": "warning",
        "message": "PostgreSQL cache hit ratio on {{labels.postgresql_database_name}} is {{value}} — shared_buffers may be too small.",
        "notify_on_resolve": true,
        "paused": false,
        "channels": []
      },
      {
        "ref": "alert-postgresql-high-rollback-ratio",
        "name": "PostgreSQL high rollback ratio",
        "description": null,
        "query": "sum by (postgresql_database_name) (rate(postgresql_rollbacks_total[5m])) / clamp_min(sum by (postgresql_database_name) (rate(postgresql_commits_total[5m]) + rate(postgresql_rollbacks_total[5m])), 1)",
        "condition_op": "gt",
        "threshold": 0.1,
        "interval": "1m",
        "for": "10m",
        "severity": "warning",
        "message": "PostgreSQL {{labels.postgresql_database_name}} rollback ratio is {{value | percent:1}} of transactions.",
        "notify_on_resolve": true,
        "paused": false,
        "channels": []
      },
      {
        "ref": "alert-postgresql-high-dead-tuples",
        "name": "PostgreSQL high dead tuples",
        "description": null,
        "query": "sum by (postgresql_database_name) (postgresql_rows{state=\"dead\"})",
        "condition_op": "gt",
        "threshold": 100000,
        "interval": "1m",
        "for": "15m",
        "severity": "warning",
        "message": "PostgreSQL {{labels.postgresql_database_name}} has {{value}} dead tuples — autovacuum may be falling behind.",
        "notify_on_resolve": true,
        "paused": false,
        "channels": []
      },
      {
        "ref": "alert-postgresql-replication-lag-bytes",
        "name": "PostgreSQL replication lag (bytes)",
        "description": null,
        "query": "max by (replication_client) (postgresql_replication_data_delay_bytes)",
        "condition_op": "gt",
        "threshold": 104857600,
        "interval": "1m",
        "for": "5m",
        "severity": "critical",
        "message": "PostgreSQL replica {{labels.replication_client}} is {{value | bytes}} behind the primary.",
        "notify_on_resolve": true,
        "paused": false,
        "channels": []
      },
      {
        "ref": "alert-postgresql-high-connections",
        "name": "PostgreSQL high backend count",
        "description": null,
        "query": "sum by (postgresql_database_name) (postgresql_backends)",
        "condition_op": "gt",
        "threshold": 100,
        "interval": "1m",
        "for": "5m",
        "severity": "warning",
        "message": "PostgreSQL database {{labels.postgresql_database_name}} has {{value}} active backends.",
        "notify_on_resolve": true,
        "paused": false,
        "channels": []
      }
    ]
  }
}
