최근에 터보레포 캐싱에 대해서 공부를 하고 있는데요.
아무래도 모노레포를 위한 시스템으로는 최근에 Vercel의 인수로 각광받기 시작한 터라, 번역 글이 부족하더군요.
특히 캐싱은 제가 보기엔 터보레포의 lazy한 최적화 동작을 이해하기에 필수라고 생각했어요.
따라서, 한 번씩 이해가 필요한 것들에 대해서 공식문서 번역 글을 써보려 했어요.
그럼, 시작해볼까요?!
당신이 사용해온 것들과는 달리, turbo
는 이전 커맨드 실행에서 나온 파일과 로그들을 캐싱할 수 있습니다. 이를 통해 이미 완료된 작업을 생략하고, 엄청난 시간을 절약할 수 있습니다.
기본적으로, turbo run
은 (package.json
의 script
에서 정의된) 패키지 태스크의 dist
나 build
에서 나온 파일들을 캐싱할 수 있는 것으로 여깁니다. 또한, turbo run
은 stderr
과 stdout
과 같은 로그들을 다루는데, 그것들은 자동적으로 캐싱할 수 있는 아티팩트로써 .turbo/run-<commmand>.log
로 쓰여집니다.
이러한 아티팩트로써 로그를 사용함으로써 당신은 당신의 터보레포에 어떠한 커맨드에 관해서든 거의 대부분을 캐싱할 수 있습니다.
pipeline
을 사용함으로써 당신은 당신의 터보레포에 걸쳐 캐시 컨벤션을 설정할 수 있습니다.
기본적인 캐시들을 내보내는 행동들을 오버라이드하기 위해, pipeline.<task>.outputs
배열에 일련의 여러 파일 이름의 집합(glob
)들을 넘겨주세요. 태스크를 위한 파일 이름 패턴들을 만족하는 파일들은 아티팩트로써 다루어질 겁니다. 🤗
{
"$schema": "https://turborepo.org/schema.json",
"pipeline": {
"build": {
"outputs": ["dist/**", ".next/**"],
"dependsOn": ["^build"]
},
"test": {
"outputs": [], // leave empty to only cache logs
"dependsOn": ["build"]
}
}
}
만약 당신의 일이 어떠한 파일을 내뱉지 않는다면, 그저 outputs
에 빈 배열을 넣어주시면 됩니다. 터보레포가 간편하고 빠르게 당신을 위한 로그들을 캐싱해 줄 거에요.
ESLint
캐싱을 위한 팁 - 당신은eslint
이전에TIMING=1
이라는 변수를 설정함으로써 캐싱할 수 있는 예쁜 터미널output
을 얻을 수 있습니다. (그것이 만약 에러가 아닐지라고 해도 말이지요.)
자세한 것은ESLint
공식 문서를 참조해주세요.
만약 당신이 실행하려 한다면 위의 pipeline 설정과 적절한 아웃풋 폴더들을 입력한 상태로 다음을 입력하세요.
turbo run build test
그리고 node_modules/.cache/turbo
를 열어보세요. 당신은 캐시된 아티팩트를 볼 수 있을 겁니다. 🥰
가끔 당신은 정말로 캐시 행동들을 원지 않을 수 있습니다. (예를 들자면, next dev
를 한다거나, react-scripts start
를 한다던가 할 때가 있습니다.)
전체적으로 캐싱을 하지 않게 해주려면, 어떠한 커맨드든지 --no-cache
를 써주면 됩니다!
# Run `dev` npm script in all packages and apps in parallel,
# but don't cache the output
turbo run dev --pararrel --no-cache
또한, pipeline
에 cache
옵션을 false
로 설정해줌으로써 특정한 태스크 캐싱을 항상 중단시킬 수 있어요.
{
"$schema": "https://turborepo.org/schema.json",
"pipeline": {
"dev": {
"cache": false
}
}
}
몇몇 특정한 태스크에 대해, 당신은 관련 없는 파일이 변경되었을 때의 캐시가 되지 않는 것을 원치 않을 수 있습니다.
예를 들어볼까요? README.md
를 업데이트한다는 것은 tesk
태스크에 대해 캐시가 되지 않는 것을 트리거할 필요가 없을지 모릅니다.
당신은 특정 태스크에 대해 고려해야 할 일련의 파일들을 한정하기 위해inputs
를 사용할 수 있습니다.
여기서는 test
태스크에 관해 캐시 히트(CPU가 참조하는 메모리가 캐시에 존재하는 것)를 결정하는 것에 관해 오직 .ts
와 .tsx
에 대해서만 고려하도록 했네요.
{
"$schema": "https://turborepo.org/schema.json",
"pipeline": {
// ...other tasks
"test": {
"outputs": [], // leave empty to only cache logs
"dependsOn": ["build"],
"inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"]
}
}
}
만약 당신이 turbo
를 빌드 타임 시 환경 변수를 인라인하는 도구랑 같이 쓴다면, turbo
에 꼭 알려주세요! 그렇지 않다면, 캐시된 아티팩트가 잘못된 환경 변수가 들어간 채로 보내질 지도 모릅니다.
다행이라면, 당신은 환경 변수와 파일의 내용물의 값들에 기반하여 turbo
의 캐시를 핑거프린팅(해싱)하는 것을 컨트롤 할 수 있어요.
pipeline
에 $
가 앞에 붙여진 환경변수명이 dependsOn
에 있는 것은 각 패키지 태스크나 각각의 태스크의 캐시 핑거프린트에 있어 영향을 미칩니다.globalDependencies
에 $
가 앞에 붙여진 환경변수들은 모든 태스크에 있어 캐시 핑거프린트에 영향을 미칩니다.globalDependencies
에 파일이나 파일 이름 패턴이 적힌 경우도 모든 태스크의 캐시 핑거프린트에 영향을 미칩니다.THASH
가 포함된 환경변수의 값은 모든 태스크의 캐시 핑거프린트에 영향을 미칩니다.{
"$schema": "https://turborepo.org/schema.json",
"pipeline": {
"build": {
"dependsOn": [
"^build",
// env vars will impact hashes of all "build" tasks
"$SOME_ENV_VAR",
],
"outputs": ["dist/**"]
},
"web#build": { // override settings for the "build" task for the "web" app
"dependsOn": [
"^build",
// env vars that will impact the hash of "build" task for only "web" app
"$STRIPE_SECRET_KEY",
"$NEXT_PUBLIC_STRIPE_PUBLIC_KEY",
"$NEXT_PUBLIC_ANALYTICS_ID",
],
"outputs": [".next/**"],
},
"docs#build": { // override settings for the "build" task for the "docs" app
"dependsOn": [
"^build",
// env vars that will impact the hash of "build" task for only "docs" app
"$STRIPE_SECRET_KEY",
"$NEXT_PUBLIC_STRIPE_PUBLIC_KEY",
"$NEXT_PUBLIC_ANALYTICS_ID",
],
"outputs": [".next/**"],
}
},
"baseBranch": "origin/main",
"globalDependencies": [
"$GITHUB_TOKEN",// env var that will impact the hashes of all tasks,
"tsconfig.json", // file contents will impact the hashes of all tasks,
".env.*", // glob file contents will impact the hashes of all tasks,
]
}
팁 - 대부분의 모노레포에서, 당신은 공유된 패키지의 환경 변수를 자주 사용하지 않고, 오직 애플리케이션에서만 사용할 것인데요. 따라서, 캐시 히트 비율을 높이기 위해서 당신은 그들이 사용된 앱의 특정 태스크에 있는 환경 변수를 포함해야 할 것입니다.
거꾸로, 만약 당신이 이전의 캐시된 태스크를 다시 강제 실행하기를 원하는 경우에는 --force
flag를 추가하세요.
# Run `build` npm script in all packages and apps,
# ignoring cache hits.
turbo run build --force
turbo
가 당신의 태스크들의 output들을 캐시할 뿐만 아니라, 터미널에서의 output 역시 (다시 말하자면 stdout
, stderr
의 결합) <package>/.turbo/run-<command>.log
에 기록합니다.
turbo
가 캐시된 태스크에 들어가면, 그것은 마치 다시 일어난 것처럼 output을 다시 실행할 것입니다. 다만 패키지의 이름이 약간 흐리게 표시될 것입니다.
지금까지, 당신은 turbo
가 주어진 태스크에 대해 어떻게 캐시 히트와 캐시 미스를 결정하는지에 대해 궁금하셨을텐데요! 좋은 질문입니다. 👏🏻
첫번째로, turbo
는 모노레포의 현재 전역상태를 해시로 구성합니다.
globlDependencies
에 리스트되어 있는 환경변수의 값들THASH
라는 문자가 포함된 key-value
쌍의 정렬된 환경 변수 (예를 들면 STRIPE_PUBLIC_KEY
가 아닌 STRIPE_PUBLIC_THASH_SECRET_KEY
)그러면 이는 주어진 패키지의 태스크와 관련된 여러 요소를 추가합니다.
inputs
파일 이름 패턴과 일치하는 패키지 폴더나 파일들에 있는, gitignore
에는 없는 모든 내용물들에 해시 값을 붙입니다.pipeline
에 있는 outputs
에 명시된 옵션들에 해시 값을 붙입니다.root lockfile
로부터 package.json
에 명시된, 설치된 모든 dependencies
, devDependencies
, optionalDependencies
의 버전 집합pipeline.<task-or-package-task>.dependsOn
에 리스트된, 환경 변수와 일치하는 key-value
쌍의 정렬된 환경 변수 리스트일단 turbo
가 실행된 주어진 패키지 태스크에 들어가면, (로컬과 원격 모두) 해시와 일치하는 캐시를 검사합니다.
만약 일치한다면, 태스크 실행을 스킵하고, 캐시된 output을 적절한 곳에 이동시키거나 다운로드합니다. 그리고 즉시, 이전에 기록된 로그들을 다시 실행합니다.
만약에 계산된 해쉬값과 일치하는 (원격 또는 로컬) 캐시가 없다면, 로컬로 작업을 실행한 후 turbo
는 인덱스로써 해쉬를 사용하면서 outputs
에 명시된 곳에 캐싱합니다.
주어진 태스크의 해시 값은 TURBO_HASH
환경변수로써 실행 시간에 주입됩니다.
이 값은 Dockerfile
등을 태깅하거나 outputs
에 스탬핑을 하는 데 유용합니다.
turbo
v0.6.10인 현재,turbo
의 해싱 알고리즘은npm
과pnpm
을 쓸 땐 위와 살짝 다릅니다. 만약 이들 중 하나인 패키지 매니저를 사용할 때,turbo
는 각각의 패키지 태스크에 대하여 해쉬 알고리즘에lockfile
의 해시된 내용들을 포함할 것입니다. 그것은 현재의yarn
구현과 같이 모든 종속성의 resolved된 집합들을 파싱하거나, 구문 분석을 하지 않습니다.
와... 이 글을 하나하나 번역하는 데 꽤 적지 않은 시간이 드는군요..!
그렇지만, 하나하나 의미를 뜯어보면서 꽤나 새로운 정보들을 많이 얻어가서 기분이 좋아요 🥰
다들 터보레포 즐겁게 쓰시길 바라며, 이상! 👐🏻