Metric · Support

What is ticket backlog, and how do you measure it in Metabase?

Ticket backlog is the number of open tickets at a point in time, trended daily. It's a stock where ticket volume is a flow: volume tells you what arrived, backlog tells you what's still waiting — and how long it's been waiting, which is the part customers feel. Measure it in Metabase from help desk data synced into a database (Zendesk, Intercom, Kustomer, or Front).

TL;DR — trend open tickets daily and stack them by age bucket. The count says how busy you are; the age distributionsays whether you're keeping up. A flat backlog of fresh tickets is fine; a flat backlog of 30-day-old tickets is a quiet crisis.

What ticket backlog measures

It measures the queue's standing debt, and it's best read three ways at once:

  • The level — open tickets at end of day, trended. Good for spotting step changes after launches, outages, or staffing gaps.
  • The flow — created minus solved per day. Backlog is just the running sum of this delta, so a persistently positive gap is the earliest warning that the level is about to climb.
  • The age distribution — open tickets bucketed 0-2d, 3-7d, 8-30d, 30d+. This is the view that separates "busy" from "drowning," and the 30d+ bucket deserves its own card and its own target.

Backlog also feeds capacity planning: excess backlog divided by spare daily solve capacity is your burn-down time, and if solved-per-day doesn't exceed created-per-day, the honest answer is "never" — which is the number that justifies hiring or a deflection investment.

What data does it need?

  • A tickets table with created_at, solved_at, status, and queue — enough to reconstruct "open on day X" as created on or before X and not yet solved.
  • History that survives the sync. A pipeline that mirrors current state only — purging closed tickets or overwriting statuses without timestamps — cannot reconstruct past backlog. In that case, write a daily snapshot of the open count to its own table and trend that.
  • Optionally a status-history table, so "pending on customer" tickets can be excluded from the backlog you hold agents accountable for.

SQL patterns

Daily backlog trendPostgreSQL

Reconstructs open tickets per day from created/solved timestamps — note the caveat about current-state-only syncs.

-- Daily backlog: tickets open at the end of each day, reconstructed
-- from created/solved timestamps.
-- Caveat: this only works if solved_at survives in your sync. A sync that
-- mirrors current state (and hard-deletes or overwrites history) cannot
-- reconstruct past backlog -- in that case, snapshot the open count daily
-- into its own table and trend that instead.
SELECT
  d.day,
  COUNT(t.id) AS open_tickets
FROM generate_series(
  current_date - INTERVAL '90 days',
  current_date,
  INTERVAL '1 day'
) AS d(day)
-- d.day is midnight (the START of the day), so compare against the next
-- midnight to count what was still open at the END of the day -- otherwise
-- a ticket created and solved the same day never shows up in the backlog.
LEFT JOIN tickets t
  ON t.created_at < d.day + INTERVAL '1 day'
 AND (t.solved_at IS NULL OR t.solved_at >= d.day + INTERVAL '1 day')
GROUP BY d.day
ORDER BY d.day;
Backlog age buckets by queuePostgreSQL

Splits today's open tickets into 0-2d, 3-7d, 8-30d, and 30d+ buckets per queue.

-- Current backlog by age bucket and queue. The 30d+ column is the one
-- that should alarm you.
SELECT
  t.queue,
  COUNT(*)                                                     AS open_tickets,
  COUNT(*) FILTER (WHERE now() - t.created_at <= INTERVAL '2 days')  AS age_0_2d,
  COUNT(*) FILTER (WHERE now() - t.created_at >  INTERVAL '2 days'
                     AND now() - t.created_at <= INTERVAL '7 days')  AS age_3_7d,
  COUNT(*) FILTER (WHERE now() - t.created_at >  INTERVAL '7 days'
                     AND now() - t.created_at <= INTERVAL '30 days') AS age_8_30d,
  COUNT(*) FILTER (WHERE now() - t.created_at >  INTERVAL '30 days') AS age_over_30d
FROM tickets t
WHERE t.status IN ('new', 'open', 'pending')
GROUP BY t.queue
ORDER BY age_over_30d DESC, open_tickets DESC;

Pitfalls

Watching the count and ignoring the age mix.→ A stable total can hide a rotting tail: fresh tickets get answered while old ones sink. Stack the age buckets on the trend chart, and put a separate target on the 30d+ bucket.
Trusting reconstructed history from a current-state sync.→ If the pipeline drops closed tickets or loses solve timestamps, the reconstructed backlog silently shrinks going back in time. Verify old tickets survive in the warehouse, or snapshot daily.
Counting customer-pending tickets as agent backlog.→ Tickets waiting on the customer inflate the queue without representing work you can do. Split them out (status history helps) so the actionable backlog is what the team is judged on.
Clearing backlog with bulk closes.→ Mass-closing old tickets makes the chart beautiful and the customers gone. Any backlog-reduction push should show up as a solved spike and a stable resolution rate — not a close spike.

Where this metric applies

Metrics

Analytics & dashboards

FAQ

Is a growing backlog always bad?
Not by itself — a backlog that grows during a launch week and burns down after is a queue doing its job. The signal is persistent divergence: created minus solved staying positive for weeks means demand structurally exceeds capacity, and no amount of heroics closes that gap. Watch the flow view (created vs solved per day, from the same data as ticket volume) alongside the level, and react to the trend, not to any single day's count.
Why does backlog age matter more than backlog size?
Because 200 tickets from yesterday is a busy Monday, while 200 tickets older than a month is 200 customers who've concluded you're not coming. Two backlogs of identical size can be a healthy queue and a graveyard. Bucket by age (0-2d, 3-7d, 8-30d, 30d+) and treat the oldest bucket as its own metric with its own target — ideally zero.
How do you calculate how long a backlog will take to clear?
Burn-down math: excess backlog ÷ spare daily capacity. If you hold 600 open tickets, consider 200 a healthy working level, solve 80 per day, and receive 70, your spare capacity is 10 per day — so the 400 excess takes about 40 working days. The sobering part is that the answer depends entirely on the created-vs-solved gap: if solved doesn't exceed created, the backlog never clears, which is the case for adding capacity or investing in deflection.
Why can't I reconstruct historical backlog from a normal sync?
You can, but only if the sync preserves both created and solved timestamps for every ticket — then "open on day X" is just created ≤ X and not yet solved by X. What breaks it: syncs that hard-delete closed tickets, help desks that purge old data, or status fields overwritten without history. If any of those apply, schedule a daily snapshot query that writes the open count (and age buckets) to its own table, and accept that the trend starts today.
How do you track ticket backlog in Metabase?
Sync your help desk into a SQL database with Airbyte, Fivetran, or a similar pipeline — Zendesk, Intercom, Kustomer, and Front all expose the timestamps you need. Chart the daily open count, stack the age buckets, add a created-vs-solved bar pair, and pin all three next to first-response time on the agent performance dashboard.