
# Zarządzanie zasobami

Queuety dzieli kontrolę zasobów na cztery podstawowe warstwy, a opcjonalnie także na dobór rozmiaru puli.

Pierwsza warstwa określa koszt jednej jednostki pracy. Druga ogranicza poziom współdzielonego obciążenia, jaki może jednocześnie obsługiwać kolejka lub grupa dostawców. Trzecia określa, ile może zużyć pojedyncze uruchomienie przepływu pracy. Czwarta decyduje, czy proces roboczy powinien od razu rozpocząć kolejne pobrane zadanie, czy odłożyć je do czasu, gdy bieżący proces i kontener będą mieć wystarczający zapas zasobów.

Warstwy te współdziałają ze sobą. Moduł obsługi może zadeklarować, że należy do wspólnej puli kosztownych zadań, kolejka może zarezerwować tylko określoną ilość ważonej pracy dla jednego dostawcy, przepływ pracy może ograniczyć ilość takiej pracy, którą utworzy, a proces roboczy nadal może odmówić jej rozpoczęcia, jeśli bieżący proces lub kontener zbliża się już do swoich limitów.

## Określanie kosztu jednostki pracy

Zadania, kroki przepływu pracy i akcje wejścia w stan mogą udostępniać te same trzy wskazówki dotyczące zasobów:

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

`concurrency_group` i `concurrency_limit` opisują współdzielone obciążenie jednego systemu zewnętrznego. Jeśli wiele modułów obsługi korzysta z tego samego dostawcy, konta API lub dzierżawcy, mogą użyć tej samej grupy, aby procesy robocze nie uruchamiały zbyt wielu zadań naraz.

`cost_units` określa względną wagę. Odświeżenie pamięci podręcznej i duże żądanie do dostawcy mogą być „jednym zadaniem”, ale nie powinny mieć jednakowego udziału w decyzji, czy budżet przepływu pracy lub reguła dopuszczania zadań przez proces roboczy pozwala bezpiecznie rozpocząć kolejne zadanie.

```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,
        ];
    }
}
```

Ta sama zasada dotyczy zadań, które można przekazywać, za pośrednictwem właściwości klas, a także pojedynczych przekazań za pośrednictwem `PendingJob::concurrency_group()` i `PendingJob::cost_units()`.

## Budżetowanie współdzielonych kolejek i grup dostawców

Wspólne obciążenie nie zawsze sprowadza się do prostej liczby współbieżnych zadań. Czasami koszt wynika z łącznej wagi zadań, które już są uruchomione.

Queuety obsługuje takie przypadki za pomocą ważonych budżetów kolejek i grup. Budżety kolejek są przypisane do nazwy kolejki. Budżety grup są przypisane do `concurrency_group`, dzięki czemu niezależne moduły obsługi mogą korzystać ze wspólnego limitu.

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

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

Gdy proces roboczy pobiera zadanie, sprawdza aktywne wiersze `processing`, które już należą do danej kolejki lub grupy. Jeśli bieżący koszt wraz z kosztem `cost_units` pobieranego zadania przekroczyłby skonfigurowany budżet, proces roboczy zwalnia zadanie i ponawia próbę później.

Daje to inny rodzaj kontroli niż `concurrency_limit`. Dostawca może dopuścić trzy równoczesne żądania, a jednocześnie przeznaczyć mniej jednostek kosztu na większe żądania niż na mniejsze.

## Utrzymywanie przepływu pracy w wyznaczonych granicach

Budżety przepływu pracy przydają się wtedy, gdy problemem nie jest jedno ciężkie zadanie, lecz uruchomienie, które stale odkrywa więcej pracy, niż planowano.

Często zdarza się to w systemach agentowych. Planista może ponownie odwiedzać tę samą kosztowną gałąź, dzielić ją na zbyt wiele podzadań lub stale uruchamiać podrzędne przepływy pracy, formalnie nie wychodząc poza zwykły model ponawiania prób.

Queuety udostępnia limity na poziomie przepływu pracy, które temu zapobiegają:

```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()` nie pozwala, by uruchomienie zużyło więcej ważonej pracy, niż przewidziano. `max_started_workflows()` zapobiega przekształceniu nadrzędnego przepływu w nieograniczone drzewo podrzędnych uruchomień najwyższego poziomu. Pozostałe zabezpieczenia również są istotne, ponieważ nie każdy wymykający się spod kontroli przepływ pracy generuje taki sam koszt.

Stan przepływu pracy udostępnia skonfigurowane limity i bieżące użycie za pośrednictwem publicznego ładunku `budget`, dzięki czemu przebieg pozostaje możliwy do skontrolowania, gdy te limity obowiązują.

## Pozwól workerom odraczać pracę, gdy proces jest już zbyt obciążony

Nawet gdy limity metadanych i przepływu pracy są dobrze skonfigurowane, ostateczna decyzja nadal należy do procesu workera, który ma wykonać zadanie.

W tym miejscu przydaje się przyjmowanie zadań z uwzględnieniem zasobów. Zanim rozpocznie się przejęte zadanie, Queuety może sprawdzić:

- wspólną grupę współbieżności zadania
- ważone limity kolejki i grup dostawców
- historię niedawnego czasu trwania i szczytowego zużycia pamięci przez obsługujący je handler
- zapas pamięci dostępny bieżącemu workerowi
- zapas pamięci dostępny bieżącemu kontenerowi lub hostowi
- pozostały limit czasu dla przebiegów jednorazowych

Jeśli worker jest już zbyt blisko skonfigurowanego limitu, Queuety zwalnia zadanie zamiast rozpoczynać pracę, która prawdopodobnie nie zakończy się poprawnie.

To rozwiązanie jest celowo zachowawcze. Nie jest pełnym harmonogramem klastra. To warstwa ochronna, która pomaga obciążonemu workerowi uniknąć przeciążenia procesu, w którym działa, a gdy dostępne są dane telemetryczne pamięci — także otaczającego go kontenera lub hosta.

Obsługa kontenera lub hosta jest opcjonalna. Gdy `QUEUETY_RESOURCE_SYSTEM_MEMORY_AWARENESS` jest włączone, Queuety najpierw próbuje odczytać dane pamięci cgroup, a w systemach Linux przechodzi do `/proc/meminfo`. Jeśli nie ma wiarygodnego migawkowego odczytu na poziomie systemu, worker nadal korzysta z kontroli lokalnych dla procesu.

## Skaluj pule workerów z uwzględnieniem dostępnych zasobów

Stałe pule workerów łatwo zrozumieć, ale wymuszają wybór jednej liczby dla okresów zarówno małego obciążenia, jak i jego skoków.

Pule adaptacyjne pozwalają zamiast tego ustawić dolny i górny limit:

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

Proces nadrzędny utrzymuje co najmniej minimalną liczbę aktywnych procesów potomnych, zwiększa ją w kierunku maksimum, gdy rośnie kolejka zadań do przejęcia, a następnie zmniejsza po skonfigurowanym okresie bezczynności kolejki.

Zwiększanie skali nadal uwzględnia dostępną pojemność. Jeśli świadomość pamięci systemowej jest włączona, a proces nadrzędny nie widzi wystarczającego zapasu pamięci na pełny przydział kolejnego workera, pozostawia pulę w obecnym rozmiarze zamiast uruchamiać kolejne procesy potomne.

## Praktyczny przykład połączenia tych warstw

Wyobraź sobie przepływ badawczy, który planuje wywołania dostawców, uruchamia specjalistyczne przepływy, a następnie syntetyzuje wynik.

Krok komunikujący się z dostawcą może opublikować `cost_units` i wspólny `concurrency_group`, aby wiele workerów nie przeciążało tego samego dostawcy modeli. Kolejka może zarezerwować ważony limit dla ruchu do tego dostawcy. Nadrzędny przepływ pracy może ograniczyć `max_cost_units()` i `max_started_workflows()`, aby planer nie rozbudowywał przebiegu w nieskończoność. Worker nadal może odroczyć świeżo przejęte wywołanie dostawcy, jeśli niedawne dane o szczytowym zużyciu pamięci wskazują, że proces lub kontener jest już zbyt blisko limitu.

Daje to cztery odrębne mechanizmy kontroli:

1. określenie wagi pracy
2. rezerwacja wspólnej pojemności kolejki lub dostawcy
3. ograniczenie tego, co może utworzyć jeden przebieg
4. powstrzymanie obciążonego workera przed rozpoczynaniem zbyt wielu zadań

## Gdzie znaleźć szczegółowe API

Ta strona zawiera omówienie koncepcyjne.

Szczegółowe ustawienia znajdziesz tutaj:

- [Workers](https://queuety.inline0.dev/pl/docs/features/workers) opisuje zachowanie podczas przyjmowania zadań i stałe środowiska uruchomieniowego
- [CLI](https://queuety.inline0.dev/pl/docs/cli) opisuje polecenia dotyczące stałych i adaptacyjnych pul workerów
- [Ograniczenia przepływu pracy](https://queuety.inline0.dev/pl/docs/workflows/guardrails) opisuje limity przepływów pracy
- [Jobs](https://queuety.inline0.dev/pl/docs/jobs) opisuje metadane zadań i handlerów
- [Maszyny stanów](https://queuety.inline0.dev/pl/docs/state-machines) opisuje metadane zasobów akcji stanów
- [API](https://queuety.inline0.dev/pl/docs/api) zawiera dokładne metody i kształty zwracanych danych
