
# Hjerteslag

Langvarige jobs og streamingtrin kan sende hjerteslag for at vise, at de stadig er aktive. Uden hjerteslag kan registreringen af forældede jobs overtage et job, der stadig er i gang, fordi dets tidsstempel for `reserved_at` ikke er blevet opdateret.

## Sådan fungerer det

Når en worker begynder at behandle et job, kalder den `Heartbeat::init()` for at angive den aktuelle jobkontekst. Inde i din handler opdaterer et kald til `Heartbeat::beat()` jobbets kolonne `reserved_at` til `NOW()`. Registreringen af forældede jobs bruger `reserved_at` til at afgøre, om et job er gået i stå, så en opdatering holder jobbet sikkert.

Når jobbet er færdigt, kalder workeren `Heartbeat::clear()` for at nulstille konteksten.

## Send hjerteslag

Kald `Heartbeat::beat()` inde fra en vilkårlig jobhandler eller et streamingtrin:

```php
use Queuety\Heartbeat;

class LongRunningStep implements \Queuety\Step {

    public function handle( array $state ): array {
        $items = $state['items'];

        foreach ( $items as $i => $item ) {
            $this->process( $item );
            Heartbeat::beat( [ 'processed' => $i + 1, 'total' => count( $items ) ] );
        }

        return [ 'done' => true ];
    }

    public function config(): array {
        return [];
    }

    private function process( $item ): void {
        // Expensive work here.
    }
}
```

## Fremdriftsdata

Parameteren `$progress` er et valgfrit associativt array. Når den angives, kodes den som JSON og gemmes i kolonnen `heartbeat_data` på jobrækken. Det gør det muligt at følge fremdriften uden for workeren:

```php
Heartbeat::beat( [
    'processed' => 450,
    'total'     => 1000,
    'percent'   => 45,
] );
```

Når funktionen kaldes uden argumenter, opdateres kun `reserved_at`:

```php
Heartbeat::beat();
```

## Hjerteslag API

Klassen `Queuety\Heartbeat` består udelukkende af statiske metoder. Du opretter ikke en instans af den.

| Metode | Beskrivelse |
|---|---|
| `Heartbeat::init( int $job_id, Connection $conn )` | Angiv jobkonteksten. Kaldes automatisk af workeren før behandlingen. |
| `Heartbeat::beat( array $progress = [] )` | Send et hjerteslag. Opdaterer `reserved_at` og gemmer eventuelt fremdriftsdata. |
| `Heartbeat::clear()` | Ryd jobkonteksten. Kaldes automatisk af workeren efter behandlingen. |
| `Heartbeat::current_job_id()` | Hent det aktuelle job-ID (nyttigt til test). |

## Brug i streamingtrin

Workeren sender allerede et hjerteslag efter hver chunk, den gemmer, med `heartbeat_data` angivet til `{"streaming_chunks": n}`, så en stream, der jævnligt afgiver data, holder sig opdateret af sig selv. Kald selv `Heartbeat::beat()`, når der kan gå lang tid mellem dataafgivelser, f.eks. mens der ventes på det første token. Data, du sender, overskrives af workerens næste hjerteslag efter en chunk:

```php
use Queuety\Contracts\StreamingStep;
use Queuety\Heartbeat;

class StreamAPIResponse implements StreamingStep {

    public function stream( array $state, array $existing_chunks = [] ): \Generator {
        $response = $this->client->stream( $state['prompt'] );

        foreach ( $response as $i => $chunk ) {
            Heartbeat::beat( [ 'chunks' => $i + 1 ] );
            yield $chunk;
        }
    }

    public function on_complete( array $chunks, array $state ): array {
        return [ 'response' => implode( '', $chunks ) ];
    }

    public function config(): array {
        return [];
    }
}
```

## Hvornår skal hjerteslag bruges?

Brug hjerteslag, når et enkelt job eller trin varer længere end `QUEUETY_STALE_TIMEOUT` sekunder (standard: 600). Almindelige tilfælde:

- LLM-kald API med streamingresponser
- Store fildownloads eller -uploads
- Gentagen batchbehandling i et enkelt trin
- Ethvert eksternt API-kald, der kan tage flere minutter at besvare

Hvis dit trin afsluttes på under et minut, er hjerteslag unødvendige. Registreringen af forældede jobs træder først i kraft, når den konfigurerede timeout er udløbet.
