GitHub(@github)

⭐ We shaped our new repo star history API around production evidence and maintainer feedback. Now it...

6.9内容质量

TL;DR · AI 摘要

GitHub on X: "⭐ We shaped our new repo star history API around production evidence and maintainer feedback. Now it provi...

核心要点

  • 主题聚焦:⭐ We shaped our new repo star history API around
  • 来源:GitHub(@github),建议结合原文判断细节。
  • AI 分析暂不可用,本条为保底评分与摘要。
#AI#编程#前端#后端#安全
打开原文

GitHub on X: "⭐ 我们根据生产环境的数据和维护者的反馈,设计了新的仓库星标历史API。现在该API能够在不返回个人身份信息或不需要无限制数据库操作的情况下提供趋势数据。实现这一目标的实际工程权衡 ⬇️ https://t.co/WJ18vkYmdO" / X

GitHub

@github

⭐ 我们根据生产环境的数据和维护者的反馈,设计了新的仓库星标历史API。现在该API能够在不返回个人身份信息或不需要无限制数据库操作的情况下提供趋势数据。实现这一目标的实际工程权衡 ⬇️

$

从2500次请求到24次:在不暴露星标者的情况下重建星标历史

32

14

129

19

作者:Taylor Cuilty & Sarita Iyer

维护者查看星标历史是为了回答一个实际问题:一次发布、版本更新或事件是否改变了项目的轨迹?要通过GitHub API重建这条曲线,工具必须请求大量星标者记录(每个当前星标该仓库的用户及其星标时间),保存时间戳并丢弃身份信息。

随着时间推移,这种设计在两端都产生了问题。身份信息可以被批量收集,导致恶意行为者开始抓取公开的星标者列表用于垃圾邮件和钓鱼攻击。对于高星标仓库,该接口也可能需要处理数千条数据库记录才能返回单页数据。

解决方案看似明确:返回统计数字而非个人数据。但减少数据量并未真正降低查询成本。

现在维护者可以直接获取曲线:我们构建了一个接口,单次请求即可返回最多30周的每日星标统计,且不附带任何身份信息。要保持服务成本低廉,关键在于减少数据库处理的数据量,而不仅仅是减少返回的数据量。该接口通过在统计前按时间过滤星标记录实现了这一点。

维护者对星标历史的实际需求

我们首先通过限制星标者和观察者列表仅对仓库管理员和协作者开放,解决了对开发者和数据库的直接风险。权限变更后,没有相应权限的调用者将收到空响应或403禁止访问响应。

这一变更也切断了依赖旧响应计算星标历史的工具,这些工具通常不会显示或使用返回的用户名。因此我们与维护者和工具开发者沟通,了解他们仍需什么。他们将星标历史作为兴趣的近似信号:用于比较发布前后活动变化,或查看公告是否触达了新开发者。他们真正关注的是曲线本身。

旧API响应让身份信息看起来像是契约的一部分,因为每个时间戳都附带用户信息。实际上,如Star History等工具需要的是统计数字和日期,而非用户名或其他用户数据。当身份信息不再成为需求后,我们就能为实际任务设计聚合接口:以实用的精度获取随时间变化的星标统计。

“当API变更影响到Star History时,用户们纷纷支持这个项目。GitHub社区团队倾听并帮助我们与产品工程团队取得联系,他们深入研究了我们的使用场景,并与我们合作设计了一个定制的端点。原本困难的局面最终演变为GitHub、Star History和更广泛社区之间的建设性合作,最终带来了更好的解决方案。” – Tianzhou Chen,Star History维护者和Bytebase联合创始人

为什么原始端点成本高昂

在了解维护者需求后,下一步是理解为什么原始设计无法以低成本提供所需功能。原始的GET /repos/{owner}/{repo}/stargazers端点旨在回答一个简单问题:谁为这个仓库添加了星标?它返回一个分页的人员列表,如果请求指定了自定义媒体类型,每个条目还会包含添加星标的时间。

星标模型的设计初衷也是如此。它记录了当前为仓库添加星标的人员集合,这使得显示星标数量或星标者列表变得简单。但该模型并未设计为历史事件日志:当有人移除星标时,相关记录会被删除。当工具开始将列表作为时间序列使用时,重建增长曲线需要检索每个个体的身份并处理所有当前星标。由于每页最多显示100人,工作量随着仓库受欢迎程度增长:为react/react仓库(约251,000个星标)构建完整曲线需要约2,511次请求,导致工具经常遇到速率限制或仅采样部分页面而非读取全部。

提供一页数据需要数据库查询仓库的当前星标。由于分页实现存在缺陷,每次请求还会统计所有星标以计算总页数,尽管调用者仅收到最多100人的一页数据。对于高星标仓库而言,这意味着每次请求都要统计数千条记录。在我们限制访问权限前的一周内,近37,000次计数查询运行缓慢到被记录,平均每次耗时近3秒,数据库保护机制终止了其中超过1,000次查询,这导致了请求超时问题。

下图展示了所有这些计数操作的成本。在限制访问权限后,数据库提供的星标者请求量减少了约60%,用于计数查询的总时间下降了约80%。

数据库服务器汇总的每日查询执行时间 · 2026年6月22日 – 8月21日(UTC)

我们考虑的方案

移除身份信息满足了隐私需求。更具挑战性的是如何避免每次请求都扫描仓库的完整当前星标集合。

保留事件但不包含身份信息

我们的首次尝试是为每个星标保留一条记录,并在调用者没有协作者权限时返回starred_at值而不包含星标者身份。起初这看似是最快的解决方案,因为它直接满足了工具构建者提出的需求。同时,通过小幅修改现有序列化和授权流程即可关闭身份信息收集的途径。

这种方法解决了身份暴露问题,并允许现有工具继续运行。但API消费者仍需分页处理并处理可能多达数千条的个体记录以构建聚合历史。此外,这种方案将新使用场景绑定到一个原本为列出人员而非获取趋势设计的端点和分页模型上。

聚合后再分页

聚合看起来是显而易见的下一步。查询不再返回每个星标对应一行数据,而是可以将星标按天或周分组,对这些分组进行排序,并对数量更少的结果进行分页。

问题在于分页发生的时机。在普通的SQL查询中,数据库首先会找到仓库的所有星标,将它们分组到不同的桶中,然后对这些桶进行排序。只有在完成这些操作后,才会应用LIMIT(控制返回的桶数量)和OFFSET(确定分页起始位置):

所有匹配的星标 -> 按天或周对每一行进行分组 -> 对每个桶进行排序 -> OFFSET/LIMIT -> 响应

对于一个受欢迎的仓库,匿名请求仍可能扫描并聚合数千个索引条目,只为返回少量的桶。返回更少的桶虽然会减少JSON响应的大小,但不会减少数据库已经执行过的昂贵的分组和排序操作。对于react/react仓库,通过这种方式读取完整历史记录意味着需要检查约600万行数据,因为每个请求都会重新处理所有星标。为了降低查询成本,我们必须在聚合开始前就缩小行数范围。

在分组前按时间过滤

我们最终设计的方案是按日历周对星标历史进行分页。对于每个请求,它会将页码和每页数量转换为一个索引的created_at时间范围,过滤出该周的星标,然后将匹配的记录分组为每日统计。一个响应最多包含30周数据,增加页码会向后遍历仓库的历史记录。

索引时间范围 -> 对该范围内的行进行分组 -> 每日桶统计 -> 响应

由于首先应用了时间范围,每个请求现在处理的是要返回的周数,而不是仓库中所有当前的星标。对于工具来说,这意味着一个请求现在可以覆盖最多30周的历史数据,无论仓库有多少星标。对于react/react仓库,这意味着大约24个请求,而不是之前的约2511个请求,数据库在整个历史记录中只检查每个星标一次(约251,000行)。这种设计符合主要使用场景,因为维护者通常会查看周度或特定已知发布、启动或事件周围的增长情况。基于周的范围也与其他仓库活动API保持一致:响应结构与每周提交活动端点GET /repos/{owner}/{repo}/stats/commit_activity的格式相同。

时间范围设计将每个请求限制在特定时间段内,但不会限制该时间段内的星标数量。因此,某个周如果星标数量异常多,仍可能导致查询变慢或成本增加。虽然常规请求路径不再查询所有当前星标,但异常密集的周数仍然是扩展限制。

比较新旧API路径的性能时,我们还发现了GitHub其他地方按需统计星标的情况。我们在GitHub.com的仓库星标页面发现了相同的模式,并更新为使用仓库的缓存星标计数。该页面之前每周有数百个慢查询,现在已降至零。我们最近还为其他星标API端点部署了类似的修复方案。

下图显示了每周来自不同来源的缓慢或被终止的星标计数查询数量。

每周缓慢或被终止查询的来源分布 · 2026年5月5日-10月7日周报

我们的收获

  • 更小的响应结果并不一定意味着更便宜的查询。LIMIT 和 OFFSET 减少了返回结果的数量,但并未减少数据库已经完成的分组和排序操作。我们必须关注工作执行的顺序。
  • 从人们实际想要完成的任务出发。维护人员需要比较趋势。身份列表只是旧接口恰好提供底层数据的方式。
  • 仅返回用例所需的内容。任何多余的数据都可能被他人滥用。一旦我们理解了任务需求,就可以在不损害维护人员依赖的信息的前提下移除单个身份数据。
  • 明确剩余的权衡取舍。新接口更安全且更可预测,但日历边界、移除的星标和异常密集的周数据仍然会影响其返回结果。这些细节也是 API 协议的一部分。

限制原始接口解决了即时问题。倾听维护人员的反馈让我们得到了比最初设想更好的方案:更易于获取、服务成本更低且对所有星标项目更安全的星标历史数据。

获取仓库星标历史数据,然后在 GitHub 社区讨论中分享反馈。

/$

10:00 PM · Oct 14, 2026

·

36K

Views