mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	Improve sync performance for pull-mirrors (#19125)
This addresses https://github.com/go-gitea/gitea/issues/18352 It aims to improve performance (and resource use) of the `SyncReleasesWithTags` operation for pull-mirrors. For large repositories with many tags, `SyncReleasesWithTags` can be a costly operation (taking several minutes to complete). The reason is two-fold: 1. on sync, every upstream repo tag is compared (for changes) against existing local entries in the release table to ensure that they are up-to-date. 2. the procedure for getting _each tag_ involves a series of git operations ```bash git show-ref --tags -- v8.2.4477 git cat-file -t 29ab6ce9f36660cffaad3c8789e71162e5db5d2f git cat-file -p 29ab6ce9f36660cffaad3c8789e71162e5db5d2f git rev-list --count 29ab6ce9f36660cffaad3c8789e71162e5db5d2f ``` of which the `git rev-list --count` can be particularly heavy. This PR optimizes performance for pull-mirrors. We utilize the fact that a pull-mirror is always identical to its upstream and rebuild the entire release table on every sync and use a batch `git for-each-ref .. refs/tags` call to retrieve all tags in one go. For large mirror repos, with hundreds of annotated tags, this brings down the duration of the sync operation from several minutes to a few seconds. A few unscientific examples run on my local machine: - https://github.com/spring-projects/spring-boot (223 tags) - before: `0m28,673s` - after: `0m2,244s` - https://github.com/kubernetes/kubernetes (890 tags) - before: `8m00s` - after: `0m8,520s` - https://github.com/vim/vim (13954 tags) - before: `14m20,383s` - after: `0m35,467s` I added a `foreachref` package which contains a flexible way of specifying which reference fields are of interest (`git-for-each-ref(1)`) and to produce a parser for the expected output. These could be reused in other places where `for-each-ref` is used. I'll add unit tests for those if the overall PR looks promising.
This commit is contained in:
		| @@ -150,6 +150,9 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, | ||||
| 		} | ||||
|  | ||||
| 		if !opts.Releases { | ||||
| 			// note: this will greatly improve release (tag) sync | ||||
| 			// for pull-mirrors with many tags | ||||
| 			repo.IsMirror = opts.Mirror | ||||
| 			if err = SyncReleasesWithTags(repo, gitRepo); err != nil { | ||||
| 				log.Error("Failed to synchronize tags to releases for repository: %v", err) | ||||
| 			} | ||||
| @@ -254,6 +257,14 @@ func CleanUpMigrateInfo(ctx context.Context, repo *repo_model.Repository) (*repo | ||||
|  | ||||
| // SyncReleasesWithTags synchronizes release table with repository tags | ||||
| func SyncReleasesWithTags(repo *repo_model.Repository, gitRepo *git.Repository) error { | ||||
| 	log.Debug("SyncReleasesWithTags: in Repo[%d:%s/%s]", repo.ID, repo.OwnerName, repo.Name) | ||||
|  | ||||
| 	// optimized procedure for pull-mirrors which saves a lot of time (in | ||||
| 	// particular for repos with many tags). | ||||
| 	if repo.IsMirror { | ||||
| 		return pullMirrorReleaseSync(repo, gitRepo) | ||||
| 	} | ||||
|  | ||||
| 	existingRelTags := make(map[string]struct{}) | ||||
| 	opts := models.FindReleasesOptions{ | ||||
| 		IncludeDrafts: true, | ||||
| @@ -450,3 +461,52 @@ func StoreMissingLfsObjectsInRepository(ctx context.Context, repo *repo_model.Re | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // pullMirrorReleaseSync is a pull-mirror specific tag<->release table | ||||
| // synchronization which overwrites all Releases from the repository tags. This | ||||
| // can be relied on since a pull-mirror is always identical to its | ||||
| // upstream. Hence, after each sync we want the pull-mirror release set to be | ||||
| // identical to the upstream tag set. This is much more efficient for | ||||
| // repositories like https://github.com/vim/vim (with over 13000 tags). | ||||
| func pullMirrorReleaseSync(repo *repo_model.Repository, gitRepo *git.Repository) error { | ||||
| 	log.Trace("pullMirrorReleaseSync: rebuilding releases for pull-mirror Repo[%d:%s/%s]", repo.ID, repo.OwnerName, repo.Name) | ||||
| 	tags, numTags, err := gitRepo.GetTagInfos(0, 0) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("unable to GetTagInfos in pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err) | ||||
| 	} | ||||
| 	err = db.WithTx(func(ctx context.Context) error { | ||||
| 		// | ||||
| 		// clear out existing releases | ||||
| 		// | ||||
| 		if _, err := db.DeleteByBean(ctx, &models.Release{RepoID: repo.ID}); err != nil { | ||||
| 			return fmt.Errorf("unable to clear releases for pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err) | ||||
| 		} | ||||
| 		// | ||||
| 		// make release set identical to upstream tags | ||||
| 		// | ||||
| 		for _, tag := range tags { | ||||
| 			release := models.Release{ | ||||
| 				RepoID:       repo.ID, | ||||
| 				TagName:      tag.Name, | ||||
| 				LowerTagName: strings.ToLower(tag.Name), | ||||
| 				Sha1:         tag.Object.String(), | ||||
| 				// NOTE: ignored, since NumCommits are unused | ||||
| 				// for pull-mirrors (only relevant when | ||||
| 				// displaying releases, IsTag: false) | ||||
| 				NumCommits:  -1, | ||||
| 				CreatedUnix: timeutil.TimeStamp(tag.Tagger.When.Unix()), | ||||
| 				IsTag:       true, | ||||
| 			} | ||||
| 			if err := db.Insert(ctx, release); err != nil { | ||||
| 				return fmt.Errorf("unable insert tag %s for pull-mirror Repo[%d:%s/%s]: %w", tag.Name, repo.ID, repo.OwnerName, repo.Name, err) | ||||
| 			} | ||||
| 		} | ||||
| 		return nil | ||||
| 	}) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("unable to rebuild release table for pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err) | ||||
| 	} | ||||
|  | ||||
| 	log.Trace("pullMirrorReleaseSync: done rebuilding %d releases", numTags) | ||||
| 	return nil | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Peter Gardfjäll
					Peter Gardfjäll