Files
2nd/10_Wiki/Topics/Coding/Backend_Cron_Workflows_Inngest.md
T
2026-05-10 22:08:15 +09:00

6.4 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
backend-cron-workflows-inngest Inngest / Trigger.dev — function-as-a-workflow Coding draft B conceptual 2026-05-09 2026-05-09
backend
workflow
vibe-coding
language applicable_to
TS
Backend
Inngest
Trigger.dev
function workflow
durable function
event-driven
cron
retry

Inngest / Trigger.dev

Modern async / cron / workflow. Code 가 function, retry / step / wait 자동. Temporal 의 simple version.

📖 핵심 개념

  • Function = workflow.
  • Step = retry / dedup unit.
  • Event 가 trigger.
  • Cron / wait 가 native.

💻 코드 패턴

Inngest function

import { Inngest } from 'inngest';
const inngest = new Inngest({ id: 'my-app' });

export const dailyReport = inngest.createFunction(
  { id: 'daily-report' },
  { cron: '0 9 * * *' },
  async ({ event, step }) => {
    const data = await step.run('fetch', async () => fetchData());
    
    await step.sleep('wait', '1m');
    
    await step.run('email', async () => sendEmail(data));
    
    return { ok: true };
  }
);

→ 매 step 가 retry / dedup 자동.

Event-triggered

export const onUserSignup = inngest.createFunction(
  { id: 'welcome-email' },
  { event: 'user/signup' },
  async ({ event, step }) => {
    await step.run('send', () => sendWelcome(event.data.email));
    
    await step.sleep('wait-3-days', '3d');
    
    await step.run('check-active', async () => {
      const user = await getUser(event.data.userId);
      if (!user.active) await sendReengagement(user);
    });
  }
);

// Trigger
await inngest.send({ name: 'user/signup', data: { userId, email } });

Step.sleep (durable wait)

await step.sleep('wait', '1d');
// → Function 가 종료. 1 day 후 resume.
// → Server restart 가 OK (state durable).

→ Long-running workflow.

Step.waitForEvent

const event = await step.waitForEvent('payment-confirmed', {
  event: 'payment/confirmed',
  match: 'data.userId',
  timeout: '1h',
});

if (!event) {
  // Timeout
}

→ "다음 event 가 도착 까지 wait".

Trigger.dev

import { client } from './trigger';

client.defineJob({
  id: 'daily-report',
  trigger: cronTrigger({ cron: '0 9 * * *' }),
  run: async (payload, io) => {
    const data = await io.runTask('fetch', async () => fetchData());
    await io.sendEvent('email', { name: 'send', payload: { data } });
  },
});

→ Inngest 와 매우 비슷.

Concurrency limit

{ concurrency: { limit: 10 } }
// → 10 instance max parallel.

Throttle

{ throttle: { limit: 100, period: '1m' } }
// → 1 min 안 100 max.

Retry policy

inngest.createFunction(
  { id: 'task', retries: 5 },
  ...
  async ({ step }) => {
    await step.run('flaky', async () => {
      // Auto retry 5 times.
    });
  }
);

Cancel running function

{
  cancelOn: [{ event: 'user/deleted', if: 'event.data.userId == async.data.userId' }]
}

→ 외부 event 가 cancel.

Local dev

npx inngest-cli dev
# → Dashboard at localhost:8288.

→ Function trace + replay + UI.

Production deploy

// Vercel / Cloudflare / Lambda
// Inngest 가 serverless 친화.

// app/api/inngest/route.ts (Next.js)
import { serve } from 'inngest/next';
import { dailyReport, onUserSignup } from '../../inngest';

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

vs Temporal

Temporal:
- 강력 (workflow language).
- Self-host 가 가능.
- 큰 enterprise.
- Steeper.

Inngest:
- TS-friendly.
- Managed 친화.
- Simple.
- 작은 / 중간.

→ Modern serverless = Inngest.
큰 / on-prem = Temporal.

vs BullMQ

BullMQ:
- Redis-backed.
- Self-host.
- 더 raw.

Inngest:
- Managed.
- Workflow primitive.
- Step / sleep native.

→ Inngest 가 더 production friendly.

vs AWS Step Functions

Step Functions:
- AWS native.
- ASL (state machine).
- Managed scaling.

Inngest:
- TS code (no DSL).
- Multi-cloud.

→ AWS-only = Step Functions.
Multi-cloud / TS = Inngest.

Use case

- Email sequence (welcome → 3 day → 7 day).
- LLM agent workflow.
- Image processing pipeline.
- Daily report.
- Subscription billing.
- Scheduled cleanup.
- Data sync (매 hour).

LLM agent (Inngest)

export const agent = inngest.createFunction(
  { id: 'agent' },
  { event: 'agent/start' },
  async ({ event, step }) => {
    let context = event.data.task;
    
    for (let i = 0; i < 5; i++) {
      const action = await step.run(`think-${i}`, async () => llm.complete(...));
      
      if (action.type === 'done') return action.result;
      
      const result = await step.run(`act-${i}`, async () => execute(action));
      context += result;
    }
  }
);

→ Multi-step agent + retry / pause / resume.

Idempotency

{
  idempotency: 'event.data.orderId',
}
// → 같은 orderId 의 event 가 1번만.

Batching

{
  batchEvents: { maxSize: 100, timeout: '5s' }
}
// → 5 sec 또는 100 event 마다 batch.

Cost

Inngest free: 1k step / month.
Pro: $20-200 / month.

Trigger.dev: 비슷.

→ Self-host alternative 가 BullMQ + cron.

Anti-pattern

- Step 안에 step.run 가 안: nested 안 됨.
- Side effect 가 step.run 외: retry 시 중복.
- Long step (>30 sec): timeout (function level).
- Throw vs return error: 매 다름.

Best practice

1. 매 side effect 가 step.run.
2. Idempotent step (key).
3. Concurrency / throttle (외부 API rate limit).
4. Timeout (긴 wait 도).
5. Error monitoring.

🤔 의사결정 기준

상황 추천
Modern serverless Inngest / Trigger.dev
큰 / on-prem Temporal
Self-host simple BullMQ
AWS-only Step Functions
LLM agent Inngest (durable + retry)
Email sequence Inngest (sleep)

안티패턴

  • Side effect 가 step 외: retry 중복.
  • Long sleep 가 step.sleep 안: function timeout.
  • No idempotency: replay 시 중복.
  • No concurrency limit: external API 폭발.
  • Step 가 nested: 안 됨.

🤖 LLM 활용 힌트

  • Inngest = durable function + step.
  • Cron / event / wait native.
  • LLM agent 에 강함.
  • Temporal 의 simple alternative.

🔗 관련 문서