
For readers following ItInsightsWorld SoftwareDevelopment, this rebuild is less about star counts and more about contract design. An API that looked like a roster of people was being forced to act like a time-series source. The old pattern could require 2,500 requests, while the redesigned path needs 24. The engineering task was to serve the same maintainer question, without names, and without making the storage layer reread every current star.
Repository administrators and collaborators retained permission to see stargazer and watcher data. Callers without that access could receive an empty response or a 403 Forbidden. That change reduced the risk that public stargazer lists could be scraped and reused for spam or phishing.
It also disconnected tools that had been deriving growth history from the GitHub API. Those tools often paged through records, preserved created_at values, and discarded identities before drawing a curve. Discussions with maintainers and tool builders clarified the actual signal: a Star History trend could show whether a release, launch, announcement, or event changed attention. The exact usernames were not necessary for that comparison.
When the API change disrupted Star History, users rallied around the project. GitHub’s Community team listened and connected us with product engineering, who dug into our use case and worked alongside us to design a purpose-built endpoint. A difficult situation became a constructive collaboration between GitHub, Star History, and the broader community—ultimately leading to a better solution.
Tianzhou Chen, star-history.com maintainer and Bytebase co-founder
The original GET /repos/{owner}/{repo}/stargazers endpoint answered a membership question: who starred this repository? It returned a paginated roster. With a custom media type, each row also carried a timestamp. The underlying star model records the current set of people who have starred a repository, which makes simple star counts easy. It is not a permanent event log. If someone removes a star, the corresponding record disappears.
When that response was repurposed as history, the cost scaled with popularity. Each page held at most 100 people, so a full curve for react/react, with about 251,000 stars, required about 2,511 requests. Tools then met rate limits, skipped pages, or sampled data instead of reconstructing the whole sequence.
Serving each page also exposed a pagination bug. To produce a single page of up to 100 people, the database counted every current star for the repository so it could know how many pages existed. In the week before we restricted access, nearly 37,000 count queries ran slowly enough to be logged, averaging nearly 3 seconds each. More than 1,000 queries were killed by safeguards, which contributed to timeouts.
After we restricted access, the database handled about 60% fewer stargazer requests, and the total time spent on the count query fell by about 80%.
Removing identities satisfied privacy. The harder design problem was preventing every request from scanning the repository’s full current star set.
One spike serialized starred_at without stargazer identity for unauthorized callers. It reduced exposure with a limited change to serialization and authorization, and existing tools could continue to work. The weakness was that the payload remained a long list. API consumers still had to paginate and aggregate thousands of rows to build a trend, while the endpoint remained tied to a person-list model.
Grouping stars by day or week appeared cheaper because the response would contain few buckets. The issue was SQL execution order. A straightforward query first finds all stars, groups them into buckets, sorts the buckets, and only then applies LIMIT and OFFSET. The client gets a smaller JSON body, but the expensive work has already happened. For react/react, full-history reads under this plan would examine roughly 6 million rows, because each of the about 24 requests would reprocess all of its stars.
The shipped design paginates history by calendar week. It converts page and per_page into an indexed created_at range, restricts stars to those weeks, and groups only the matching records into daily counts. A response includes up to 30 weeks. Increasing page moves backward through the repository’s history.
For react/react, that design means about 24 requests rather than about 2,511 requests, and the database examines each star about once across the complete history, or roughly 251,000 rows. The response also matches the weekly commit activity shape used by GET /repos/{owner}/{repo}/stats/commit_activity.
The method is not free of limits. It bounds a request to a period, but not to a maximum number of stars within that period. A very dense week can still produce an expensive query.
| Design | Client payload | Database behavior | Engineering outcome |
|---|---|---|---|
| Original stargazer pages | People, optionally with created_at | Scan current stars and count all of them for page boundaries | Privacy exposure, timeout pressure, and about 2,511 requests for react/react |
| Rows without names | Timestamped star records without identity | Still processes many rows per repository | Privacy improves, but aggregate work remains on the client |
| Aggregate then paginate | Day or week buckets | Find, group, and sort before paging | Smaller response, still roughly 6 million rows for react/react |
| Time-filtered aggregation | Daily counts inside a week range, up to 30 weeks | Filter by indexed created_at range first, then group | about 24 requests, roughly 251,000 rows, and no identities |
Measuring the two paths surfaced other places where star counts were being computed on demand. The repository stargazers page on GitHub.com was switched to the cached star count. Slow queries from that page, which had been in the hundreds each week, dropped to zero. Similar fixes were later shipped to other stargazer API endpoints.
The collaboration path mattered too. The original access restriction addressed abuse, but the conversation around Star History, star-history.com, GitHub, Bytebase, and GitHub Community helped shape a purpose-built endpoint rather than a blunt break.
No. The purpose-built endpoint returns daily star counts and does not attach identities to the timestamps.
up to 30 weeks of daily counts. Long histories are reached by moving page backward through earlier weeks.
Because pages carried at most 100 people, and the repository had about 251,000 stars. A full reconstruction required about 2,511 requests.
No. It avoids scanning every current star for each page, but a week with an exceptionally large number of stars can still produce a slow or expensive query.
The shipped design requires about 24 requests and processes each star about once across the full history, or roughly 251,000 rows.
The case is useful for platform teams because it shows that API redesign succeeds when the contract, the data model, and the query plan point to the same job. The safer, cheaper, and more predictable outcome came from serving a maintainer-facing trend, not from preserving a list-shaped accident.