Files
twenty/packages/twenty-docs/l/de/developers/extend/apps/logic/background-jobs.mdx
T
github-actions[bot] 550aeafd90 i18n - docs translations (#23589)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-07-30 17:20:41 +02:00

166 lines
8.1 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: Hintergrundjobs
description: Übergib lang laufende oder rate-limitierte Arbeiten an die Twenty-Worker, indem du einen weiteren Lauf einer Logikfunktion in die Warteschlange stellst, statt alles inline auszuführen.
icon: layer-group
---
Ein Logikfunktionslauf ist durch seine `timeoutSeconds` begrenzt (maximal 900 Sekunden). Alles, was in diesem Zeitfenster nicht abgeschlossen werden kann ein vollständiger Re-Sync, ein Fan-out pro Datensatz, eine Drittanbieter-API, die dich rate-limitiert muss in kleinere Läufe aufgeteilt werden.
`enqueueJob` macht genau das: Es weist die Twenty-Worker an, eine der Logikfunktionen deiner App später, in einem eigenen Prozess, mit eigenem Timeout-Budget auszuführen. Der Aufrufer kehrt sofort zurück.
```text
┌─────────────────┐ enqueueJob(...) ┌──────────────┐ ┌────────────────────┐
│ Logic function │ ─────────────────▶ │ Job queue │──▶│ Logic function │
│ (returns now) │ │ (workers) │ │ (fresh run/timeout)│
└─────────────────┘ └──────────────┘ └────────────────────┘
```
## Einen Lauf in die Warteschlange stellen
Importiere `enqueueJob` aus `twenty-sdk/logic-function` und gib den `universalIdentifier` der Logikfunktion an, die du ausführen möchtest.
```ts src/logic-functions/sync-all-contacts.ts
import { enqueueJob } from 'twenty-sdk/logic-function';
await enqueueJob({
logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
payload: { page: 1 },
});
```
Die Zielfunktion erhält `payload` als Handler-Argument, genau wie jeder andere Trigger. Sie muss zur **gleichen Applikation** gehören wie der Aufrufer — das Einreihen der Funktion einer anderen App wird mit `Logic function not found` abgelehnt.
<Note>
`enqueueJob` kehrt zurück, sobald der Job akzeptiert wurde, nicht wenn er ausgeführt wurde. Sie gibt nicht das Ergebnis des Ziels zurück — lasse das Ziel das Ergebnis in den [Key-Value-Speicher](/l/de/developers/extend/apps/logic/key-value-store) oder in einen Workspace-Datensatz schreiben, wenn du es wieder auslesen musst.
</Note>
## Job-Optionen
| Option | Standard | Bereich | Was es tut |
| ------------ | -------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `retryLimit` | `0` | `0``10` | Zusätzliche Versuche, falls der Lauf eine Exception auslöst. Erhöhe diesen Wert nur für Handler, die sicher zweimal ausgeführt werden können. |
| `delayMs` | `0` | `0``604800000` (7 Tage) | So lange warten, bevor der Lauf zur Ausführung ansteht. |
```ts
await enqueueJob({
logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
payload: { page: 1 },
retryLimit: 3,
delayMs: 60_000,
});
```
<Note>
**Priorität ist noch nicht konfigurierbar.** Eingereihte Jobs laufen immer mit der niedrigsten Priorität, sodass Plattformarbeit niemals hinter Applikationsjobs verzögert wird. Steuerung der Priorität kommt bald.
</Note>
Der eingereihte Lauf erbt den agierenden Benutzer der Funktion, die ihn eingereiht hat, sodass er mit denselben Berechtigungen agiert.
## Verwendung: eine lange Synchronisation seitenweise verarbeiten
Die klassische Form ist eine Funktion, die *sich selbst* mit dem nächsten Cursor in die Warteschlange stellt. Jeder Lauf erledigt eine Seite Arbeit deutlich innerhalb seines eigenen Timeouts, und die Kette endet, wenn nichts mehr übrig ist.
```ts src/logic-functions/sync-contacts-page.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import { enqueueJob } from 'twenty-sdk/logic-function';
const SYNC_CONTACTS_PAGE = '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33';
const handler = async (params: { cursor?: string }) => {
const { contacts, nextCursor } = await fetchContactsPage(params.cursor);
await importContacts(contacts);
if (nextCursor) {
await enqueueJob({
logicFunctionUniversalIdentifier: SYNC_CONTACTS_PAGE,
payload: { cursor: nextCursor },
delayMs: 2_000,
});
}
return { imported: contacts.length, done: !nextCursor };
};
export default defineLogicFunction({
universalIdentifier: SYNC_CONTACTS_PAGE,
name: 'sync-contacts-page',
timeoutSeconds: 120,
handler,
});
```
## Aufteilung pro Datensatz
Wenn die Arbeit natürlich pro Element anfällt, reihe einen Job pro Element ein und lasse die Worker sie parallel verarbeiten, anstatt inline zu iterieren.
```ts
const companies = await listCompaniesToEnrich();
await Promise.all(
companies.map((company) =>
enqueueJob({
logicFunctionUniversalIdentifier: ENRICH_COMPANY,
payload: { companyId: company.id },
retryLimit: 2,
}),
),
);
```
## Bewährte Vorgehensweisen für langlaufende Arbeit
Zwei Regeln decken fast jeden langen Job ab: **rekursiv statt in einer Schleife arbeiten** und **pro Lauf einen begrenzten Chunk verarbeiten**.
Ein Lauf, der versucht, alles zu erledigen, ist der Fehlerfall — er erreicht das Timeout, und bei einem Retry startet er den gesamten Vorgang erneut von vorn. Bestimme stattdessen die Größe eines Chunks so, dass er bequem innerhalb von `timeoutSeconds` fertig wird, speichere deine Position und reihe den nächsten Lauf ein.
```ts src/logic-functions/enrich-companies-batch.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import { enqueueJob, kv } from 'twenty-sdk/logic-function';
const ENRICH_COMPANIES_BATCH = '3f9d1c02-8a44-4f0e-b1d7-9c2e5a7b4f10';
const CHUNK_SIZE = 50;
const handler = async (params: { offset?: number }) => {
const offset = params.offset ?? 0;
const companies = await listCompaniesToEnrich({
offset,
limit: CHUNK_SIZE,
});
for (const company of companies) {
await enrichCompany(company);
}
await kv.set('enrich:progress', { offset: offset + companies.length });
if (companies.length === CHUNK_SIZE) {
await enqueueJob({
logicFunctionUniversalIdentifier: ENRICH_COMPANIES_BATCH,
payload: { offset: offset + CHUNK_SIZE },
});
}
return { processed: companies.length, done: companies.length < CHUNK_SIZE };
};
export default defineLogicFunction({
universalIdentifier: ENRICH_COMPANIES_BATCH,
name: 'enrich-companies-batch',
timeoutSeconds: 300,
handler,
});
```
Warum das funktioniert:
* **Lege die Chunk-Größe anhand des langsamsten Elements fest, nicht anhand des Durchschnitts.** `CHUNK_SIZE × Worst-Case-Elementzeit` muss mit ausreichendem Puffer in `timeoutSeconds` passen, sonst geht das Ende eines Chunks verloren, wenn der Lauf abgeschnitten wird.
* **Mache die Abbruchbedingung explizit.** Rekursiere nur, solange ein voller Chunk zurückgekommen ist. Eine Kette, die nur bei „keine Ergebnisse“ anhält, läuft für immer weiter, wenn die Quelle unterwegs einmal eine verkürzte Seite zurückgibt.
* **Persistiere den Fortschritt, bevor du den nächsten Lauf einreihst,** damit ein fehlgeschlagener Link beim letzten abgeschlossenen Chunk und nicht am Anfang neu startet.
* **Halte jeden Chunk idempotent.** Die erneute Verarbeitung eines Chunks nach einem Retry darf nicht zu doppelten Schreibvorgängen führen — führe Schlüsselschreibvorgänge auf dem Datensatz oder der externen ID aus, die du verarbeitest.
* **Bevorzuge eine verkettete Chunk-Verarbeitung gegenüber einem riesigen Fan-out**, wenn die Arbeit auf einen rate-limitierten Drittanbieter trifft: Eine Kette mit `delayMs` taktet sich selbst, wohingegen Tausende auf einmal eingereihte Jobs sofort zur Ausführung anstehen.
<Warning>
Retries führen den gesamten Handler erneut aus. Halte eingereihte Handler idempotent, bevor du `retryLimit` über `0` setzt.
</Warning>