Skip to main content
MQL (Metrics Query Language) is a specialized query language for querying metrics and timeseries data in Snuba. It provides a concise syntax for aggregating, filtering, and combining metrics.

Overview

MQL queries consist of:
  • Timeseries: Aggregations over metrics
  • Filters: Conditions on metric tags
  • Grouping: Group results by tag values
  • Formulas: Arithmetic operations and functions over timeseries
Unlike SnQL, MQL queries are designed specifically for metrics data and include additional context like time ranges and rollup configuration.

Query Structure

An MQL query has two parts:
  1. Query String: The MQL expression
  2. Context Object: Metadata about the query (time range, scope, etc.)
{
  "mql": "sum(transaction.duration){environment:prod} by status_code",
  "mql_context": {
    "entity": "generic_metrics_distributions",
    "start": "2023-01-02T03:04:05+00:00",
    "end": "2023-01-16T03:04:05+00:00",
    "rollup": {
      "granularity": 3600,
      "interval": 3600,
      "with_totals": null
    },
    "scope": {
      "org_ids": [1],
      "project_ids": [11],
      "use_case_id": "transactions"
    },
    "limit": 100,
    "offset": 0
  }
}

Timeseries

The core building block of MQL is the timeseries, which represents an aggregation over a metric.

Basic Syntax

aggregate(metric_name)
sum(c:transactions/count_per_root_project@none)

Metric Identifiers

Metrics can be referenced by:
  • Public name: transaction.duration
  • MRI (Metric Resource Identifier): d:transactions/duration@millisecond
The MRI format is:
type:namespace/name@unit
Where:
  • type: c (counter), d (distribution), s (set), g (gauge)
  • namespace: Metric category (e.g., transactions, sessions)
  • name: Metric name
  • unit: Unit of measurement

Aggregation Functions

  • sum() - Sum of all values
  • count() - Count of data points
  • avg() - Average value
  • min() - Minimum value
  • max() - Maximum value

Filtering

Filter timeseries by tag values using curly braces:
sum(transaction.duration){environment:prod}

Multiple Filters

Use commas to combine filters (AND logic):
sum(transaction.duration){environment:prod, status_code:200}

Negation

Use ! to negate a filter:
sum(transaction.duration){!status_code:500}

Wildcard Matching

Use * for wildcard patterns:
sum(transaction.duration){transaction:"/api/*"}

Examples

sum(transaction.duration){environment:prod}

Grouping

Group results by tag values using by:
sum(transaction.duration) by status_code

Multiple Groups

sum(transaction.duration) by status_code, environment

Combining Filters and Grouping

sum(transaction.duration){environment:prod} by status_code, transaction

Formulas

Combine multiple timeseries with arithmetic operations.

Arithmetic Operators

  • + Addition
  • - Subtraction
  • * Multiplication
  • / Division

Examples

count(transaction.duration){!status_code:200} / count(transaction.duration)

Functions

MQL supports custom functions for advanced calculations.

Apdex

Calculate Apdex score with a target threshold:
apdex(sum(transaction.duration), 300)
Parameters:
  • First argument: Duration timeseries
  • Second argument: Target threshold in milliseconds
Apdex score formula:
(satisfied + tolerated/2) / total
Where:
  • Satisfied: duration ≤ threshold
  • Tolerated: threshold < duration ≤ 4 * threshold
  • Frustrated: duration > 4 * threshold

Combining Functions with Filters

apdex(sum(transaction.duration) + sum(transaction.measurements.fcp), 300){environment:prod} by status_code

Context Object

The mql_context object provides essential metadata for the query.

Time Range

start
string
required
ISO 8601 timestamp for query start time
end
string
required
ISO 8601 timestamp for query end time
{
  "start": "2023-01-02T03:04:05+00:00",
  "end": "2023-01-16T03:04:05+00:00"
}

Rollup

Controls time-based grouping and totals.
rollup.interval
integer
Time interval for grouping results in seconds (e.g., 3600 for hourly)
rollup.granularity
integer
Storage granularity in seconds (inferred from interval and time range)
rollup.with_totals
boolean
If interval is set, determines whether to include a totals row. If interval is not set, returns a single aggregated value instead of a timeseries
rollup.orderby
string
Column to order by when returning single values (no interval)
{
  "rollup": {
    "interval": 3600,
    "granularity": 3600,
    "with_totals": true
  }
}

Scope

Defines the organizational scope of the query.
scope.org_ids
array
required
List of organization IDs to query
scope.project_ids
array
required
List of project IDs to query
scope.use_case_id
string
required
Use case identifier (e.g., “transactions”, “sessions”, “performance”)
{
  "scope": {
    "org_ids": [1],
    "project_ids": [11, 12, 13],
    "use_case_id": "transactions"
  }
}

Limits

limit
integer
default:"100"
Maximum number of rows to return
offset
integer
default:"0"
Number of rows to skip

Entity

entity
string
required
Metrics entity to query:
  • generic_metrics_counters
  • generic_metrics_distributions
  • generic_metrics_sets
  • generic_metrics_gauges

Complete Examples

Transaction Error Rate Over Time

{
  "mql": "count(transaction.duration){!status_code:200} / count(transaction.duration)",
  "mql_context": {
    "entity": "generic_metrics_distributions",
    "start": "2023-01-01T00:00:00+00:00",
    "end": "2023-01-02T00:00:00+00:00",
    "rollup": {
      "interval": 3600,
      "granularity": 3600,
      "with_totals": false
    },
    "scope": {
      "org_ids": [1],
      "project_ids": [11],
      "use_case_id": "transactions"
    },
    "limit": 1000
  }
}

P95 Latency by Status Code

{
  "mql": "quantiles(0.95)(transaction.duration){environment:prod} by status_code",
  "mql_context": {
    "entity": "generic_metrics_distributions",
    "start": "2023-01-01T00:00:00+00:00",
    "end": "2023-01-02T00:00:00+00:00",
    "rollup": {
      "interval": 300,
      "granularity": 60
    },
    "scope": {
      "org_ids": [1],
      "project_ids": [11, 12],
      "use_case_id": "transactions"
    },
    "limit": 100
  }
}

Apdex Score by Transaction

{
  "mql": "apdex(sum(transaction.duration), 300) by transaction",
  "mql_context": {
    "entity": "generic_metrics_distributions",
    "start": "2023-01-01T00:00:00+00:00",
    "end": "2023-01-02T00:00:00+00:00",
    "rollup": {
      "granularity": 60,
      "with_totals": true,
      "orderby": "aggregate_value"
    },
    "scope": {
      "org_ids": [1],
      "project_ids": [11],
      "use_case_id": "transactions"
    },
    "limit": 50
  }
}

Unique Users Across Environments

{
  "mql": "uniq(s:transactions/user@none) by environment",
  "mql_context": {
    "entity": "generic_metrics_sets",
    "start": "2023-01-01T00:00:00+00:00",
    "end": "2023-01-08T00:00:00+00:00",
    "rollup": {
      "interval": 86400,
      "granularity": 3600
    },
    "scope": {
      "org_ids": [1],
      "project_ids": [11],
      "use_case_id": "transactions"
    },
    "limit": 100
  }
}

Response Format

MQL queries return data in a timeseries format:
{
  "data": [
    {
      "time": "2023-01-01T00:00:00+00:00",
      "aggregate_value": 123.45,
      "status_code": "200"
    },
    {
      "time": "2023-01-01T01:00:00+00:00",
      "aggregate_value": 145.67,
      "status_code": "200"
    }
  ],
  "meta": [...],
  "timing": {...},
  "stats": {...}
}
When grouped by tags, each unique tag combination appears in separate rows.

Using the Python SDK

The Snuba SDK provides Python classes for building MQL queries:
from datetime import datetime, timedelta
from snuba_sdk import (
    Column,
    Condition,
    Metric,
    MetricsQuery,
    MetricsScope,
    Op,
    Rollup,
    Timeseries,
)

query = MetricsQuery(
    query=Timeseries(
        metric=Metric(
            public_name="transaction.duration",
            mri="d:transactions/duration@millisecond",
        ),
        aggregate="quantiles(0.95)",
        filters=[
            Condition(Column("environment"), Op.EQ, "prod")
        ],
        groupby=[Column("status_code")],
    ),
    start=datetime(2023, 1, 1),
    end=datetime(2023, 1, 2),
    rollup=Rollup(interval=3600, granularity=3600),
    scope=MetricsScope(
        org_ids=[1],
        project_ids=[11],
        use_case_id="transactions",
    ),
    limit=100,
)

Best Practices

  • Use appropriate aggregations: Match aggregation function to metric type
  • Keep time ranges reasonable: Smaller ranges are more performant
  • Leverage filters: Filter at query time rather than post-processing
  • Use appropriate granularity: Match to your storage configuration
  • Limit cardinality in grouping: High-cardinality groups can be expensive
  • Cache results when possible: Repeated queries benefit from caching
  • Use MRI when available: More specific than public names
Queries across different use case IDs are not supported. All metrics in a single query must share the same use case ID.

Common Patterns

Failure Rate

count(transaction.duration){status_code:500} / count(transaction.duration)

Success Rate

count(transaction.duration){status_code:200} / count(transaction.duration)

Latency Breakdown

sum(transaction.duration) + sum(transaction.measurements.fcp) + sum(transaction.measurements.lcp)

Environment Comparison

sum(transaction.duration){environment:prod} / sum(transaction.duration){environment:staging}

Error Budget

1 - (count(transaction.duration){status_code:500} / count(transaction.duration))

Next Steps

SnQL Syntax

Learn SnQL for event queries

Query Optimization

Optimize your MQL queries

Build docs developers (and LLMs) love