xiaohongshu
xiaohongshu
analyzing-tdigest-metrics
Analyze percentile metrics (tdigest type) using OPAL for latency analysis and SLO tracking. Use when calculating p50, p95, p99 from pre-aggregated duration or latency metrics. Covers the critical double-combine pattern with align + m_tdigest() + tdigest_combine + aggregate. For simple metrics (co...
Full skill instructions
TDigest metrics in Observe store pre-aggregated percentile data for efficient latency and duration analysis. This skill teaches the specialized pattern for querying tdigest metrics using OPAL.
tdigesttdigestdiscover_context() to find and inspect tdigest metricsTDigest (t-digest) is a probabilistic data structure for estimating percentiles efficiently:
Pre-aggregated percentile data: Not raw values, but compressed statistical summaries
Why percentiles matter:
Common Examples:
span_sn_service_node_duration_tdigest_5m - Service-to-service latency percentilesspan_sn_service_edge_duration_tdigest_5m - Edge latency percentilesrequest_duration_tdigest_5m - Request duration percentilesdatabase_query_duration_tdigest_5m - Database query latency percentilesTDigest metrics require a special pattern that's different from gauge metrics:
# WRONG - Missing second combine ❌
align options(bins: 1), combined:tdigest_combine(m_tdigest("metric"))
aggregate p95:tdigest_quantile(combined, 0.95)
# CORRECT - Double-combine pattern ✅
align options(bins: 1), combined:tdigest_combine(m_tdigest("metric"))
aggregate p95:tdigest_quantile(tdigest_combine(combined), 0.95)
Why the double combine?
tdigest_combine (in align): Combines tdigest data points within time bucketstdigest_combine (in aggregate): Re-combines tdigests across groups/dimensionstdigest_quantile: Calculates the actual percentile valuePattern breakdown:
align options(bins: 1),
combined:tdigest_combine(m_tdigest("metric_name")) ← First combine
aggregate p95:tdigest_quantile(
tdigest_combine(combined), ← Second combine (NESTED!)
0.95), ← Quantile value (0.0-1.0)
group_by(service_name)
Percentiles are specified as decimal values from 0.0 to 1.0:
| Percentile | Value | Meaning |
|---|---|---|
| p50 (median) | 0.50 | 50% of values are below this |
| p75 | 0.75 | 75% of values are below this |
| p90 | 0.90 | 90% of values are below this |
| p95 | 0.95 | 95% of values are below this |
| p99 | 0.99 | 99% of values are below this |
| p99.9 | 0.999 | 99.9% of values are below this |
Common SLO targets: p95 < 500ms, p99 < 1000ms
| Output Type | Pattern | Result | Pipe? |
|---|---|---|---|
| Summary | options(bins: 1) | One row per group | NO | |
| Time-Series | 5m, 1h | Many rows per group | YES | |
Step 1: Search for tdigest metrics
discover_context("duration tdigest", result_type="metric")
discover_context("latency percentile", result_type="metric")
Step 2: Get detailed metric schema
discover_context(metric_name="span_sn_service_node_duration_tdigest_5m")
Step 3: Verify metric type
Look for: Type: tdigest (critical!)
Step 4: Note available dimensions
Used for group_by():
service_name, for_service_nameenvironment, for_environmentStep 5: Write query Use double-combine pattern with correct dimensions
Calculate percentiles across all data:
align options(bins: 1), combined:tdigest_combine(m_tdigest("span_sn_service_node_duration_tdigest_5m"))
aggregate p50:tdigest_quantile(tdigest_combine(combined), 0.50),
p95:tdigest_quantile(tdigest_combine(combined), 0.95),
p99:tdigest_quantile(tdigest_combine(combined), 0.99)
Output: Single row with overall p50, p95, p99 across entire time range.
Note: Both combines present, no group_by.
Calculate percentiles broken down by dimension:
align options(bins: 1), combined:tdigest_combine(m_tdigest("span_sn_service_node_duration_tdigest_5m"))
aggregate p50:tdigest_quantile(tdigest_combine(combined), 0.50),
p95:tdigest_quantile(tdigest_combine(combined), 0.95),
p99:tdigest_quantile(tdigest_combine(combined), 0.99),
group_by(service_name)
Output: One row per service with percentiles.
Get just p95 for SLO tracking:
align options(bins: 1), combined:tdigest_combine(m_tdigest("span_sn_service_node_duration_tdigest_5m"))
aggregate p95:tdigest_quantile(tdigest_combine(combined), 0.95),
group_by(service_name)
| sort desc(p95)
| limit 10
Output: Top 10 services by p95 latency.
Use case: Identify slowest services for optimization.
TDigest values are often in nanoseconds - convert for readability:
align options(bins: 1), combined:tdigest_combine(m_tdigest("span_sn_service_node_duration_tdigest_5m"))
aggregate p50_ns:tdigest_quantile(tdigest_combine(combined), 0.50),
p95_ns:tdigest_quantile(tdigest_combine(combined), 0.95),
p99_ns:tdigest_quantile(tdigest_combine(combined), 0.99),
group_by(service_name)
| make_col p50_ms:p50_ns / 1000000,
p95_ms:p95_ns / 1000000,
p99_ms:p99_ns / 1000000
Output: Percentiles in both nanoseconds and milliseconds.
Note: Check sample values in discover_context() to identify units.
Track percentiles over time buckets:
align 5m, combined:tdigest_combine(m_tdigest("span_sn_service_node_duration_tdigest_5m"))
| aggregate p95:tdigest_quantile(tdigest_combine(combined), 0.95),
group_by(service_name)
Output: Multiple rows per service (one per 5-minute interval).
Note: Pipe | required for time-series pattern.
Use case: Dashboard charts showing latency trends over time.
align options(bins: 1), combined:tdigest_combine(m_tdigest("span_sn_service_node_duration_tdigest_5m"))
aggregate p95_ns:tdigest_quantile(tdigest_combine(combined), 0.95),
group_by(service_name)
| make_col p95_ms:p95_ns / 1000000
| make_col slo_target:500,
meets_slo:if(p95_ms < 500, "yes", "no")
| sort desc(p95_ms)
Use case: Check which services meet p95 < 500ms SLO target.
Output: Services with SLO compliance status.
align options(bins: 1), combined:tdigest_combine(m_tdigest("span_sn_service_node_duration_tdigest_5m"))
aggregate p50:tdigest_quantile(tdigest_combine(combined), 0.50),
p75:tdigest_quantile(tdigest_combine(combined), 0.75),
p90:tdigest_quantile(tdigest_combine(combined), 0.90),
p95:tdigest_quantile(tdigest_combine(combined), 0.95),
p99:tdigest_quantile(tdigest_combine(combined), 0.99),
group_by(service_name)
| make_col p50_ms:p50 / 1000000,
p95_ms:p95 / 1000000,
p99_ms:p99 / 1000000
Use case: Understand full latency distribution to identify outliers.
Insight: Large gap between p95 and p99 indicates inconsistent performance.
align options(bins: 1), combined:tdigest_combine(m_tdigest("span_sn_service_node_duration_tdigest_5m"))
aggregate p95:tdigest_quantile(tdigest_combine(combined), 0.95),
group_by(service_name)
| make_col p95_ms:p95 / 1000000
| sort desc(p95_ms)
| limit 10
Use case: Find slowest services to prioritize optimization efforts.
align 5m, combined:tdigest_combine(m_tdigest("span_sn_service_node_duration_tdigest_5m"))
| aggregate p95:tdigest_quantile(tdigest_combine(combined), 0.95),
group_by(service_name)
| filter service_name = "frontend"
| make_col p95_ms:p95 / 1000000
Use case: See when latency spiked during an incident.
Output: Timeline of p95 latency for specific service.
align options(bins: 1), combined:tdigest_combine(m_tdigest("span_sn_service_node_duration_tdigest_5m"))
aggregate p95:tdigest_quantile(tdigest_combine(combined), 0.95),
group_by(service_name, environment)
| make_col p95_ms:p95 / 1000000
| sort desc(p95_ms)
Use case: Compare latency across services AND environments.
Scenario: You're tracking SLOs for your microservices. The target is p95 latency < 500ms and p99 latency < 1000ms for all production services.
Step 1: Discover tdigest metrics
discover_context("duration tdigest", result_type="metric")
Found: span_sn_service_node_duration_tdigest_5m (type: tdigest)
Step 2: Get metric details
discover_context(metric_name="span_sn_service_node_duration_tdigest_5m")
Available dimensions: service_name, environment, for_service_name
Step 3: Query for SLO compliance
align options(bins: 1), combined:tdigest_combine(m_tdigest("span_sn_service_node_duration_tdigest_5m"))
aggregate p95_ns:tdigest_quantile(tdigest_combine(combined), 0.95),
p99_ns:tdigest_quantile(tdigest_combine(combined), 0.99),
group_by(service_name, environment)
| make_col p95_ms:p95_ns / 1000000,
p99_ms:p99_ns / 1000000
| make_col p95_slo:if(p95_ms < 500, "✓", "✗"),
p99_slo:if(p99_ms < 1000, "✓", "✗")
| filter environment = "production"
| sort desc(p95_ms)
Step 4: Interpret results
| service_name | environment | p95_ms | p99_ms | p95_slo | p99_slo |
|---|---|---|---|---|---|
| frontend | production | 19373.5 | 5641328.2 | ✗ | ✗ |
| featureflagservice | production | 5838.8 | 7473.9 | ✗ | ✗ |
| cartservice | production | 4136.6 | 5898.3 | ✗ | ✗ |
| productcatalogservice | production | 257.0 | 313.1 | ✓ | ✓ |
| currencyservice | production | 54.1 | 125.1 | ✓ | ✓ |
Insight: Frontend, featureflagservice, and cartservice are violating SLOs - need optimization.
Step 5: Investigate frontend latency over time
align 1h, combined:tdigest_combine(m_tdigest("span_sn_service_node_duration_tdigest_5m"))
| aggregate p95:tdigest_quantile(tdigest_combine(combined), 0.95),
p99:tdigest_quantile(tdigest_combine(combined), 0.99),
group_by(service_name)
| filter service_name = "frontend"
| make_col p95_ms:p95 / 1000000, p99_ms:p99 / 1000000
Output: Hourly p95/p99 trends to identify when latency degraded.
❌ Wrong (most common mistake):
align options(bins: 1), combined:tdigest_combine(m_tdigest("metric"))
aggregate p95:tdigest_quantile(combined, 0.95)
✅ Correct:
align options(bins: 1), combined:tdigest_combine(m_tdigest("metric"))
aggregate p95:tdigest_quantile(tdigest_combine(combined), 0.95)
Why: TDigest requires combining twice - once in align, once in aggregate.
Error message: "the field has to be aggregated or grouped"
❌ Wrong:
align options(bins: 1), combined:tdigest_combine(m("duration_tdigest_5m"))
✅ Correct:
align options(bins: 1), combined:tdigest_combine(m_tdigest("duration_tdigest_5m"))
Why: Tdigest metrics require m_tdigest() function, not m().
Check: Look for Type: tdigest in discover_context() output.
❌ Wrong (pipe with bins:1):
align options(bins: 1), combined:tdigest_combine(m_tdigest("metric"))
| aggregate p95:tdigest_quantile(tdigest_combine(combined), 0.95)
✅ Correct:
# Summary - NO pipe
align options(bins: 1), combined:tdigest_combine(m_tdigest("metric"))
aggregate p95:tdigest_quantile(tdigest_combine(combined), 0.95)
# Time-series - YES pipe
align 5m, combined:tdigest_combine(m_tdigest("metric"))
| aggregate p95:tdigest_quantile(tdigest_combine(combined), 0.95)
❌ Wrong:
aggregate p95:tdigest_quantile(tdigest_combine(combined), 95)
✅ Correct:
aggregate p95:tdigest_quantile(tdigest_combine(combined), 0.95)
Why: Quantile values must be 0.0 to 1.0 (not 1 to 100).
❌ Wrong (values in nanoseconds, hard to read):
aggregate p95:tdigest_quantile(tdigest_combine(combined), 0.95)
Result: p95 = 14675991.25 (what unit is this?)
✅ Correct (convert to milliseconds):
aggregate p95_ns:tdigest_quantile(tdigest_combine(combined), 0.95)
| make_col p95_ms:p95_ns / 1000000
Result: p95_ms = 14.68 (clearly milliseconds)
Tip: Check sample values in discovery to identify units (19-digit numbers = nanoseconds).
Common percentiles and their meanings:
| Percentile | Decimal | Meaning | Common Use |
|---|---|---|---|
| p50 | 0.50 | Median (middle value) | Typical user experience |
| p75 | 0.75 | 75th percentile | Better than average case |
| p90 | 0.90 | 90th percentile | Catching most outliers |
| p95 | 0.95 | 95th percentile | Standard SLO target |
| p99 | 0.99 | 99th percentile | Tail latency / worst 1% |
| p99.9 | 0.999 | 99.9th percentile | Extreme outliers |
SLO best practice: Track p95 and p99, not just averages.
Common time unit conversions (assuming nanoseconds):
# Nanoseconds to milliseconds (most common)
make_col value_ms:value_ns / 1000000
# Nanoseconds to seconds
make_col value_sec:value_ns / 1000000000
# Nanoseconds to microseconds
make_col value_us:value_ns / 1000
How to identify units: Check sample values in discover_context():
tdigest (not gauge)sort desc() | limit 10TDigest metrics enable efficient percentile calculations:
align + m_tdigest() + double tdigest_combine + tdigest_quantiletdigest_combine() TWICE (in align AND in aggregate)m_tdigest() (NOT m())Key distinction: TDigest metrics use special double-combine pattern, while gauge metrics use simple m() + aggregate.
Last Updated: November 14, 2025 Version: 1.0 Tested With: Observe OPAL (ServiceExplorer/Service Inspector Metrics)
xiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience