← dean's list
data engineering · 2026 · ● live

Music Growth Pipeline

Longitudinal listener tracking across weekly Last.fm snapshots. Median growth falls monotonically as artists get larger.

view on github ↗
visit site ↗

This is the actual product, not a demo — search, artist pages, leaderboards, and genre breakdowns, reading live from Postgres through a rate-limited, read-only API layer.

19 weeks of data · updated 2026-09-06
median 19-week growth by starting listener size
under 69k
3.25% avg 102.04% · p90 20.41% · n=4,442
69k–137k
2.94% avg 5.10% · p90 12.11% · n=4,441
137k–236k
2.51% avg 3.65% · p90 7.67% · n=4,441
236k–471k
2.10% avg 2.96% · p90 5.89% · n=4,441
471k+
2.04% avg 2.50% · p90 4.75% · n=4,441
rows run smallest to largest · quintiles cut on listener count at the start of the window
fastest-growing artists · 2026-09-06
under 250k starting listeners (size bands <10k through 100k–250k), 5,000+ starting — not the full tracked catalogue
ridgeclub15k → 129k+767.8%
Alyssa Grace17k → 88k+413.1%
[redacted]31k → 151k+388.3%
Laurinha Costa15k → 50k+234.3%
The Bausa24k → 79k+231.9%
Median growth falls with artist size: 3.25% for the smallest fifth (under 69k listeners) down to 2.04% for the largest (471k+). Averages run far higher (102.04% in the smallest band) — a thin tail of viral outliers pulls them up, which is why the median is the honest number. 37,435 artists tracked.
how it's built

The Last.fm API returns only cumulative all-time stats — there is no native time series. To study whether an artist's audience size correlates with listener growth over time, you have to build the longitudinal dataset yourself by snapshotting repeatedly.

Weekly ingestion pipeline snapshots listener data for artists from the Last.fm global chart into Postgres on Neon. Artists are bucketed into size quintiles by their listener count at the start of the measurement window, so growth is compared across audience size rather than chart position. A dbt transformation layer (staging + marts + a dedicated api/ serving layer, pre-joined and pre-indexed so the app never queries the marts directly) powers both cross-sectional and longitudinal analysis. That serving layer feeds a public Next.js 15 (App Router) web app, self-hosted on EC2 behind Caddy (auto-HTTPS) at music.deanslist.dev — search, artist pages with growth charts and genre/size-band/similar-artist comparisons, leaderboards, and genre breakdowns, all reading live from Postgres through a rate-limited, read-only (app_readonly) API layer. After each weekly snapshot, dbt rebuilds the marts, a stats script writes pipeline_stats.json to GitHub, and both this portfolio card and the live site pick up the fresh data.

  • Core finding: median listener growth declines monotonically with artist size — the smallest band grows roughly 1.5× the median rate of the largest
  • Averages sit well above medians in every band — a thin tail of viral breakouts skews the mean, so the median carries the finding
  • Genre signal: EDM shows highest median growth rate; classical and metal are slowest — genre appears secondary to artist size as a growth predictor
  • Standout cases: several small artists grew 100–400% over the window — growth patterns split between viral spikes and steady week-over-week acceleration
  • Caught a real security incident before shipping: a pasted read-only connection string was actually owner-authenticated, so the "read-only" app could write — closed by hardening a role-identity check that fails closed (500) if the connected DB role isn't app_readonly
  • Migrated from Vercel to self-hosted EC2 behind Caddy: eliminated the self-fetch pattern where server components were fetching their own /api/* routes via HTTP (which needs a running server during build) — pages now query the DB directly via a shared queries module, and the API routes remain as thin wrappers for client-side consumers
PythonPostgreSQL (Neon)dbt Core (dbt-postgres)Next.js (App Router, TS)AWS EC2CaddyUpstash RedisLast.fm APIGitHub ActionsSQL
view archived report ↗

An earlier Power BI report built to explore the same listener-growth data — kept as an archived artifact, not maintained or refreshed anymore. Listener growth by tier and genre, a listener count vs. growth rate plot with per-artist drill-through, and an Artist Spotlight page.