Transcript
00:00:00안녕하세요, 오늘 함께해주셔서 정말 감사합니다.
00:00:02저는 Vercel 워크플로 팀의 Praneet입니다.
00:00:05안녕하세요, 저도 워크플로 팀의 Nate입니다.
00:00:08Nate, 당신과 저 모두 처음부터 워크플로 팀에 있었죠.
00:00:12지난 6개월 동안 우리가 출시한 많은 기능 중에서
00:00:15저는 훅(hook)과 웹훅이 가장 마음에 드는 기능 중 하나예요.
00:00:18오늘 당신이 이야기할 주제도 바로 그것이고요.
00:00:21저도 훅과 웹훅을 가장 좋아합니다.
00:00:23정말 강력한 기능인데, 왜 그런지 몇 가지 데모를 통해 보여드릴게요.
00:00:28첫 번째 데모는 우리 모두에게 익숙한 '매직 링크'입니다.
00:00:33매직 링크는 로그인 양식입니다. 이메일을 입력하면 편지함으로 메일을 받고,
00:00:40그 링크를 클릭하면 서비스에 로그인이 되죠.
00:00:44네, 기억하기로 Vercel이 Vercel이라 불리기 전,
00:00:48그러니까 Zeit였던 시절에도 매직 링크가 유일한 인증 방식이었어요.
00:00:52당시에는 이 시스템 전체를 우리가 직접 구축했었죠.
00:00:56맞아요, 그래서 아직도 그때의 고생이 기억나네요.
00:01:01워크플로 없이는 이런 시스템을 구현하는 게 겉보기보다 훨씬 복잡하거든요.
00:01:08로직이 여러 파일에 흩어지게 됩니다.
00:01:12상태를 추적하기 위해 데이터베이스도 필요하고, 금방 지저분해지죠.
00:01:19네, 저도 이걸 어떻게 구조화하고 어떤 데이터베이스를 쓸지 고민했었는데,
00:01:24이런 종류의 문제는 전에도 만들어 본 적이 있는 아주 흔한 고민이죠.
00:01:28그래서 어떻게 구현됐는지 정말 보고 싶네요.
00:01:30네, 제가 말씀드린 그 불편한 점들을 보여드리기 위해,
00:01:38우선 워크플로 없이 구현한 "전통적인" 버전의 매직 링크 로그인을 준비했습니다.
00:01:43여기에는 세 개의 엔드포인트가 관여합니다.
00:01:47첫 번째는 로그인 양식이 제출될 때인데,
00:01:50세션을 생성하고 이를 Redis 같은 데이터베이스에 저장해야 합니다.
00:01:57TTL을 구현해야 하고, 데이터를 영원히 둘 수 없으니 만료도 시켜야 하죠.
00:02:06그리고 이메일을 보내는데, 이게 실패하면 로그인이 안 되어 사용자에게 답답한 경험을 줍니다.
00:02:14맞아요, 그러면 다시 돌아가서 크론 잡을 돌리거나 인턴을 시켜서 DB를 정리해야 하죠.
00:02:19그 당시엔 제가 그 인턴이었을지도 모르겠네요.
00:02:22하지만 두 번째 엔드포인트도 있습니다. 사용자가 이메일의 링크를 클릭할 때 실행되죠.
00:02:28이건 데이터베이스를 쿼리해서 첫 번째 엔드포인트에서 생성된 상태를 복구해야 합니다.
00:02:36이미 코드가 굉장히 복잡한 '스파게티 코드'가 되어가고 있네요.
00:02:38이게 어떤 모습일지 상상해 봤을 때, 이 코드는 정말 익숙하고 저도 이렇게 짰을 것 같아요.
00:02:48아주 간단한 개념인데도 얼마나 빨리 복잡해지는지 알 수 있죠.
00:02:54그럼 이제 워크플로에서는 어떻게 구현하는지 살펴봅시다.
00:02:59Workflow SDK를 사용한 매직 링크 구현은 이런 모습입니다.
00:03:05여기 함수가 있고 'useWorkflow' 디렉티브가 있는데, 이것이 우리의 워크플로 함수라는 뜻입니다.
00:03:11가장 먼저 하는 일은 워크플로 패키지의 'createWebhook' 함수를 호출하는 것입니다.
00:03:18여기서는 'respondWithManual' 옵션을 사용하고 있는데, 이는 웹훅을 트리거하는 HTTP 요청에 대한 응답을 워크플로 함수가 직접 작성한다는 의미입니다.
00:03:36이건 사용자가 로그인한 후에 리다이렉트 등을 처리하기 위한 건가요?
00:03:40네, 어떤 응답을 보낼지 결정하는 데 필요한 정보가 워크플로 함수 안에 있을 때 사용합니다.
00:03:51첫 번째 엔드포인트와 마찬가지로 로그인 이메일을 보냅니다. 이건 'useStep' 함수입니다.
00:03:57이렇게 하면 실패하더라도 Workflow SDK가 자동으로 재시도합니다.
00:04:03이러한 내구성이 이미 기존 방식보다 이점을 제공하고 있죠.
00:04:10그러니까 'sendLoginEmails'가 하나의 단계이고, 이메일 전송에 실패하면 이미 생성된 웹훅 URL을 사용해서 전송을 재시도하는 거군요.
00:04:21그리고 여기를 보시면 아주 흥미로운 패턴이 있습니다.
00:04:265분의 'sleep'과 함께 'promise.race'를 사용하고 있죠.
00:04:30이 웹훅 객체가 프로미스(promise)를 구현하고 있기 때문에 가능한 일입니다.
00:04:35웹훅 요청을 기다리려면 그냥 'await Webhook'을 하면 됩니다.
00:04:40여기서는 'race'와 함께 사용했죠. 웹훅 기능에 타임아웃 옵션이 따로 있을 거라 생각했는데 참 멋지네요.
00:04:50타임아웃을 웹훅과 'sleep' 사이의 'race'로 모델링하니 코드가 훨씬 깔끔해 보여요.
00:04:58이걸로 훨씬 많은 걸 할 수 있을 것 같아요. 두 개의 서로 다른 웹훅으로 'race'를 시킬 수도 있겠고요.
00:05:02함수 인자가 몇 개만 있을 때는 할 수 있는 게 많지 않죠.
00:05:06하지만 그냥 프로미스라서 'promise.race'를 'sleep'이나 다른 단계와 함께 쓸 수 있다는 게 좋네요.
00:05:12이 패턴 정말 마음에 들어요. 이걸로 무엇을 만들 수 있을지 머릿속에 아이디어가 샘솟네요.
00:05:16맞아요, 그게 바로 Workflow SDK가 제공하는 프리미티브(primitives)의 아름다움입니다.
00:05:21모든 것이 프로미스로 노출됩니다.
00:05:23그게 가장 핵심이죠.
00:05:28그리고 여기서 주목할 점은 Redis도, 데이터베이스도 없다는 겁니다.
00:05:33전통적인 예제에서는 타임아웃 구현을 위해 Redis의 TTL을 사용했었죠.
00:05:41이 경우에는 워크플로의 'sleep' 프리미티브를 사용하고 있습니다.
00:05:44또한 나중에 지저분한 DB를 정리해야 할 인턴도 필요 없고요.
00:05:50그게 제일 좋은 부분이네요.
00:05:51보시다시피 워크플로는 로그인 성공 페이지로 리다이렉트하여 공용 요청에 응답합니다.
00:05:59그런 다음 로그인을 시작한 클라이언트에 반환할 사용자 정보를 가져옵니다.
00:06:07이것이 워크플로 전체입니다. 매직 링크 구현이 단 50줄의 코드로 끝났죠.
00:06:12정말 놀랍네요. 실제로 작동하는 걸 볼 수 있을까요?
00:06:17네, 여기 매직 링크 데모가 있습니다. 제 이메일을 입력해 볼게요.
00:06:24워크플로가 시작되어 이메일을 보냈습니다. 그리고 웹훅이 대기 중이죠.
00:06:31사실 지금 워크플로는 중단(suspended)된 상태입니다. 사용자가 링크를 클릭하길 기다리는 동안 컴퓨팅 자원을 전혀 소모하지 않죠.
00:06:41오, Vercel에서는 어떤 모습인가요? 대기 중인 실행 상태를 볼 수 있을까요?
00:06:47네, 이메일이 왔네요. 클릭하기 전에 관찰 도구(observability)를 살펴봅시다.
00:06:52제가 좀 서두르는 것 같지만, 이걸 보는 게 정말 즐겁네요.
00:06:57좋아요, 여기 실행 기록이 있고 40초 전에 시작된 걸 볼 수 있습니다.
00:07:02들어가 보면 워크플로가 제공하는 표준 관찰 기능들이 있습니다.
00:07:08워크플로 실행에 입력된 값들을 볼 수 있죠. 로그인 양식에 입력한 제 이메일 주소가 보입니다.
00:07:13그리고 흥미롭게도 여기 훅이 그냥 기다리고 있는 것을 볼 수 있습니다.
00:07:17지금 컴퓨팅 자원이 실행되지 않는다고 하셨죠. 상태는 보이지만 실제로 제가 훅을 클릭하기를 기다리며 상주하는 프로세스는 없다는 거죠?
00:07:25맞습니다. 훅은 대기 중이고 'sleep'은 작동 중이지만, 두 가지 모두 실제 컴퓨팅 자원을 사용하지 않습니다.
00:07:39하지만 여기 훅을 보시면, 기억하시겠지만 이 둘은 'promise.race'에서 경합 중입니다.
00:07:46워크플로가 계속 진행되려면 둘 중 하나가 먼저 완료되어야 합니다.
00:07:50자, 링크를 클릭하면... 로그인 성공 페이지로 리다이렉트되었습니다. 우리 워크플로 로직의 단계 중 하나였죠.
00:07:59다시 로그인 양식으로 돌아가 보면...
00:08:01네, 대시보드에서도 완료된 것으로 나올 겁니다.
00:08:05맞습니다. 워크플로가 완료되었습니다.
00:08:08훅이 승리하자마자 타이머도 바로 멈추는군요.
00:08:11네, 약 50줄의 코드로 매직 링크를 구현할 수 있었습니다.
00:08:17정말 깔끔하네요. 화이트보드에 매직 링크가 어떻게 작동하는지 그려서 설명하는 것과 같아요.
00:08:27코드에 있는 단계들이 구상했던 로직과 정확히 일치하는데, 그게 곧 최종 코드가 된 셈이니까요.
00:08:34중간에 별도의 데이터베이스도 없고 API 라우트가 여러 개일 필요도 없죠. 보여주신 코드가 정말 직관적입니다.
00:08:41제 생각에 Workflow SDK의 가장 강력한 면은, 인프라에 맞추기 위해 억지로 늘리는 게 아니라 로직이 논리적으로 흐르도록 애플리케이션 구조를 짜게 해준다는 점입니다.
00:08:59맞아요. 또한 여기서 웹훅이라는 이름을 쓴 게 마음에 들어요. 웹훅에 대해 완전히 다르게 생각하게 해주거든요.
00:09:07그저 생성하고 중단할 수 있는 일회성 URL인 거죠.
00:09:10사실 이게 좋은 연결고리가 되는데, Vercel에서 에이전트를 많이 만들잖아요.
00:09:16Slack이나 GitHub 에이전트들을 만드는데, 보통 GitHub나 Slack에서 오는 웹훅을 구독하죠?
00:09:23PR에 새 댓글이 달릴 때마다 Vercel 에이전트를 가동하고 싶을 때, GitHub가 보내는 웹훅 이벤트에 기반해서 하잖아요.
00:09:31워크플로 웹훅을 사용해서 GitHub 같은 곳의 이벤트를 구독할 수도 있나요?
00:09:36Slack이나 GitHub에서 발송되는 웹훅의 경우, 보통 대시보드에 들어가서 정적인 콜백 URL을 수동으로 설정해야 합니다.
00:09:49그렇죠. 이메일처럼 일회성 URL을 줄 수는 없으니까요.
00:09:54맞습니다. 'webhook 생성' 기능은 그보다 좀 더 고수준의 기능으로, 무작위로 생성된 고유한 웹훅 URL을 제공합니다.
00:10:04그것은 하나의 특정 워크플로우 실행에 매핑됩니다.
00:10:07우리의 GitHub나 Slack 웹훅 경로의 경우, 수많은 워크플로우 실행에 매핑될 수 있습니다.
00:10:14맞아요. 여러 개의 풀 리퀘스트가 있어도 모두 동일한 엔드포인트로 가도록 미리 설정해야 하죠.
00:10:20그래서 Workflow SDK로 이를 구현하기 위해, 한 단계 더 내려가서 더 저수준의 'hook' 프리미티브를 사용할 것입니다.
00:10:28그것을 보여드리기 위한 데모를 준비했습니다.
00:10:31한번 보시죠.
00:10:32좋습니다. 이것은 '스토리타임(storytime)' 봇입니다.
00:10:35제가 1년 조금 전쯤에 Workflow SDK로 작성한 아주 초기 애플리케이션 중 하나입니다.
00:10:40작동 방식은 'storytime/' 명령어를 입력하면 스레드가 생성되는 것을 볼 수 있습니다.
00:10:47각 스레드는 개별 워크플로우 실행으로 표현됩니다.
00:10:52스레드를 확장하면 LLM이 우리를 위해 이야기를 시작한 것을 볼 수 있고, 저나 당신, 또는 이 채널에 있는 누구든 이야기를 이어갈 수 있습니다.
00:11:05그러면 LLM이 이야기가 최종 결론에 도달할 수 있도록 도와줍니다.
00:11:09좋아요. 루나가 마법의 씨앗을 가졌고, 다음에 무슨 일이 일어날까요? 그녀는 씨앗을 심습니다.
00:11:13네, 여기서 어떤 활동이 일어나는 것을 볼 수 있습니다.
00:11:17다음에 무슨 일이 생길까요? 마법 같은 일이요.
00:11:20이야기가 끝났고 최종 결과물이 나왔습니다. 작은 이미지도 함께 생성될 거예요.
00:11:26하지만 그 부분은 나중에 다시 살펴보겠습니다.
00:11:28벌써 정말 궁금한데요, 웹훅 요청이 한 번일 줄 알았는데 메시지가 두 개라서 최소 두 번의 요청이 있었거든요.
00:11:35그래서 코드에서는 어떤 모습일지 정말 궁금합니다.
00:11:38좋습니다. 이것이 우리 스토리타임 봇의 워크플로우 함수입니다.
00:11:44스토리타임 봇 채널인 채널 ID를 입력으로 받는 것을 볼 수 있습니다.
00:11:50전달할 수 있는 몇 가지 구성 옵션들이 있고요.
00:11:53흥미롭게도 'messages' 배열이 있는데, AI SDK에 익숙하시다면 이것이 AI 대화를 저장하는 데이터 형식이라는 걸 아실 겁니다.
00:12:04우리가 여기서 만든 것과 같은 전형적인 Slack 봇 애플리케이션에서는 보통 이런 데이터를 데이터베이스에 저장하고, 각 반복이나 웹훅 이벤트마다, 즉 스레드에 메시지가 입력될 때마다 상태를 복원하고 데이터베이스에서 대화 내용을 조회해야 합니다.
00:12:23그런데 여기서는 그렇지 않습니다. 그냥 함수 내의 배열일 뿐이죠.
00:12:27네. 아까 도입부에서 "엄마, 큐(queue)나 키-값 저장소(KV)가 없어요"라는 주석을 보고 웃음이 났거든요.
00:12:34데이터베이스 임포트(import)도 없네요. 그냥 Workflow만 임포트하고 계시고요.
00:12:40그리고 마지막 메시지 부분으로 돌아가면, 자칫 놓치기 쉽지만 여기 'final story'라는 변수가 있습니다. 아마 시간이 지나면서 이 배열에 메시지를 푸시(push)하겠죠.
00:12:55그러면 최종 이야기가 여기서 문자열로 나타날 텐데, 데이터베이스로 갈 필요가 없습니다. 마치 'let'이 여기서 당신의 데이터베이스인 것 같네요.
00:13:04네, "let이 당신의 데이터베이스다"라는 표현 좋네요. 우리가 만든 용어로 써야겠어요.
00:13:10제가 당신에게서 훔쳐온 말일 수도 있겠지만요.
00:13:14여기서 흥미로운 점이자 우리가 이야기하러 온 부분은 'hook' 기능입니다. 여기서 hook을 생성하고 있죠. 매직 링크가 있던 웹훅 예제와 다른 점은 여기서 '토큰'을 제공한다는 것입니다.
00:13:28이 토큰은 해당 워크플로우 실행에 고유한 식별 정보를 포함하는 문자열입니다.
00:13:35TS는 스레드 ID입니다. 따라서 이 문자열은 이 워크플로우 실행을 유일하게 식별하는 토큰이 됩니다.
00:13:44웹훅 경로의 코드를 보면 Slack이 보내는 이벤트 페이로드가 이 식별자를 결정론적으로 재현하는 데 필요한 모든 정보를 포함하고 있음을 알 수 있습니다.
00:13:58그것이 바로 웹훅이 개별 워크플로우 실행으로 다시 매핑되는 마법 같은 방식입니다.
00:14:04맞아요, 웹훅을 봤을 때 궁금했거든요. 매직 링크 예제에서는 새 URL을 생성했지만, Slack 봇을 만들어 본 경험상 모든 스레드마다 새 URL을 줄 수는 없으니까요.
00:14:17여기서 하시는 방식을 이해하자면, API가 이미 Slack에 연결되어 있지만 메시지를 받을 때마다 재개(resumption) 측에서 동일한 토큰을 계산하는 것이군요.
00:14:29그래서 워크플로우는 기본적으로 이 토큰을 기다릴 수 있고, 메시지 페이로드로부터 동일한 토큰을 구성하여 이 워크플로우 실행을 재개할 수 있는 거네요.
00:14:37정확합니다. Slack 봇은 대시보드에서 수동으로 한 번 설정되었고, 정적인 웹훅 콜백 URL을 정의해야 합니다.
00:14:50그래서 토큰을 동적으로 재현할 수 있는 저수준의 hook 프리미티브가 이 경우에 더 잘 작동하는 것입니다.
00:14:59잠깐 살펴보면, 이것이 웹훅 경로인데 실제로 별로 대단한 건 없습니다.
00:15:07핵심은 Slack에서 전달된 데이터로부터 토큰을 어떻게 재현하느냐입니다.
00:15:13그런 다음 재개(resume) 함수를 호출하면 해당 실행에 고유한 워크플로우 실행이 재개됩니다.
00:15:20정말 멋지네요. 제 생각에는 웹훅으로 하시는 일도 기본적으로는 같은 방식인 것 같아요.
00:15:28웹훅이 기본적으로 그냥 무작위 토큰을 만들고, 동일한 무작위 토큰을 해결하는 HTTP 엔드포인트를 갖는 건가요?
00:15:35네, 웹훅 기능과의 차이점은 코드에 해당 API 경로를 정의할 필요가 없다는 것입니다.
00:15:44Workflow SDK가 실제로 웹훅 기능을 위한 기본 경로를 대신 구현해 줍니다.
00:15:50하지만 그 외에는 특정 워크플로우 실행에 고유한 무작위로 생성된 토큰이라는 점은 같습니다.
00:15:55하지만 이 경우 토큰이 있는 hook을 사용하고, 방금 언급하신 것처럼 이 hook은 데이터를 여러 번 받을 수 있습니다.
00:16:06이는 한 번만 트리거되면 되었던 매직 링크 예제와는 다릅니다.
00:16:11이 경우에는 누군가 Slack 스레드에 입력하는 각각의 고유한 메시지에 대해 hook이 실행되기를 원합니다.
00:16:17그렇게 하기 위해 비동기 이터레이터(async iterator)에서 흔히 사용하는 JavaScript의 'for await' 구문을 사용합니다.
00:16:25이 경우, 우리의 hook을 사용하여 Slack 웹훅으로부터 여러 이벤트 페이로드를 수신하게 됩니다.
00:16:33정말 멋지네요. 비동기 이터레이터와 제너레이터를 좋아해서 오래전에 관련 발표도 했었지만, 마땅한 사용 사례를 찾지 못했었거든요.
00:16:42데모에는 좋았지만 실제 활용법을 찾기 어려웠죠.
00:16:46여기서는 그냥 루프를 돌리는 것처럼 보이네요.
00:16:50고정된 항목들이나 타임스탬프를 루프 도는 대신, 'for await'을 hook에 사용함으로써 정확히 매핑되는 루프가 생겼습니다.
00:17:01루프 내부의 모든 것이 하나의 사용자 메시지에 매핑되네요.
00:17:05새로운 사용자 메시지가 이 루프의 또 다른 반복을 일으키고, 그것이 계속 쌓여서 진행된다는 점이 생각하기에 참 좋은 방식입니다.
00:17:12이 방식의 아름다운 점은 사용자가 다음 메시지를 입력하기를 기다리는 동안, 각 반복 사이에 컴퓨팅 자원이 전혀 소모되지 않는다는 것입니다.
00:17:22워크플로우는 완전히 중단(suspend)된 상태이며, 다음 메시지는 몇 분 뒤에 올 수도, 며칠 뒤에 올 수도, 혹은 영영 오지 않을 수도 있지만 그래도 상관없습니다.
00:17:33그럼 그 Slack 채널의 스레드 중에, 아무도 대답하지 않아서 몇 주 동안 그냥 대기 중인 워크플로우 실행이 있을 수도 있겠네요.
00:17:42정말 멋집니다.
00:17:43그리고 아까 언급한 messages 배열로 돌아가서, 이제 배열을 수정합니다.
00:17:48새 사용자 메시지를 푸시하는데, messages 배열이 로컬 변수이기 때문에 이것이 곧 우리의 데이터베이스 수정이 되는 셈이죠.
00:17:57대단하네요. 그리고 중간에 더 많은 단계를 병렬화하기 위해 'promise.all'을 더 사용하시는 것도 보이네요.
00:18:03Slack의 모든 루프와 모든 메시지에 대해 코드가 정말 깔끔하게 읽힙니다.
00:18:08해커톤 같은 곳에서 이걸 만든다면 딱 이렇게 모델링하고 싶을 것 같아요.
00:18:12모든 메시지에서 일어나는 일을 그냥 그대로 적어 내려가는 식이니까요.
00:18:16네, 'promise.all' 모델은 그냥 일반적인 step 함수들이고 이들을 병렬로 실행하려는 아이디어입니다.
00:18:23Slack 메시지에 리액션을 추가하는 것 같은 작업은 사용자에게 무언가 진행 중이라는 즉각적인 피드백을 주기 위한 것입니다.
00:18:32동시에 LLM을 시작해서 이야기 생성 과정이 진행되도록 하고 싶고요.
00:18:39나중에 기회가 되면 관측성(observability)이 어떤 모습일지도 정말 보고 싶네요. 그런 스팬(span)들이 동시에 시작되는 게 아주 명확하게 보일 것 같거든요.
00:18:49스토리타임에 대한 관측성 화면이 여기 있습니다.
00:18:52완료되었으므로 아까 그 이미지도 확인해 봐야겠네요.
00:18:56우리의 hook을 볼 수 있습니다.
00:18:58여기서 흥미로운 점은 이 경우 두 번의 hook 수신 이벤트가 있다는 것입니다.
00:19:05이것은 제가 Slack 스레드에 입력한 두 개의 메시지에 매핑됩니다.
00:19:10그리고 관측성을 통해 hook에 제공된 개별 데이터를 확인할 수 있습니다.
00:19:14오, 정말 멋지네요.
00:19:16이게 기본적으로 Slack 페이로드군요. 추가로 로그를 남길 필요 없이 Slack 페이로드가 이벤트로 대시보드에 나타나서 다시 점검할 수 있다는 거죠.
00:19:25맞습니다. hook 페이로드가 수신될 때마다 워크플로우 실행이 이어지고 단계들이 계속 진행되는 것을 볼 수 있습니다.
00:19:34그리고 마지막으로 스토리보드 이미지를 생성한 결과가 저기에 나타납니다.
00:19:40이것이 스토리타임 봇입니다.
00:19:42정말 멋집니다.
00:19:43매직 링크로 생성된 웹훅과, 이제 더 저수준의 hook 프리미티브를 사용하여 루프 내에서 여러 이벤트를 처리하는 것까지 모두 보게 되어 정말 좋네요.
00:19:54정말 훌륭합니다.
00:19:55웹훅을 이용한 휴먼 오퍼레이션(human operations)이 어떤 모델인지 확실히 와닿네요.
00:20:02hook을 다른 용도로도 사용할 수 있을까요?
00:20:05네, 물론입니다.
00:20:06제가 준비한 마지막 데모는 Slack 웹훅에 응답하는 것과 매우 유사한 패턴입니다.
00:20:17하지만 이 경우에는 웹훅을 우리 애플리케이션 코드의 실행을 잠시 넘겨주고, 워크플로우가 중단된 상태에서 외부의 연산이 끝나기를 기다리는 용도로 사용할 것입니다.
00:20:32그런 다음 해당 웹훅 URL을 사용해 다시 호출하면, 우리 애플리케이션 로직의 나머지 작업을 마무리할 수 있습니다.
00:20:41이 예제에서는 Vercel Sandbox를 사용해 FFmpeg로 파일을 변환하는 것과 같은 오래 걸리는 연산 작업을 수행할 것입니다.
00:20:51이것이 우리의 FFmpeg 변환 워크플로우입니다.
00:20:56가장 먼저 일어나는 일 중 하나는 Vercel Sandbox를 생성하는 것입니다.
00:21:00흥미로운 점은 실제로 Sandbox NPM 패키지가 제공하는 함수들 내부에서 'use step'을 사용하고 있다는 것입니다.
00:21:09그래서 실제로 이 작업 자체가 하나의 Step입니다.
00:21:12즉, NPM 패키지를 출시할 수 있는 거죠.
00:21:15Sandbox는 기본적으로 함수 내부에 'use Step' 디렉티브가 포함된 NPM 패키지를 배포하는 방식입니다.
00:21:21따라서 워크플로우 내에서 이를 임포트해 사용하면, 별도의 코드를 작성하지 않아도 Sandbox가 자동으로 Step이 됩니다.
00:21:29그렇다고 워크플로우 외부에서 Sandbox를 생성할 수 없다는 뜻은 아닙니다.
00:21:32워크플로우 없이 호출하면 어떻게 되나요?
00:21:35아시다시피 디렉티브는 그냥 문자열일 뿐이라서, 워크플로우 컴파일러 없이 실행하면 그 문자열은 아무런 기능도 하지 않습니다.
00:21:47그래서 그냥 정상적으로 작동하죠.
00:21:49NPM 패키지에 'use Step'을 추가하는 것은 워크플로우 SDK 없이도 전혀 문제가 없습니다.
00:21:55그리고 워크플로우 SDK 내부에서 해당 함수를 사용하면, 즉시 내구성(durability)이라는 추가적인 혜택을 얻게 됩니다.
00:22:03알겠습니다, Sandbox는 일반적인 작업들을 수행하는군요.
00:22:07기본적으로 제공되지 않는 FFmpeg을 설치하고,
00:22:11우리가 지정할 파일의 URL을 다운로드합니다.
00:22:14그럼 이 각각의 실행 과정들도 지금은 다 Step인가요?
00:22:17네, Sandbox에서 개별 명령어를 실행하는 것들이 모두 Step이며, 관찰 도구(observability)에서 확인할 수 있습니다.
00:22:29그리고 다시 'create-webhook' 호출로 돌아가는데, 매직 링크 데모에서 보셨던 것과 비슷할 겁니다.
00:22:36하지만 이번에는 웹훅 URL을 Sandbox에서 실행할 Bash 스크립트에 전달할 것입니다.
00:22:43여기서 일어나는 일은 FFmpeg을 실행하여 UI에서 요청한 형식으로 파일을 변환하는 것입니다.
00:22:53변환이 완료되면 Bash 스크립트가 웹훅의 콜백 URL로 cURL 요청을 보냅니다.
00:22:59해당 cURL 요청이 발생하면 워크플로우 로직이 다시 재개됩니다.
00:23:04아, 이해했습니다. 정말 멋지네요. 제가 조금 앞서 살펴봤는데, 이 실행 부분에 'AND'가 있는 걸 봤어요.
00:23:11FFmpeg Step은 시간이 꽤 걸릴 수 있기 때문에, 실제로 스크립트를 작성해서 백그라운드에서 실행하시는 거군요.
00:23:17단순히 Step이 가만히 앉아서 완료를 기다리게 하고 싶지 않으신 거죠.
00:23:20맞습니다. 바로 이 줄이 백그라운드에서 FFmpeg 변환 스크립트를 시작하는 부분입니다.
00:23:28그러면 워크플로우 함수는 일시 중단(suspend)되고 웹훅이 재개되기를 기다립니다.
00:23:34그리고 다시 1시간의 sleep과 함께 promise race를 사용하는군요. 정말 멋진 패턴입니다.
00:23:40그렇죠. 이번 FFmpeg 변환 프로세스는 꽤 오래 걸릴 수도 있습니다.
00:23:46매우 큰 미디어 파일일 수도 있으니까요. 그래서 이 경우에는 1시간의 타임아웃을 지정했습니다.
00:23:51워크플로우에서는 사실상 무제한으로 sleep 상태를 유지할 수 있으므로 전혀 문제없습니다.
00:23:56다시 말씀드리지만, 웹훅이 재개되기를 기다리는 동안 돌아가는 연산(compute) 비용은 0입니다.
00:24:01이걸 직접 볼 수 있을까요? 실행되는 데모가 있나요?
00:24:04네, 있습니다.
00:24:05약간 뻔한 예시이긴 하지만요.
00:24:07아니요, 바로 'Big Buck Bunny' 예시라는 걸 알아봤어요. Blender에서 만든 거죠.
00:24:12맞아요. 아주 예전에 Blender를 배울 때 이 영상들을 봤던 기억이 나네요.
00:24:16와, 부러운데요.
00:24:19미디어 파일 URL을 붙여넣었습니다. 이번에는 여기서 오디오 레이어만 추출해 보겠습니다.
00:24:26버튼을 클릭하면 워크플로우가 시작되고, 관찰 도구 페이지로 이동해서 확인할 수 있습니다.
00:24:33아, 여기 있네요. Sandbox 생성을 확인할 수 있습니다.
00:24:37그리고 Sandbox 인스턴스가 반환되네요. 정말 멋집니다.
00:24:42이건 워크플로우의 모든 요소가 직렬화(serializable) 가능해야 하기 때문이죠.
00:24:46말씀하신 대로 Sandbox가 직렬화를 구현했기 때문에 워크플로우에서도 직렬화되어 나타나는 거군요.
00:24:53맞습니다. Vercel Sandbox NPM 패키지에는 Sandbox 클래스가 있고, 그 클래스가 워크플로우 직렬화 함수를 구현하고 있습니다.
00:25:03그래서 관찰 도구에서도 바로 잘 작동하는 것이죠.
00:25:06어떤 패키지든 이렇게 할 수 있다는 거죠? Sandbox만 특별한 게 아니라, 워크플로우 내부에서 작동하고 싶은 클래스라면 동일한 심볼을 구현하고 'use Step' 디렉티브를 가질 수 있겠네요.
00:25:17네, 맞습니다. 이번에는 20초 만에 웹훅이 다시 호출된 것을 볼 수 있습니다.
00:25:25파일이 작아서 변환이 좀 빨랐지만, 시간이 얼마나 걸렸든 상관없었을 겁니다.
00:25:31Sandbox가 생성되고 초기화된 후, 웹훅이 생성되어 Sandbox에 전달되었고 FFmpeg 명령이 시작된 것을 확인할 수 있습니다.
00:25:43그리고 작업이 끝났을 때 Sandbox로부터 페이로드를 전달받았습니다.
00:25:48이게 아까 Bash 스크립트 내부에서 실행된 cURL이군요. 명령을 작성한 뒤 Sandbox에서 말 그대로 cURL을 사용해 웹훅을 완료하는 거죠.
00:25:57맞습니다. Sandbox가 할 일을 마쳤으므로 제어권을 다시 워크플로우로 넘겨주는 것입니다.
00:26:04이제 제가 이해하기로는, 워크플로우의 Step은 Step을 실행하고 백그라운드에서 코드를 돌린 뒤 워크플로우를 이어가는 방식인데요.
00:26:13하지만 Hook과 Webhook은 더 로우 레벨(lower level)처럼 느껴집니다. 토큰이나 URL을 생성하고 무엇이든 기다릴 수 있으니까요.
00:26:21그게 사람의 매직 링크일 수도, 이메일일 수도, Sandbox나 어떤 연산 작업일 수도 있겠죠.
00:26:27이벤트가 발생할 때까지 모든 상태를 유지한 채 워크플로우를 일시 정지시키니, Step 자체보다 더 근본적인 기능처럼 느껴지네요.
00:26:34네. 제가 생각하는 Webhook과 Hook은 외부 페이로드를 워크플로우로 전달하는 방법입니다.
00:26:42Step은 워크플로우가 일시 중단된 후 특정 연산이 끝나기를 기다렸다가 재개하는 방식이라고 생각하고요.
00:26:50하지만 Hook과 Webhook은 토큰이나 URL을 만들어서 어디로든, 여기서는 Sandbox였지만, 보낼 수 있다는 점에서 훨씬 더 로우 레벨 같습니다.
00:27:01사람에게 보낼 수도 있고, 이메일이나 혹은 다른 워크플로우로 보낼 수도 있겠죠.
00:27:05그리고 그 작업이 완료될 때마다 부모 워크플로우가 깨어나서 중단된 지점부터 바로 재개됩니다.
00:27:12그러니 Step보다 더 낮은 단계의 기능인 셈이죠. 어떤 종류의 외부 작업에 대해서도 워크플로우를 중단시킬 수 있는 방법이니까요.
00:27:19네. 저는 Hook을 워크플로우를 일시 중단하고 외부 페이로드가 다시 전달되기를 기다리는 방법으로 생각하는데, 이는 매우 강력한 기능입니다.
00:27:31정말 멋지네요. 오늘 시간이 다 됐지만, 이 데모들을 통해 왜 Hook이 제가 가장 좋아하는 기능인지 다시 한번 확인했고 계속해서 무언가를 만들어보고 싶어지네요.
00:27:42좋습니다. 즐거우셨다니 다행이네요.
Community Posts
No posts yet. Be the first to write about this video!
Write about this video