Inngest로 백그라운드 작업 만들기: 이벤트와 함수, 스텝 재시도, Cron, Next.js 배포

이 글의 핵심

Inngest는 이벤트를 받아 서버리스 함수를 실행하고, 스텝 단위로 체크포인트·재시도를 제공하는 백그라운드 작업 플랫폼입니다. 이 글에서는 핵심 개념, 이벤트·함수 정의, 스텝과 재시도, Cron, Next.js·Vercel 통합, 관측성, 이메일 발송 실전 설계를 다룹니다.

Inngest는 어디에 쓰는가

Inngest는 애플리케이션이 이벤트를 발행하면, 등록된 함수를 비동기로 실행하며, 각 스텝의 결과를 저장해 실패 시 부분 재시도를 가능하게 하는 이벤트 기반 실행 플랫폼입니다. 서버리스 환경에서 흔한 “요청 시간 안에 끝내야 하는 작업”과 “나중에 확실히 끝내야 하는 작업”을 분리할 때 유용합니다.

본문에서는 Inngest의 아키텍처적 위치, 이벤트·함수 정의, step.run·step.sleep·step.waitForEvent 등 스텝 API와 재시도 정책, Cron 기반 스케줄, Next.js App Router와 Vercel 배포, 개발용 Dev Server와 프로덕션 모니터링, 마지막으로 주문 확인 이메일을 예로 든 실전 설계를 정리합니다.


Inngest가 푸는 문제와 구성 요소

문제 정의

HTTP 요청 핸들러 안에서 결제 웹훅 처리, 대량 이메일, 이미지 변환, 외부 API 연동을 모두 끝내려 하면 타임아웃, 부분 실패 시 복구 어려움, 동시성 폭주가 한꺼번에 발생합니다. 전통적으로는 워커 프로세스와 큐를 직접 운영해 해결하지만, 팀 규모가 작을 때는 운영 부담이 큽니다.

Inngest는 이벤트 드리븐 함수와 내구성 있는 스텝 실행을 제품으로 제공해, “큐 인프라 + 워커 루프 + 재시도 상태 저장”을 최소화합니다.

구성 요소

  • 이벤트(Event): 이름(문자열)과 JSON 페이로드로 구성된 발생 사실입니다. inngest.send()로 발행하거나, 외부 소스(웹훅 등)에서 Inngest로 전달되도록 연결할 수 있습니다.
  • 함수(Function): 특정 트리거(이벤트 이름, Cron 등)에 반응해 실행되는 비동기 작업 단위입니다. 하나의 함수 안에서 여러 스텝으로 나뉩니다.
  • 스텝(Step): step.run 등으로 표시된 블록 단위입니다. 각 스텝은 실행 후 결과가 기록되며, 재시도 시 성공한 스텝은 재실행하지 않는 것이 기본적인 사용 모델입니다.
  • Inngest Cloud / Dev Server: 이벤트 수신, 함수 스케줄링, 실행 기록 저장을 담당합니다. 로컬에서는 Inngest Dev Server를 띄워 동일한 흐름을 재현합니다.

다른 도구와의 관계

Temporal·Cadence는 워크플로우 오케스트레이션에 가깝으며, RabbitMQ·Kafka는 메시지 전달 인프라에 가깝습니다. Inngest는 “앱 코드에 가까운 서버리스 함수”와 “스텝 단위 체크포인트” 사이에 위치하며, 복잡한 다중 워커 큐 라우팅보다 제품 개발 속도와 Vercel 등 PaaS와의 접착을 강조하는 편입니다.

Temporal과 가장 크게 다른 점은 실행 모델입니다. Temporal은 워커 프로세스가 태스크 큐를 끌어오는(pull) 방식이라 장기 실행 워커를 직접 운영해야 합니다. Inngest는 반대로 Inngest 쪽에서 앱의 HTTP 엔드포인트를 호출(push)합니다. 그래서 워커 서버 없이 기존 Next.js 배포만으로 동작하지만, 대신 모든 스텝 실행이 HTTP 요청 한 번이 되므로 서버리스 함수의 실행 시간 제한과 콜드 스타트, 요청 수 기반 과금의 영향을 그대로 받습니다.

반대로 Inngest가 맞지 않는 경우도 분명합니다. 초당 수만 건의 이벤트를 흘려보내는 스트림 처리라면 Kafka 같은 로그 기반 시스템이 맞고, 이벤트 하나하나를 함수 실행으로 과금하는 모델은 비용이 빠르게 커집니다. 또 매니지드 서비스에 워크플로 상태를 맡기는 구조이므로, 데이터가 외부로 나가면 안 되는 환경이라면 셀프 호스팅 가능 여부와 운영 부담을 먼저 따져 봐야 합니다.


이벤트 발행과 함수 등록

클라이언트 생성

애플리케이션 전역에서 재사용할 Inngest 클라이언트를 한 곳에 둡니다.

// src/inngest/client.ts
import { Inngest } from 'inngest';

export const inngest = new Inngest({
  id: 'my-app',
  name: 'My App',
});

id는 프로젝트를 구분하는 식별자로, 배포 환경별로 분리할지(예: my-app-prod) 팀 규칙을 정하는 것이 좋습니다.

이벤트 발행

이벤트는 도메인 사실을 표현하는 이름과, 처리에 필요한 최소 데이터를 담습니다.

// 어딘가의 서비스 레이어 (예: 주문 생성 직후)
import { inngest } from '@/inngest/client';

await inngest.send({
  name: 'order/created',
  data: {
    orderId: 'ord_123',
    userId: 'usr_456',
    totalCents: 19900,
  },
});

send는 여러 이벤트 배열도 받을 수 있어, 한 트랜잭션 후 여러 후속 작업을 한 번에 예약하는 패턴이 가능합니다.

함수 등록: 이벤트 트리거

// src/inngest/functions/order-notifications.ts
import { inngest } from '../client';

export const notifyOrderCreated = inngest.createFunction(
  {
    id: 'notify-order-created',
    name: '주문 생성 알림',
    retries: 5,
  },
  { event: 'order/created' },
  async ({ event, step }) => {
    const { orderId, userId } = event.data;
    // step 사용은 다음 절에서 상세히
    await step.run('load-order', async () => {
      /* DB 조회 */
    });
    return { ok: true, orderId };
  },
);

함수 id는 Inngest 측에서 함수를 식별하는 데 쓰이므로, 배포 후 함부로 바꾸면 동일 로직이 새 함수로 인식될 수 있습니다. 리네이밍이 필요하면 마이그레이션 계획을 세우는 것이 안전합니다.

{ event: 'order/created' }는 단일 이벤트 트리거입니다. 여러 이벤트나 복잡한 매칭이 필요하면 문서의 함수 트리거 고급 옵션을 참고합니다.


step.run·sleep·waitForEvent와 재시도

step.run: 부작용의 경계

step.run은 이름과 비동기 함수를 받습니다. 스텝이 한 번 성공하면 그 결과가 직렬화되어 저장되고, 함수 실행이 재개될 때 동일 스텝은 저장된 결과를 반환합니다.

const invoice = await step.run('create-invoice', async () => {
  const res = await fetch('https://api.billing.example/invoices', {
    method: 'POST',
    body: JSON.stringify({ orderId: event.data.orderId }),
  });
  if (!res.ok) throw new Error(`billing ${res.status}`);
  return res.json() as Promise<{ invoiceId: string }>;
});

await step.run('send-email', async () => {
  // invoice.invoiceId 사용
});

첫 번째 스텝에서 예외가 나면 해당 스텝만 재시도 대상이 되고, 두 번째 스텝은 아직 실행되지 않았습니다. 첫 스텝이 성공한 뒤 두 번째에서 실패하면, 재시도 시 첫 스텝은 건너뛰고 두 번째부터 다시 시도합니다.

이 동작을 이해하려면 Inngest가 함수를 어떻게 다시 실행하는지 알아야 합니다. Inngest는 스텝을 하나 실행할 때마다 엔드포인트를 새로 호출하고, 함수 본문을 처음부터 다시 실행합니다. 이미 완료된 step.run은 저장된 결과를 즉시 돌려주고(메모이제이션), 아직 실행되지 않은 첫 스텝을 만나면 그 스텝만 실행한 뒤 결과를 보고하고 호출을 끝냅니다. 즉 스텝이 세 개인 함수는 최소 네 번 호출될 수 있습니다.

주의: 그래서 문제가 되는 것은 step.run 밖의 비결정적 코드입니다. 함수 본문 최상단에서 const now = Date.now()나 Math.random()으로 분기하면, 호출마다 값이 달라져 이미 실행된 스텝과 다른 경로를 타게 됩니다. console.log나 DB 쓰기를 스텝 밖에 두면 호출 횟수만큼 반복 실행됩니다. 스텝 안의 비결정적 값은 한 번 실행된 결과가 저장되므로 오히려 안전합니다. 원칙은 간단합니다. 부작용과 비결정적 값은 모두 step.run 안에 넣고, 스텝 밖에는 스텝 결과를 조합하는 순수한 제어 흐름만 둡니다.

스텝 이름(첫 번째 인자)은 메모이제이션의 키 역할을 합니다. 배포 중에 실행 중인 함수가 있는데 스텝 이름을 바꾸거나 순서를 바꾸면, 새 코드가 저장된 결과를 찾지 못해 스텝을 다시 실행할 수 있습니다. 루프 안에서 step.run을 호출할 때는 send-${userId}처럼 반복마다 구별되는 이름을 쓰는 것이 안전합니다. 스텝 반환값은 JSON으로 직렬화되어 저장되므로 Date 객체는 문자열로, 클래스 인스턴스는 평범한 객체로 돌아온다는 점과, 저장 크기에 제한이 있다는 점도 기억해 두세요.

step.sleep: 지연

await step.sleep('wait-for-cooldown', '24h');

일정 시간 대기 후 다음 스텝으로 진행합니다. 서버리스에서 “긴 setTimeout”을 직접 두는 것과 달리, Inngest가 깨우기 스케줄을 관리합니다.

step.waitForEvent: 다른 이벤트 대기

결제 승인, 사용자 승인 같은 외부에서 오는 두 번째 이벤트를 기다릴 때 사용합니다. 타임아웃과 매칭 필드(예: orderId)를 지정해, 조건이 맞는 이벤트가 올 때까지 실행을 일시 정지할 수 있습니다.

const paid = await step.waitForEvent('wait-payment', {
  event: 'order/paid',
  timeout: '3d',
  match: 'data.orderId', // 트리거 이벤트와 대기 이벤트의 동일 필드로 매칭
});
if (!paid) {
  await step.run('cancel-hold', async () => {
    /* 예약 해제 */
  });
  return { status: 'expired' };
}

복잡한 조건은 CEL 기반 if 표현식(예: event.data.userId == async.data.userId && …)으로 지정할 수 있으며, match와는 동시에 쓸 수 없습니다. 세부 문법은 Inngest 문서의 waitForEvent·Writing expressions를 따르세요.

재시도 정책

함수 옵션의 retries는 함수 전체 횟수가 아니라 스텝마다 적용됩니다. retries: 5면 각 스텝이 최초 시도 후 최대 5번 재시도되고, 한 스텝이 재시도를 다 쓰면 함수 실행이 실패로 끝납니다. 재시도 간격은 지수 백오프로 늘어나므로, 외부 API가 몇 분간 장애를 겪어도 대부분 자연스럽게 복구됩니다.

모든 오류를 재시도하는 것은 낭비입니다. 존재하지 않는 사용자(404)나 잘못된 요청(400)은 몇 번을 다시 시도해도 결과가 같습니다. SDK는 이런 경우를 위해 NonRetriableError를 제공하며, 이 오류를 던지면 남은 재시도 없이 즉시 실패로 끝납니다. 반대로 429 Too Many Requests처럼 서버가 Retry-After를 알려 주는 경우에는 RetryAfterError로 다음 시도 시각을 지정할 수 있습니다.

import { NonRetriableError } from 'inngest';

const user = await step.run('fetch-user', async () => {
  const r = await fetch(`${process.env.API_URL}/users/${userId}`);
  if (r.status === 404) throw new NonRetriableError(`user ${userId} not found`);
  if (!r.ok) throw new Error(`user fetch ${r.status}`); // 5xx 등은 재시도
  return r.json();
});

재시도를 다 쓰고 실패한 실행은 onFailure 핸들러로 받아 알림을 보내거나 보상 작업을 실행할 수 있습니다. 큐 시스템의 데드 레터 큐에 해당하는 역할입니다.


Cron 트리거와 이벤트의 공존

Cron 트리거

정기 배치(리포트, 정리 작업, 동기화)는 이벤트 대신 Cron으로 트리거합니다.

export const dailyDigest = inngest.createFunction(
  { id: 'daily-digest', name: '일일 요약' },
  { cron: '0 9 * * *' }, // 매일 09:00 (타임존은 Inngest 설정 확인)
  async ({ step }) => {
    await step.run('aggregate', async () => {
      /* 전일 통계 집계 */
    });
    await step.run('send-digest', async () => {
      /* 이메일 발송 */
    });
  },
);

Cron 표현식은 기본적으로 UTC 기준으로 해석됩니다. 위 예제는 한국 시간으로는 18시에 실행됩니다. 현지 시간에 맞추려면 { cron: 'TZ=Asia/Seoul 0 9 * * *' }처럼 표현식 앞에 타임존을 붙입니다. 타임존을 붙이면 서머타임이 있는 지역에서도 현지 시각 기준으로 맞춰지지만, 서머타임 전환일에는 해당 시각이 없거나 두 번 오는 문제가 생길 수 있으므로 전환 구간(보통 새벽 1~3시)에 배치를 잡지 않는 편이 안전합니다.

이벤트와 Cron의 공존

동일한 작업을 “사용자가 버튼을 눌렀을 때”와 “매일 자동” 모두에서 실행하려면, 내부 로직을 공유 함수로 두고 Cron 함수와 이벤트 함수에서 각각 step.run을 호출하거나, Cron에서 내부 이벤트를 send해 하나의 핸들러로 모으는 패턴도 흔합니다.


Next.js App Router·Vercel 통합

App Router: 단일 엔드포인트

Next.js App Router에서는 serve로 Inngest가 호출할 엔드포인트를 노출합니다. GET은 함수 목록 조회(디버그), PUT은 함수 등록(동기화), POST는 실제 스텝 실행에 쓰입니다.

// src/app/api/inngest/route.ts
import { serve } from 'inngest/next';
import { inngest } from '@/inngest/client';
import { notifyOrderCreated, dailyDigest } from '@/inngest/functions';

export const maxDuration = 300;

export const { GET, POST, PUT } = serve({
  client: inngest,
  functions: [notifyOrderCreated, dailyDigest],
});

maxDuration은 Vercel 등에서 서버리스 함수의 최대 실행 시간입니다. 요금제마다 설정할 수 있는 상한이 다르므로 사용하는 플랜의 한도를 확인해야 합니다. Inngest는 스텝마다 체크포인트를 두므로 전체 워크플로우는 며칠이 걸려도 괜찮지만, 한 번의 호출은 이 한도 안에 끝나야 합니다. 한 스텝이 한도를 넘기면 호출이 강제 종료되어 실패로 기록되고, 재시도해도 같은 지점에서 다시 끊깁니다. 로그에 FUNCTION_INVOCATION_TIMEOUT이 반복된다면 그 스텝을 여러 스텝으로 쪼개야 한다는 신호입니다.

배포 후 처음 겪기 쉬운 문제는 함수 동기화입니다. 코드를 배포해도 Inngest Cloud가 새 함수 목록을 모르면 이벤트가 와도 아무 일도 일어나지 않습니다. Vercel 통합을 쓰면 배포마다 자동으로 PUT 동기화가 일어나지만, 다른 플랫폼에서는 배포 후 앱 URL을 Inngest 대시보드에 등록하거나 동기화 요청을 보내야 합니다. Vercel의 Deployment Protection이 켜져 있으면 Inngest의 요청이 인증 페이지에 막혀 401을 받으므로, 보호 우회 설정도 함께 확인해야 합니다.

Vercel 배포와 환경 변수

Vercel 통합을 쓰면 배포 시 함수 동기화와 환경 변수 주입이 단순해집니다. 일반적으로 다음을 설정합니다.

  • INNGEST_EVENT_KEY: 앱이 Inngest로 이벤트를 보낼 때 인증
  • INNGEST_SIGNING_KEY: Inngest Cloud가 앱의 /api/inngest로 요청할 때 서명 검증

로컬에서는 .env.local에 Dev Server가 안내하는 값을 넣으며, 프로덕션은 Vercel 대시보드 또는 통합으로 주입합니다.

의존성과 빌드

inngest 패키지와 함께, 프로젝트의 TypeScript·번들러 설정이 서버 전용 코드를 클라이언트로 끌어오지 않게 주의합니다. Inngest 관련 모듈은 서버 측(Route Handler, Server Actions, 별도 워커)에서만 import하는 것이 안전합니다.


Dev Server와 프로덕션 대시보드로 디버깅

Inngest Dev Server (로컬)

로컬 개발 시 Inngest Dev Server를 실행하면 대시보드에서 이벤트·함수 실행·스텝 로그를 확인할 수 있습니다. 보통 npx inngest-cli@latest dev -u http://localhost:3000/api/inngest처럼 앱의 serve URL을 지정해 띄우고, 기본적으로 http://localhost:8288에서 대시보드가 열립니다. Dev Server는 클라우드 키 없이 동작하므로 로컬에서는 이벤트 키가 없어도 inngest.send가 Dev Server로 전달됩니다.

팁: 이벤트가 함수에 매칭되지 않으면 “함수 미등록·이벤트 이름 불일치·앱 URL 불일치”를 우선 의심합니다.

프로덕션 대시보드

Inngest Cloud에서는 실행별 상태, 재시도 횟수, 스텝 타임라인, 실패 원인을 볼 수 있습니다. 운영 시 알림(Slack 등)과 연동해 실패율 급증을 감지하면 대응이 빨라집니다.

관측성 보강

애플리케이션 로그에 runId·event.id·functionId를 남기면, Inngest UI의 실행과 로그 시스템을 상호 참조하기 쉽습니다. OpenTelemetry 등과 연동하는 예시는 Inngest 문서의 관측성 섹션을 참고하세요.


이메일 발송 시스템 구현

주문이 생성되면 환영 이메일과 영수증 이메일을 순서대로 보내되, 외부 SMTP/API 장애 시 일부만 재시도되도록 스텝으로 나눕니다. 여기서는 Resend 같은 HTTP API를 가정합니다.

// src/inngest/functions/order-emails.ts
import { inngest } from '../client';

export const sendOrderEmails = inngest.createFunction(
  {
    id: 'send-order-emails',
    name: '주문 이메일 발송',
    retries: 8,
  },
  { event: 'order/created' },
  async ({ event, step }) => {
    const { orderId, userId } = event.data;

    const user = await step.run('fetch-user', async () => {
      const r = await fetch(`${process.env.API_URL}/users/${userId}`);
      if (!r.ok) throw new Error('user fetch failed');
      return r.json() as Promise<{ email: string; name: string }>;
    });

    await step.run('send-welcome', async () => {
      const r = await fetch('https://api.resend.com/emails', {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${process.env.RESEND_API_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          from: '[email protected]',
          to: user.email,
          subject: `${user.name}님, 주문이 접수되었습니다`,
          html: `<p>주문번호 ${orderId}</p>`,
        }),
      });
      if (!r.ok) throw new Error(`resend welcome ${r.status}`);
    });

    await step.run('send-receipt', async () => {
      const r = await fetch('https://api.resend.com/emails', {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${process.env.RESEND_API_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          from: '[email protected]',
          to: user.email,
          subject: `영수증 #${orderId}`,
          html: '<p>상세 내역 …</p>',
        }),
      });
      if (!r.ok) throw new Error(`resend receipt ${r.status}`);
    });

    return { sent: true, orderId };
  },
);

설계 포인트

  • 사용자 조회가 실패하면 아직 이메일을 보내지 않은 상태이므로 fetch-user 스텝만 재시도되고, 이후 스텝은 조회가 성공할 때까지 실행되지 않습니다.
  • 환영 메일만 성공하고 영수증에서 실패하면, 재시도 시 fetch-user와 send-welcome은 저장된 결과로 건너뛰고 send-receipt부터 다시 시도합니다. 스텝 경계 덕분에 정상적인 실패에서는 환영 메일이 중복 발송되지 않습니다.
  • 그러나 스텝 시맨틱만으로 완전한 멱등성이 보장되지는 않습니다. 메일 API가 요청을 처리하고 메일을 보낸 뒤 응답이 네트워크에서 유실되거나, 응답을 받기 직전에 서버리스 호출이 타임아웃으로 끊기면, Inngest 입장에서는 그 스텝이 실패한 것이라 다시 실행합니다. 이메일·결제처럼 중복이 사용자 눈에 보이는 호출에는 order-${orderId}-welcome 같은 결정적 idempotency key를 요청 헤더로 넘기거나, DB에 “발송 완료” 레코드를 두어 이중 방어를 합니다.

이메일 두 통을 굳이 스텝 두 개로 나눈 이유가 여기에 있습니다. 한 스텝에서 두 통을 연달아 보내면, 두 번째 전송이 실패했을 때 스텝 전체가 재시도되어 첫 번째 메일이 다시 나갑니다. 스텝은 “여기까지 성공했다”를 기록하는 단위이므로, 외부에 되돌릴 수 없는 효과를 남기는 호출은 하나씩 별도 스텝에 두는 것이 원칙입니다.

같은 사용자에게 짧은 시간에 주문이 몰리면 메일 API의 요청 한도에 걸릴 수 있습니다. 함수 옵션의 concurrency: { limit: 5 }로 동시에 실행되는 인스턴스 수를 제한하거나, throttle로 시간당 시작 횟수를 조절할 수 있고, key: 'event.data.userId'를 주면 사용자별로 제한을 따로 걸 수 있습니다.

트랜잭션 경계

DB에 주문을 커밋한 직후 inngest.send를 호출하는 패턴이 일반적입니다. DB 커밋 전에 이벤트를 보내면 주문이 없는데 이메일만 가는 불일치가 생길 수 있으며, 반대로 커밋 후 전송 실패 시에는 보상 트랜잭션 또는 재전송 큐를 별도로 두어야 합니다. Inngest는 후자(비동기 처리)를 맡기 쉬운 위치에 있습니다.

제가 이 구조에서 가장 조심하는 부분은 “커밋은 됐는데 send가 실패한” 틈입니다. Inngest API가 잠깐 응답하지 않거나 프로세스가 커밋 직후 종료되면 주문은 있는데 이메일 함수는 영영 실행되지 않고, 이 상황은 에러 로그 한 줄 외에는 흔적이 남지 않습니다. 중요한 이벤트라면 주문 트랜잭션 안에서 outbox 테이블에 이벤트를 함께 기록하고, 별도의 주기 작업이 아직 보내지 않은 행을 send하는 아웃박스 패턴으로 이 틈을 메울 수 있습니다. 이벤트에 id 필드를 지정하면 Inngest가 같은 ID의 이벤트를 일정 시간 동안 중복 제거하므로, 아웃박스에서 같은 행을 두 번 보내도 함수가 두 번 실행되지 않습니다.


이벤트 이름·페이로드·시크릿·테스트 원칙

  • 이벤트 이름: domain/action 형태(order/created)로 일관되게 유지하며, 변경 시 구버전 이벤트를 병행 수신할지 결정합니다.
  • 페이로드 크기: 대용량 바이너리는 스토리지 URL만 넘기고, 함수 안에서 스트리밍 처리합니다.
  • 시크릿: 함수 코드에 API 키를 하드코딩하지 말고 환경 변수·시크릿 매니저를 사용합니다.
  • 테스트: 스텝 로직은 순수 함수로 분리해 단위 테스트하며, 통합 테스트에서는 Dev Server 또는 모킹된 클라이언트로 send·실행을 검증합니다.

Inngest 사용 요약

Inngest는 이벤트로 백그라운드 작업을 선언하며, 스텝으로 실행을 쪼개 저장하며, Cron으로 정기 작업을 같은 모델에 얹을 수 있는 플랫폼입니다. Next.js Route Handler 한 곳에 serve를 두고 Vercel에 올리면, 서버리스 한계와 운영 복잡도 사이에서 실용적인 균형을 잡기 좋습니다. 이메일 예제에서 보았듯 step.run 경계가 곧 재시도·중복 방지 설계의 기준이 되므로, 처음부터 스텝을 도메인 의미에 맞게 나누는 습관을 들이면 이후 장애 대응이 수월합니다.


자주 묻는 질문 (FAQ)

Q. 큰 파일을 Inngest 이벤트 페이로드에 담아 보내도 되나요?

A. 권장하지 않습니다. 이벤트 페이로드는 작게 유지하고, 대용량 바이너리는 먼저 스토리지에 올린 뒤 URL이나 키만 이벤트에 담아 보내는 편이 안전합니다. 함수 안에서는 그 URL로 파일을 스트리밍해 처리하면 되고, 이렇게 하면 스텝이 재시도될 때도 큰 데이터를 다시 주고받지 않아도 됩니다.


같이 보면 좋은 글