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:
Query String : The MQL expression
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
Counter Metric
Distribution Metric
Set Metric
Gauge Metric
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:
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
Counter Metrics
Distribution Metrics
Set Metrics
Gauge Metrics
sum() - Sum of all values
count() - Count of data points
avg() - Average value
min() - Minimum value
max() - Maximum value
quantile(p) - Single quantile (e.g., quantile(0.95))
quantiles(p1, p2, ...) - Multiple quantiles
avg() - Average of distribution
sum() - Sum of distribution
min() - Minimum value
max() - Maximum value
count() - Count of values
uniq() - Unique count
count() - Count of sets
avg() - Average of gauge values
min() - Minimum gauge value
max() - Maximum gauge value
last() - Last recorded 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
Single Filter
Multiple Filters
Negation
Wildcard
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
Combine multiple timeseries with arithmetic operations.
Arithmetic Operators
+ Addition
- Subtraction
* Multiplication
/ Division
Examples
Failure Rate
Total Latency
Average per Transaction
Rate Comparison
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
ISO 8601 timestamp for query start time
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.
Time interval for grouping results in seconds (e.g., 3600 for hourly)
Storage granularity in seconds (inferred from interval and time range)
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
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.
List of organization IDs to query
List of project IDs to query
Use case identifier (e.g., “transactions”, “sessions”, “performance”)
{
"scope" : {
"org_ids" : [ 1 ],
"project_ids" : [ 11 , 12 , 13 ],
"use_case_id" : "transactions"
}
}
Limits
Maximum number of rows to return
Entity
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
}
}
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