You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
perf(ci): PR 검증 4개 job이 같은 debug variant 를 각자 컴파일 — build cache 가 전역 off #996
gradle.properties:33org.gradle.caching=false — Gradle build cache가 저장소 전역으로 꺼져 있다. ~/.gradle/caches/build-cache-1이 애초에 채워지지 않으므로 job 간은 물론 run 간 재사용도 0이다.
PR job은 모두 cache-read-only: true — cache poisoning 방지 목적이라 유지해야 한다. 다만 (1) 때문에 읽을 build cache 자체가 없다.
org.gradle.caching=false의 주석은 비활성화 근거를 이렇게 적고 있다.
Studio/JDK 업데이트나 빌드 중 OOM 등으로 input 해시는 같지만 산출물이 손상된 cache 엔트리가 hit되는 사례가 있어 비활성화.
이 실패 모드는 장기 존속하는 로컬 Gradle User Home에서 발생한다. CI 러너는 매 run이 새 VM이고 GUH는 actions cache에서 복원되는 불변 스냅샷이라 같은 조건이 아니다. 로컬 기본값은 그대로 두고 CI에서만 켜는 것이 가능하다.
범위
gradle.properties의 org.gradle.caching=false는 로컬 기본값으로 유지한다. 주석의 우려는 로컬에 그대로 유효하다.
CI에서만 build cache를 켠다 (--build-cache 플래그 또는 CI 전용 init script). 로컬 개발자 빌드 동작은 바뀌지 않아야 한다.
#1000 에서 --build-cache 를 CI 호출에 넣었다. 그 PR 의 CI 에서는 단축이 나오지 않는다 — 현재 develop 캐시가 --build-cache 없이 만들어져 caches/build-cache-1 이 비어 있고, PR job 은 cache-read-only: true 라 채우지도 않는다.
실효는 머지 뒤 develop push 의 CodeQL(assembleDebug, cache-read-only: false)이 캐시를 한 번 채운 다음 PR 부터다. 그 시점에 확인해 여기에 수치를 남긴다.
#1000 에서 codeql.yml 에도 --build-cache 를 넣었는데 잘못이다.#1014 로 되돌린다.
codeql.yml 의 해당 자리 주석이 조건을 적고 있었다.
Java/Kotlin extraction observes compiler invocations, so a clean manual build is the contract.
CodeQL Java 추출기는 컴파일러 호출을 가로채 소스를 database 에 담는다. compile task 가 FROM-CACHE 로 넘어가면 컴파일러가 실행되지 않아 추출이 비고, verify-codeql-database.mjs 가 실패한다. #1000·#1001·#1002 연속 머지로 develop push CodeQL 이 cancel-in-progress 에 전부 취소돼 아직 발현되지는 않았다.
그 결과 이 이슈의 전제가 깨졌다
이 이슈는 "develop push 의 CodeQL(assembleDebug, cache-read-only: false)이 캐시를 채우고 PR 이 읽는다" 를 전제로 했다. develop push 에서 debug 컴파일 캐시를 채울 수 있는 워크플로를 전수 확인하니 codeql.yml 이 유일했다.
workflow
push 대상
cache-read-only
gradlew
codeql.yml
develop, main
pull_request 일 때만 true
assembleDebug
release-distribution.yml
main
false
assembleRelease (debug 무관)
따라서 #1014 머지 후에는 캐시를 채우는 경로가 없어lint·unit-test·screenshot 에 남은 --build-cache 가 항상 miss 한다. 지금 상태의 실효는 0 이다.
남은 선택지
develop push 전용 워밍 경로 신설 — assembleDebug 를 --build-cache 로 돌려 캐시만 채우는 job. 러너 시간을 추가로 쓰므로 PR 쪽 절감분과 비교해 판단해야 한다.
--build-cache 를 전부 되돌린다 — 채울 경로가 없으면 플래그만 남아 오해를 부른다.
PR job 의 cache-read-only: true 완화 — 채택하지 않는다. cache poisoning 방지 경계다.
앞선 코멘트에서 --build-cache 가 CodeQL 추출을 무력화한다고 단정했는데 실측으로 확인하니 그 단정은 틀렸다.
develop push 가 캐시를 채운 뒤의 PR CodeQL run 에서 FROM-CACHE 가 118건 찍힌다.
그럼에도 Verify analyzed sources in the CodeQL database 스텝은 success 다 — 추출이 정상 동작 중이다.
실패한 fix/952-receiver-count-dash 의 CodeQL 은 캐시가 아니라 Hilt 문제였다 (@HiltAndroidApp base class must extend Application. Found: kotlin.Any → :app:kspDebugKotlin FAILED).
캐시 히트가 컴파일 task 전체를 덮지 않아 추출에 필요한 컴파일러 호출이 남아 있는 것으로 보인다. 다만 그것은 언제 발현될지 모르는 상태에 기대는 구조 이므로, 워크플로 주석이 명시한 계약("clean manual build is the contract")을 지키는 #1014 되돌림은 그대로 유지한다. 성격만 "긴급 차단" 에서 "예방" 으로 바뀐다.
이 이슈의 전제가 깨졌다는 앞선 코멘트의 나머지 내용(캐시를 채우는 경로가 codeql.yml 하나뿐이라 되돌림 후 실효가 0 이 된다)은 그대로 유효하다.
PR #1037 로 머지됐다. 머지 후 실측 결과를 남긴다 — 완료 조건은 충족하지만, 워밍 워크플로 자체의 기여는 아직 실측되지 않았다. 갈라서 적는다.
1. PR run 에서 FROM-CACHE 가 찍힌다 ✅
머지 직후 PR run 의 Run Unit Tests 에서:
run
브랜치
FROM-CACHE
32719292011
develop
143건
32718982723
test/1030
148건
완료 조건 «develop push run 이후의 PR run 에서 컴파일 task 가 FROM-CACHE 로 보고된다» 는 충족된다.
2. 다만 그 hit 은 워밍이 준 것이 아니다 ⚠️
복원된 키를 추적하면 setup-java-Linux-x64-gradle-3b1d9c72… 이고, 생성 시각이 09:36 — 워밍 워크플로가 도입되기 전, CodeQL push 가 --build-cache 로 돌던 시절 저장한 엔트리다.
첫 워밍 run(32719009735)은 success 로 끝났지만 캐시를 저장하지 못했다.
Basic caching restored entry with key `setup-java-Linux-x64-gradle-3b1d9c72…`. Save was skipped.
setup-gradle 은 exact match 로 복원하면 저장을 건너뛴다. 그래서 이번 run 이 --build-cache 로 채운 build-cache-1 항목은 버려졌다.
3. 그럼 워밍은 언제 실제로 채우는가
캐시 키는 빌드 스크립트 해시(**/*.gradle* 등)로 갈린다. 키가 바뀌면 워밍이 exact miss → restore-keys 폴백으로 이전 캐시를 이어받아 빌드 → 새 키로 저장 성공. 그 시점부터 그 키의 공급자는 워밍 하나다(#1014 로 CodeQL 은 더 이상 캐시를 채우지 않는다).
이 키가 얼마나 자주 갈리는지는 실측이 있다 — 오늘 하루 develop 스코프에만 5개가 생겼다 (08:17, 09:17, 09:20, 09:28, 09:36). PR 을 머지할 때마다 모듈 build.gradle.kts 가 바뀌므로 20분~1시간 간격이다.
4. 남은 한계와 판단
빌드 스크립트가 안 바뀌는 동안 소스만 바뀌면 build cache 가 갱신되지 않아 hit rate 이 서서히 떨어진다. Actions 캐시가 같은 키를 덮어쓰지 못하는 구조적 제약이라, 강제로 갱신하려면 cache-write-only: true(복원 포기 → 워밍이 매번 전체 빌드) + 이전 엔트리 삭제(actions: write) 조합이 필요하다. 워밍 job 소요가 10분 → 15~20분으로 늘고, cancel-in-progress: false 인 상태에서 삭제가 겹치면 워밍끼리 서로의 캐시를 지우는 경합이 생긴다.
지금은 되돌리지 않는다. 되돌림 조건은 «단축이 확인되지 않으면» 인데 FROM-CACHE 는 실제로 찍히고 있고, 워밍은 키가 갈리는 다음 develop push 부터 그 공급을 이어받는 구조다. 다음 키 변경 이후의 워밍 run 에서 Save was skipped 가 사라지고 저장이 성공하는지를 확인해 이 이슈에 추가로 남긴다. 그때도 저장되지 않으면 위 조합을 후속으로 검토한다.
저장 스킵 조건은 matchedKey === primaryKey — exact match 로 복원했을 때다.
따라서 실제 동작은 이렇다.
빌드 스크립트
복원
저장
바뀜 (새 키)
miss — 빈 GUH 로 시작
성공
그대로 (같은 키)
exact hit
스킵
즉 build cache 는 빌드 스크립트가 바뀔 때마다 통째로 리셋된다. 오늘 develop 스코프에 키가 5개 생겼다는 것은 리셋이 5번 있었다는 뜻이고, 그 직후 첫 PR run 들이 빈 캐시를 만난다는 뜻이다. 워밍의 가치는 바로 그 자리에 있다.
실측 — 워밍 2건 모두 저장 0건
run
결과
소요
캐시
32719009735
success
10.8분
Entries: 1 restored, 0 saved
32719287425
success
11.4분
Entries: 1 restored, 0 saved
두 push 사이에 빌드 스크립트가 바뀌지 않아 키가 같았고, 그래서 둘 다 Save was skipped 였다. 11분씩 러너를 쓰고 아무것도 저장하지 못했다.
남은 결함 — 설계는 맞고 트리거가 틀렸다
키가 바뀌는 push 에서는 워밍이 제 역할을 한다(위 표의 첫 행). 문제는 키가 안 바뀌는 push 에서도 도는 것이다. 그 run 은 저장이 원천적으로 불가능하므로 순수 낭비다. develop push 는 PR 을 머지할 때마다 나므로 빈도가 높다 — 오늘 10:52~10:56 사이에만 3회였다.
Activity
1hyok commentedon Aug 24, 2026
#1000 에서
--build-cache를 CI 호출에 넣었다. 그 PR 의 CI 에서는 단축이 나오지 않는다 — 현재 develop 캐시가--build-cache없이 만들어져caches/build-cache-1이 비어 있고, PR job 은cache-read-only: true라 채우지도 않는다.실효는 머지 뒤 develop push 의 CodeQL(
assembleDebug,cache-read-only: false)이 캐시를 한 번 채운 다음 PR 부터다. 그 시점에 확인해 여기에 수치를 남긴다.FROM-CACHE로 보고되는가fix/724-view-state= CodeQL 10분 / screenshot 8분 / lint 7분 / unit-test 7분, 합계 약 38분)단축이 확인되지 않으면
--build-cache를 되돌린다.1hyok commentedon Aug 24, 2026
정정 — CodeQL 은
--build-cache예외다#1000 에서
codeql.yml에도--build-cache를 넣었는데 잘못이다. #1014 로 되돌린다.codeql.yml의 해당 자리 주석이 조건을 적고 있었다.CodeQL Java 추출기는 컴파일러 호출을 가로채 소스를 database 에 담는다. compile task 가
FROM-CACHE로 넘어가면 컴파일러가 실행되지 않아 추출이 비고,verify-codeql-database.mjs가 실패한다. #1000·#1001·#1002 연속 머지로 develop push CodeQL 이cancel-in-progress에 전부 취소돼 아직 발현되지는 않았다.그 결과 이 이슈의 전제가 깨졌다
이 이슈는 "develop push 의 CodeQL(
assembleDebug,cache-read-only: false)이 캐시를 채우고 PR 이 읽는다" 를 전제로 했다. develop push 에서 debug 컴파일 캐시를 채울 수 있는 워크플로를 전수 확인하니codeql.yml이 유일했다.codeql.ymlpull_request일 때만 trueassembleDebugrelease-distribution.ymlassembleRelease(debug 무관)따라서 #1014 머지 후에는 캐시를 채우는 경로가 없어
lint·unit-test·screenshot에 남은--build-cache가 항상 miss 한다. 지금 상태의 실효는 0 이다.남은 선택지
assembleDebug를--build-cache로 돌려 캐시만 채우는 job. 러너 시간을 추가로 쓰므로 PR 쪽 절감분과 비교해 판단해야 한다.--build-cache를 전부 되돌린다 — 채울 경로가 없으면 플래그만 남아 오해를 부른다.cache-read-only: true완화 — 채택하지 않는다. cache poisoning 방지 경계다.어느 쪽이든 실측이 먼저다. 이 이슈는 실효가 확인될 때까지 열어 둔다.
1hyok commentedon Aug 24, 2026
정정 — CodeQL 추출은 아직 깨지지 않았다
앞선 코멘트에서
--build-cache가 CodeQL 추출을 무력화한다고 단정했는데 실측으로 확인하니 그 단정은 틀렸다.FROM-CACHE가 118건 찍힌다.Verify analyzed sources in the CodeQL database스텝은 success 다 — 추출이 정상 동작 중이다.fix/952-receiver-count-dash의 CodeQL 은 캐시가 아니라 Hilt 문제였다 (@HiltAndroidApp base class must extend Application. Found: kotlin.Any→:app:kspDebugKotlin FAILED).캐시 히트가 컴파일 task 전체를 덮지 않아 추출에 필요한 컴파일러 호출이 남아 있는 것으로 보인다. 다만 그것은 언제 발현될지 모르는 상태에 기대는 구조 이므로, 워크플로 주석이 명시한 계약("clean manual build is the contract")을 지키는 #1014 되돌림은 그대로 유지한다. 성격만 "긴급 차단" 에서 "예방" 으로 바뀐다.
이 이슈의 전제가 깨졌다는 앞선 코멘트의 나머지 내용(캐시를 채우는 경로가
codeql.yml하나뿐이라 되돌림 후 실효가 0 이 된다)은 그대로 유효하다.1hyok commentedon Aug 24, 2026
PR #1037 로 머지됐다. 머지 후 실측 결과를 남긴다 — 완료 조건은 충족하지만, 워밍 워크플로 자체의 기여는 아직 실측되지 않았다. 갈라서 적는다.
1. PR run 에서 FROM-CACHE 가 찍힌다 ✅
머지 직후 PR run 의
Run Unit Tests에서:완료 조건 «develop push run 이후의 PR run 에서 컴파일 task 가 FROM-CACHE 로 보고된다» 는 충족된다.
2. 다만 그 hit 은 워밍이 준 것이 아니다⚠️
복원된 키를 추적하면
setup-java-Linux-x64-gradle-3b1d9c72…이고, 생성 시각이 09:36 — 워밍 워크플로가 도입되기 전, CodeQL push 가--build-cache로 돌던 시절 저장한 엔트리다.첫 워밍 run(32719009735)은
success로 끝났지만 캐시를 저장하지 못했다.setup-gradle 은 exact match 로 복원하면 저장을 건너뛴다. 그래서 이번 run 이
--build-cache로 채운 build-cache-1 항목은 버려졌다.3. 그럼 워밍은 언제 실제로 채우는가
캐시 키는 빌드 스크립트 해시(
**/*.gradle*등)로 갈린다. 키가 바뀌면 워밍이 exact miss → restore-keys 폴백으로 이전 캐시를 이어받아 빌드 → 새 키로 저장 성공. 그 시점부터 그 키의 공급자는 워밍 하나다(#1014 로 CodeQL 은 더 이상 캐시를 채우지 않는다).이 키가 얼마나 자주 갈리는지는 실측이 있다 — 오늘 하루 develop 스코프에만 5개가 생겼다 (08:17, 09:17, 09:20, 09:28, 09:36). PR 을 머지할 때마다 모듈
build.gradle.kts가 바뀌므로 20분~1시간 간격이다.4. 남은 한계와 판단
빌드 스크립트가 안 바뀌는 동안 소스만 바뀌면 build cache 가 갱신되지 않아 hit rate 이 서서히 떨어진다. Actions 캐시가 같은 키를 덮어쓰지 못하는 구조적 제약이라, 강제로 갱신하려면
cache-write-only: true(복원 포기 → 워밍이 매번 전체 빌드) + 이전 엔트리 삭제(actions: write) 조합이 필요하다. 워밍 job 소요가 10분 → 15~20분으로 늘고,cancel-in-progress: false인 상태에서 삭제가 겹치면 워밍끼리 서로의 캐시를 지우는 경합이 생긴다.지금은 되돌리지 않는다. 되돌림 조건은 «단축이 확인되지 않으면» 인데 FROM-CACHE 는 실제로 찍히고 있고, 워밍은 키가 갈리는 다음 develop push 부터 그 공급을 이어받는 구조다. 다음 키 변경 이후의 워밍 run 에서
Save was skipped가 사라지고 저장이 성공하는지를 확인해 이 이슈에 추가로 남긴다. 그때도 저장되지 않으면 위 조합을 후속으로 검토한다.1hyok commentedon Aug 24, 2026
앞 코멘트에 틀린 서술이 있어 정정한다. 그리고 워밍 run 2건이 끝나 실측을 보탠다.
정정 — restore-keys 폴백은 없다
앞에서 «키가 바뀌면 restore-keys 폴백으로 이전 캐시를 이어받아 빌드» 라고 적었는데 사실이 아니다.
actions/setup-java의 gradle 캐시는 restoreKeys 를 두지 않는다.키 해시 입력은 다음 7종이다.
저장 스킵 조건은
matchedKey === primaryKey— exact match 로 복원했을 때다.따라서 실제 동작은 이렇다.
즉 build cache 는 빌드 스크립트가 바뀔 때마다 통째로 리셋된다. 오늘 develop 스코프에 키가 5개 생겼다는 것은 리셋이 5번 있었다는 뜻이고, 그 직후 첫 PR run 들이 빈 캐시를 만난다는 뜻이다. 워밍의 가치는 바로 그 자리에 있다.
실측 — 워밍 2건 모두 저장 0건
Entries: 1 restored, 0 savedEntries: 1 restored, 0 saved두 push 사이에 빌드 스크립트가 바뀌지 않아 키가 같았고, 그래서 둘 다
Save was skipped였다. 11분씩 러너를 쓰고 아무것도 저장하지 못했다.남은 결함 — 설계는 맞고 트리거가 틀렸다
키가 바뀌는 push 에서는 워밍이 제 역할을 한다(위 표의 첫 행). 문제는 키가 안 바뀌는 push 에서도 도는 것이다. 그 run 은 저장이 원천적으로 불가능하므로 순수 낭비다. develop push 는 PR 을 머지할 때마다 나므로 빈도가 높다 — 오늘 10:52~10:56 사이에만 3회였다.
트리거를 캐시 키 입력과 같은
paths로 좁히면 정확히 해결된다. 후속으로 올린다.