f8b21af4be
10_Wiki/Topics 대규모 정리: - 오류 캡처/미완성 stub 문서 227개 제거 - 교차폴더 중복 43클러스터 병합 (63파일 → redirect) - 링크명 정규화: 깨진 링크 수정·redirect 직결·개념 매핑 ~2,400건 - 카테고리 MOC 6개 신규 생성 - Graph 섹션 미해결 related-keyword 링크 10,058건 제거 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4.3 KiB
4.3 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-patterns | Cron / Scheduled Jobs — 분산 / 멱등 / 락 | Coding | draft | B | conceptual | 2026-05-09 | 2026-05-09 |
|
|
|
Cron / Scheduled Jobs
분산 환경 cron = leader 락 + 멱등 안 하면 N대 서버에 N번 실행. DB row lock / Redis lock / Kubernetes CronJob / 클라우드 매니지드 스케줄러 중 1개. 매 실행 멱등.
📖 핵심 개념
- Single instance: 한 시점에 한 곳만 실행.
- Idempotent: 두 번 실행돼도 결과 동일.
- Catch-up: 시간 지나가면 늦게라도 실행할지 / skip 할지.
- At-least-once: 보통 OK, 단 멱등 보장 필요.
💻 코드 패턴
node-cron / BullMQ repeat
import { Queue } from 'bullmq';
const q = new Queue('reports');
await q.add('daily', {}, {
repeat: { pattern: '0 9 * * *', tz: 'America/New_York' }, // 매일 9시 NY
jobId: 'daily-report', // 같은 jobId = 중복 등록 방지
});
DB-based lock
-- locks 테이블
CREATE TABLE job_locks (
name TEXT PRIMARY KEY,
locked_by TEXT,
locked_at TIMESTAMPTZ,
expires_at TIMESTAMPTZ
);
-- 시도
INSERT INTO job_locks (name, locked_by, locked_at, expires_at)
VALUES ('daily-report', $hostname, NOW(), NOW() + INTERVAL '10 minutes')
ON CONFLICT (name) DO UPDATE
SET locked_by = $hostname, locked_at = NOW(), expires_at = NOW() + INTERVAL '10 minutes'
WHERE job_locks.expires_at < NOW()
RETURNING *;
async function runWithLock(name: string, fn: () => Promise<void>) {
const got = await db.tryLock(name, hostname());
if (!got) return; // 다른 노드가 잡음
try { await fn(); } finally { await db.releaseLock(name); }
}
Redis-based lock (Redlock)
import Redlock from 'redlock';
const lock = await redlock.acquire(['locks:daily-report'], 60_000);
try { await runDaily(); } finally { await lock.release(); }
Idempotency
async function dailyReport(date: string) {
const id = `report:${date}`;
if (await db.exists(id)) return; // 이미 만들어짐
const r = await build(date);
await db.put(id, r);
}
Catch-up
// 마지막 실행 시간 저장 → 차이만큼 반복
const last = await db.get('cursor:reports');
for (let d = nextDay(last); d <= today(); d = nextDay(d)) {
await dailyReport(d);
await db.put('cursor:reports', d);
}
Kubernetes CronJob
apiVersion: batch/v1
kind: CronJob
metadata:
name: daily-report
spec:
schedule: "0 9 * * *"
concurrencyPolicy: Forbid # 이전 job 안 끝났으면 새거 skip
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5
startingDeadlineSeconds: 600 # 10분 안에 시작 안되면 skip
jobTemplate:
spec:
backoffLimit: 2
template:
spec:
containers:
- name: report
image: app:latest
args: ["node", "dist/jobs/daily-report.js"]
restartPolicy: OnFailure
Cron expression (UTC vs local)
0 9 * * * # 매일 9:00 UTC (서버 타임존이 UTC면)
0 9 * * 1-5 # 평일 9시
*/15 * * * * # 15분마다
0 0 1 * * # 매월 1일
🤔 의사결정 기준
| 환경 | 추천 |
|---|---|
| Vercel / Cloudflare | Vercel Cron / CF Cron |
| AWS | EventBridge → Lambda |
| K8s | CronJob + concurrencyPolicy: Forbid |
| 단일 Node | node-cron + DB lock |
| 큐 기반 | BullMQ repeat / Sidekiq |
| 정확한 시간 보장 X | drift 감수 또는 클라우드 |
❌ 안티패턴
- N대 서버에 cron 동시 실행: 단일 락 또는 leader 만.
- Idempotency 미보장: 재실행 시 데이터 망가짐.
- 타임존 confusion: UTC 명시 또는 cron expression 에 tz.
- Long-running job 크론에서: 다음 실행 충돌. 큐로 보내기.
- Catch-up 무한: 24시간 정지 후 돌아오면 1440번 실행.
- Job 결과 남기지 않음: 실패 추적 불가.
- At-most-once 가정: 분산 = 항상 at-least-once.
🤖 LLM 활용 힌트
- 항상 멱등 + 락 + 결과 기록.
- K8s CronJob = concurrencyPolicy: Forbid.
- 시간 = UTC 또는 명시 tz.