music growth

How it works

The story behind this site. How it's built and what was learned.

Architecture

Every Sunday, cron runs the whole pipeline on one Hetzner box: Python snapshots Last.fm into Postgres, dbt models it, and a JSON export feeds this static site.

Architecture diagram: Last.fm API to Python ingestion, Postgres, dbt, JSON export, and the static site served by Caddy.

Data model

Only raw tables live in Postgres; every transformation is a dbt model. Full lineage and column docs are in the dbt docs.

ERD diagram of raw Postgres tables.

Lessons learned

Simpler > trendier

Music Growth originally had a Next.js/React frontend. Due to its complexity and my unfamiliarity with the framework, I rewrote the frontend in simple HTML/Tailwind CSS/JS, drawing from a JSON export layer. The frontend shrank from 53 โ†’ 9 files and 9,226 โ†’ 721 lines.

Neon vs. the JSON export layer

This site originally drew from a cloud Postgres server (Neon). However, the data is small enough to live in the same cloud instance that hosts the frontend. A weekly script turns modeled Postgres data into JSON files the frontend can read directly. This is simpler, faster, and more cost-effective.

Compare over equal windows

This page once said bigger artists grow faster. When a second analysis disagreed, I dug in and found why: I had added ~16k artists to the tracked set weeks after the rest, so most small artists were measured over about half as many weeks as large ones. Now every artist is measured over the same weeks, and the result flipped.

What the data says

Growth means new listeners, not more plays

Last.fm counts every user who has ever played an artist as a listener, so the number only goes up. Listener growth here means new people finding an artist, not existing fans playing more.

Smaller artists grow faster

Median listener growth falls steadily from the smallest fifth of artists to the largest.

median p90

Last.fm's chart ranks by scrobbles, not size

Last.fm's chart API orders artists roughly by scrobble count, so chart position isn't a measure of size. I group artists into listener-count quintiles before comparing them instead.

Some artist names are redacted by a regex profanity filter. If yours was caught by mistake, let me know.

If you made it to the bottom of this page, thanks for sticking around! You may want to check out my other stuff.