
# 워커

워커는 데이터베이스에서 대기 중인 작업을 폴링하고 실행한 뒤 워크플로를 진행하는 장기 실행 프로세스입니다. `wp queuety work` 명령은 WP-CLI 내에서 실행되며, 대기열 자체는 사용 가능한 최적의 Queuety DB 드라이버를 통해 MySQL에서 직접 확보합니다.

플러그인은 셸에 접근할 수 없는 환경을 위해 기본 분당 1회 WordPress cron 대체 수단도 설치합니다. 이 페이지에서는 처리량이 더 높은 권장 배포 방식인 전용 WP-CLI 워커를 다룹니다.

## 워커 시작

```bash
wp queuety work
```

이 명령은 `default` 대기열에서 워커 하나를 시작합니다. 작업을 확보하고 실행하는 과정을 반복합니다. 대기열이 비어 있으면 다시 폴링하기 전에 `QUEUETY_WORKER_SLEEP`초 동안 대기합니다(기본값: 1).

### 옵션

| 옵션 | 설명 |
|---|---|
| `--queue=<queue>` | 처리할 Queue(s)(우선순위 순으로 쉼표로 구분, 기본값: `default`) |
| `--once` | 작업 하나만 처리하고 종료 |
| `--workers=<n>` | 워커 프로세스 N개 분기(`pcntl` 및 `posix` 확장 필요) |
| `--min-workers=<n>` | 적응형 풀의 최소 워커 수. `--max-workers` 필요 |
| `--max-workers=<n>` | 적응형 풀의 최대 워커 수 |

```bash
# Process a specific queue
wp queuety work --queue=emails

# Process at most one job and exit
wp queuety work --once

# Run 4 concurrent workers
wp queuety work --workers=4

# Scale between 2 and 6 workers as backlog changes
wp queuety work --queue=providers --min-workers=2 --max-workers=6
```

## 여러 대기열의 우선순위

워커 하나로 여러 대기열을 우선순위 순서대로 처리할 수 있습니다. 쉼표로 구분한 대기열 이름 목록을 `--queue`에 전달하세요.

```bash
wp queuety work --queue=critical,default,low
```

워커는 나열된 순서대로 각 대기열에서 작업을 확보하려고 합니다. 먼저 `critical`을 확인합니다. `critical`에 작업이 있으면 해당 작업을 처리합니다. `critical`가 비어 있으면 `default`로 넘어간 다음 `low`로 이동합니다. 따라서 우선순위가 높은 대기열은 항상 낮은 대기열보다 먼저 비워집니다.

### 우선순위 정렬 방식

각 반복에서 워커는 목록의 첫 번째 대기열에서 작업을 확보하려고 합니다. 해당 대기열이 비었거나 일시 중지된 경우 다음 대기열로 넘어가 작업을 찾거나 목록을 모두 확인할 때까지 계속합니다. 어디에서도 작업을 찾지 못하면 워커는 대기한 뒤 다시 시도합니다. 이는 엄격한 우선순위 모델이므로 `critical` 작업이 계속 유입되면 `default` 및 `low` 작업이 처리되지 못할 수 있습니다. 우선순위가 낮은 대기열에도 처리량을 보장해야 한다면 전용 워커를 사용하세요.

### PHP에서 여러 대기열 사용

`Worker::run()` 및 `Worker::flush()` 메서드는 문자열이나 배열을 받습니다.

```php
// Comma-separated string
Queuety::worker()->run( 'critical,default,low' );

// Array
Queuety::worker()->run( [ 'critical', 'default', 'low' ] );

// flush() also supports multi-queue
$count = Queuety::worker()->flush( 'critical,default,low' );
```

### 배포 전략

**대기열별 전용 워커**를 사용하면 동시성과 격리를 완전히 제어할 수 있습니다. 각 대기열은 자체 워커 process(es)를 사용하므로 다른 대기열 때문에 처리가 막히지 않습니다.

```ini
[program:queuety-critical]
command=wp queuety work --queue=critical
numprocs=4

[program:queuety-default]
command=wp queuety work --queue=default
numprocs=2

[program:queuety-low]
command=wp queuety work --queue=low
numprocs=1
```

**워커 하나로 우선순위 정렬**을 사용하면 관리가 간단하며 우선순위가 낮은 대기열의 지연을 감수할 수 있는 경우 효과적입니다.

```ini
[program:queuety-worker]
command=wp queuety work --queue=critical,default,low
numprocs=2
```

두 방식을 함께 사용할 수도 있습니다. 처리량을 보장할 `critical`에는 전용 워커를 사용하고, 나머지는 우선순위에 따라 워커 하나로 처리합니다.

```ini
[program:queuety-critical]
command=wp queuety work --queue=critical
numprocs=2

[program:queuety-fallback]
command=wp queuety work --queue=default,low
numprocs=2
```

## 리소스 인식형 작업 허용

워커는 다음 대기 작업을 확보하는 데 그치지 않습니다. 확보한 작업이 시작되기 전에 Queuety은 작업의 동시성 그룹과 해당 핸들러의 최근 실행 프로필을 바탕으로 대략적인 허용 여부를 확인할 수 있습니다.

작업 메타데이터 및 워크플로 예산과의 연계를 포함한 전체 모델은 [리소스 관리](https://queuety.inline0.dev/ko/docs/features/resource-management)를 참조하세요.

동시성 그룹은 여러 핸들러나 작업 클래스에 공통 상한을 적용합니다. 비용이 큰 여러 제공자에 같은 그룹과 한도를 지정하면, 그룹이 한도에 도달했을 때 워커는 새로 확보한 작업을 시작하지 않고 반환합니다. 따라서 유휴 워커가 여러 개 있더라도 서로 독립적인 대기열이 같은 다운스트림 시스템에 한꺼번에 몰리는 일을 막습니다.

최근 실행 로그는 간단한 허용 프로필에도 반영됩니다. 리소스 허용이 활성화되면 Queuety은 실행하려는 핸들러의 최근 평균 실행 시간과 최대 메모리를 확인합니다. 현재 워커가 설정된 메모리 한도에 가까워졌거나, 대기열 또는 제공자 그룹이 이미 가중 예산에 도달했거나, 현재 컨테이너에 해당 핸들러의 관측 프로필을 감당할 여유 메모리가 부족해 보이거나, `--once` 방식 실행의 시간 한도에 가까워진 경우 Queuety은 깔끔하게 끝날 가능성이 낮은 작업을 시작하는 대신 나중으로 미룹니다.

이 정책은 워커 명령이 아니라 작업 정의에서 활성화합니다.

```php
class CallProviderStep implements \Queuety\Step {
    public function handle( array $state ): array {
        // ...
    }

    public function config(): array {
        return [
            'concurrency_group' => 'providers',
            'concurrency_limit' => 3,
            'cost_units' => 5,
        ];
    }
}
```

같은 메타데이터는 공개 속성이 있는 디스패치 가능한 작업 클래스나 `PendingJob::concurrency_group()` 및 `PendingJob::cost_units()`을 통한 개별 디스패치에도 연결할 수 있습니다.

워커 측 허용 프로필은 다음 상수로 제어합니다.

| 상수 | 설명 |
|---|---|
| `QUEUETY_RESOURCE_ADMISSION` | 프로필 기반 허용 검사 활성화 또는 비활성화 |
| `QUEUETY_RESOURCE_PROFILE_WINDOW_MINUTES` | 최근 핸들러 프로필을 작성할 때 조회할 기간 |
| `QUEUETY_RESOURCE_PROFILE_TTL` | 최근 핸들러 프로필의 캐시 TTL |
| `QUEUETY_RESOURCE_MEMORY_HEADROOM_MB` | 다른 작업을 허용하기 전에 확보할 메모리 여유분 |
| `QUEUETY_RESOURCE_SYSTEM_MEMORY_AWARENESS` | 최선 노력 방식의 컨테이너 또는 호스트 메모리 검사 활성화 또는 비활성화 |
| `QUEUETY_RESOURCE_SYSTEM_MEMORY_HEADROOM_MB` | 다른 작업을 허용하기 전에 확보할 시스템 메모리 여유분 |
| `QUEUETY_RESOURCE_QUEUE_COST_BUDGETS` | 대기열 이름별 가중 비용 예산 |
| `QUEUETY_RESOURCE_GROUP_COST_BUDGETS` | `concurrency_group`별 가중 비용 예산 |
| `QUEUETY_RESOURCE_TIME_HEADROOM_MS` | 일회성 워커 실행을 위해 확보할 시간 여유분 |

## 워커 수명 주기

현재 작업을 마친 뒤 워커는 다음 조건 중 하나라도 충족되면 정상적으로 중지됩니다.

| 조건 | 설정 |
|---|---|
| 처리한 최대 작업 수 | `QUEUETY_WORKER_MAX_JOBS`(기본값: 1000) |
| 최대 메모리 초과 | `QUEUETY_WORKER_MAX_MEMORY`(기본값: 128 MB) |

중지 후 워커는 정상적으로 종료됩니다. 프로덕션에서는 프로세스 관리자가 자동으로 다시 시작합니다.

아직 시그널은 정상적으로 처리되지 않습니다([#4](https://github.com/inline0/queuety/issues/4)). 단일 `wp queuety work` 프로세스는 시그널 핸들러를 설치하지 않으므로 작업 도중에도 `SIGTERM` 또는 `SIGINT`(Ctrl+C)가 프로세스를 즉시 종료합니다. 해당 작업은 `processing` 상태로 남아 있다가 `QUEUETY_STALE_TIMEOUT`초 후 오래된 작업 복구가 초기화하므로 핸들러는 재시도에 안전하게 작성하세요. 워커 풀은 `SIGTERM`을 자식 프로세스로 전달하지만 자식은 이에 반응하지 않으므로, 위 사유 중 하나로 중지될 때까지 풀이 계속 실행됩니다. 풀을 중지해야 할 때는 프로세스 관리자에 중지 시간 제한을 지정하고, 제한 후 `SIGKILL`을 보내도록 하세요.

## 여러 워커의 동시 실행

`--workers=N` 플래그는 `pcntl_fork()`을 사용해 자식 프로세스 N개를 분기합니다. 각 자식은 자체 데이터베이스 연결을 만들고 독립적으로 작업을 처리합니다. 부모 프로세스는 자식을 모니터링하고 비정상 종료된 자식을 다시 시작합니다.

```bash
wp queuety work --queue=default --workers=4
```

요구 사항:
`pcntl` 및 `posix` PHP 확장이 설치되어 있어야 합니다(`pcntl`이 없으면 명령이 "pcntl extension is required for --workers=N." 오류와 함께 실패합니다). 각 자식은 자체 데이터베이스 연결을 사용하므로 MySQL `max_connections` 설정은 풀의 연결 수를 감당해야 합니다. 적응형 풀(`--min-workers`/`--max-workers`)에도 같은 요구 사항이 적용됩니다.

## 적응형 워커 풀

적응형 풀은 최소 워커 수를 상시 유지한 뒤 설정된 범위 안에서 워커 수를 늘리거나 줄입니다.

```bash
wp queuety work --queue=providers --min-workers=2 --max-workers=6
```

부모 프로세스는 선택한 대기열 집합에서 처리 가능한 대기 작업을 확인하고, 설정된 최대치를 넘지 않는 범위에서 자식 수를 대기 작업량에 맞춥니다. 대기열이 한산해지면 짧은 작업량 급증에 따라 프로세스 목록이 바로 변동하지 않도록 유휴 유예 시간이 지난 뒤 풀 규모를 줄입니다.

확장 규모는 보수적으로 조정됩니다. Queuety이 컨테이너 또는 호스트 메모리 정보를 확인할 수 있으면, 새 워커 하나를 온전히 실행할 메모리가 부족할 때 자식을 더 추가하지 않습니다.

부모 프로세스의 확장 루프는 다음 항목으로 제어합니다.

| 상수 | 설명 |
|---|---|
| `QUEUETY_WORKER_POOL_SCALE_INTERVAL` | 부모 프로세스가 풀 크기를 조정하는 간격 |
| `QUEUETY_WORKER_POOL_IDLE_GRACE` | 풀 규모를 줄이기 전에 대기 작업이 없어야 하는 시간 |

## 오래된 작업 복구

작업을 처리하던 워커가 비정상 종료되면 작업은 `processing` 상태로 남습니다. 장기 실행 워커는 약 1분마다 `processing` 상태가 `QUEUETY_STALE_TIMEOUT`초 넘게 지속된 작업을 확인합니다. 재시도 횟수가 남은 오래된 작업은 대기 시간 후 재시도되고, 횟수를 모두 사용한 작업은 묻히며 워크플로는 실패로 표시됩니다. `--once` 워커와 WP-Cron 대체 수단은 이 검사를 실행하지 않습니다.

오래된 작업을 수동으로 복구합니다.

```bash
wp queuety recover
```

## 비우기 모드

대기 중인 작업을 모두 처리하고 종료합니다. 테스트나 일회성 일괄 처리에 유용합니다.

```bash
wp queuety flush
wp queuety flush --queue=emails
```

## 프로덕션 배포

프로덕션에서는 워커를 계속 실행 상태로 유지하도록 프로세스 관리자를 사용하세요. 일반적인 방법 두 가지를 소개합니다.

### Supervisord

```ini
[program:queuety-default]
command=wp queuety work --queue=default
directory=/var/www/html
autostart=true
autorestart=true
numprocs=2
process_name=%(program_name)s_%(process_num)02d
stdout_logfile=/var/log/queuety-default.log
stderr_logfile=/var/log/queuety-default-error.log

[program:queuety-emails]
command=wp queuety work --queue=emails
directory=/var/www/html
autostart=true
autorestart=true
numprocs=1
stdout_logfile=/var/log/queuety-emails.log
stderr_logfile=/var/log/queuety-emails-error.log
```

### Systemd

```ini
[Unit]
Description=Queuety Worker (default queue)
After=mysql.service

[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/html
ExecStart=/usr/local/bin/wp queuety work --queue=default
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
```

활성화하고 시작합니다.

```bash
sudo systemctl enable queuety-worker
sudo systemctl start queuety-worker
```

### 배포 팁

격리를 강화해야 한다면 대기열별로 워커를 따로 실행하세요. CPU 집약적인 작업에는 `--workers=N`을 사용하고, I/O가 대부분인 작업에는 워커를 개별적으로 감독하는 방식을 권장합니다. 메모리 확보를 위해 주기적으로 다시 시작하려면 `QUEUETY_WORKER_MAX_JOBS`을 설정하고, `buried` 수는 [웹훅](https://queuety.inline0.dev/ko/docs/features/webhooks) 또는 [메트릭](https://queuety.inline0.dev/ko/docs/features/metrics)을 통해 확인하세요.
