Skip to content

feat(migrations): GraphQL fast path for GitHub migration + rate-limit resilience - #38694

Open
davidlovas wants to merge 4 commits into
go-gitea:mainfrom
davidlovas:mirror/01-downloader-interface
Open

feat(migrations): GraphQL fast path for GitHub migration + rate-limit resilience#38694
davidlovas wants to merge 4 commits into
go-gitea:mainfrom
davidlovas:mirror/01-downloader-interface

Conversation

@davidlovas

@davidlovas davidlovas commented Jul 29, 2026

Copy link
Copy Markdown

Adds a batched GraphQL downloader for GitHub migrations, sharply reducing API rate-limit pressure on large repositories by collapsing the N+1 REST call pattern into single GraphQL requests. Default for all GitHub migrations — no opt-in needed.

What's in this PR

  • github_graphql.go: issue stream — fetches issues + comments in one request (100/page); issue-level reactions included inline when enabled
  • github_graphql_pr.go: PR stream — PRs + reviews + review comments, DESC order with early-exit at the watermark for efficient incremental syncs
  • github_graphql_timeline.go: timeline events (15 types, issue/PR union split)
  • http_client.go: retry transport with exponential backoff for transient errors and secondary rate limits
  • SkipReactions + SYNC_REACTIONS_FOR_MIRROR: controls whether reactions are fetched. Issue-level and PR-level reactions are nested inline in the GraphQL query (no extra API call). Comment-level reactions are NOT included — nesting reactions under comments under issues (100×100×100 = 1M nodes) exceeds GitHub's MAX_NODE_LIMIT_EXCEEDED ceiling. Off by default.
  • Progress logging: each sync phase logs what it's fetching and how many items
  • SyncIssues/SyncPullRequests on MigrateOptions for mirror metadata sync

Why

Addresses #13243 (migration exceeds API rate limit). The GraphQL path collapses dozens of REST calls into one, billed on a separate points budget. Single-token benchmark on go-gitea/gitea (~38k issues): ~4.5h (GraphQL) vs ~45h (REST). Multi-token composes with it (the real win is the N+1 collapse, not just budget pooling).

Standalone — works for one-time migrations with no mirror dependency. No impact on non-GitHub migration sources (GitLab, Gitea, Codebase, OneDev).

This is also the foundation for the read-only metadata-mirror feature proposed in #18369 (continuing #20311).

Test plan

  • Builds clean against current upstream main
  • gofmt clean
  • Unit tests for timeline conversion, query shape, reaction mapping
  • Tested end-to-end on a live self-hosted instance (gitea.rexovas.com) mirroring go-gitea/gitea

Developed with AI assistance (Anthropic Claude), per the AI contribution policy. I reviewed every line, understand and can defend it, and tested it end-to-end.

… resilience

Adds a batched GraphQL downloader for GitHub migrations, sharply reducing API
rate-limit pressure on large repositories by collapsing the N+1 REST call pattern
(issues + comments + reactions per-issue) into single GraphQL requests.
- github_graphql.go: issue stream with comments + reactions in one request
- github_graphql_pr.go: PR stream with reviews + review comments, DESC order
  with early-exit at the watermark for efficient incremental syncs
- github_graphql_timeline.go: timeline events (15 types, issue/PR union split)
- http_client.go: retry transport with exponential backoff for transient errors
- Progress logging: each sync phase logs what it's fetching and how many items
- UseGraphQL migrate option + USE_GRAPHQL_FOR_MIRROR site setting
- SkipReactions + SYNC_REACTIONS_FOR_MIRROR (opt-in, off by default)
- SyncIssues/SyncPullRequests on MigrateOptions for mirror metadata sync
Addresses go-gitea#13243 (migration exceeds API rate limit). Standalone — works for
one-time migrations with no mirror dependency.
Signed-off-by: David E. Lovas <david@rexovas.com>
@GiteaBot GiteaBot added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Jul 29, 2026
@github-actions github-actions Bot added the type/feature Completely new functionality. Can only be merged if feature freeze is not active. label Jul 29, 2026
- Replace encoding/json with gitea.dev/modules/json (depguard)
- sort.Slice/sort.Strings → slices.Sort (modernize)
- Rename base_ → baseBranch (revive: no underscores)
- Add missing test fixture for issues listing without page= param

Signed-off-by: David E. Lovas <david@rexovas.com>
@davidlovas
davidlovas force-pushed the mirror/01-downloader-interface branch from 1f727b1 to 76bb6df Compare July 29, 2026 04:06
- Fix import ordering (gci)
- Fix gofumpt formatting
- atomic.Int32 modernize
- Replace nil,nil return with empty map (nilnil)
- maps.Copy instead of manual loop (modernize)
- Fix useless-assert (store value before comparing)
- encoding/json → modules/json in timeline test

Signed-off-by: David E. Lovas <david@rexovas.com>
@davidlovas
davidlovas force-pushed the mirror/01-downloader-interface branch from 76bb6df to 3a8f150 Compare July 29, 2026 04:09
Signed-off-by: David E. Lovas <david@rexovas.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. type/feature Completely new functionality. Can only be merged if feature freeze is not active.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants