대청소 완료: GitHub 캐시를 드디어 제대로 쓰게 됐어요 🧹
지난 기술 글에서 우리 빌드와 검사 파이프라인에 정말로 드는 비용을 재 봤고, 돈이 아니라 기다리는 시간과 저장 공간이 문제라는 걸 알게 됐어요. 그 글은 “정리 작업은 계획했지만 아직 하지 못했다”로 끝났죠. 생각보다 빨리 끝났어요. 오늘 낮부터 메인 브랜치에 들어가 있어요. 숫자를 보여 드릴게요.
결과부터요. 저장소의 캐시 사용량이 항목 63개에 5.32GB에서, 13개에 2.46GB로 줄었어요. 풀 리퀘스트마다 돌던 C# 보안 분석은 이제 거기서 4분 30초가 아니라 4초가 걸리는데, 사용자에게 배포되는 것은 여전히 모두 검사해요. 여러분을 지켜 주는 검사는 하나도 빼지 않았어요.
간단히: 캐시는 무엇에 쓰는 걸까요? 빌드는 매번 새로 만든 빈 컴퓨터에서 시작해요. 그래서 매번 처음부터 시작하지 않도록 중간 결과를 저장해 두고, 다음에는 다시 만드는 대신 내려받아요. 저장소마다 이를 위해 10GB를 쓸 수 있어요. 가득 차면 GitHub이 가장 오래 쓰이지 않은 것부터 비워요. 우리 문제가 바로 그거였어요. 공간은 차 있는데, 엉뚱한 것들이 차지하고 있었어요.
실수 1: 플랫폼 없는 키. Unity는 빌드할 때 커다란 중간 폴더를 만들어요. 빌드 네 개(Windows, Linux, macOS, WebGL)가 이걸 쓰고, 각자 자기 것이 필요해요. 그런데 Windows 빌드만 캐시 키에 플랫폼 표시가 없었어요. 그래서 처음 맞는 항목, 곧 WebGL 것과 맞아떨어졌어요. 1.7GB를 내려받았지만 Unity가 “여기 것이 아니다”라고 제대로 알아보고 처음부터 다시 가져왔죠. 로그에는 85초가 아니라 257초의 임포트가 찍혔어요. 이제 키는 Library-Windows-이고, 유령 소동은 끝났어요.
실수 2: 아무도 저장하지 않았어요. 더 부끄러운 발견이에요. 빌드 네 개 모두 성실하게 중간 폴더를 복원하려 했는데, 그걸 써 넣는 워크플로는 하나도 없었어요. 유일하게 있던 항목도 손으로 실행하는 도구에서 나온 거였어요. 프로젝트에서 가장 중요한 캐시가 우연히 유지되고 있었던 거예요. 🙈 이제는 일부러 써 넣어요.
공간을 아끼는 결정: 임계 경로만 중요해요. 뻔한 선택은 네 플랫폼을 다 캐시하는 거였어요. 실제 릴리스의 시간을 보면 그게 왜 말이 안 되는지 알 수 있어요. 네 빌드가 동시에 시작하고, Windows는 11분 뒤, Linux와 macOS는 13.7분 뒤, WebGL은 31.6분 뒤에 끝나요. WebGL이 도는 동안 나머지는 어차피 기다려요. 그러니 WebGL의 캐시만 대기 시간을 줄여 줘요. 나머지 셋은 10GB 중 약 4.5GB를 쓰면서 0분도 줄이지 못해요. 그래서 딱 하나만 캐시해요.
실수 3: Docker가 나머지를 먹어 치웠어요. 서버 이미지를 빌드할 때 모든 중간 레이어를 캐시로 저장했어요. 항목 24개에 약 3.27GB, 전체 용량의 64%였고, 푸시할 때마다 갱신됐어요. 이 홍수가 정작 중요한 Unity 캐시를 번번이 밀어냈어요. 이제는 마지막 단계만 저장하고, 이미지 네 개가 각각 자기 네임스페이스를 가져요. 전에는 하나를 같이 쓰면서 서로를 계속 쫓아냈어요. 3.27GB가 약 150MB가 됐어요.
보안 분석: 중요한 곳에서 검사해요. GitHub의 보안 취약점 탐지 도구인 CodeQL이 두 번째로 큰 시간 먹는 하마였어요. 8일 동안 230분이었고, 거의 다 C# 부분이었어요. 분석을 위해 프로젝트 전체를 다시 빌드하는데, 풀 리퀘스트마다 그랬죠. 이제는 메인 브랜치로 병합할 때와 주 1회만 전부 돌고, 풀 리퀘스트에서는 빠른 검사만 해요. 바꾼 뒤 측정해 보니 풀 리퀘스트는 4초, 메인 브랜치는 변함없이 넉넉한 4분이에요. 그래서 여러분에게 실제로 닿는 모든 줄은 여전히 검사해요. 세 번씩은 안 할 뿐이에요.
모이면 큰 작은 것들. .NET 패키지도 이제 캐시하지만, 사람이 기다리는 경로에서만 해요. 다른 프로세서 아키텍처용 에뮬레이터는 정말로 그걸 위해 빌드할 때만 설치해요. 막 켠 컴퓨터에서는 할 일이 없는데도 빌드마다 최대 1.4분이 걸리던 정리 명령은 없앴어요. 그리고 릴리스 빌드의 중간 파일은 90일이 아니라 7일 뒤에 지워져요. 703개, 합쳐서 7.4GB가 쌓여 있었거든요.
일부러 하지 않은 것. 우리 분석에서 나온 제안 두 가지는 측정해 보고 접었어요. 변경 기록에 멋진 줄로 올리지 않았어요. 테스트를 병렬 작업 두 개로 나누면 딱 4초를 벌었을 거예요(한쪽은 2:54, 다른 쪽은 4초). 게다가 필수 검사의 이름이 바뀌어서 모든 풀 리퀘스트가 영원히 “대기 중”에 걸렸을 거예요. 릴리스 전에 큰 테스트 관문을 건너뛰어도 얻는 게 없었을 거예요. WebGL이 아직 빌드 중일 때 그 관문은 이미 한참 전에 끝나 있으니까요. 얻는 것 없이 안전망만 없애는 최적화는 최적화가 아니에요.
그러다 진짜 버그가 하나 나왔어요. 이 정리 도중에 갑자기 모든 풀 리퀘스트가 시간 제한에 걸려 실패했어요. 테스트 하나가 허용 시간 120초 대신 212초가 걸렸어요. 같은 실행에서 두 번째로 느린 건 10.5초였어요. 원인은 서버의 오류 처리 경로였어요. 잘못된 HTTP 요청을 깔끔하게 닫지 않고 그냥 끊어 버렸어요. Linux에서는 시스템이 그런 연결을 2분마다 도는 청소에서만 거두어 가고, 종료는 그걸 참을성 있게 기다렸어요. Windows에서는 한 번도 나타나지 않아서 빌드 서버에서만 문제가 됐어요. 이제 서버는 그런 경우에 정직한 오류 코드로 답하고 연결을 스스로 닫아요. 테스트는 넉넉히 20초 만에 끝나고, 실제 플레이어가 그런 문제를 만나도 끊어진 연결 대신 이해할 수 있는 오류를 받아요. CI 정리의 날에 실서비스 버그를 찾았다면 좋은 날이죠. 🐛
이 가운데 어느 것도 여러분에게 직접 달라지는 건 없어요. 새 블록도, 새 동물도 없어요. “코딩 끝”과 “여러분이 설치할 수 있음” 사이의 길을 줄여 줄 뿐이에요. 그리고 지난번과 같은 말을 증명해요. 먼저 재고, 그다음에 손대자. 🚀
댓글