CLI · 단계 · 게이트
7. CLI와 보고
| 옵션 | 무엇 |
|---|---|
--full | 캐시를 확인하지 않고 전부 실행합니다. 인장은 새로 기록합니다 |
--cache-dir <경로> | 인장을 둘 곳. 기본은 작업 디렉터리의 .tabbit/으로, logs/와 같은 자리입니다. CI에서 캐시를 마운트하려면 이 옵션이 필요합니다 |
--force-output | 캐시 판정과 무관하게 모든 출력 타깃을 실행합니다. 입력 쪽 판정은 그대로 사용합니다(§7.1) |
--detailed-exit-code | 할 일이 없어서 끝난 실행을 종료 코드 2로 알립니다(§7.2) |
기본은 캐시를 사용합니다. 그러면 아무것도 하지 않는 실행이 정상 동작이 되므로, 왜 그랬는지 항상 보고해야 합니다.
[I] [Caching ] Nothing to do. 36 workbooks, the recipe and 7 targets are unchanged.
[I] [Caching ] 9,470 output files verified. Pass --full to convert anyway.
전체 실행이 된 경우에는 그 이유를 함께 보고합니다.
[I] [Caching ] Full run: 2 workbooks changed (<워크북 A>.xlsx, <워크북 B>.xlsb).
[I] [Caching ] Full run: the recipe's Sources section changed.
[I] [Caching ] Full run: a different build of Tabbit wrote this cache (1.4.0).
[I] [Caching ] Full run: 3 output files are missing.
이유를 보고하지 않는 캐시는 신뢰받지 못하고, 신뢰받지 못하는 캐시는 --full이 습관이
되어 없는 것과 같아집니다. 이 보고는 편의 기능이 아니라 캐시가 사용되기 위한 조건입니다.
LogCategory에 Caching을 추가합니다.
이유가 사람이 조치할 수 있는 것일 때는 조치까지 적 습니다. 캐시가 동작하지 못하는 원인 중에는 「바뀌었으니 다시 한다」가 아니라 「권한을 한 번 주면 끝난다」인 것이 있고, 그 둘을 같은 문장으로 보고하면 후자가 매 실행 반복되는 상태로 남습니다. 자리와 문안은 §6.5.1에 있습니다.
7.1 강제가 2가지인 이유
--full과 --force-output은 보는 것이 다릅니다.
| 옵션 | 무엇을 의심하는가 · 무엇을 원하는가 |
|---|---|
--full | 캐시를 의심합니다. 판정이 틀렸을 수 있으니 입력부터 전부 다시 봅니다. 인장이 무엇을 적어 두었든 읽지 않습니다 |
--force-output | 캐시를 신뢰하면서 산출을 다시 원합니다. 입력이 안 바뀌었다는 판정은 그대로 쓰고, 타깃만 전부 실행합니다 |
하나로 합칠 수 없는 이유가 비용입니다. 산출을 다시 원하는 사람에게 워크북 36개를 다시
디코딩하게 하는 것은 필요 없는 82초입니다(§1). 1단계에서는 모델이 있어야 산출을 만들 수
있으므로 임포트와 쿠킹이 실행되어 차이가 작지만, 3단계의 원시 격자 캐시가 들어오면 이
옵션이 산출 시간만 지불합니다. 의미를 처음부터 갈라 두는 쪽이 맞습니다 — 나중에 갈라면
--full의 뜻이 조용히 바뀝니다.
--force-output이 필요한 자리는 캐시의 판정이 출력에 대해 알 수 없는 것입니다.
| 자리 | 무엇 |
|---|---|
| 소비하는 쪽이 수정 시각을 봅니다 | 내용이 같아도 다시 쓰이기를 원하는 빌드가 있습니다 |
| 출력 검증을 크기까지만 한 경우 | §6.4에서 비용 때문에 해시를 생략했다면, 내용의 손상은 이 옵션으로만 복구됩니다 |
| 산출 디렉터리를 옮긴 직후 | 인장이 가리키는 경로와 지금 원하는 경로가 다릅니다. 레시피를 고쳤다면 타깃 키가 이미 달라지므로 이 옵션은 필요 없고, 디렉터리만 손으로 옮긴 경우가 이 자리입니다 |
타깃을 골라서 강제하는 것은 두지 않습니다. 2단계의 타깃 키가 「바뀐 타깃만」을 이미
자동으로 하므로, 손으로 고르는 것이 필요한 상황은 캐시의 판정이 틀렸을 때뿐입니다 — 그때
필요한 것은 --full입니다.
--validate-only와 함께 쓰는 것은 거부합니다. 산출을 만들지 않는 실행에 산출을 강제하는
것이므로, 둘 중 하나는 사람이 착각한 것입니다. 우선순위를 정해 조용히 하나를 무시하는 것은
그 착각을 실행이 끝날 때까지 숨깁니다.
7.2 종료 코드
파이프라인은 「변환이 성공했는가」와 「배포할 것이 생겼는가」를 따로 알아야 합니다. 로그에서 영어 문장을 파싱해서 알아내는 것은, 도구가 그냥 말해 줄 수 있는 것을 그렇게 하는 것입니다.
| 코드 | 무엇 |
|---|---|
0 | 요청받은 것을 했습니다 |
1 | 실패했고 이유를 출력했습니다 |
2 | 할 일이 없었습니다. --detailed-exit-code를 준 실행에서만 |
기본값으로 두지 않는 이유가 호환입니다. 명령줄 도구를 부르는 것들은 대개 0이 아닌 코드를
실패로 취급합니다. 기본으로 2를 내면 뒤에 단계를 이어 붙인 모든 스크립트가 캐시가 처음
동작한 날 멈추고, 그날은 새 결함으로 보이기에 가장 나쁜 날입니다. terraform plan의
-detailed-exit-code가 같은 이유로 같은 형태입니다.
2는 실패가 아니므로 그 실행도 인장을 갱신하고 스윕을 합니다(§6.1).
8. 단계
| 단계 | 무엇 | 무엇이 줄어드는가 | 상태 |
|---|---|---|---|
| 0 | 계측. 실행이 단계별 소요를 스스로 보고합니다 — 임포트 · 쿠킹 · 검증 · 산출 · 커밋, 그리고 --verbose에서 타깃별 | 아무것도. 1단계 이후를 판단할 근거 | 됨 |
| 1 | 인장 하나. 무변경이면 즉시 종료 | 무변경 재실행 161초 → 판정 비용만 | 됨 |
| 2 | 타깃별 인장. 모델이 그대로이고 타깃 항목만 바뀐 실행은 그 타깃만 | 타깃 하나 수정: 161초 → 82초 + 그 타깃 | 됨 — 1단계와 함께(§10) |
| 3 | 워크북별 원시 격자 캐시. 바뀌지 않은 워크북은 다시 디코딩하지 않습니다 | 시트 하나 수정: 82초 → 미측정 | 남음 |
0단계를 먼저 하는 이유가 3단계에 있습니다. 81.8초 안에서 디코딩과 쿠킹의 비율을 모르면 3단계의 이득을 모릅니다. 쿠킹이 대부분이면 격자를 캐시해도 거의 줄지 않습니다.
3단계에는 조건이 하나 더 있습니다. 워크북 읽기가 「원시 격자 캐시」를 「이 교체 뒤에 남는 비용을 보고 판단한다」로 미뤄 두었습니다. 리더가 교체되면 격자의 형태가 달라질 수 있으므로 순서가 거꾸로 되면 두 번 만들게 됩니다.
8.1 하지 않기로 한 것
| 무엇 | 왜 |
|---|---|
| 테이블별 부분 생성 | 테이블 하나가 바뀔 때 그 테이블의 파일만 다시 내는 것. 의존 그래프가 필요합니다 — 그 테이블이 참조하는 테이블, 그 테이블을 참조하는 테이블, 값이 사용된 enum, 파일 이름 규칙. 그래프가 하나라도 누락하면 조용히 낡은 파일이 남습니다. 그리고 코드 생성 전체가 7.5초입니다(§1) — 이 위험을 지불할 이유가 없습니다 |
| 내용이 같은 파일을 커밋하지 않는 것 | 캐시가 아니라 독립된 개선입니다. 지금은 매 실행 9,470개를 삭제하고 이동하므로, 내용이 같아도 수정 시각이 바뀌어 유니티와 MSVC가 전부 다시 처리합니다. 커밋 자체는 2.6초이므로 이 도구의 시간을 줄이는 일이 아니고, 소비하는 쪽의 시간을 줄이는 일입니다. 여기에 기록만 해 둡니다 |
| 검증 결과의 캐시 | §6.3. 외부 상태를 읽는 규칙이 있으므로 인장으로 판정할 수 없습니다 |
| 규칙 컴파일 캐시 | 검증 파이프라인 §7이 이미 미뤄 둔 항목이고, 키의 조건도 그 문서에 있습니다. 이 문서와 독립적입니다 |
| 원격 캐시 공유 | 기계 사이에 인장과 산출을 공유하는 것. 절대 경로와 기계별 상태가 인장에 들어 있어 먼저 정리해야 하고, 1~3단계의 이득을 확인하기 전에 만들 이유가 없습니다 |
9. 게이트
| 게이트 | 무엇을 확인하는가 |
|---|---|
| 두 번 실행하여 동일한가 | 골든 픽스처를 캐시를 켠 상태로 두 번 실행하여, 두 번째가 건너뛰었고 산출 트리가 한 바이트도 다르지 않은 것 |
| 스윕이 삭제하지 않는가 | 타깃 2개 중 하나만 무효화한 뒤 실행하여, 건너뛴 타깃의 파일이 전부 남아 있는 것(§6.1) |
| 변경 종류별 표 | 무엇을 수정하였을 때 무엇이 다시 실행되는가를 표로 두고 검사합니다 — 워크북 내용 · 워크북 추가 · 레시피 섹션별 · 규칙 파일 · 애셋 파일 추가 · 환경변수 · 암호화 키 · 도구 버전 · 수정 시각만 변경. 각 행이 「다시 실행된다」와 「실행되지 않는다」 중 정해진 쪽인 것 |
| 옵션 분류 누락 | Options의 모든 프로퍼티에 분류가 선언되어 있는 것(§4) |
--full과의 대조 | 같은 입력에 대해 --full 실행과 캐시 실행의 산출이 같은 것 |
3번째가 이 목록의 핵심입니다. 캐시의 결함은 「무효화가 덜 된 것」이고, 그것은 정상 실행에서는 나타나지 않습니다 — 산출이 그대로이므로 골든도 통과합니다. 무효화를 확인하는 방법은 바꾸고 나서 다시 실행되는지 보는 것뿐입니다.
그리고 샘플 재생성 절차가 --full을 사용해야 합니다. 아키텍처와
개발이 「생성기·템플릿을 건드렸으면 골든 재기록 → 전
언어 비교본 재생성 → 샘플 재생성」을 유일한 방어선으로 적어 두었습니다. 도구 버전이 모델
키에 있으므로 빌드가 바뀌면 캐시는 자동으로 무효화되지만, 그 절차에 의존하지 않고 명시하는
쪽이 맞습니다.
10. 구현 기록 — 설계가 어디서 바뀌었는지
2단계 가 1단계와 함께 들어갔습니다. 따로 둘 이유가 없어졌습니다 — 타깃별 키를 계산하고
항목별 산출 목록을 인장에 적는 것이 1단계에도 필요했고(§6.1의 스윕), 거기까지 하면 「그 항목만
다시 실행」은 판정 한 줄입니다. 그리고 1단계만 넣으면 StagingFiles.Keep이 아무도 부르지 않는
코드가 됩니다.
결정적이지 않은 타깃이 하나가 아니라 둘이었습니다. §5에 html만 적어 두었는데, summary도
생성 시각을 문서에 적습니다(SummaryBuilder의 GeneratedAt). 예측이 틀린 자리이고, 찾은 것은
추론이 아니라 실행입니다 — 첫 시험에서 그 항목이 매번 다시 실행되는 것으로 나왔습니다.
「건너뛰지 않는 타깃」에 목록이 필요하지 않았습니다. §5의 후반에 적었습니다.
ShouldRun을 설계하고 호출하지 않았습니다. 판정은 다 만들어 놓고 TargetRegistry.RunAll의
루프에 한 줄을 넣지 않았고, 그래서 캐시가 「건너뛴다」고 보고하면서 전부 실행했습니다.
보고가 맞고 동작이 틀린 상태였으므로 로그로는 보이지 않습니다. 드러낸 것은 §9의 두 번째
게이트입니다 — 타깃 하나만 무효화한 뒤 나머지 항목의 파일이 그대로인지 비교했더니 생성 시각이
1초 뒤였습니다. 게이트를 먼저 만들지 않았으면 통과했을 결함입니다.
구글 시트는 패키지를 늘리지 않고 됩니다. Drive 클라이언트 라이브러리 대신 자격 증명으로 초기화한 HTTP 요청 하나입니다. 필드 한 개를 읽으려고 API 어셈블리를 더할 이유가 없습니다.
개인 OAuth는 스코프를 늘려도 브라우저가 열리지 않습니다. §6.5에 처음 「다음 실행이 브라우저를
엽니다」로 적었고, 틀렸습니다 — FileDataStore의 토큰은 스코프를 비교하지 않고 그대로 쓰이므로
시트 읽기는 되고 Drive 호출만 403이 됩니다. 그래서 §6.5.1의 메시지가 이 설계의 일부입니다:
사람이 토큰을 지워야 하고, 그 사실을 아는 것은 이 도구뿐입니다.
실측 — 무변경 재실행
| 무엇 | 값 |
|---|---|
| 전량 변환(테이블 548개·7개 타깃) | 161초 |
| 같은 것을 다시, 캐시로 | 0.48초 — 그중 판정 0.47초 |
| 픽스처 하나(테이블 8개·8개 타깃) | 0.98초 → 0.06초 |
0.47초가 무엇의 값인지 적어 둡니다 — 인장이 기록한 입력 40개의 크기·시각 확인, 소스 디렉터리 하나의 목록 재열거, 그리고 산출 3,739개의 크기·해시 확인입니다. 입력 쪽이 1,149 MB이지만 전부 그대로였으므로 한 바이트도 읽지 않았습니다(§3의 1단). 값을 치른 것은 산출 쪽 해시입니다.
335배입니다. 그리고 이 비율은 프로젝트가 커질수록 좋아지는 쪽입니다 — 변환 비용은 셀 수에 비례하고, 판정 비용은 파일 수에 비례합니다.