소스 정보
- 저장소
- Dev-Toolbelt/dev-team-agents
- 최근 소스 활동
- 2026년 7월 31일 16:08
- 감지된 SKILL.md 언어
- 영어
- 스타
- 4
- 포크
- 0
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
메뉴
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/Dev-Toolbelt/dev-team-agents --skill async-jobs명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SOC 직업 분류 기준
SKILL.md 표시 중
| name | async-jobs |
| description | Async jobs — idempotency, retry strategy, DLQ, observability. |
queue, worker, job, or consumer directories or files in the projectlaravel/horizon, sidekiq, celery, bullmq, bee-queue, faktory, resque, delayed_jobaws-sdk with SQS usage, @google-cloud/pubsub, Azure Service Bus, RabbitMQ, KafkaQUEUE_* / REDIS_QUEUE_* / SQS_* env varsapp/Console/Kernel.php, celerybeat, node-cron, whenever gemUse a background job when:
Keep it synchronous when:
Every job must be safe to run more than once with the same payload.
Queues guarantee at-least-once delivery — a job will be retried on failure and may execute multiple times. A non-idempotent job causes duplicate records, double charges, or double notifications.
# ❌ Non-idempotent — creates a duplicate on retry
def send_welcome_email(user_id: str):
user = User.find(user_id)
Email.send(to=user.email, template="welcome")
# ✅ Idempotent — checks state before acting
def send_welcome_email(user_id: str):
user = User.find(user_id)
if user.welcome_email_sent_at is not None:
return # already sent — safe to skip
Email.send(to=user.email, template="welcome")
user.update(welcome_email_sent_at=now())
Idempotency patterns:
(job_type, entity_id) to prevent duplicate executionTreat job payloads with the same rigor as HTTP input — validate before processing:
// ✅ Validate payload shape before using it
async handle(payload: unknown) {
const { orderId, userId } = validateOrderPayload(payload); // throws on invalid
const order = await Order.findOrFail(orderId);
// ...
}
Rules:
| Failure type | Action |
|---|---|
| Transient (network timeout, 503) | Retry with exponential backoff |
| Business rule failure (record not found yet) | Retry with delay |
| Validation failure (malformed payload) | Fail permanently — do not retry |
| Unexpected exception | Retry up to max attempts, then DLQ |
Exponential backoff formula: base_delay * 2^attempt with jitter — prevents thundering herd when many jobs fail simultaneously.
Minimum retry baseline — unless the project defines otherwise: 3 attempts with exponential backoff starting at 2 s (2s → 4s → 8s), then DLQ.
Configure per-job retry limits (not global defaults) — a payment job and an analytics job have different tolerance for retries. A payment job warrants fewer attempts and faster DLQ escalation than an analytics event.
Every queue must have a DLQ configured. Jobs that exhaust retries move there instead of being silently dropped.
After moving to DLQ:
Never delete DLQ messages without inspecting them — they contain the data of failed operations that may need manual resolution.
// TypeScript / BullMQ example — same principles apply in any framework
class ProcessOrderJob {
static queue = 'orders';
static attempts = 3;
static backoff = { type: 'exponential', delay: 2000 };
async handle(payload: ProcessOrderPayload): Promise<void> {
// 1. Validate payload
const { orderId } = validateProcessOrderPayload(payload);
// 2. Idempotency check
const order = await Order.findOrFail(orderId);
if (order.status !== OrderStatus.PENDING) return;
// 3. Execute in a transaction if multiple writes are involved
await db.transaction(async (trx) => {
await order.process({ trx });
await Inventory.reserve(order.items, { trx });
});
// 4. Trigger downstream jobs (don't chain synchronously)
await SendOrderConfirmationJob.dispatch({ orderId });
}
}
# ✅ Structured log with context
logger.info("order.processed", extra={
"job_id": self.request.id,
"order_id": order_id,
"duration_ms": elapsed,
})
| Framework | Key consideration |
|---|---|
| Laravel Queue | Use ShouldBeUnique for deduplication; ShouldBeIdempotent pattern via uniqueId(); Horizon for monitoring |
| Sidekiq / ActiveJob | Use sidekiq-unique-jobs for deduplication; set retry and dead thresholds per worker class |
| Celery | Set acks_late=True for at-least-once; use task_id for idempotency; Flower for monitoring |
| BullMQ | Use jobId option for deduplication; configure removeOnComplete to avoid memory bloat |
| AWS SQS | Visibility timeout must exceed max job duration; use MessageDeduplicationId on FIFO queues |
| RabbitMQ | Set acks_late / manual ack — never auto-ack before processing completes; configure a dead-letter exchange (x-dead-letter-exchange) per queue; use x-message-ttl to cap retry window; prefer quorum queues over classic for durability |
| Apache Kafka | Idempotency is consumer-side — commit offset only after successful processing (enable.auto.commit=false); use a unique group.id per consumer app; store processed offsets or business keys to detect replays; DLQ = a dedicated topic (e.g. topic.DLT); monitor consumer lag as the primary health signal |