Files
2nd/10_Wiki/Topics/Coding/Backend_Job_Scheduling_Temporal.md
T
2026-05-09 21:08:02 +09:00

5.8 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-job-scheduling-temporal Temporal — Workflow / Activity / Durable Coding draft B conceptual 2026-05-09 2026-05-09
backend
temporal
workflow
durable
vibe-coding
language applicable_to
TS / Temporal
Backend
Temporal
Cadence
durable execution
workflow
activity
signal
query

Temporal

긴 / 복잡 / 신뢰 critical workflow. 코드가 곧 state machine, 자동 재시도 / 영속 / 시간. Saga 의 정답. AWS Step Functions / Airflow 의 코드 버전.

📖 핵심 개념

  • Workflow: 결정 로직 (deterministic).
  • Activity: 외부 부수효과 (network, DB).
  • Signal: 외부에서 workflow 에 이벤트.
  • Query: workflow 상태 read.
  • Durable: 모든 step history 영속 — server crash 후 재개.

💻 코드 패턴

Activity (idempotent 권장)

// activities/payment.ts
export async function chargeCard(orderId: string): Promise<string> {
  const id = await stripe.paymentIntents.create({
    amount: 1000, currency: 'usd',
  }, { idempotencyKey: orderId });
  return id.id;
}

export async function refund(paymentId: string): Promise<void> {
  await stripe.refunds.create({ payment_intent: paymentId });
}

export async function reserveInventory(orderId: string): Promise<string> {
  return await db.inventory.reserve(orderId);
}

export async function releaseInventory(reservationId: string): Promise<void> {
  await db.inventory.release(reservationId);
}

Workflow (deterministic)

// workflows/order.ts
import { proxyActivities, log } from '@temporalio/workflow';
import type * as activities from '../activities';

const { chargeCard, refund, reserveInventory, releaseInventory, ship } 
  = proxyActivities<typeof activities>({
    startToCloseTimeout: '1 minute',
    retry: { maximumAttempts: 3 },
  });

export async function orderWorkflow(orderId: string): Promise<void> {
  let paymentId: string | undefined;
  let reservationId: string | undefined;

  try {
    paymentId = await chargeCard(orderId);
    reservationId = await reserveInventory(orderId);
    await ship(orderId);
  } catch (e) {
    log.error('order failed', { orderId, e });
    if (reservationId) await releaseInventory(reservationId);
    if (paymentId) await refund(paymentId);
    throw e;
  }
}

Worker (run)

// worker.ts
import { Worker } from '@temporalio/worker';

const worker = await Worker.create({
  workflowsPath: require.resolve('./workflows'),
  activities: require('./activities'),
  taskQueue: 'orders',
});

await worker.run();

Client (시작)

import { Client } from '@temporalio/client';

const client = new Client();
const handle = await client.workflow.start(orderWorkflow, {
  args: ['order-42'],
  taskQueue: 'orders',
  workflowId: `order-42`,        // 멱등 — 같은 ID = 같은 workflow
  workflowExecutionTimeout: '1 hour',
});

// 결과 기다림
await handle.result();

Signal (외부 이벤트)

import { defineSignal, setHandler, condition } from '@temporalio/workflow';

export const cancelSignal = defineSignal('cancel');

export async function orderWorkflow(orderId: string) {
  let cancelled = false;
  setHandler(cancelSignal, () => { cancelled = true; });

  // payment 후 사용자가 cancel 가능
  paymentId = await chargeCard(orderId);
  
  // 30초 동안 cancel 가능
  await condition(() => cancelled, '30 seconds');
  if (cancelled) {
    await refund(paymentId);
    return;
  }
  
  await ship(orderId);
}
// 외부에서 signal 보내기
const handle = client.workflow.getHandle(`order-42`);
await handle.signal(cancelSignal);

Query (외부에서 read)

import { defineQuery, setHandler } from '@temporalio/workflow';

export const statusQuery = defineQuery<string>('status');

export async function orderWorkflow(orderId: string) {
  let status = 'init';
  setHandler(statusQuery, () => status);

  status = 'charging';
  paymentId = await chargeCard(orderId);
  status = 'shipping';
  await ship(orderId);
  status = 'done';
}
const handle = client.workflow.getHandle(`order-42`);
const s = await handle.query(statusQuery);

Sleep / timer (durable)

import { sleep } from '@temporalio/workflow';

await sleep('7 days');  // 7일 후 재개. server crash 도 OK.
await sendReminderEmail(userId);

Cron workflow

await client.workflow.start(reportWorkflow, {
  cronSchedule: '0 9 * * 1',  // 월요일 9시
  workflowId: 'weekly-report',
  taskQueue: 'reports',
});

Versioning (schema 변경)

import { patched } from '@temporalio/workflow';

export async function workflow() {
  if (patched('new-step')) {
    await newStep(); // 새 logic
  }
  // 옛 in-flight workflow 도 안전
}

🤔 의사결정 기준

상황 추천
Saga / 멀티 step Temporal
시간 의존 (3일 후 알림) Temporal sleep
큐 / 단순 job BullMQ / SQS
Cron 만 k8s CronJob
큰 throughput stream Kafka
AWS only Step Functions
Self-host vs Cloud Temporal Cloud / 자체

안티패턴

  • Workflow 안 외부 호출 (fetch / DB): deterministic 깨짐. activity 로.
  • Random / Date.now() workflow 안: deterministic 깨짐 — workflowInfo, sleep, random 사용.
  • Activity 가 무한 retry: backoff + max attempts.
  • Worker 영원 한 task queue: scaling 어려움.
  • Signal 무한 받음: 메시지 누적. condition 으로 처리.
  • Workflow 너무 큼 (월 단위): continueAsNew 로 reset.
  • Activity timeout 없음: hang.

🤖 LLM 활용 힌트

  • Workflow = 순수 결정 / Activity = 부수효과.
  • workflowId = idempotency key.
  • Signal + Query 로 외부 통신.
  • Sleep 이 마법 — 7일 후도 OK.

🔗 관련 문서