Skip to main content
The queries section defines SQL queries to execute against databases. Each query updates one or more metrics with its results.

Basic Configuration

queries:
  active_users:
    databases: [mydb]
    metrics: [user_count]
    sql: SELECT COUNT(*) AS user_count FROM users WHERE active = true
    interval: 60

Configuration Options

databases
array[string]
required
List of databases to run the query on. Names must match those defined in the databases section.
  • Must contain at least 1 database
  • Must not contain duplicates
  • Metrics are automatically tagged with the database label
metrics
array[string]
required
List of metrics that the query updates. Names must match those defined in the metrics section.
  • Must contain at least 1 metric
  • Must not contain duplicates
sql
string
required
The SQL text of the query.The query must return columns with names matching:
  • The metric names defined in metrics
  • All label names for those metrics (if any)
See Writing Queries below for details.
interval
string | integer
The time interval at which the query is run.
  • Can be an integer (interpreted as seconds)
  • Can be a string with suffix: s (seconds), m (minutes), h (hours), d (days)
  • Must be a positive number
  • Cannot be specified together with schedule
  • If not specified, query only runs on HTTP requests
Examples: 30, 5m, 1h, 7d
schedule
string
A Cron-like schedule expression for executing queries at specific times.Format: minute hour day month day_of_weekExamples:
  • */5 * * * * - Every 5 minutes
  • 0 * * * * - Every hour at minute 0
  • 0 0 * * * - Daily at midnight
  • 0 0 * * 0 - Weekly on Sunday at midnight
Cannot be specified together with interval.
parameters
array[object] | object
Optional parameters to run the query with. Can be specified as a list or a matrix.See Query Parameters below for details.
timeout
float
Query timeout in seconds. Must be a positive number and a multiple of 0.1.Examples: 0.5, 1.0, 30.0

Interval vs Schedule

Using Interval

Runs the query repeatedly at fixed intervals:
queries:
  every_minute:
    databases: [db]
    metrics: [metric1]
    sql: SELECT value AS metric1 FROM table
    interval: 60  # Every 60 seconds

Using Schedule

Runs the query on a Cron schedule:
queries:
  daily_report:
    databases: [db]
    metrics: [daily_total]
    sql: SELECT SUM(amount) AS daily_total FROM transactions
    schedule: "0 0 * * *"  # Every day at midnight

On-Demand Only

If neither interval nor schedule is specified, the query only runs when the /metrics endpoint is accessed:
queries:
  on_demand:
    databases: [db]
    metrics: [current_value]
    sql: SELECT value AS current_value FROM config
You cannot specify both interval and schedule for the same query.

Writing Queries

Column Names Must Match Metrics

Your SQL query must return columns with names matching the metric names:
metrics:
  user_count:
    type: gauge

queries:
  count_users:
    databases: [db]
    metrics: [user_count]
    sql: SELECT COUNT(*) AS user_count FROM users

Multiple Metrics

Queries can update multiple metrics at once:
metrics:
  active_users:
    type: gauge
  total_users:
    type: gauge

queries:
  user_stats:
    databases: [db]
    metrics: [active_users, total_users]
    sql: |
      SELECT 
        COUNT(*) FILTER (WHERE active = true) AS active_users,
        COUNT(*) AS total_users
      FROM users

Including Labels

When metrics have labels, the query must return columns for each label:
metrics:
  orders_count:
    type: counter
    labels: [status, region]

queries:
  count_orders:
    databases: [db]
    metrics: [orders_count]
    sql: |
      SELECT 
        COUNT(*) AS orders_count,
        status,
        region
      FROM orders
      GROUP BY status, region
The database label is automatically added. You don’t need to include it in your query.

Multi-line SQL

Use YAML’s | or > syntax for multi-line queries:
queries:
  complex_query:
    databases: [db]
    metrics: [result]
    sql: |
      SELECT 
        SUM(amount) AS result
      FROM transactions t
      JOIN users u ON t.user_id = u.id
      WHERE t.created_at > NOW() - INTERVAL '1 hour'
        AND u.active = true
      GROUP BY u.region

Query Parameters

Parameters allow you to run the same query multiple times with different values.

List Format

Run the query once for each parameter set:
queries:
  table_size:
    databases: [db]
    metrics: [row_count]
    sql: |
      SELECT COUNT(*) AS row_count 
      FROM :table_name
    parameters:
      - table_name: users
      - table_name: orders
      - table_name: products
This runs three queries:
  • SELECT COUNT(*) AS row_count FROM users
  • SELECT COUNT(*) AS row_count FROM orders
  • SELECT COUNT(*) AS row_count FROM products

Matrix Format

Generate all permutations of parameter combinations:
queries:
  app_count:
    databases: [db]
    metrics: [apps_count]
    sql: |
      SELECT COUNT(1) AS apps_count 
      FROM apps_list
      WHERE os = :os__name 
        AND arch = :os__arch 
        AND lang = :lang__name
    parameters:
      os:
        - name: MacOS
          arch: arm64
        - name: Linux
          arch: amd64
        - name: Windows
          arch: amd64
      lang:
        - name: Python3
        - name: Java
        - name: TypeScript
Variable format in matrix mode: :top_level_key__inner_key
This generates 9 queries (3 OS × 3 languages):
  • WHERE os = 'MacOS' AND arch = 'arm64' AND lang = 'Python3'
  • WHERE os = 'MacOS' AND arch = 'arm64' AND lang = 'Java'
  • … and so on

Escaping Colons

Since : is used for parameter markers, literal colons at the beginning of words must be escaped:
sql: SELECT '\:bar' FROM table  # Escaped
sql: SELECT 'foo:bar' FROM table  # No escape needed (colon not at start)

Timeout

Set a timeout to prevent long-running queries from blocking:
queries:
  slow_query:
    databases: [db]
    metrics: [result]
    sql: SELECT expensive_calculation() AS result
    timeout: 5.0  # 5 seconds
Timeout must be a multiple of 0.1 seconds. Examples: 0.5, 1.0, 10.5

Complete Examples

Simple Gauge Query

queries:
  connection_count:
    databases: [postgres]
    metrics: [active_connections]
    interval: 30
    sql: |
      SELECT COUNT(*) AS active_connections
      FROM pg_stat_activity
      WHERE state = 'active'

Counter with Increment

metrics:
  page_views:
    type: counter
    labels: [page, user_type]
    increment: true

queries:
  count_page_views:
    databases: [analytics]
    metrics: [page_views]
    interval: 5m
    sql: |
      SELECT 
        COUNT(*) AS page_views,
        page,
        user_type
      FROM page_view_log
      WHERE created_at > NOW() - INTERVAL '5 minutes'
      GROUP BY page, user_type

Scheduled Report Query

queries:
  daily_revenue:
    databases: [sales]
    metrics: [revenue_total]
    schedule: "0 0 * * *"  # Midnight every day
    sql: |
      SELECT 
        COALESCE(SUM(amount), 0) AS revenue_total
      FROM orders
      WHERE DATE(created_at) = CURRENT_DATE - INTERVAL '1 day'

Multi-Database Query

queries:
  user_count_all_regions:
    databases: [db_us_east, db_us_west, db_eu]
    metrics: [total_users]
    interval: 5m
    sql: SELECT COUNT(*) AS total_users FROM users
Each database will have separate metric series thanks to the automatic database label.

Using External SQL File

queries:
  complex_stats:
    databases: [db]
    metrics: [metric1, metric2]
    interval: 10m
    sql: !file queries/complex_stats.sql

Parameterized Query with Timeout

queries:
  table_stats:
    databases: [postgres]
    metrics: [table_size_bytes]
    interval: 1h
    timeout: 10.0
    sql: |
      SELECT 
        pg_total_relation_size(:schema_name || '.' || :table_name) AS table_size_bytes
    parameters:
      - schema_name: public
        table_name: users
      - schema_name: public
        table_name: orders
      - schema_name: analytics
        table_name: events

Histogram Example

metrics:
  query_duration_seconds:
    type: histogram
    buckets: [0.1, 0.5, 1.0, 5.0, 10.0]
    labels: [query_type]

queries:
  track_query_duration:
    databases: [postgres]
    metrics: [query_duration_seconds]
    interval: 1m
    sql: |
      SELECT 
        mean_exec_time / 1000.0 AS query_duration_seconds,
        CASE 
          WHEN query LIKE 'SELECT%' THEN 'select'
          WHEN query LIKE 'INSERT%' THEN 'insert'
          WHEN query LIKE 'UPDATE%' THEN 'update'
          ELSE 'other'
        END AS query_type
      FROM pg_stat_statements
      WHERE mean_exec_time > 0

Build docs developers (and LLMs) love