CDN Bandwidth Analytics Dashboard Design: Visualizing Traffic Patterns That Matter

I got paged at 2:47 AM last winter because a major e-commerce client's bandwidth chart flatlined, then spiked to 40 Gbps. Their CDN dashboard showed green across the board, so their ops team assumed it was an attack we weren't blocking. It wasn't. It was a misconfigured image optimizer origin that started serving uncompressed 12MB TIFFs to every edge, and our pretty charts had smoothed the anomaly right out of existence.

The Aggregation Trap: Why Your 5-Minute Averages Are Lying to You

Early in my career, I built what I thought was a beautiful bandwidth analytics panel. Smooth lines, 5-minute averages, auto-scaling Y-axis. My manager loved it. Customers hated it. The problem wasn't the design — it was that averaging hides exactly what you need to see. When you average bandwidth over five minutes, a 30-second burst that saturates a 100G link and causes packet loss looks like a gentle bump. I've seen teams chase phantom capacity issues for weeks because their dashboard design was literally averaging the evidence away.

That's the first hard lesson in traffic visualization: bandwidth isn't a single number, it's a distribution. For any serious CDN dashboard, you can't just plot sum(bytes) / interval. You need to show percentiles, peaks, and the spread across PoPs. A client serving video will have a completely different bandwidth profile than one serving API responses, and if your dashboard treats them the same, you're not helping anyone make decisions. You'll want to understand how your logs are collected and rolled up before you draw a single chart, which is why reading up on CDN Log Aggregation Architecture: Collecting Billions of Events Without Dropping Data is critical before you touch visualization.

What I do now is store raw byte counts at 10-second granularity at the edge, then compute percentiles at query time instead of pre-aggregating averages. Yes, it costs more storage. But when you're trying to figure out if you need to add capacity in Ashburn or if a single large file is skewing your numbers, you need that resolution. If storage costs worry you, sampling can help — but you have to sample intelligently, as covered in CDN Log Sampling Strategies: Reducing Storage Costs Without Losing Signal — random sampling will destroy your ability to see tail events.

-- Calculate true bandwidth percentiles per PoP, not just averages
-- Uses 10s edge counters, weighted by request count for accuracy
SELECT
  toStartOfMinute(timestamp) AS minute,
  pop_id,
  quantilesExactWeighted(0.50, 0.95, 0.99)(bandwidth_mbps, request_count) AS bandwidth_percentiles,
  max(bandwidth_mbps) AS peak_mbps,
  sum(bytes_served) / 60 / 125000 AS avg_mbps -- bytes to megabits per second
FROM cdn_edge_bandwidth_10s
WHERE timestamp >= now() - INTERVAL 6 HOUR
  AND bytes_served > 0
GROUP BY minute, pop_id
ORDER BY minute ASC
-- Compare avg_mbps vs peak_mbps: if peak is >3x avg, you're hiding bursts

Structuring the Data Layer for Real-Time Traffic Visualization

Here's where most teams mess up their CDN dashboard: they try to visualize directly from their log pipeline without a dedicated serving layer. I've watched dashboards grind to a halt during major traffic spikes — exactly when you need them most — because every panel was running a full scan over raw logs. Your bandwidth analytics pipeline needs two distinct paths. One for real-time, low-latency visualization (last 6-24 hours), and one for historical analysis where you can tolerate a few minutes of delay for better compression and accuracy.

For the real-time path, we push edge counters every 10 seconds via a streaming buffer into ClickHouse, pre-aggregated by PoP, cache status, and content type. That gives us sub-5-second freshness for the current traffic view without hammering our origin log store. The historical path lands in object storage, gets de-duplicated, and is optimized for large range scans. This split is what makes a dashboard feel instant instead of sluggish. If you haven't built this yet, start with CDN Real-Time Log Streaming Architecture: From Edge Events to Analytics in Seconds — you can't visualize what you can't deliver quickly.

I learned this the hard way during a Super Bowl event in 2019. Our dashboard was polling a Postgres replica that also served billing queries. At kickoff, bandwidth tripled, Postgres fell over, and our NOC was blind. Now we isolate analytics storage completely and use materialized views for the common rollups. We also add cache-status breakdowns to every bandwidth chart, because per the HTTP Working Group Specifications and caching guidance in web.dev Articles, a hit and a miss have totally different implications for origin cost, even if they look identical on a total bandwidth graph. You don't want to celebrate a bandwidth drop that was actually a cache purge failure.

// Frontend transform: normalize bandwidth for stacked area chart
// Expects rows: { timestamp, pop_id, cache_status, bytes_served }
function buildBandwidthSeries(rows, intervalSeconds = 10) {
  const grouped = new Map();
  for (const r of rows) {
    const key = r.timestamp;
    if (!grouped.has(key)) grouped.set(key, { hit: 0, miss: 0, revalidated: 0 });
    const bytesToMbps = (r.bytes_served * 8) / (intervalSeconds * 1e6);
    grouped.get(key)[r.cache_status] += bytesToMbps;
  }
  // Return sorted series for visualization library
  return Array.from(grouped.entries())
    .sort((a, b) => a[0] - b[0])
    .map(([time, vals]) => ({ time, ...vals, total: vals.hit + vals.miss + vals.revalidated }));
}

Choosing Visuals That Actually Help You Make Decisions at 2 AM

When you're half-awake and trying to decide whether to fail over a PoP, you don't want a dashboard that makes you think. That 2 AM image optimizer incident I mentioned? The dashboard used a single smoothed line for global bandwidth. It looked calm. What we needed was a stacked area by cache status, a separate panel for 95th percentile vs average, and a heatmap of bandwidth by PoP. After we rebuilt it, the next origin misconfiguration was obvious within 45 seconds — the miss bandwidth (origin egress) shot up bright orange while hit bandwidth stayed flat.

For bandwidth analytics, I've settled on a small set of visualizations that work together. Don't try to cram everything into one chart. Use a primary time-series for total and cache-split bandwidth, a secondary chart for percentile bands, and a distribution view (heatmap or box plot) for geographic spread. And please, stop using pie charts for bandwidth by PoP or content type — you can't see trends in a pie. A horizontal bar that updates in real-time or a treemap works far better when you're comparing 30+ PoPs.

Your refresh rates and time windows matter as much as the chart type. Real-time panels should update every 5-10 seconds with a 1-3 hour window. Historical panels can be 1-minute or 5-minute resolution over 7-30 days. If you use the same granularity for both, you'll either overwhelm the browser with data points or hide the short bursts that cause rebuffering and TCP backoff. The table below is the cheat sheet I give every new engineer on our team when they're tasked with dashboard design.

Panel Purpose Recommended Visualization Granularity / Refresh Key Metric to Plot Common Pitfall to Avoid
Current Traffic Health Stacked Area (hit / miss / pass) 10s / 5s refresh, 3-hour window Mbps by cache_status Smoothing with averages hides origin spikes
Capacity & Burst Detection Line with Percentile Band (p50, p95, p99) 1 min / 10s refresh, 6-hour window p95 and max Mbps vs avg Using avg alone masks 30s saturations
Geographic Distribution Heatmap by PoP (time vs PoP) 1 min / 30s refresh, 12-hour window Mbps per PoP, normalized Absolute values hide regional imbalances
Content Type Breakdown Horizontal Bar (sorted) 1 min / 30s refresh, 1-hour window % of total bandwidth by content-type Pie charts obscure trends over time
Cost & Origin Egress Separate Line (miss + revalidated only) 5 min / 1 min refresh, 7-day window Origin Mbps and $/hour estimate Mixing hit traffic inflates cost perception

Building the Live Pipeline: From Edge Logs to Dashboard in Under 3 Seconds

Here's where most teams stall — they've got the right visuals mocked up, but the pipeline behind it can't keep up. If your dashboard lags by 30-45 seconds, you're not doing real-time analytics, you're doing recent-history review. For bandwidth, that delay means you miss the burst that actually matters. We target end-to-end latency of under 3 seconds from edge POP to visualization, and it's absolutely achievable without throwing absurd hardware at the problem.

The pattern that works consistently for us is edge aggregation before centralization. Don't ship raw log lines to Kafka and try to sort it out centrally — you'll drown. Instead, run a lightweight aggregator (we use Vector on each POP) that rolls per-second counters into 5-second buckets keyed by POP, cache status, ASN, and content type. That drops your ingest volume by roughly 85-90% before it ever hits your ClickHouse or Druid cluster. From there, a 1-second flush to a central Kafka topic keeps the dashboard hot, while a second, batched 60-second rollup feeds your long-term storage. You're not losing fidelity, you're just not paying to transmit every single 200 OK in real time.

Once it's centralized, materialize your percentiles. Don't calculate p95 on the fly over raw events — pre-aggregate it. We've seen query times drop from 4.2 seconds to under 180ms just by moving from on-the-fly quantile calculations to materialized views with 5-second granularity. That’s the difference between a dashboard you trust at 2 AM and one you refresh three times hoping it loads.

-- Materialized view for 5-second percentile rollups in ClickHouse
CREATE MATERIALIZED VIEW cdn.bandwidth_5s_mv TO cdn.bandwidth_5s AS
SELECT
    toStartOfInterval(timestamp, INTERVAL 5 SECOND) AS ts,
    pop_id,
    asn,
    cache_status,
    quantileExactWeighted(0.95)(mbps, req_count) AS p95_mbps,
    quantileExactWeighted(0.99)(mbps, req_count) AS p99_mbps,
    avg(mbps) AS avg_mbps,
    sum(bytes_served) AS total_bytes,
    sum(req_count) AS total_reqs
FROM cdn.edge_aggregated
GROUP BY ts, pop_id, asn, cache_status;

-- Query for dashboard: p95 vs avg to expose hidden bursts
SELECT ts, p95_mbps, avg_mbps, (p95_mbps / avg_mbps) AS burst_ratio
FROM cdn.bandwidth_5s
WHERE ts >= now() - INTERVAL 1 HOUR AND pop_id = 'iad-03'
ORDER BY ts WITH FILL STEP 5;

Advanced Patterns: Correlating Spikes With What Actually Caused Them

A bandwidth graph by itself just tells you *that* you spiked. It doesn't tell you *why*, and that's where 90% of dashboards fail an incident review. The mature setup overlays bandwidth with the behavioral signals that explain it. When we see a 2.3 Tbps spike in us-east, we immediately want to know: was it a cache purge gone wrong, a bot scrape, or a legitimate viral event? You can't answer that without joining traffic data to request intent.

This is where you need to pull in edge-derived behavioral analytics. Instead of relying on client-side JS that ad blockers kill, enrich your bandwidth pipeline with navigation patterns derived directly from CDN logs. For example, correlating a 40% bandwidth surge with a flat conversion rate often points to non-human traffic. Our team links bandwidth percentiles to Clickstream Analysis at CDN Edge: Understanding User Navigation Patterns to distinguish between a legitimate product launch and a scraper hammering uncached API endpoints. We've caught scrapers pushing 800k req/min that barely moved the 5-minute average but blew out p99 bandwidth by 320% — invisible until we joined the two datasets. Similarly, layering in User Journey Analytics from CDN Logs: Mapping the Path to Conversion lets you see if that traffic is actually progressing through funnel stages or just bouncing off a single heavy asset.

The other correlation you can't skip is failure mode. Bandwidth and error rates are coupled, but most teams chart them separately. Add an overlay for origin offload ratio and 5xx rate directly on your bandwidth chart. If p99 bandwidth and origin offload both jump from 78% to 98% in the same 30-second window, you're not looking at more users — you're looking at a cache key collapse. Once you see it, solving it is 10 minutes of work; without that overlay, you'll chase it for two hours. And if you're doing any revenue-adjacent work, tie it to Conversion Attribution from CDN Edge: First-Party Tracking Without Client-Side Scripts so you can prove to the business that the 15-minute degradation that spiked bandwidth actually cost $22k in attributed conversions. That's how you get budget for the fix.

Frequently Asked Questions

Why does my p95 bandwidth look normal when users are complaining about rebuffering?

Because you're probably averaging over too wide a window or across too many POPs. A p95 calculated over 5 minutes across 15 POPs will hide a 45-second, single-POP event that hammered rebuffer rates for 12% of your users. Shrink your bucket to 5-10 seconds, filter by POP and ASN, and compare p95 to p50. If that burst ratio exceeds 2.5x, you've found your ghost event. Don't trust global aggregates for regional pain.

How much granularity do I actually need to retain for long-term capacity planning?

You don't need per-second data for 90 days. What we do is tiered retention: 5-second rollups for 48 hours (for incident review), 1-minute p95/p99 for 30 days, and 5-minute aggregates for 13 months. That keeps storage under 2.1 TB per 100 Tbps of edge traffic while still letting you spot month-over-month growth in p99 — which is what you size your commits and transit on, not the average. Averages will make you under-provision by 25-30%.

Should I alert on average bandwidth or percentile bandwidth?

Alert on percentile, specifically p95 or p99 over a 1-minute window. Average-based alerts are too noisy or too late. We set dynamic thresholds: alert if 1-minute p95 exceeds the trailing 7-day p95 for that same hour by 35% for more than 2 minutes. That cuts false positives by about 60% compared to static thresholds and catches real anomalies — like a 1.8 Tbps cache bypass — within 90 seconds instead of 8-10 minutes.

How do I handle bandwidth data when 40-60% of my traffic is cached?

Split it in the visualization. Always chart three lines: edge-served (cache HIT), origin-served (MISS/PASS), and total. If you only watch total, a cache efficiency drop from 92% to 85% looks like a modest 18% bandwidth increase, but it's actually a 140% increase in origin egress that will blow your origin bill and latency. We put a stacked area behind the p95 line so you can see cache vs. origin contribution at a glance.

Related Articles

References & Standards: HTTP Working Group Specifications · web.dev Articles · IETF Datatracker