Alejandro Rioja.
AI Agents

이벤트 트리거 vs 스케줄 에이전트: 어떤 작업에 어떤 패턴을 쓸까

Alejandro Rioja
Alejandro Rioja
6 분 읽기
TL;DR

사용자 행동에 즉각적인 응답이 필요할 때는 이벤트 트리거 에이전트를 쓴다 — 몇 초만 지연돼도 경험이 망가진다. 타이밍이 예측 가능한 배치 또는 주기적 작업에는 스케줄 에이전트를 쓴다. 제약: 이벤트 트리거 에이전트는 stateless이고 빨라야 한다. 스케줄 에이전트는 stateful이고 느려도 된다.

무료 뉴스레터

매주 수요일. 28,400명+ 구독자. 핵심만.

목차

2026년 5월 업데이트.

TL;DR: 사용자 행동에 즉각적인 응답이 필요할 때는 이벤트 트리거 에이전트를 쓴다 — 몇 초만 지연돼도 경험이 망가진다. 타이밍이 예측 가능한 배치 또는 주기적 작업에는 스케줄 에이전트를 쓴다. 제약: 이벤트 트리거 에이전트는 stateless이고 빨라야 한다. 스케줄 에이전트는 stateful이고 느려도 된다.

[운영자의 관점] 나는 컨설팅 브랜드와 Pickleland(텍사스 Pflugerville의 피클볼 시설)에서 30개 이상의 프로덕션 에이전트를 운영한다. 그 중 어느 것도 두 가지 패턴 중 하나를 벗어나지 않는다: 이벤트로 실행되거나, 시계로 실행되거나. 이걸 잘못 선택하면 돈을 낭비하고 망가진 경험을 제공하게 된다.

두 패턴을 쉽게 설명하면

이벤트 트리거 에이전트는 무언가 일어났기 때문에 깨어난다. 예약이 들어왔다. 댓글이 달렸다. 폼이 제출됐다. 트리거는 외부에서 오고 타이밍은 예측 불가능하다. 할 일: 빠르게 반응하기.

스케줄 에이전트는 시계가 말했기 때문에 깨어난다. 매일 아침 7시. 매주 일요일 오후 6시. 매시 정각. 트리거는 내부에 있고 완전히 예측 가능하다. 할 일: 꼼꼼한 작업 수행하기.

그게 전부다. 복잡하게 생각하지 마라. 아키텍처는 하나의 질문에 대한 답에서 나온다: 사용자나 시스템이 지금 당장 응답이 필요한가, 아니면 특정 시간까지 기다릴 수 있는가?

이벤트 트리거: 소셜 댓글 응답 에이전트

내 소셜 리플라이 에이전트는 모니터링 중인 Facebook 게시물에 새 댓글이 올라올 때마다 실행된다. 에이전트는 댓글을 읽고 의도를 분류하고(질문, 불만, 칭찬, 스팸), 답글을 작성해 게시한다 — 확신도가 낮으면 사람이 검토하도록 플래그를 세운다.

전체 왕복이 30초 안에 끝나지 않으면 답글이 오래된 것처럼 느껴진다. 이것이 이벤트 트리거 문제다.

소셜 모니터링 서비스의 webhook을 처리하는 간소화된 Cloudflare Worker다:

typescript
// workers/social-reply.ts
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    if (request.method !== "POST") {
      return new Response("Method not allowed", { status: 405 });
    }

    // webhook 서명 검증
    const sig = request.headers.get("x-webhook-signature") ?? "";
    const body = await request.text();
    const valid = await verifySignature(body, sig, env.WEBHOOK_SECRET);
    if (!valid) return new Response("Unauthorized", { status: 401 });

    const event = JSON.parse(body) as SocialCommentEvent;

    // 분류하고 답글 — 200을 빠르게 반환하기 위해 async로 처리
    env.REPLY_QUEUE.send(event);

    return new Response("OK", { status: 200 });
  },
};

// 큐 컨슈머 — 실제 AI 작업을 수행
export const queue: ExportedHandlerQueueHandler<Env, SocialCommentEvent> =
  async (batch, env) => {
    for (const msg of batch.messages) {
      const comment = msg.body;

      const classification = await classifyComment(comment.text, env);
      if (classification.intent === "spam") {
        msg.ack();
        continue;
      }

      const reply = await draftReply(comment, classification, env);

      if (classification.confidence > 0.85) {
        await postReply(comment.postId, comment.id, reply, env);
      } else {
        await flagForReview(comment, reply, env);
      }

      msg.ack();
    }
  };

두 가지를 주목하라. 첫째, fetch 핸들러는 즉시 200을 반환하고 실제 작업을 큐에 넘긴다. 이렇게 하면 webhook 응답이 빨라지고 모니터링 서비스가 재시도하는 것을 막는다. 둘째, 큐 컨슈머가 열린 HTTP 연결의 시간 압박 없이 실제 AI 호출(분류 및 작성)을 수행한다.

스케줄: Pickleland 이벤트 프로모터

Pickleland는 코트와 이벤트를 관리한다. 매주 누군가가 좌석을 채우기 위해 다가오는 이벤트를 적절한 Facebook 그룹에 올려야 한다. 이것은 순수한 주기적 배치 작업이다 — 사용자 행동이 트리거가 되지 않고, 실시간으로 처리될 필요도 없다.

Pickleland 이벤트 프로모터는 cron으로 실행되어 예약 시스템에서 4일 내 이벤트를 확인하고, 매칭된 각 Facebook 그룹에 맞는 게시물 초안을 작성하고, 어떤 것도 라이브가 되기 전에 내 검토를 위해 제출한다.

typescript
// workers/event-promoter.ts
export default {
  async scheduled(
    event: ScheduledEvent,
    env: Env,
    ctx: ExecutionContext
  ): Promise<void> {
    ctx.waitUntil(runPromoter(env));
  },
};

async function runPromoter(env: Env): Promise<void> {
  // 예약 시스템에서 이벤트 가져오기
  const upcomingEvents = await fetchUpcomingEvents(env, { daysAhead: 4 });

  if (upcomingEvents.length === 0) return;

  const drafts: PromoDraft[] = [];

  for (const event of upcomingEvents) {
    // 각 이벤트를 적합한 FB 그룹에 매칭
    const groups = await matchFacebookGroups(event, env);

    for (const group of groups) {
      const post = await draftPromoPost(event, group, env);
      drafts.push({ event, group, post });
    }
  }

  // 검토를 위해 Airtable에 초안 저장 — 자동으로 게시되는 것은 없음
  await saveDraftsForReview(drafts, env);

  // Slack으로 알림
  await notifyOperator(
    `${drafts.length}개의 프로모 초안이 검토 대기 중`,
    env
  );
}

모든 것을 연결하는 wrangler 설정:

toml
# wrangler.toml
[[triggers]]
crons = ["0 18 * * 0"]  # 매주 일요일 18시 UTC

스케줄 에이전트가 이벤트 트리거 에이전트는 할 수 없는 것을 할 수 있음을 주목하라: 여러 이벤트를 반복 처리하고, 데이터베이스에 쓰고, 요약 알림을 보낸다. 배치 작업을 수행하는 것이다. 이벤트 트리거 에이전트는 가볍게 유지하고 빠르게 응답해야 한다.

스케줄: 데일리 브리프

매일 아침 7시에 내 데일리 브리프 에이전트가 실행된다. 밤새 온 이메일, 오늘 일정, 최우선 과제, 그리고 내가 관련 있다고 표시한 뉴스를 가져온다. 모든 것을 하나의 문서로 포맷해서 AI Workspace 폴더에 넣는다.

이것은 순수 스케줄이다. 트리거가 될 이벤트가 없다 — 단지 매일 아침 일을 시작하기 전에 갖고 싶을 뿐이다.

typescript
// workers/daily-brief.ts
export default {
  async scheduled(
    event: ScheduledEvent,
    env: Env,
    ctx: ExecutionContext
  ): Promise<void> {
    ctx.waitUntil(buildDailyBrief(env));
  },
};

async function buildDailyBrief(env: Env): Promise<void> {
  const [emails, calendar, tasks] = await Promise.all([
    fetchOvernightEmails(env),
    fetchTodayCalendar(env),
    fetchTopTasks(env),
  ]);

  const brief = await synthesizeBrief({ emails, calendar, tasks }, env);

  await writeToWorkspace(brief, env);
}
toml
[[triggers]]
crons = ["0 7 * * *"]  # 매일 7시 UTC

병렬 Promise.all은 의도적이다. 스케줄 에이전트는 기다리는 사람이 없다 — 하지만 그렇다고 필요 이상으로 느려서는 안 된다. 모든 데이터 소스를 병렬로 가져온 다음 AI 합성을 한 번만 수행하라.

이벤트 트리거가 실패하는 경우

내가 가장 많이 보는 실패 패턴: 핸들러에서 너무 많은 작업을 하는 이벤트 트리거 에이전트를 만드는 것.

예약이 들어온다. 에이전트는 고객 프로필을 가져오고, 세 개의 외부 API로 보강하고, 개인화 모델을 실행하고, CRM에 쓰고, 확인 이메일을 보내고, 대시보드를 업데이트한다. 전체 과정이 45초 걸린다. 예약 플랫폼은 200을 충분히 빠르게 받지 못했기 때문에 재시도한다. 이제 에이전트가 두 번 실행된다.

소셜 리플라이 에이전트와 똑같은 방식으로 고쳐라: 즉시 200을 반환하고, 이벤트를 큐에 넣고, 큐 컨슈머가 무거운 작업을 비동기로 처리하게 하라.

다른 실패 패턴: 실제로는 주기적인 작업에 이벤트 트리거를 쓰는 것. “주간 요약 보내기”는 이벤트가 아니다. 합성 cron webhook에 연결하지 마라 — 적절한 스케줄 트리거를 사용하라.

스케줄이 실패하는 경우

스케줄 에이전트는 작업이 실제로 지연 민감할 때 실패한다. 사용자가 폼을 제출하고 처리 에이전트가 5분 cron으로 실행된다면, 사용자는 최대 5분 동안 스피너를 바라본다. 이것은 스케줄 작업이 아니다 — 스케줄인 척하는 느린 이벤트 트리거 작업이다.

또 다른 실패: 무한정한 작업으로 확장되는 스케줄 에이전트. cron이 매분 실행되고 각 호출이 수백 개의 레코드를 처리할 수 있다면, Cloudflare의 CPU 제한에 빠르게 도달한다. cron 간격을 늘리거나, 호출당 작업을 제한하는 큐를 추가하거나, 장기 실행 조정을 위해 Durable Objects로 전환하라.

패턴 혼합: 예약 파이프라인

일부 워크플로는 진정으로 둘 다 필요하다. Pickleland 예약 파이프라인은 이렇게 작동한다:

  1. 이벤트 트리거: 새 예약 webhook → 예약 확인, 고객에게 영수증 전송, 가용성 업데이트. 10초 이내에 완료되어야 한다.
  2. 스케줄: 매주 일요일 → 지난 주 모든 예약 검토, 요약 보고서 생성, 이상 징후 플래그 (중복 예약, 비정상적인 취소율).

같은 도메인, 두 패턴, 두 에이전트. 이벤트 트리거는 실시간 사용자 경험을 담당한다. 스케줄은 주간 운영 리뷰를 담당한다. 데이터베이스는 공유하지만 그 외에는 아무것도 공유하지 않는다.

“모든 것을 하는” 하나의 에이전트로 합치려 하지 마라. 이벤트에는 너무 느리고 배치 작업에는 실시간 흐름에 너무 강하게 결합된 것을 만들게 된다.

Cloudflare Workers: 두 가지 모두에 적합한 인프라인 이유

Cloudflare Workers는 두 패턴을 모두 네이티브로 처리한다:

엣지 배포는 이벤트 트리거 에이전트가 전 세계적으로 빠르게 응답함을 의미한다. 무료 티어는 아무것도 쓰지 않고 두 패턴을 프로토타입하기에 충분히 넉넉하다. 통합된 wrangler.toml 설정은 두 패턴을 위한 두 개의 별도 인프라 설정을 관리하지 않아도 됨을 의미한다.

Workers가 잘 해결하지 못하는 한 가지: 몇 분 이상 실행해야 하는 에이전트. 그런 경우 Durable Objects를 쓰거나 더 오래 실행되는 백엔드에 위임하라.

운영자의 결론

에이전트 코드 한 줄을 쓰기 전에 패턴을 결정하라. 사람이 기다리는 모든 것에는 이벤트 트리거; 시계에 따라 실행되는 모든 것에는 스케줄. 이벤트 트리거 핸들러는 가볍게 유지하라 — 빠르게 반환하고, 작업을 큐에 넣어라. 스케줄 에이전트는 병렬로 유지하라 — 병렬화할 수 있는 것을 직렬화하지 마라. 아키텍처는 단순하다. 그것을 위반하는 데서 복잡성이 생긴다.

계속 읽기

AI 플레이북을 받아보세요

매주 수요일. 28,400명+ 구독자. 핵심만.

↵ 전체 결과 보기 esc esc 닫기