
# 리소스 관리

Queuety는 리소스 제어를 네 가지 핵심 계층과 선택적인 풀 크기 조정 계층으로 나눕니다.

첫 번째 계층은 작업 단위 하나의 비용을 설명합니다. 두 번째 계층은 하나의 대기열이나 공급자 그룹이 한 번에 감당할 수 있는 공유 부하를 제한합니다. 세 번째 계층은 워크플로 한 번의 실행에서 소비할 수 있는 양을 정의합니다. 네 번째 계층은 작업자가 다음으로 가져온 작업을 지금 시작할지, 현재 프로세스와 컨테이너에 충분한 여유가 생길 때까지 미룰지 결정합니다.

이 계층들은 함께 작동합니다. 핸들러는 고비용 공유 풀에 속한다고 지정할 수 있고, 대기열은 한 공급자에 대해 가중 작업량을 일정 수준까지만 예약할 수 있으며, 워크플로는 생성하려는 해당 작업량을 제한할 수 있습니다. 또한 현재 프로세스나 컨테이너의 한계가 가까우면 작업자는 해당 작업을 더 시작하지 않을 수 있습니다.

## 작업 단위 하나의 비용 설명

작업, 워크플로 단계 및 상태 진입 동작은 모두 동일한 세 가지 리소스 힌트를 게시할 수 있습니다.

- `concurrency_group`
- `concurrency_limit`
- `cost_units`

`concurrency_group` 및 `concurrency_limit`은 하나의 다운스트림 시스템에 가해지는 공유 부하를 설명합니다. 여러 핸들러가 동일한 공급자, API 계정 또는 테넌트를 사용하는 경우 같은 그룹을 지정하면 작업자가 한 번에 너무 많은 작업을 시작하지 않도록 할 수 있습니다.

`cost_units`은 상대적 가중치를 설명합니다. 캐시 새로 고침과 대규모 공급자 호출이 모두 “작업 하나”일 수 있지만, 워크플로 예산이나 작업자 승인 규칙이 추가 작업의 안전성을 판단할 때 둘을 동일하게 계산해서는 안 됩니다.

```php
final 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는 가중 대기열 및 그룹 예산으로 이를 지원합니다. 대기열 예산은 대기열 이름을 기준으로 설정됩니다. 그룹 예산은 `concurrency_group`를 기준으로 설정되므로 서로 무관한 핸들러도 동일한 공유 한도에 참여할 수 있습니다.

```php
define( 'QUEUETY_RESOURCE_QUEUE_COST_BUDGETS', [
    'providers' => 20,
    'exports' => 8,
] );

define( 'QUEUETY_RESOURCE_GROUP_COST_BUDGETS', [
    'openai' => 12,
    'tenant:acme' => 6,
] );
```

작업자가 작업을 가져오면 해당 대기열 또는 그룹에 이미 속한 활성 `processing` 행을 확인합니다. 현재 실행 중인 비용과 가져온 작업의 `cost_units`를 합한 값이 설정된 예산을 초과하면 작업을 놓아주고 나중에 다시 시도합니다.

이는 `concurrency_limit`와 다른 제어 방식입니다. 공급자는 동시 요청을 3개까지 허용하면서도 작은 요청보다 큰 요청에 더 적은 총 비용 단위를 할당할 수 있습니다.

## 단일 워크플로를 한도 내에서 유지

워크플로 예산은 무거운 작업 하나가 문제가 아니라 실행 중 예상보다 많은 작업이 계속 발견되는 경우를 위한 기능입니다.

이는 에이전트 시스템에서 흔히 발생합니다. 플래너가 같은 고비용 분기를 다시 살펴보거나 너무 많은 하위 작업으로 분기하거나 일반적인 재시도 모델의 범위 안에 머물면서 자식 워크플로를 계속 시작할 수 있습니다.

Queuety는 이를 위한 워크플로 수준 제한을 제공합니다.

```php
Queuety::workflow( 'research_run' )
    ->max_transitions( 20 )
    ->max_for_each_items( 12 )
    ->max_state_bytes( 32768 )
    ->max_cost_units( 40 )
    ->max_started_workflows( 8 )
    ->then( PlanResearchStep::class )
    ->for_each( 'tasks', ExecuteResearchTask::class, 'results' )
    ->dispatch();
```

`max_cost_units()`는 실행이 의도한 것보다 많은 가중 작업을 소비하지 않도록 합니다. `max_started_workflows()`은 부모 워크플로가 제한 없이 늘어나는 최상위 자식 실행 트리로 확장되지 않도록 막습니다. 모든 폭주 워크플로가 같은 방식으로 비용이 발생하는 것은 아니므로 다른 안전장치도 여전히 중요합니다.

워크플로 상태는 공개 `budget` 페이로드를 통해 설정된 한도와 현재 사용량을 보여 주므로, 해당 한도가 적용되는 동안 실행 상태를 확인할 수 있습니다.

## 프로세스가 이미 과부하 상태라면 작업을 미루도록 워커 허용

메타데이터 및 워크플로 예산을 적절히 설정했더라도, 최종 결정은 작업을 실행하려는 워커 프로세스에 달려 있습니다.

이때 리소스를 고려한 작업 수락 기능이 작동합니다. 작업을 가져온 뒤 시작하기 전에 Queuety은 다음 항목을 확인할 수 있습니다:

- 작업이 공유하는 동시성 그룹
- 가중치가 적용된 대기열 및 제공업체 그룹 예산
- 핸들러의 최근 실행 시간 및 최대 메모리 사용량 기록
- 현재 워커에서 사용할 수 있는 메모리 여유 공간
- 현재 컨테이너 또는 호스트에서 사용할 수 있는 메모리 여유 공간
- 단일 실행 방식의 작업에 허용된 남은 시간

워커가 이미 설정된 한도에 너무 가까우면 Queuety은 정상적으로 완료될 가능성이 낮은 작업을 시작하는 대신 작업을 반환합니다.

이 기능은 의도적으로 보수적으로 설계되었습니다. 전체 클러스터 스케줄러는 아닙니다. 사용 중인 워커가 실행 중인 프로세스의 리소스를 과도하게 할당하지 않도록 돕는 안전 계층이며, 메모리 정보를 확인할 수 있는 경우 주변 컨테이너나 호스트도 고려합니다.

컨테이너 또는 호스트 확인은 선택 사항입니다. `QUEUETY_RESOURCE_SYSTEM_MEMORY_AWARENESS`이 활성화되면 Queuety은 먼저 cgroup 메모리 데이터를 읽고, Linux 호스트에서는 `/proc/meminfo`로 대체해 확인합니다. 신뢰할 수 있는 시스템 수준의 스냅샷을 사용할 수 없어도 워커는 프로세스 수준의 검사를 계속 수행합니다.

## 용량을 고려하면서 워커 풀 확장

고정 워커 풀은 이해하기 쉽지만, 한산한 시간대와 급증하는 시간대에 모두 적용할 워커 수를 하나만 정해야 합니다.

적응형 풀에서는 대신 최소값과 최대값을 설정할 수 있습니다:

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

상위 프로세스는 최소 수의 자식 프로세스를 계속 실행하고, 가져올 수 있는 대기 작업이 늘어나면 최대 수까지 확장하며, 설정된 유휴 대기 시간 동안 대기열이 비어 있으면 다시 축소합니다.

확장할 때도 사용 가능한 용량을 고려합니다. 시스템 메모리 인식 기능이 켜져 있고 상위 프로세스가 새 워커 하나를 온전히 실행할 만큼 메모리가 남지 않았다고 판단하면, 자식 프로세스를 더 시작하지 않고 현재 풀 크기를 유지합니다.

## 각 계층을 실제로 조합하는 방법

제공업체 호출을 계획하고, 전문 워크플로를 시작한 다음 결과를 종합하는 연구 워크플로를 생각해 보세요.

제공업체를 호출하는 단계에서는 `cost_units`과 공유 `concurrency_group`을 게시해 여러 워커가 같은 모델 제공업체에 한꺼번에 요청하지 않도록 할 수 있습니다. 대기열은 해당 제공업체 트래픽에 가중치 예산을 예약할 수 있습니다. 상위 워크플로는 `max_cost_units()`과 `max_started_workflows()`에 한도를 설정해 플래너가 실행을 끝없이 확장하지 못하게 할 수 있습니다. 그런 다음 최근 최대 메모리 사용량 기록을 바탕으로 프로세스나 컨테이너가 이미 한도에 너무 가까운 것으로 판단되면, 워커가 새로 가져온 제공업체 호출 작업을 미룰 수 있습니다.

이렇게 하면 네 가지 제어 수단을 각각 사용할 수 있습니다:

1. 작업의 가중치 지정
2. 대기열 또는 제공업체의 공유 용량 예약
3. 한 번의 실행에서 생성할 수 있는 항목의 수 제한
4. 사용 중인 워커가 지나치게 많은 작업을 시작하지 않도록 제어

## 자세한 API 안내

이 페이지에서는 개념을 개괄적으로 설명합니다.

구체적인 설정을 알아보려면 다음을 참고하세요:

- [워커](https://queuety.inline0.dev/ko/docs/features/workers)에서는 작업 수락 동작과 런타임 상수를 설명합니다.
- [CLI](https://queuety.inline0.dev/ko/docs/cli)에서는 고정 및 적응형 워커 풀 명령을 설명합니다.
- [워크플로 안전장치](https://queuety.inline0.dev/ko/docs/workflows/guardrails)에서는 워크플로 예산을 설명합니다.
- [작업](https://queuety.inline0.dev/ko/docs/jobs)에서는 작업 및 핸들러 메타데이터를 설명합니다.
- [상태 머신](https://queuety.inline0.dev/ko/docs/state-machines)에서는 상태 액션 리소스 메타데이터를 설명합니다.
- [API](https://queuety.inline0.dev/ko/docs/api)에는 정확한 메서드와 반환 형식이 나와 있습니다.
