Jobs
Purpose#
A job runs a set of SQL Pipelines tasks together, in dependency order, as one unit with one status and one run history.
Running tasks one at a time is fine while you are building them. As soon as a staging table feeds a fact table which feeds a summary, you want the three to run in the right order, to stop sensibly when one fails, and to report as a single outcome. That is a job.
Jobs live at /jobs.
Before you begin#
- Tasks that already run cleanly on their own. A job is a bad place to debug a task. See SQL pipelines.
- One environment. A job is single-environment: every task in it must belong to the same environment as the job. Adding a task from a different environment is rejected. Promote the task into this environment first — see Environments and promotion.
Required role#
| Action | Global role | Project capability | Typical project role |
|---|---|---|---|
| View jobs, runs, logs | viewer or higher |
schedule.view, task.view |
any member |
| Create or edit a job, manage groups and members, manage job parameters | editor or higher |
schedule.manage |
Developer (unprotected environments only), Maintainer, Owner |
| Run or cancel a job run | editor or higher |
task.run |
Developer (unprotected environments only), Maintainer, Owner |
Both schedule.manage and task.run are environment-gated: a Developer holds
them in unprotected environments only and is refused in a protected one with the
reason protected_environment. The global admin role is an implicit Owner on
every project.
Create a job and add tasks#
- Go to /jobs and choose New job.
- Name it and pick its environment.
- Set its policies — failure, concurrency, retries, timeout (see Field reference).
- Save.
- Add at least one group.
- Add tasks to the group.
Groups organise; they do not order#
This is the single most misunderstood thing about jobs, so it is worth being precise.
A group is a named bucket with a description and a display order. Groups exist so a forty-task job is readable — "staging", "facts", "marts" — and so the run detail is navigable.
Groups do not determine execution order. Neither does a task's position within a group.
Execution order is derived from the dependencies between the tasks themselves: Abrq DIP builds a DAG for the environment by reading the table references in each task's SQL, then runs the tasks in an order consistent with that DAG. A task that reads a table another task writes runs after it — regardless of which group either one is in.
Note. Group ordering and within-group sequence survive only as a tie-break for tasks the dependency graph cannot rank relative to each other. Do not use groups to express "this must run first". Express it in SQL: if task B reads what task A writes, the order is already correct and self- maintaining.
The practical consequence is a good one — the ordering cannot drift out of sync with reality, because it is reality. It also means a job's order changes when you edit a task's SQL, which is why the run detail records what actually ran.
If the tasks form a dependency cycle, the whole run is refused before any task executes, naming the cycle. Break the cycle — usually by splitting a task or introducing a staging table.
Execution today is sequential#
Tasks in a job run one after another. There is no parallel execution of independent branches yet.
Two settings relate to parallelism and are worth understanding honestly:
max_parallelismon the job (0–64, default0meaning unbounded) and the deployment-wide capABRQ_JOB_MAX_PARALLELISM_CAP(default0, also meaning no global cap). The intended semantics are smaller-wins: with a job set to 3 and a cap of 5 you get 3; with a job set to 8 and a cap of 5 you get 5; if either is0it does not constrain, and if both are0there is no bound.- Today the job runner executes sequentially and does not read either value. The setting is stored, validated and displayed, but it does not change how a run behaves.
Warning. Set
max_parallelismto record your intent if you like, but do not rely on it as a throttle — it does not currently limit anything. If you need to bound concurrent work, use the job's concurrency policy (below), which is enforced.
Failure and concurrency policies#
Failure policy#
Decides what happens to the rest of the job when a task fails.
failure_policy |
Behaviour |
|---|---|
fail_job |
Default. The first failure aborts the job. Every remaining task is recorded as skipped. The job run ends failed. |
continue_other_groups |
Only the failed task's dependants are blocked. Independent branches carry on. |
stop_group_only |
Same behaviour as continue_other_groups: the failed task's dependants are blocked, independent branches continue. |
With either of the continuing policies, a job that ends with both successes and
failures reports partial_success. Under fail_job, any failure makes the job
run failed.
Tip.
fail_jobis the right default for a nightly warehouse load, where a partial load is worse than no load. The continuing policies suit jobs that bundle genuinely independent pipelines for convenience.
Concurrency policy#
Decides what happens when a run is triggered while a previous run is still going — the common case for a schedule that fires faster than the job finishes.
concurrency_policy |
Behaviour |
|---|---|
skip_if_running |
Default. The new trigger is refused with a conflict naming the active run. |
queue_if_running |
The new run waits for the active one. |
allow_parallel |
Both run. |
cancel_previous |
The active run is cancelled and the new one starts. |
Job parameters#
Job parameters let a job's tasks share values that you can change in one place — a cut-off date, a region code, a batch label.
Define a parameter#
- Open the job and go to its Parameters tab.
- Add a parameter with a name, a value, and a value type.
| Field | Rules |
|---|---|
| Name | 1–128 characters, starts with a letter or underscore, then letters, digits and underscores. Unique per job. Immutable — to rename, delete and recreate. |
| Value | Up to 2048 characters. Stored as text. |
| Value type | string, date, datetime, integer, number, boolean. A display hint only — substitution is always textual. |
Use a parameter in SQL#
Reference it in a task's SQL with double braces:
SELECT *
FROM sales.orders
WHERE order_date >= '{{ cutoff_date }}'
AND region = '{{ region_code }}';
Whitespace inside the braces is optional. There are no filters, no expressions, and no inline defaults — the name is replaced with the value, verbatim.
Things to know before you use them:
- Parameters apply to job runs only. Running that same task on its own, or from the task builder, performs no substitution — the placeholder is left as written and the SQL will fail. Test parameterised tasks by running the job.
- Values are read once per job run. The run snapshots every parameter at the start, so all tasks in one run see a consistent set.
- There are no per-run parameter overrides. Triggering a run takes no inputs. To run with a different value, change the parameter first, then run.
- An undefined placeholder fails that task, with an error naming the parameter it could not resolve. It does not silently render as empty.
- Substitution is plain text, not SQL binding. It is not protection against SQL injection. Treat parameter values as trusted operator input, never as something an end user supplies.
- Values are not secret. There is no masking or write-only handling. Do not put credentials in job parameters — use a connector.
Tip. In the builder, render preview on a task shows which placeholders remain unresolved. Job parameters legitimately appear there, because they are supplied at job-run time. See SQL pipelines.
Run a job#
- Open the job and choose Run.
- The run is accepted immediately and appears in the job's run list.
A job run can be triggered two ways:
- Manually, from the job page.
- By a schedule attached to the job — see Schedules.
Note. There is no webhook trigger. Abrq DIP does not expose a token-in-the-URL endpoint for starting a job from an external system. If you need to trigger a job from outside, call the authenticated jobs API with an API token — see API tokens and the API reference.
Cancel a run#
Cancelling is cooperative and best-effort: the run stops dispatching further tasks, but a task already executing is not killed mid-statement. A run that has already reached a terminal status cannot be cancelled.
Read the job run detail#
A job run is a three-level structure, and the run detail mirrors it:
Job run → group runs → task runs.
| Level | Status vocabulary |
|---|---|
| Job run | pending, running, success, failed, partial_success, cancelled |
| Group run | pending, running, success, failed, cancelled, skipped |
| Task run | pending, running, success, failed, cancelled, skipped |
The job run header carries the trigger (manual or schedule), who started it,
start and finish times, duration, and the error message when it failed.
Each task run row shows its status, start and finish times, rows read, rows written, and its error message.
Logs are split by level, deliberately:
- The job run log carries orchestration events — the order that was computed, which task started, policy decisions, refusals.
- Per-task detail is not duplicated here. Each task run links through to its real task run in SQL Pipelines, where you get the full log, the rendered SQL, the metrics, the attempt number, and any quarantined rows.
That link is the one to follow when a task failed. The job run tells you what failed; the task run tells you why.
Job runs also appear in the cross-family monitor at Executions
under the etl_job kind, alongside their constituent etl_task runs.
Field reference#
Job fields#
| Field | Type | Default | Notes |
|---|---|---|---|
| Name | text | — | Identifies the job. |
| Environment | reference | — | A job is single-environment. All its tasks must match. |
failure_policy |
fail_job, continue_other_groups, stop_group_only |
fail_job |
See Failure policy. |
concurrency_policy |
skip_if_running, queue_if_running, allow_parallel, cancel_previous |
skip_if_running |
Enforced. |
max_parallelism |
integer 0–64 | 0 (unbounded) |
Stored and validated; not currently enforced — execution is sequential. |
retry_count |
integer 0–10 | 0 |
Automatic re-attempts. |
retry_delay_seconds |
integer 0–86400 | 60 |
Wait between attempts. |
timeout_seconds |
integer 1–86400 | 3600 |
Bounds the job run. |
| Timezone | text | UTC |
|
| Active | boolean | on | An inactive job is not triggered by its schedules. |
Group fields#
| Field | Type | Default | Notes |
|---|---|---|---|
| Name | text | — | Unique within the job. |
| Description | text | — | Optional. |
| Display order | integer | 0 |
Organisational. Only a tie-break for execution order. |
Deployment setting#
| Setting | Default | Notes |
|---|---|---|
ABRQ_JOB_MAX_PARALLELISM_CAP |
0 |
Deployment-wide cap. 0 means no global cap; otherwise the smaller of it and the job's max_parallelism is intended to win. Not currently enforced. |
Failure modes#
Protected-environment preflight refusal#
A job run targeting a protected environment is checked before any task
executes. If any connector role the job's tasks require does not resolve in
that environment, the run is created and immediately finalised as failed,
with an error naming the environment and the missing roles (source,
destination, or both) and pointing you at the project's Environments screen.
This is not an HTTP error you will see in a browser — with a scheduled trigger nobody is watching. It surfaces as a failed job run with no task runs underneath it, which is the tell.
Fix: bind the missing roles in that environment. See Environments and promotion.
License expired#
When the deployment's license has expired or is invalid, the platform enters
read-only mode. Triggering a job run, editing a job, and adding a parameter all
fail with 403 and the machine-readable code license_expired; the message
explains that reads still work and writes are blocked.
Reading jobs, runs and logs continues to work throughout — this is deliberate, so you can still diagnose during an expiry. An already-dispatched run is not interrupted.
Fix: renew the license. See Expiry and clock.
Everything else#
| What you see | Cause | Fix |
|---|---|---|
| Run refused: dependency cycle | The tasks' SQL references form a loop. | Break the cycle — split a task or introduce a staging table. |
| Cannot add a task to the job | The task belongs to a different environment; jobs are single-environment. | Promote the task into the job's environment first. |
| Trigger refused, a run is already active | concurrency_policy = skip_if_running. |
Wait, or change the policy to queue_if_running. |
| A task run recorded as failed: task is disabled | The task's Enabled flag is off. | Enable the task, or remove it from the job. |
Remaining tasks all skipped after one failure |
failure_policy = fail_job. |
Expected. Use a continuing policy if the branches are independent. |
Job reports partial_success |
A continuing failure policy, with both successes and failures. | Read the failed task runs; the job is not fully done. |
| Tasks did not run in the order the groups suggest | Groups do not set order; the dependency DAG does. | Read the job run log for the computed order. Express order through SQL references. |
max_parallelism appears to have no effect |
It is not currently enforced; runs are sequential. | Expected. Use concurrency_policy for real limits. |
| A task fails on an undefined job parameter | The SQL references a placeholder with no matching parameter, or the task was run outside a job. | Define the parameter, or run the task through the job. |
| Cancel rejected | The run is already terminal. | None. |
See also#
- SQL pipelines — the tasks a job orchestrates
- Environments and promotion — protected environments and connector roles
- Schedules — running a job unattended
- Executions — job runs in the cross-family monitor
- Notifications and alerts — being told when a job fails