이벤트 트리거 vs 스케줄 에이전트: 어떤 작업에 어떤 패턴을 쓸까
사용자 행동에 즉각적인 응답이 필요할 때는 이벤트 트리거 에이전트를 쓴다 — 몇 초만 지연돼도 경험이 망가진다. 타이밍이 예측 가능한 배치 또는 주기적 작업에는 스케줄 에이전트를 쓴다. 제약: 이벤트 트리거 에이전트는 stateless이고 빨라야 한다. 스케줄 에이전트는 stateful이고 느려도 된다.
매주 수요일. 28,400명+ 구독자. 핵심만.
✓ 받은편지함을 확인하세요 — 확인 링크를 클릭해 가입을 완료하세요.
✓ 구독이 완료되었습니다!
✓ 이미 목록에 있습니다.
[운영자의 관점] 나는 컨설팅 브랜드와 Pickleland(텍사스 Pflugerville의 피클볼 시설)에서 30개 이상의 프로덕션 에이전트를 운영한다. 그 중 어느 것도 두 가지 패턴 중 하나를 벗어나지 않는다: 이벤트로 실행되거나, 시계로 실행되거나. 이걸 잘못 선택하면 돈을 낭비하고 망가진 경험을 제공하게 된다.
목차
목차 열기
두 패턴을 쉽게 설명하면
이벤트 트리거 에이전트는 무언가 일어났기 때문에 깨어난다. 예약이 들어왔다. 댓글이 달렸다. 폼이 제출됐다. 트리거는 외부에서 오고 타이밍은 예측 불가능하다. 할 일: 빠르게 반응하기.
스케줄 에이전트는 시계가 말했기 때문에 깨어난다. 매일 아침 7시. 매주 일요일 오후 6시. 매시 정각. 트리거는 내부에 있고 완전히 예측 가능하다. 할 일: 꼼꼼한 작업 수행하기.
그게 전부다. 복잡하게 생각하지 마라. 아키텍처는 하나의 질문에 대한 답에서 나온다: 사용자나 시스템이 지금 당장 응답이 필요한가, 아니면 특정 시간까지 기다릴 수 있는가?
이벤트 트리거: 소셜 댓글 응답 에이전트
내 소셜 리플라이 에이전트는 모니터링 중인 Facebook 게시물에 새 댓글이 올라올 때마다 실행된다. 에이전트는 댓글을 읽고 의도를 분류하고(질문, 불만, 칭찬, 스팸), 답글을 작성해 게시한다 — 확신도가 낮으면 사람이 검토하도록 플래그를 세운다.
전체 왕복이 30초 안에 끝나지 않으면 답글이 오래된 것처럼 느껴진다. 이것이 이벤트 트리거 문제다.
소셜 모니터링 서비스의 webhook을 처리하는 간소화된 Cloudflare Worker다:
// 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 그룹에 맞는 게시물 초안을 작성하고, 어떤 것도 라이브가 되기 전에 내 검토를 위해 제출한다.
// 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 설정:
# wrangler.toml
[[triggers]]
crons = ["0 18 * * 0"] # 매주 일요일 18시 UTC스케줄 에이전트가 이벤트 트리거 에이전트는 할 수 없는 것을 할 수 있음을 주목하라: 여러 이벤트를 반복 처리하고, 데이터베이스에 쓰고, 요약 알림을 보낸다. 배치 작업을 수행하는 것이다. 이벤트 트리거 에이전트는 가볍게 유지하고 빠르게 응답해야 한다.
스케줄: 데일리 브리프
매일 아침 7시에 내 데일리 브리프 에이전트가 실행된다. 밤새 온 이메일, 오늘 일정, 최우선 과제, 그리고 내가 관련 있다고 표시한 뉴스를 가져온다. 모든 것을 하나의 문서로 포맷해서 AI Workspace 폴더에 넣는다.
이것은 순수 스케줄이다. 트리거가 될 이벤트가 없다 — 단지 매일 아침 일을 시작하기 전에 갖고 싶을 뿐이다.
// 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);
}[[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 예약 파이프라인은 이렇게 작동한다:
- 이벤트 트리거: 새 예약 webhook → 예약 확인, 고객에게 영수증 전송, 가용성 업데이트. 10초 이내에 완료되어야 한다.
- 스케줄: 매주 일요일 → 지난 주 모든 예약 검토, 요약 보고서 생성, 이상 징후 플래그 (중복 예약, 비정상적인 취소율).
같은 도메인, 두 패턴, 두 에이전트. 이벤트 트리거는 실시간 사용자 경험을 담당한다. 스케줄은 주간 운영 리뷰를 담당한다. 데이터베이스는 공유하지만 그 외에는 아무것도 공유하지 않는다.
“모든 것을 하는” 하나의 에이전트로 합치려 하지 마라. 이벤트에는 너무 느리고 배치 작업에는 실시간 흐름에 너무 강하게 결합된 것을 만들게 된다.
Cloudflare Workers: 두 가지 모두에 적합한 인프라인 이유
Cloudflare Workers는 두 패턴을 모두 네이티브로 처리한다:
fetch핸들러 → 이벤트 트리거 (webhooks, API 호출)scheduled핸들러 → cron 기반 (wrangler.toml의[[triggers]]경유)queue컨슈머 → HTTP 레이어에서 분리된 비동기 처리
엣지 배포는 이벤트 트리거 에이전트가 전 세계적으로 빠르게 응답함을 의미한다. 무료 티어는 아무것도 쓰지 않고 두 패턴을 프로토타입하기에 충분히 넉넉하다. 통합된 wrangler.toml 설정은 두 패턴을 위한 두 개의 별도 인프라 설정을 관리하지 않아도 됨을 의미한다.
Workers가 잘 해결하지 못하는 한 가지: 몇 분 이상 실행해야 하는 에이전트. 그런 경우 Durable Objects를 쓰거나 더 오래 실행되는 백엔드에 위임하라.
운영자의 결론
에이전트 코드 한 줄을 쓰기 전에 패턴을 결정하라. 사람이 기다리는 모든 것에는 이벤트 트리거; 시계에 따라 실행되는 모든 것에는 스케줄. 이벤트 트리거 핸들러는 가볍게 유지하라 — 빠르게 반환하고, 작업을 큐에 넣어라. 스케줄 에이전트는 병렬로 유지하라 — 병렬화할 수 있는 것을 직렬화하지 마라. 아키텍처는 단순하다. 그것을 위반하는 데서 복잡성이 생긴다.
매주 수요일. 28,400명+ 구독자. 핵심만.
✓ 받은편지함을 확인하세요 — 확인 링크를 클릭해 가입을 완료하세요.
✓ 구독이 완료되었습니다!
✓ 이미 목록에 있습니다.
관련 게시물
Claude 에이전트 vs. Zapier: 내가 실제로 쓰는 기준
Zapier는 규칙에 따라 앱 사이에 데이터를 옮긴다. Claude 에이전트는 지저분한 입력을 판단한다. 내가 실제로 둘을 나눠 쓰는 기준을 소개한다.
AI AgentsAI 에이전트 범위 문서 작성법
막연한 AI 에이전트 요청을 고정 구축비 견적으로 바꾸는 범위 문서 — 필요한 항목, 템플릿, 초안을 작성할 때 쓰는 프롬프트.
AI AgentsAI 에이전트를 만들지 말아야 할 때 (대신 이렇게 하세요)
AI 에이전트가 잘못된 도구라는 다섯 가지 신호와, 에이전트 코드를 쓰기 전에 제가 먼저 시도하는 더 간단한 대안들의 사다리를 소개합니다.
AI 플레이북을 받아보세요
매주 수요일. 28,400명+ 구독자. 핵심만.
받은편지함을 확인하세요.
확인 이메일을 보냈습니다 — 링크를 클릭해 구독을 완료하세요. 1분 안에 보이지 않으면 스팸함을 확인하세요.
구독이 완료되었습니다.
환영합니다 — 다음 호가 곧 받은편지함에 도착합니다.
이미 목록에 있습니다 — 매주 수요일에 확인하세요.