
# Ressourcenverwaltung

Queuety gliedert die Ressourcensteuerung in vier Kernschichten und eine optionale Dimensionierung der Pools.

Die erste Schicht beschreibt, wie aufwendig eine Arbeitseinheit ist. Die zweite begrenzt, wie viel gemeinsame Last eine Warteschlange oder Anbietergruppe gleichzeitig tragen darf. Die dritte legt fest, wie viele Ressourcen ein einzelner Workflow-Lauf verbrauchen darf. Die vierte entscheidet, ob ein Worker den nächsten übernommenen Job sofort startet oder ihn zurückstellt, bis im aktuellen Prozess und Container noch genügend Kapazität verfügbar ist.

Diese Schichten greifen ineinander. Ein Handler kann angeben, dass er zu einem gemeinsamen Pool mit hohem Ressourcenbedarf gehört. Eine Warteschlange kann für einen Anbieter nur eine begrenzte gewichtete Arbeitslast zulassen. Ein Workflow kann begrenzen, wie viel solcher Arbeit er erzeugt. Und der Worker kann weiterhin den Start weiterer solcher Arbeit ablehnen, wenn der aktuelle Prozess oder Container bereits nahe an seinen Grenzwerten liegt.

## Kosten einer Arbeitseinheit beschreiben

Jobs, Workflow-Schritte und Aktionen beim Eintritt in einen Zustand können dieselben drei Ressourcenhinweise bereitstellen:

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

`concurrency_group` und `concurrency_limit` beschreiben die gemeinsame Belastung eines nachgelagerten Systems. Wenn mehrere Handler denselben Anbieter, dasselbe API-Konto oder denselben Mandanten nutzen, können sie dieselbe Gruppe verwenden, damit Worker nicht zu viele Vorgänge gleichzeitig starten.

`cost_units` beschreibt das relative Gewicht. Eine Cache-Aktualisierung und ein umfangreicher Anbieteraufruf können beide „ein Job“ sein, sollten aber nicht gleich gewichtet werden, wenn ein Workflow-Budget oder eine Worker-Zulassungsregel entscheidet, ob weitere Arbeit noch vertretbar ist.

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

Dasselbe gilt für dispatchbare Jobs über Klasseneigenschaften und für einzelne Dispatches über `PendingJob::concurrency_group()` und `PendingJob::cost_units()`.

## Gemeinsame Warteschlangen und Anbietergruppen budgetieren

Gemeinsame Last lässt sich nicht immer durch eine einfache Anzahl gleichzeitiger Vorgänge steuern. Manchmal ist die kombinierte Gewichtung der bereits laufenden Vorgänge entscheidend.

Queuety unterstützt dies mit gewichteten Budgets für Warteschlangen und Gruppen. Budgets für Warteschlangen werden anhand des Warteschlangennamens zugeordnet. Gruppenbudgets richten sich nach `concurrency_group`, sodass voneinander unabhängige Handler denselben gemeinsamen Rahmen nutzen können.

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

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

Wenn ein Worker einen Job übernimmt, prüft er die aktiven `processing`-Einträge, die bereits zu dieser Warteschlange oder Gruppe gehören. Würden die Kosten der laufenden Arbeit zusammen mit `cost_units` des übernommenen Jobs das festgelegte Budget überschreiten, gibt der Worker den Job frei und versucht es später erneut.

Das bietet dir eine andere Art der Steuerung als `concurrency_limit`. Ein Anbieter kann drei gleichzeitige Anfragen zulassen und trotzdem für größere Anfragen weniger Kosteneinheiten reservieren als für kleinere.

## Einen Workflow innerhalb eines Rahmens halten

Workflow-Budgets sind für Fälle gedacht, in denen nicht ein einzelner aufwendiger Job das Problem ist, sondern ein Lauf fortwährend mehr Arbeit entdeckt als vorgesehen.

Das kommt häufig in agentischen Systemen vor. Ein Planer kann denselben aufwendigen Zweig erneut durchlaufen, zu viele Teilaufgaben erzeugen oder immer wieder untergeordnete Workflows starten und dabei trotzdem innerhalb eines gewöhnlichen Wiederholungsmodells bleiben.

Queuety bietet dafür Limits auf Workflow-Ebene:

```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()` verhindert, dass der Lauf mehr gewichtete Arbeit verbraucht als vorgesehen. `max_started_workflows()` verhindert, dass ein übergeordneter Workflow zu einem unbegrenzten Baum aus untergeordneten Top-Level-Läufen wird. Die übrigen Schutzmechanismen bleiben wichtig, da nicht jeder außer Kontrolle geratene Workflow auf dieselbe Weise ressourcenintensiv ist.

Der Workflow-Status enthält die festgelegten Limits und die aktuelle Nutzung in der öffentlichen Nutzlast `budget`. So bleibt der Lauf während der aktiven Limits überprüfbar.

## Worker Arbeit zurückstellen lassen, wenn der Prozess bereits ausgelastet ist

Auch wenn Metadaten und Workflow-Budgets gut eingerichtet sind, liegt die letzte Entscheidung beim Worker-Prozess, der den Job ausführen soll.

Hier kommt die ressourcenbewusste Zulassung ins Spiel. Bevor ein übernommener Job startet, kann Queuety Folgendes prüfen:

- die gemeinsame Nebenläufigkeitsgruppe des Jobs
- gewichtete Budgets für Warteschlangen und Anbietergruppen
- bisherige Laufzeiten und Spitzenwerte des Speicherverbrauchs des Handlers
- den verbleibenden Speicherpuffer des aktuellen Workers
- den verbleibenden Speicherpuffer des aktuellen Containers oder Hosts
- den verbleibenden Zeitrahmen für einmalige Läufe

Ist der Worker bereits nahe an seinem festgelegten Grenzwert, gibt Queuety den Job frei, statt eine Arbeit zu starten, die wahrscheinlich nicht sauber abgeschlossen wird.

Das System ist bewusst konservativ ausgelegt. Es ist kein vollständiger Cluster-Scheduler, sondern eine Schutzschicht, die einem ausgelasteten Worker hilft, den Prozess, in dem er läuft, nicht zu überlasten. Wenn Speichermesswerte verfügbar sind, schützt sie auch den Container oder Host.

Die Prüfung des Containers oder Hosts ist optional. Wenn `QUEUETY_RESOURCE_SYSTEM_MEMORY_AWARENESS` aktiviert ist, versucht Queuety zunächst, cgroup-Speicherdaten auszulesen, und greift auf `/proc/meminfo` auf Linux-Hosts zurück. Ist keine verlässliche Momentaufnahme auf Systemebene verfügbar, nutzt der Worker weiterhin die prozessinternen Prüfungen.

## Worker-Pools skalieren, ohne die Kapazität aus den Augen zu verlieren

Feste Worker-Pools sind leicht zu verstehen, zwingen dich aber dazu, für ruhige Zeiten und Spitzen denselben Wert festzulegen.

Bei adaptiven Pools kannst du stattdessen eine Unter- und Obergrenze festlegen:

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

Der übergeordnete Prozess hält mindestens die Mindestanzahl an aktiven Kindprozessen vor, skaliert bei wachsendem abarbeitbarem Rückstand bis zum Maximum hoch und reduziert die Anzahl wieder, wenn die Warteschlange während des festgelegten Leerlaufzeitraums ruhig war.

Beim Hochskalieren wird die verfügbare Kapazität weiterhin berücksichtigt. Ist das Bewusstsein für den Systemspeicher aktiviert und reicht der verbleibende Speicher laut übergeordnetem Prozess nicht für einen weiteren vollständigen Worker-Rahmen, bleibt der Pool auf seiner aktuellen Größe, statt weitere Kindprozesse zu starten.

## Die Schichten sinnvoll kombinieren

Stell dir einen Recherche-Workflow vor, der Aufrufe bei Anbietern plant, spezialisierte Workflows startet und anschließend das Ergebnis zusammenfasst.

Der Anbieterschritt kann `cost_units` und eine gemeinsame `concurrency_group` bereitstellen, damit mehrere Worker nicht gleichzeitig denselben Modellanbieter überlasten. Die Warteschlange kann ein gewichtetes Budget für diesen Anbieterverkehr reservieren. Der übergeordnete Workflow kann `max_cost_units()` und `max_started_workflows()` begrenzen, damit ein Planer den Lauf nicht endlos wachsen lässt. Der Worker kann einen neu übernommenen Anbieteraufruf weiterhin zurückstellen, wenn bisherige Spitzenwerte beim Speicherverbrauch zeigen, dass der Prozess oder Container bereits nahe an seinem Grenzwert liegt.

Damit erhältst du vier getrennte Steuerungsmöglichkeiten:

1. das Gewicht der Arbeit beschreiben
2. gemeinsame Kapazitäten für Warteschlangen oder Anbieter reservieren
3. begrenzen, was ein einzelner Lauf erzeugen darf
4. verhindern, dass ein ausgelasteter Worker mehr startet als vorgesehen

## Wo du die detaillierten APIs findest

Diese Seite bietet einen konzeptionellen Überblick.

Die konkreten Einstellungen findest du hier:

- [Workers](https://queuety.inline0.dev/de/docs/features/workers) behandelt das Zulassungsverhalten und Laufzeitkonstanten
- [CLI](https://queuety.inline0.dev/de/docs/cli) behandelt Befehle für feste und adaptive Worker-Pools
- [Workflow Guardrails](https://queuety.inline0.dev/de/docs/workflows/guardrails) behandelt Workflow-Budgets
- [Jobs](https://queuety.inline0.dev/de/docs/jobs) behandelt Job- und Handler-Metadaten
- [State Machines](https://queuety.inline0.dev/de/docs/state-machines) behandelt Metadaten zu State-Action-Ressourcen
- [API](https://queuety.inline0.dev/de/docs/api) listet die genauen Methoden und Rückgabeformen auf
