Files
upstream-mirror e7da97194a
Some checks failed
App Prod-Parity E2E Dispatch / dispatch (push) Failing after 10s
CD Storybook UI / build (push) Failing after 3m25s
CD Storybook UI / dispatch (push) Has been skipped
CD deploy main / website-changed-files (push) Successful in 18m8s
CI Agent Skills / changed-files-check (push) Successful in 18m9s
CI Create App E2E minimal / changed-files-check (push) Successful in 24s
CI Create App / changed-files-check (push) Successful in 26s
CI Docs / changed-files-check (push) Successful in 26s
CI Emails / changed-files-check (push) Successful in 28s
CI Example App Hello World / changed-files-check (push) Successful in 23s
CI Front / changed-files-check (push) Has been skipped
CI Example App Postcard / changed-files-check (push) Successful in 27s
CI UI / changed-files-check (push) Has been skipped
CI Utils Test / changed-files-check (push) Successful in 45s
CI Twenty Apps / discover (push) Successful in 1m3s
CD deploy main / deploy-main (push) Failing after 3s
CI Agent Skills / agent-skills-validate (push) Has been skipped
CI Create App / create-app-test (lint) (push) Has been skipped
CI Emails / emails-test (push) Has been skipped
CI Front / front-task (lingui:compile) (push) Has been skipped
CI Front / front-task (lint) (push) Has been skipped
CI Example App Postcard / example-app-postcard (push) Has been skipped
CI UI / ui-task (lint) (push) Has been skipped
CI UI / ui-task (pack-size) (push) Has been skipped
CI UI / ui-task (size) (push) Has been skipped
CI UI / ui-task (test) (push) Has been skipped
CI UI / ui-task (typecheck) (push) Has been skipped
CI Utils Test / utils-test (push) Has been skipped
CI Twenty Apps / ${{ matrix.app.name }} (push) Has been skipped
CI Emails / ci-emails-status-check (push) Successful in 2s
CI Example App Hello World / ci-example-app-hello-world-status-check (push) Successful in 2s
CI Example App Postcard / ci-example-app-postcard-status-check (push) Successful in 3s
CI UI / ui-sb-test (push) Has been skipped
CI Utils Test / ci-utils-test-status-check (push) Successful in 2s
CI Twenty Apps / ci-twenty-apps-status-check (push) Successful in 2s
CI UI / ci-ui-status-check (push) Successful in 2s
CI Desktop / desktop (push) Failing after 1m8s
Push docs to Crowdin / Push documentation to Crowdin (push) Failing after 55s
Push translations to Crowdin / Extract and upload translations (push) Failing after 5s
Push website translations to Crowdin / Extract and upload website translations (push) Failing after 5s
CI Create App / create-app-test (test) (push) Has been skipped
CI Create App / create-app-test (typecheck) (push) Has been skipped
CI Docs / docs-lint (push) Has been skipped
CI Example App Hello World / example-app-hello-world (push) Has been skipped
CI Front / front-task (lingui:extract) (push) Has been skipped
CI Front / front-task (test) (push) Has been skipped
CI Front / front-task (typecheck) (push) Has been skipped
CI UI / ui-sb-build (push) Failing after 1m32s
CI Twenty Apps / ${{ matrix.app.name }} (${{ matrix.server-source }}) (push) Has been skipped
CI Agent Skills / agent-skills-publish (push) Has been skipped
CI Create App / ci-create-app-status-check (push) Successful in 2s
CI Agent Skills / agent-skills-remote-source (push) Has been skipped
CI Create App E2E minimal / create-app-e2e-minimal (push) Has been cancelled
CI E2E Main / e2e-test (push) Has been cancelled
CI Front / front-sb-build (push) Has been cancelled
CI Front / front-build (push) Has been cancelled
CI Create App E2E minimal / ci-create-app-e2e-minimal-status-check (push) Has been cancelled
CI E2E Main / ci-e2e-main-status-check (push) Has been cancelled
CI E2E Main / notify-main-ci-failure (push) Has been cancelled
CI E2E Main / qa-scout-comment (push) Has been cancelled
CI Front / front-sb-test (push) Has been cancelled
CI Front / front-sb-screenshots (push) Has been cancelled
CI Front / ci-front-status-check (push) Has been cancelled
DPA Sub-Processor Sync / sync-subprocessors (push) Failing after 3s
AI Catalog Sync / sync-catalog (push) Failing after 2s
snapshot of https://github.com/twentyhq/twenty.git (main, 2026-09-17; blobs > 131072B omitted)
2026-09-17 23:28:13 +05:00

198 lines
11 KiB
Plaintext
Raw Permalink 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: Trabajos en segundo plano
description: Puede delegar el trabajo prolongado o sujeto a limitaciones de tasa a los workers de Twenty encolando otra ejecución de una función lógica en lugar de hacerlo todo en el mismo flujo.
icon: layer-group
---
La ejecución de una función lógica está limitada por su `timeoutSeconds` (900 segundos como máximo). Cualquier cosa que no pueda terminar en esa ventana — una resincronización completa, una distribución por registro, una API de terceros que impone límites de tasa — debe dividirse en ejecuciones más pequeñas.
`enqueueJobs` hace exactamente eso: solicita a los workers de Twenty que ejecuten más tarde una de las funciones lógicas de tu aplicación, una vez por carga útil, cada ejecución en su propio proceso con su propio presupuesto de tiempo de espera. La llamada devuelve de inmediato.
```text
┌─────────────────┐ enqueueJobs(...) ┌──────────────┐ ┌────────────────────┐
│ Logic function │ ─────────────────▶ │ Job queue │──▶│ Logic function │
│ (returns now) │ │ (workers) │ │ (fresh run/timeout)│
└─────────────────┘ └──────────────┘ └────────────────────┘
```
## Poner en cola ejecuciones
Importa `enqueueJobs` desde `twenty-sdk/logic-function`, apúntalo al `universalIdentifier` de la función lógica que se ejecutará y pasa una carga útil por ejecución para poner en cola.
```ts src/logic-functions/sync-all-contacts.ts
import { enqueueJobs } from 'twenty-sdk/logic-function';
await enqueueJobs({
logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
payloads: [{ page: 1 }],
});
```
Cada ejecución recibe su carga útil como argumento del manejador, exactamente igual que cualquier otro disparador. El destino debe pertenecer a la **misma aplicación** que quien realiza la llamada; poner en cola la función de otra aplicación se rechaza con `Logic function not found` y no se pone nada en cola. Una sola llamada acepta hasta `200` cargas útiles.
<Note>
`enqueueJobs` devuelve tan pronto como se aceptan los trabajos, no cuando se han ejecutado. No devuelve los resultados de los destinos: haz que cada destino escriba lo que produce en el [almacenamiento de pares clave-valor](/l/es/developers/extend/apps/logic/key-value-store) o en un registro del espacio de trabajo si necesitas leerlo después.
</Note>
<Note>
El auxiliar `enqueueJob` más antiguo, que pone en cola un único trabajo por llamada, ha quedado obsoleto. Usa `enqueueJobs` con una lista `payloads` de un elemento en su lugar.
</Note>
## Opciones del trabajo
Las opciones se aplican a todas las ejecuciones del lote.
| Opción | Predeterminado | Rango | Qué hace |
| ------------ | -------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `retryLimit` | `0` | `0`–`10` | Intentos adicionales totales en la cola. Los reintentos solicitados por la aplicación tienen un límite de `3`. Solo incrementa este valor para manejadores que se puedan ejecutar dos veces de forma segura. |
| `delayMs` | `0` | `0`–`604800000` (7 días) | Espera este tiempo antes de que las ejecuciones sean elegibles. |
```ts
await enqueueJobs({
logicFunctionUniversalIdentifier: '9f1c3d7e-51b8-4a29-8f0d-7c4e2a6b1d33',
payloads: [{ page: 1 }],
retryLimit: 3,
delayMs: 60_000,
});
```
<Note>
**La prioridad todavía no es configurable.** Los trabajos encolados siempre se ejecutan con la prioridad más baja, para que el trabajo de la plataforma nunca se retrase por detrás de los trabajos de las aplicaciones. El control sobre la prioridad llegará pronto.
</Note>
La ejecución encolada hereda el usuario activo de la función que la puso en cola, por lo que actúa con los mismos permisos.
## Reintentar un fallo transitorio
Twenty no vuelve a intentar todas las excepciones del código de la aplicación. Un error ordinario lanzado se trata como un fallo permanente. Para un fallo transitorio, una función lógica en cola puede solicitar hasta tres reintentos lanzando `RetryableLogicFunctionError`.
```ts
import {
type LogicFunctionExecutionContext,
RetryableLogicFunctionError,
} from 'twenty-sdk/logic-function';
export const handler = async (
_payload: unknown,
{ retryCount, maxRetries }: LogicFunctionExecutionContext,
) => {
const response = await fetch('https://api.example.com/contacts');
if (response.status === 429 || response.status >= 500) {
throw new RetryableLogicFunctionError(
`The contacts API is temporarily unavailable (${response.status}); retry ${retryCount} of ${maxRetries}`,
);
}
};
```
Lanza `RetryableLogicFunctionError` directamente cuando sea posible. Si lo extiendes, no sustituyas su `name`: Twenty reconoce el nombre serializado `RetryableLogicFunctionError` en todos los entornos de ejecución.
`retryCount` es `0` para la ejecución inicial y aumenta solo cuando el código de la aplicación solicita un reintento. `maxRetries` es como máximo `3` y puede ser menor cuando el trabajo en cola tiene un límite general de reintentos más bajo. Los fallos de la plataforma no aumentan `retryCount`, aunque siguen consumiendo el presupuesto general de seguridad de la cola.
La cola retrasa los intentos de reintento con una espera exponencial y fluctuación. El retraso exacto no se garantiza intencionadamente, por lo que el código de la aplicación no debe depender de que un reintento ocurra en un momento preciso. Una vez que se alcanza `maxRetries`, otro `RetryableLogicFunctionError` se registra como el fallo final de la aplicación sin otra ejecución.
<Warning>
Los reintentos vuelven a ejecutar todo el controlador y pueden ocurrir después de que algunos efectos secundarios se hayan realizado correctamente. Haz que el controlador sea idempotente antes de solicitar reintentos.
</Warning>
## Úsalo: recorre por páginas una sincronización larga
La forma clásica es una función que se pone en cola *a sí misma* con el cursor siguiente. Cada ejecución hace una página de trabajo bien dentro de su propio tiempo de espera, y la cadena se detiene cuando no queda nada.
```ts src/logic-functions/sync-contacts-page.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import { enqueueJobs } 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 enqueueJobs({
logicFunctionUniversalIdentifier: SYNC_CONTACTS_PAGE,
payloads: [{ 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,
});
```
## Dividir por registro
Cuando el trabajo es de forma natural por elemento, pon en cola un trabajo por elemento en una sola llamada y deja que los workers los procesen en paralelo en lugar de iterar en línea.
```ts
const companies = await listCompaniesToEnrich();
await enqueueJobs({
logicFunctionUniversalIdentifier: ENRICH_COMPANY,
payloads: companies.map((company) => ({ companyId: company.id })),
retryLimit: 2,
});
```
## Buenas prácticas para trabajos de larga duración
Dos reglas cubren casi cualquier trabajo largo: **usa recursión en lugar de bucles** y **procesa un bloque acotado por ejecución**.
Una ejecución que intenta hacerlo todo es la forma en que falla: alcanza el tiempo de espera y, con un reintento, vuelve a empezar todo desde cero. En su lugar, dimensiona un bloque para que termine con holgura dentro de `timeoutSeconds`, guarda tu posición y pon en cola la siguiente ejecución.
```ts src/logic-functions/enrich-companies-batch.ts
import { defineLogicFunction } from 'twenty-sdk/define';
import { enqueueJobs, 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 enqueueJobs({
logicFunctionUniversalIdentifier: ENRICH_COMPANIES_BATCH,
payloads: [{ 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,
});
```
Por qué esto se mantiene bien:
* **Dimensiona el bloque a partir del elemento más lento, no del promedio.** `CHUNK_SIZE × worst-case item time` tiene que caber en `timeoutSeconds` con margen de sobra, o la parte final de un bloque se pierde cuando se corta la ejecución.
* **Haz que la condición de terminación sea explícita.** Haz recursión solo mientras haya vuelto un bloque completo. Una cadena que se detiene solo con "sin resultados" seguirá ejecutándose para siempre si la fuente alguna vez devuelve una página corta a mitad de camino.
* **Guarda el progreso antes de poner en cola la siguiente ejecución**, de modo que un eslabón fallido se reinicie desde el último bloque completado en lugar de desde el principio.
* **Mantén cada bloque idempotente.** Volver a procesar un bloque después de un reintento no debe escribir dos veces — indexa las escrituras por el registro o el id externo que estés procesando.
* **Prefiere una cadena por bloques en lugar de una expansión masiva** cuando el trabajo interactúa con un tercero con limitación de tasa: una cadena con `delayMs` se autorregula, mientras que miles de trabajos encolados a la vez se vuelven elegibles de inmediato.