00:00:00(경쾌한 음악) 네, 여러분 안녕하세요, 감사합니다.
00:00:07저는 루크 샌드버그입니다.
00:00:09저는 Vercel의 소프트웨어 엔지니어이며, Turbo Pack 개발을 담당하고 있습니다.
00:00:12Vercel에 온 지 약 6개월 정도 되었는데요,
00:00:15그 시간 동안 제가 직접 하지 않은 멋진 작업들에 대해 이 무대에서 말씀드릴 기회를 얻었습니다.
00:00:23Vercel에 오기 전에는 Google에 있었습니다.
00:00:26그곳에서 사내 웹 툴체인 작업을 했고,
00:00:29TSX를 자바 바이트코드로 컴파일하는 컴파일러를 만들거나 클로저 컴파일러 작업을 하는 등 특이한 일들을 했습니다..
00:00:37그래서 Vercel에 왔을 때, 마치 다른 행성에 발을 디딘 것 같았어요. 모든 것이 달랐죠.
00:00:45팀에서 하는 일들과 우리의 목표에 꽤 놀랐습니다.
00:00:50오늘 저는 Turbo Pack에서 내린 몇 가지 설계 결정과 그 결정들이 우리가 이미 달성한 환상적인 성능을 계속해서 발전시키는 데 어떻게 기여할지 말씀드리겠습니다.
00:01:01이해를 돕기 위해, 이것이 우리의 전반적인 설계 목표입니다.
00:01:06이것만 봐도 우리가 어려운 선택을 했다는 것을 바로 짐작할 수 있습니다.
00:01:14그럼 콜드 빌드는 어떨까요?
00:01:17그것들도 중요하지만, 우리의 생각은 애초에 콜드 빌드를 경험할 일이 없어야 한다는 것입니다.
00:01:22오늘 강연은 바로 이 점에 초점을 맞출 것입니다.
00:01:24기조연설에서 번들링 성능 향상을 위해 증분성을 어떻게 활용하는지 잠시 들으셨을 겁니다.
00:01:31증분성을 위한 핵심 아이디어는 캐싱입니다.
00:01:35번들러가 하는 모든 작업을 캐시 가능하게 만들고 싶습니다.
00:01:38그래서 변경 사항이 생길 때마다 해당 변경과 관련된 작업만 다시 하면 되도록 말이죠..
00:01:43다른 말로 하자면,
00:01:44빌드 비용은 애플리케이션의 크기나 복잡성보다는 변경 사항의 크기나 복잡성에 비례해야 한다는 것입니다.
00:01:53이렇게 하면 아무리 많은 아이콘 라이브러리를 가져와도 Turbo Pack이 개발자에게 계속해서 좋은 성능을 제공할 수 있습니다.
00:02:01이 아이디어를 이해하고 설명하기 위해, 세상에서 가장 간단한 번들러를 상상해 봅시다.
00:02:07아마 이런 모습일 겁니다..
00:02:09자, 여기 우리의 '아기 번들러'가 있습니다.
00:02:12이것은 슬라이드에 넣기에는 코드가 좀 많을 수도 있지만, 앞으로는 더 복잡해질 겁니다.
00:02:17여기서는 모든 진입점을 파싱합니다.
00:02:20그리고 가져오기를 따라가고,
00:02:21참조를 해결하며,
00:02:23애플리케이션 전체를 재귀적으로 탐색하여 의존하는 모든 것을 찾습니다.
00:02:28마지막으로, 각 진입점이 의존하는 모든 것을 단순히 수집하여 출력 파일에 넣습니다.
00:02:35야호, 아기 번들러가 생겼습니다!
00:02:38물론 이것은 순진한 방식이지만, 증분적인 관점에서 보면, 이 중 어떤 부분도 증분적이지 않습니다.
00:02:45특정 파일은 여러 번 파싱하게 될 것이고, 아마도 몇 번 가져오느냐에 따라 달라지겠죠.
00:02:51이건 정말 좋지 않습니다..
00:02:53React 가져오기는 수백, 수천 번 해결해야 할 것입니다.
00:02:57그러니, 비효율적일 겁니다.
00:03:01이것을 최소한 조금이라도 더 증분적으로 만들려면, 중복 작업을 피할 방법을 찾아야 합니다.
00:03:08캐시를 추가해 봅시다.
00:03:10이것이 우리의 파싱 함수라고 상상해 볼 수 있습니다.
00:03:15아주 간단하죠.
00:03:15아마도 우리 번들러의 핵심 작업일 겁니다.
00:03:19보시다시피, 아주 간단합니다.
00:03:19파일 내용을 읽고, SWC에 넘겨서 AST를 얻습니다.
00:03:25이제 캐시를 추가해 봅시다.
00:03:27좋습니다. 이것은 분명 간단하고 좋은 성과입니다.
00:03:31하지만, 여러분 중 일부는 이전에 캐싱 코드를 작성해 보셨을 겁니다.
00:03:36아마도 여기에 몇 가지 문제가 있을 수 있습니다.
00:03:38예를 들어, 파일이 변경되면 어떻게 될까요?
00:03:41이것은 분명 우리가 신경 써야 할 부분입니다.
00:03:46그리고 파일이 실제 파일이 아니라, 트렌치코트 안에 세 개의 심볼릭 링크처럼 복잡한 구조라면 어떨까요?
00:03:52많은 패키지 관리자들이 의존성을 그런 식으로 구성합니다.
00:03:55우리는 파일 이름을 캐시 키로 사용하고 있습니다.
00:03:59그것으로 충분할까요?
00:04:00예를 들어, 우리는 클라이언트와 서버를 위해 번들링합니다.
00:04:03같은 파일이 둘 다에 포함될 수 있습니다.
00:04:04이게 제대로 작동할까요?
00:04:05우리는 AST를 저장하고 반환하기도 합니다.
00:04:08이제 변형(mutation)에 대해 걱정해야 합니다.
00:04:11그리고 마지막으로, 이것은 파싱하는 데 너무 순진한 방법 아닌가요?
00:04:16모두가 컴파일러에 대한 방대한 설정을 가지고 있다는 것을 압니다.
00:04:21그중 일부는 여기에 포함되어야 합니다.
00:04:23네, 이 모든 것이 훌륭한 피드백입니다.
00:04:27그리고 이것은 매우 순진한 접근 방식입니다.
00:04:32물론, 저는 '네, 이건 작동하지 않을 겁니다'라고 말할 것입니다.
00:04:36그럼 이 문제들을 해결하기 위해 무엇을 해야 할까요?
00:04:39실수 없이 고쳐야 합니다.
00:04:44네, 좋습니다.
00:04:46아마도 이것이 조금 더 나을 겁니다.
00:04:49보시다시피, 여기에는 몇 가지 변환(transform)이 있습니다.
00:04:52각 파일에 대해 맞춤형 작업을 해야 합니다.
00:04:54예를 들어, 다운레벨링을 하거나 'use cache'를 구현하는 것과 같이요..
00:04:58또한 몇 가지 설정도 있습니다.
00:05:00따라서 당연히 캐시 키에 그 설정을 포함해야 합니다.
00:05:04하지만 아마도 여러분은 바로 의심할 겁니다.
00:05:08이게 정확한가요?
00:05:09이름만으로 변환을 식별하는 것이 정말 충분할까요?
00:05:13모르겠습니다. 아마도 그 자체로 복잡한 설정을 가지고 있을 수도 있습니다.
00:05:16그리고, 이 두 개의 JSON 값이 우리가 신경 쓰는 모든 것을 실제로 담아낼 수 있을까요?
00:05:24개발자들이 이것을 유지보수할까요?
00:05:26이 캐시 키들은 얼마나 커질까요?
00:05:29설정 파일의 복사본은 몇 개나 가지게 될까요?
00:05:31저는 실제로 이런 코드를 직접 본 적이 있는데, 이해하기가 거의 불가능하다고 생각합니다.
00:05:37좋습니다. 우리는 무효화(invalidation)와 관련된 다른 문제도 해결하려고 했습니다.
00:05:43그래서 파일 읽기(read file)에 콜백 API를 추가했습니다.
00:05:46이것은 좋습니다.
00:05:47파일이 변경되면 캐시에서 바로 삭제하여 오래된 내용을 계속 제공하지 않도록 할 수 있습니다..
00:05:55하지만 이것은 사실 꽤 순진한 생각입니다.
00:05:57물론 캐시를 삭제해야 하지만,
00:05:58호출자(caller)도 새 복사본을 가져와야 한다는 것을 알아야 하기 때문이죠..
00:06:03좋습니다. 그럼 콜백을 연결하기 시작해 봅시다.
00:06:06좋습니다, 해냈습니다.
00:06:09스택을 통해 콜백을 연결했습니다.
00:06:12여기서 호출자가 변경 사항을 구독할 수 있도록 허용하는 것을 볼 수 있습니다.
00:06:16무엇이든 변경되면 전체 번들을 다시 실행할 수 있고, 파일이 변경되면 호출합니다.
00:06:22좋습니다. 반응형 번들러가 생겼습니다.
00:06:25하지만 이것은 여전히 거의 증분적이지 않습니다.
00:06:28파일이 변경되면 모든 모듈을 다시 탐색하고 모든 출력 파일을 생성해야 합니다.
00:06:37파싱 캐시를 통해 많은 작업을 절약했지만, 이것만으로는 충분하지 않습니다.
00:06:45그리고 마지막으로, 이 모든 다른 중복 작업들이 있습니다.
00:06:49예를 들어, 우리는 가져오기(imports)를 확실히 캐시하고 싶습니다.
00:06:52파일을 여러 번 찾을 수 있고, 그 가져오기가 계속 필요하므로, 그곳에 캐시를 두어야 합니다.
00:06:57그리고,
00:06:58해결 결과(resolve results)는 실제로 꽤 복잡하므로,
00:07:01React를 해결하는 데 했던 작업을 재사용할 수 있도록 확실히 캐시해야 합니다.
00:07:08하지만, 좋습니다. 이제 또 다른 문제가 있습니다.
00:07:11의존성을 업데이트하거나 새 파일을 추가하면 해결 결과가 변경되므로, 그곳에도 또 다른 콜백이 필요합니다.
00:07:18그리고 우리는 출력물을 생성하는 로직도 확실히 캐시하고 싶습니다.
00:07:22HMR 세션을 생각해 보면,
00:07:24애플리케이션의 한 부분만 편집하는데,
00:07:27왜 매번 모든 출력물을 다시 작성해야 할까요??
00:07:31또한, 출력 파일을 삭제할 수도 있으므로, 그곳의 변경 사항도 들어야 할 것입니다.
00:07:39좋습니다.
00:07:39아마도 이 모든 것을 해결했을지 모르지만, 여전히 문제가 있습니다.
00:07:44바로 무엇이든 변경될 때마다 처음부터 다시 시작한다는 것입니다..
00:07:48그래서 이 함수의 전체 제어 흐름은 작동하지 않습니다.
00:07:51단일 파일이 변경되면, 우리는 그 for 루프의 중간으로 바로 건너뛰고 싶을 것이기 때문입니다..
00:07:56그리고 마지막으로, 호출자에게 제공하는 우리의 API도 절망적으로 순진합니다.
00:08:03그들은 아마도 어떤 파일이 변경되었는지 알고 싶어 할 것입니다.
00:08:05그래야 클라이언트에 업데이트를 푸시할 수 있으니까요..
00:08:07네, 맞습니다.
00:08:11그래서 이 접근 방식은 제대로 작동하지 않습니다.
00:08:13그리고 우리가 어떻게든 이 모든 곳에 콜백을 연결했다고 해도,
00:08:17이 코드를 실제로 유지보수할 수 있다고 생각하시나요?
00:08:21여기에 새로운 기능을 추가할 수 있다고 생각하시나요?
00:08:24저는 그렇게 생각하지 않습니다.
00:08:25이것은 그냥 망가질 겁니다.
00:08:28그리고, 그에 대해 저는 '네, 맞습니다'라고 말할 것입니다.
00:08:34그럼 다시 한번, 우리는 무엇을 해야 할까요?
00:08:36LLM과 대화할 때처럼, 먼저 무엇을 원하는지 정확히 알아야 합니다.
00:08:43그리고 그것을 매우 명확하게 표현해야 합니다.
00:08:48그럼 우리는 대체 무엇을 원하는 걸까요?
00:08:50우리는 많은 다른 접근 방식들을 고려했고,
00:08:53팀의 많은 사람들이 실제로 번들러 작업에 대한 많은 경험을 가지고 있었습니다.
00:08:59그래서 우리는 이런 종류의 대략적인 요구 사항들을 도출했습니다.
00:09:02우리는 번들러의 모든 비용이 많이 드는 작업을 확실히 캐시할 수 있기를 원합니다.
00:09:05그리고 이것은 정말 쉬워야 합니다.
00:09:08새로운 캐시를 추가할 때마다 코드 리뷰에서 15개의 댓글을 받아서는 안 됩니다.
00:09:12그리고 저는 개발자들이 올바른 캐시 키를 작성하거나,
00:09:17입력 또는 의존성을 수동으로 추적하는 것을 실제로 신뢰하지 않습니다.
00:09:24그래서 우리가 처리해야 합니다.
00:09:26실수할 여지 없게 만들어야 합니다.
00:09:30다음으로, 변경되는 입력을 처리해야 합니다.
00:09:33이것은 HMR의 큰 아이디어와 같지만, 세션 간에도 마찬가지입니다.
00:09:36주로 파일이겠지만, 설정 값과 같은 것들도 포함될 수 있습니다.
00:09:40그리고 파일 시스템 캐시를 사용하면, 환경 변수와 같은 것들도 결국 포함됩니다.
00:09:45그래서 우리는 반응형이 되기를 원합니다.
00:09:47무엇이든 변경되자마자 다시 계산할 수 있기를 원하고, 모든 곳에 콜백을 연결하고 싶지 않습니다.
00:09:54마지막으로, 우리는 현대적인 아키텍처를 활용하고, 멀티스레딩을 사용하며, 전반적으로 빨라야 합니다.
00:10:02아마도 여러분은 이 요구 사항들을 보면서,
00:10:06일부는 '이게 번들러랑 무슨 상관이지?'라고 생각할 수도 있습니다.
00:10:12그에 대해 저는 물론, 제 경영진이 이 방에 있으니, 그 이야기는 굳이 할 필요가 없다고 말씀드리겠습니다.
00:10:20하지만 사실, 여러분 중 많은 분들이 훨씬 더 명확한 결론에 도달했을 것이라고 짐작합니다.
00:10:24이것은 시그널(signals)과 매우 비슷하게 들립니다.
00:10:28네, 저는 시그널과 같은 시스템을 설명하고 있습니다.
00:10:31이것은 계산을 구성하고, 의존성을 추적하며, 어느 정도의 자동 메모이제이션을 제공하는 방식입니다.
00:10:37그리고 우리는 모든 종류의 시스템에서 영감을 얻었으며,
00:10:40특히 Rust 컴파일러와 Salsa라는 시스템에서 영감을 받았다는 점을 말씀드려야 합니다.
00:10:45관심 있으시다면, Adaptons라고 불리는 이 개념에 대한 학술 문헌도 있습니다.
00:10:51좋습니다.
00:10:51그럼 이것이 실제로 어떻게 작동하는지 살펴보고,
00:10:55그다음에는 JavaScript 코드 샘플에서 Rust로 매우 갑작스럽게 전환할 것입니다..
00:11:01여기 우리가 구축한 인프라의 예시가 있습니다.
00:11:05TurboTask 함수는 우리 컴파일러에서 캐시되는 작업 단위입니다.
00:11:12따라서 이런 식으로 함수에 주석을 달면,
00:11:16우리는 그것을 추적하고,
00:11:18매개변수로부터 캐시 키를 구성할 수 있습니다.
00:11:21이를 통해 필요할 때 캐시하고 다시 실행할 수 있습니다..
00:11:28여기 VC 타입은 시그널(signals)과 같다고 생각할 수 있습니다.
00:11:32이것은 반응형 값이며,
00:11:33VC는 'value cell'의 약자이지만,
00:11:36'signal'이 더 나은 이름일 수도 있습니다..
00:11:39이런 식으로 매개변수를 선언하면,
00:11:41'이것은 변경될 수 있으며,
00:11:43변경되면 다시 실행하고 싶다'고 말하는 것입니다.
00:11:47그럼 어떻게 그것을 알 수 있을까요?
00:11:49우리는 'await'를 통해 이 값들을 읽습니다.
00:11:52이렇게 반응형 값을 'await'하면, 우리는 자동으로 의존성을 추적합니다.
00:11:58그리고 마지막으로, 물론 우리가 원했던 실제 계산을 수행하고, 그것을 셀(cell)에 저장합니다.
00:12:07우리가 의존성을 자동으로 추적했기 때문에, 이 함수가 파일 내용과 설정 값 모두에 의존한다는 것을 압니다.
00:12:17그리고 셀에 새로운 결과를 저장할 때마다,
00:12:21이전 결과와 비교하여 변경되었다면,
00:12:24그 값을 읽은 모든 사람에게 알림을 전파할 수 있습니다.
00:12:29따라서 '변경'이라는 이 개념은 증분성에 대한 우리의 접근 방식의 핵심입니다.
00:12:33네, 다시 말하지만, 가장 간단한 경우는 바로 여기 있습니다.
00:12:37파일이 변경되면, Turbo Pack은 이를 감지하고, 이 함수 실행을 무효화한 다음 즉시 다시 실행합니다.
00:12:45그리고 만약 우리가 우연히 동일한 AST를 생성한다면,
00:12:48우리는 동일한 셀을 계산했으므로 바로 거기서 멈출 것입니다.
00:12:53이제 파일을 파싱하는 경우, AST를 실제로 변경하지 않는 편집은 거의 없습니다.
00:13:00하지만 우리는 Turbo Pack 함수의 근본적인 조합성을 활용하여 이를 더욱 발전시킬 수 있습니다.
00:13:07여기서 우리는 모듈에서 가져오기(imports)를 추출하는 또 다른 Turbo Pack 캐시 함수를 볼 수 있습니다.
00:13:15이것은 번들러에서 매우 흔한 작업이라고 상상할 수 있습니다.
00:13:20애플리케이션의 모든 모듈을 찾기 위해서라도 가져오기를 추출해야 합니다.
00:13:25우리는 그것들을 활용하여 모듈을 청크로 묶는 가장 좋은 방법을 선택합니다.
00:13:29그리고 물론, 가져오기 그래프는 트리 쉐이킹과 같은 기본적인 작업에 중요합니다.
00:13:34따라서 가져오기 데이터를 사용하는 소비자가 너무 많기 때문에 캐시는 매우 합리적입니다.
00:13:41따라서 이 구현은 특별하지 않습니다.
00:13:44이것은 어떤 종류의 번들러에서도 볼 수 있는 것과 같습니다.
00:13:46우리는 AST를 탐색하고,
00:13:48우리가 선호하는 특별한 데이터 구조에 가져오기를 수집한 다음,
00:13:53그것들을 반환합니다.
00:13:55하지만 여기서 핵심 아이디어는 그것들을 다른 셀에 저장한다는 것입니다.
00:13:58따라서 모듈이 변경되면, 우리가 그것을 읽었기 때문에 이 함수를 다시 실행해야 합니다.
00:14:05하지만 모듈에 가하는 변경 사항의 종류를 생각해 보면, 실제로 가져오기에 영향을 미치는 것은 거의 없습니다.
00:14:12모듈을 변경하고, 함수 본문, 문자열 리터럴, 어떤 종류의 구현 세부 사항이든 업데이트합니다.
00:14:20그것은 이 함수를 무효화하고, 우리는 동일한 가져오기 집합을 계산할 것입니다.
00:14:25그리고 이것을 읽은 어떤 것도 무효화하지 않습니다.
00:14:29따라서 HMR 세션에서 이것을 생각해 보면,
00:14:32이것은 파일을 다시 파싱해야 하지만,
00:14:35더 이상 청킹 결정을 어떻게 할지 고민할 필요가 없다는 것을 의미합니다.
00:14:40트리 쉐이킹 결과에 대해 어떤 것도 생각할 필요가 없습니다.
00:14:43그것들이 변경되지 않았다는 것을 알기 때문입니다..
00:14:45따라서 파일을 파싱하고, 이 간단한 분석을 수행한 다음, 바로 출력물을 생성하는 단계로 넘어갈 수 있습니다.
00:14:53그리고 이것이 우리가 정말 빠른 새로고침 시간을 가지는 방법 중 하나입니다.
00:14:57이것은 꽤 명령형입니다.
00:15:02이 기본적인 아이디어를 생각하는 또 다른 방법은 노드 그래프로 보는 것입니다.
00:15:06여기 왼쪽에, 콜드 빌드를 상상할 수 있습니다.
00:15:12처음에는 실제로 모든 파일을 읽고, 모두 파싱하고, 모든 가져오기를 분석해야 합니다.
00:15:17그리고 그 부작용으로, 우리는 애플리케이션의 모든 의존성 정보를 수집했습니다.
00:15:21그리고 무언가 변경되면,
00:15:23우리가 구축한 의존성 그래프를 활용하여 무효화를 전파하고,
00:15:27스택을 거슬러 올라가 Turbo Pack 함수를 다시 실행할 수 있습니다.
00:15:32그래서 만약 새로운 값을 생성하면, 거기서 멈춥니다.
00:15:35그렇지 않으면, 무효화를 계속 전파합니다.
00:15:37아주 좋습니다.
00:15:41아마 짐작하시겠지만, 이것은 우리가 실제로 하는 일에 대한 엄청난 과도한 단순화입니다.
00:15:47오늘날 Turbo Pack에는 약 2,500개의 다른 TurboTask 함수가 있습니다.
00:15:53그리고 일반적인 빌드에서는 말 그대로 수백만 개의 다른 작업을 가질 수 있습니다.
00:15:58그래서 실제로는 아마도 이런 모습에 더 가까울 겁니다.
00:16:01이제 여러분이 이것을 읽을 수 있을 거라고는 기대하지 않습니다.
00:16:04슬라이드에 다 담을 수 없었습니다.
00:16:06그럼 좀 더 넓게 볼까요?
00:16:08좋습니다. 분명히 도움이 되지 않네요.
00:16:14실제로 우리는 Turbo Pack 내부에서 일어나는 일을 추적하고 시각화하는 더 나은 방법들을 가지고 있습니다.
00:16:21하지만 근본적으로, 그것들은 대부분의 의존성 정보를 버리는 방식으로 작동합니다.
00:16:26이제 여러분 중 일부는 시그널 작업 경험이 있을 것이고, 아마도 좋지 않은 경험일 수도 있다고 짐작합니다.
00:16:34저는 개인적으로 스택 트레이스와 디버거에서 함수 안팎으로 이동할 수 있는 것을 좋아합니다.
00:16:41그래서 이것이 완전한 만병통치약이라고 의심할 수도 있습니다.
00:16:45분명히 장단점이 따르죠.
00:16:47네,
00:16:48그래서 그에 대해 저는 물론,
00:16:51'소프트웨어 엔지니어링은 모두 트레이드오프를 관리하는 것'이라고 말씀드리겠습니다.
00:17:01우리는 항상 문제를 정확히 해결하는 것이 아니라,
00:17:04가치를 제공하기 위해 새로운 트레이드오프 세트를 선택하는 것입니다.
00:17:08따라서 Turbo Pack에서 증분 빌드에 대한 우리의 설계 목표를 달성하기 위해,
00:17:14우리는 이 증분 반응형 프로그래밍 모델에 모든 것을 걸었습니다.
00:17:19그리고 이것은 물론 몇 가지 매우 자연스러운 결과들을 가져왔습니다.
00:17:23그래서,
00:17:24아마도 우리는 수동으로 만든 캐싱 시스템과 번거로운 무효화 로직의 문제를 실제로 해결했을지도 모릅니다.
00:17:33그 대가로, 우리는 복잡한 캐싱 인프라를 관리해야 합니다.
00:17:39그리고 물론, 저에게는 그것이 정말 좋은 트레이드오프처럼 들립니다.
00:17:42저는 복잡한 캐싱 인프라를 좋아하지만, 우리 모두는 그 결과와 함께 살아가야 합니다.
00:17:48그래서 첫 번째는 물론 이 시스템의 핵심 오버헤드입니다.
00:17:54주어진 빌드나 HMR 세션에서 생각해 보면, 실제로 많은 것을 변경하지 않습니다.
00:18:04그래서 우리는 애플리케이션의 모든 가져오기와 모든 해결 결과 사이의 모든 의존성 정보를 추적하지만,
00:18:10실제로 변경하는 것은 그중 몇 개에 불과할 것입니다.
00:18:13따라서 우리가 수집하는 대부분의 의존성 정보는 실제로는 전혀 필요하지 않습니다.
00:18:16그래서 이것을 관리하기 위해,
00:18:18우리는 이 캐싱 레이어의 성능을 개선하여 오버헤드를 줄이고,
00:18:23우리 시스템이 점점 더 큰 애플리케이션으로 확장될 수 있도록 하는 데 많은 노력을 기울여야 했습니다.
00:18:30다음으로 가장 분명한 것은 단순히 메모리입니다.
00:18:34캐시는 항상 근본적으로 시간 대 메모리 트레이드오프입니다.
00:18:38그리고 우리의 캐시도 거기서 크게 다르지 않습니다.
00:18:41우리의 간단한 목표는 캐시 크기가 애플리케이션 크기에 비례하여 선형적으로 확장되어야 한다는 것입니다.
00:18:49하지만 다시 말하지만, 오버헤드에 주의해야 합니다.
00:18:51다음 것은 약간 미묘합니다.
00:18:54예상하시겠지만, 번들러에는 많은 알고리즘이 있습니다.
00:18:58그리고 그중 일부는 애플리케이션에 대한 전역적인 이해를 필요로 합니다.
00:19:03음, 그것은 문제입니다. 전역 정보에 의존할 때마다 어떤 변경이든 해당 작업을 무효화할 수 있기 때문입니다.
00:19:10따라서 우리는 이러한 알고리즘을 어떻게 설계하고, 증분성을 유지할 수 있도록 신중하게 구성해야 합니다.
00:19:17그리고 마지막으로, 이것은 아마도 개인적인 불만일 수 있습니다.
00:19:24Turbo Pack에서는 모든 것이 비동기입니다.
00:19:27그래서 이것은 수평적 확장성에는 훌륭하지만,
00:19:29다시 한번,
00:19:30디버깅 성능 프로파일링과 같은 우리의 근본적인 목표에는 해를 끼칩니다.
00:19:38여러분 중 많은 분들이 Chrome 개발자 도구에서 비동기 코드를 디버깅해 본 경험이 있을 것이라고 확신합니다.
00:19:46그리고 이것은 일반적으로 꽤 좋은 경험입니다.
00:19:48항상 이상적인 것은 아니지만요.
00:19:49그리고 LLDB를 사용하는 Rust는 훨씬 뒤떨어져 있다고 장담합니다.
00:19:53그래서 그것을 관리하기 위해, 우리는 맞춤형 시각화, 계측 및 추적 도구에 투자해야 했습니다.
00:20:01그리고 보세요, 번들러가 아닌 또 다른 인프라 프로젝트입니다.
00:20:07좋습니다. 그럼 우리가 올바른 선택을 했는지 한번 살펴봅시다.
00:20:11Vercel에는 매우 큰 프로덕션 애플리케이션이 있습니다.
00:20:17아마 세계에서 가장 큰 것 중 하나일 것이라고 생각하지만, 사실은 정확히 알 수 없습니다.
00:20:21하지만 약 8만 개의 모듈을 포함하고 있습니다.
00:20:23그럼 Turbo Pack이 이 애플리케이션에서 어떻게 작동하는지 살펴봅시다.
00:20:26빠른 새로고침(fast refresh) 면에서는 Webpack이 제공할 수 있는 것을 정말로 압도합니다.
00:20:32하지만 이것은 다소 오래된 소식입니다.
00:20:33개발용 Turbo Pack은 출시된 지 꽤 되었고,
00:20:36저는 모든 분들이 적어도 개발 환경에서는 사용하고 있기를 진심으로 바랍니다.
00:20:39하지만 오늘 여기서 새로운 소식은, 물론 빌드가 안정적이라는 것입니다.
00:20:42그럼 빌드를 살펴봅시다.
00:20:44여기서 이 애플리케이션에 대해 Webpack보다 상당한 우위를 점하는 것을 볼 수 있습니다.
00:20:49이 특정 빌드는 실제로 우리의 새로운 실험적인 파일 시스템 캐싱 레이어를 사용하여 실행되고 있습니다.
00:20:53그래서 94초 중 약 16초는 마지막에 캐시를 비우는 데 사용됩니다.
00:20:59그리고 이것은 파일 시스템 캐싱이 안정화됨에 따라 개선해 나갈 부분입니다.
00:21:04하지만 물론, 콜드 빌드의 특징은 '콜드'하다는 것입니다.
00:21:06증분적인 것이 아무것도 없습니다..
00:21:07그럼 실제 웜 빌드를 살펴봅시다.
00:21:10콜드 빌드에서 얻은 캐시를 사용하면 이것을 볼 수 있습니다.
00:21:14이것은 우리가 오늘날 어디에 와 있는지 살짝 보여주는 것입니다.
00:21:17이런 세분화된 캐싱 시스템 덕분에,
00:21:19실제로 캐시를 디스크에 쓰고,
00:21:21다음 빌드에서 다시 읽어 들여 무엇이 변경되었는지 파악한 다음 빌드를 완료할 수 있습니다.
00:21:26좋습니다.
00:21:27이것은 꽤 좋아 보이지만,
00:21:28많은 분들이 '음,
00:21:29나는 개인적으로 세계에서 가장 큰 Next.js 애플리케이션을 가지고 있지 않은데'라고 생각할 것입니다..
00:21:34그럼 더 작은 예시를 살펴봅시다.
00:21:37react.dev 웹사이트는 훨씬 더 작습니다.
00:21:41또한 React 컴파일러이기 때문에 흥미롭습니다.
00:21:44놀랍지 않게도 React 컴파일러의 초기 채택자입니다.
00:21:47그리고 React 컴파일러는 Babel로 구현되어 있습니다.
00:21:49그리고 이것은 우리의 접근 방식에 일종의 문제입니다.
00:21:51애플리케이션의 모든 파일에 대해 Babel에게 처리를 요청해야 하기 때문입니다..
00:21:55그래서 근본적으로,
00:21:56저는 우리가,
00:21:57아니 제가 React 컴파일러를 더 빠르게 만들 수는 없다고 말씀드리겠습니다.
00:22:01그것은 제 일이 아닙니다.
00:22:02제 일은 Turbo Pack입니다.
00:22:03하지만 우리는 언제 그것을 호출해야 할지 정확히 알아낼 수 있습니다.
00:22:07빠른 새로고침 시간을 보면, 저는 사실 이 결과에 약간 실망했습니다.
00:22:13그리고 밝혀진 바에 따르면, 그 140밀리초 중 약 130밀리초가 React 컴파일러 때문입니다.
00:22:18Turbo Pack과 Webpack 모두 그 작업을 수행합니다.
00:22:22하지만 Turbo Pack을 사용하면,
00:22:24React 컴파일러가 이 변경 사항을 처리한 후,
00:22:26'아,
00:22:26가져오기가 변경되지 않았네'라고 확인할 수 있습니다.
00:22:29출력물에 넣고 계속 진행합니다.
00:22:31다시 한번, 콜드 빌드에서는 이런 일관된 3배의 성능 향상을 볼 수 있습니다.
00:22:37명확히 말씀드리자면, 이것은 제 컴퓨터에서 측정한 것입니다.
00:22:39하지만 다시 말하지만, 콜드 빌드에서는 증분성이 없습니다.
00:22:44그리고 웜 빌드에서는 훨씬 더 좋은 시간을 볼 수 있습니다.
00:22:47다시 말해, 웜 빌드에서는 이미 디스크에 캐시가 있습니다.
00:22:52기본적으로 우리가 해야 할 일은 일단 시작하면,
00:22:54애플리케이션에서 어떤 파일이 변경되었는지 파악하고,
00:22:57해당 작업을 다시 실행한 다음,
00:22:58이전 빌드의 나머지 모든 것을 재사용하는 것입니다.
00:23:01그래서 기본적인 질문은, '우리는 이미 터보인가?'입니다.
00:23:05네.
00:23:06네, 물론 이것은 기조연설에서 논의되었습니다.
00:23:09Turbo Pack은 Next 16부터 안정화되었습니다.
00:23:12그리고 우리는 Next의 기본 번들러이기도 합니다.
00:23:14그래서, 임무 완료입니다. (웃음) 다들 환영합니다.
00:23:17하지만. (웃음) (청중 박수)
00:23:23그리고 기조연설에서 그 '되돌리기'를 보셨다면,
00:23:26그것은 제가 Turbo Pack을 기본으로 만들려고 시도했던 것입니다.
00:23:30세 번 만에 성공했습니다.
00:23:31하지만 제가 다시 한번 여러분께 남기고 싶은 것은 이것입니다.
00:23:35아직 끝나지 않았기 때문입니다.
00:23:37우리는 성능 면에서 아직 할 일이 많고, 파일 시스템 캐싱 레이어 작업을 마무리해야 합니다.
00:23:42개발 환경에서 모두 사용해 보시길 권합니다.
00:23:44이상입니다.
00:23:46정말 감사합니다.
00:23:47저를 찾아와 질문해 주세요.
00:23:49(청중 박수) (경쾌한 음악) (경쾌한 음악)