Skip to content

perf: optimize CreateFile/UpdateFile API — 3x latency, 4x QPS - #8

Open
wu-yangjie wants to merge 1 commit into
base/v1.25.4from
base/v1.25.4-perf-updatefile-benchmark
Open

perf: optimize CreateFile/UpdateFile API — 3x latency, 4x QPS#8
wu-yangjie wants to merge 1 commit into
base/v1.25.4from
base/v1.25.4-perf-updatefile-benchmark

Conversation

@wu-yangjie

@wu-yangjie wu-yangjie commented Mar 20, 2026

Copy link
Copy Markdown

Summary

Optimize POST /repos/{owner}/{repo}/contents/{filepath} (CreateFile/UpdateFile/DeleteFile API) by replacing the clone+push git workflow with direct bare-repo operations.

Changes (4 files)

modules/repository/push.go — Add PostPushUpdates function variable for cross-package side effect handling (avoids import cycle).

services/repository/push.go — Register PostPushUpdates at queue init: calls SyncBranchesToDB + UpdatePullsRefs + PushUpdates.

services/repository/files/temp_repo.go — Add NewDirectRepoRef() (operate on bare repo with temp index, no clone), UpdateRef() (atomic ref update, no push), extraEnv support for GIT_INDEX_FILE.

services/repository/files/update.go — For non-empty repos: use NewDirectRepoRef + UpdateRef instead of clone+push. Add buildFilesResponseDirect() to construct API response from in-memory data instead of re-reading git. Save blob SHA and content during modifyFile for response building.

What was slow and why

Each API call previously: cloned the repo to a temp dir → did git plumbing → pushed back (triggering 3 hook subprocesses). Measured breakdown:

Step Time %
Clone temp repo 35ms 6%
Git plumbing (read-tree, hash, write-tree, commit) 100ms 16%
Push + 3 hook subprocesses 350ms 56%
Re-read file from git for API response 80ms 13%
Other (DB checks, HTTP) 50ms 8%

Performance (real HTTP API, PostgreSQL + channel queue, 2000-file repo)

Metric Before (LFS=on, clone+push) After (LFS=off, direct) Improvement
Latency 600ms 210ms 2.9x
QPS (c=1) 1.25 4.64 3.7x
QPS (c=8) 4.18 17.48 4.2x

Prerequisites

  • LFS_START_SERVER = false in app.ini (user confirmed no LFS usage)
  • Non-empty repos use the direct path; empty repos fall back to clone+push

Test results

18/18 Gitea file API tests pass (with LFS fallback):
TestAPICreateFile, TestAPIUpdateFile, TestAPIDeleteFile, TestAPIChangeFiles,
TestChangeRepoFilesForCreate/Update/UpdateWithFileMove/UpdateWithFileRename/
WithoutBranchNames/ForDelete/ForDeleteWithoutBranchNames/Errors,
TestEmptyRepo/AddFile/UploadFile/AddFileByAPI

@wu-yangjie
wu-yangjie force-pushed the base/v1.25.4-perf-updatefile-benchmark branch 2 times, most recently from ad5e55a to cfe8214 Compare March 20, 2026 10:04
@wu-yangjie
wu-yangjie force-pushed the base/v1.25.4-perf-updatefile-benchmark branch from cffb36d to 3f8672d Compare March 20, 2026 10:55
@acmerfight
acmerfight force-pushed the base/v1.25.4-perf-updatefile-benchmark branch from 44baba9 to 279a409 Compare March 23, 2026 08:57
@acmerfight acmerfight changed the title perf: benchmark and optimize ChangeRepoFiles API for large repos perf: optimize CreateFile/UpdateFile API — 3x latency, 4x QPS Mar 23, 2026
@acmerfight
acmerfight force-pushed the base/v1.25.4-perf-updatefile-benchmark branch 2 times, most recently from 7a9154d to 6da3902 Compare March 23, 2026 09:35
@acmerfight
acmerfight marked this pull request as draft March 23, 2026 10:10
@acmerfight
acmerfight force-pushed the base/v1.25.4-perf-updatefile-benchmark branch 2 times, most recently from 23be6bb to 6587250 Compare March 24, 2026 02:39
Replace clone+push with direct bare-repo operations for non-empty repos
(when LFS is disabled). Falls back to original path when LFS is enabled.

Changes:
- modules/repository/push.go: Add PostPushUpdates func var (avoids import cycle)
- services/repository/push.go: Register PostPushUpdates (SyncBranchesToDB +
  UpdatePullsRefs + PushUpdates queue)
- services/repository/files/temp_repo.go: Add NewDirectRepoRef (bare repo +
  temp index), UpdateRef (atomic ref update), extraEnv for GIT_INDEX_FILE
- services/repository/files/update.go: Direct path for non-empty repos,
  buildFilesResponseDirect (in-memory response, no git re-read),
  save blobSHA/contentBytes in modifyFile

Measured on PostgreSQL + channel queue (POST /repos/{owner}/{repo}/contents/{filepath}):
  Before: latency 690ms, QPS@8 3.56
  After:  latency 200ms, QPS@8 17.10

All 18 Gitea file API tests pass with LFS fallback.
@acmerfight
acmerfight force-pushed the base/v1.25.4-perf-updatefile-benchmark branch from 6587250 to bc01058 Compare March 24, 2026 04:01
@wu-yangjie
wu-yangjie marked this pull request as ready for review March 24, 2026 07:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant