Request icon
Script icon
Process icon
Set icon
If icon
Subflow icon
Log icon
Webhook icon

Carbon-Aware Batch Job Scheduler with Deadline-Safe Deferral

Use Kestra to run batch jobs now or defer them to a lower-carbon grid window while respecting hard deadlines and API failures.

Categories
CloudInfrastructure

Compute-heavy jobs are often urgent enough to need a deadline but not so urgent that they must start this minute. This blueprint turns that flexibility into an explicit scheduling policy. A webhook receives the workload's deadline and expected duration, Kestra fetches Great Britain's public regional carbon-intensity forecast, and a Python planner chooses the lowest-intensity half-hour that still leaves enough time to finish. Kestra then starts the companion executor immediately or creates a future-dated subflow execution with scheduleDate.

This is carbon-aware scheduling, not carbon accounting. The reported impact multiplies an operator-supplied energy estimate by forecast grid intensity. It is an estimate for comparing candidate windows, not proof of measured savings. Connect server or cloud energy telemetry before using the values in sustainability reporting.

How it works

  1. submit_batch_job (io.kestra.plugin.core.trigger.Webhook) accepts job id, deadline, runtime, energy estimate, regional policy, and workload size. The same inputs support manual runs.
  2. fetch_carbon_forecast (io.kestra.plugin.core.http.Request) asks the public Carbon Intensity API for 48 hours of half-hour forecasts for the selected region. options.allowFailed handles HTTP errors and allowFailure lets transport failures reach the fallback logic.
  3. build_execution_plan (io.kestra.plugin.scripts.python.Script) rejects expired deadlines, excludes slots that cannot finish before the deadline, compares the current window with the cleanest eligible future window, and emits a structured plan. It runs now when the grid is already below the threshold, the deadline is close, or forecast savings are too small.
  4. If the forecast is absent or malformed, the default policy is RUN_NOW so a data-provider outage cannot silently make a deadline-bound business job late. Set fail_closed_on_forecast_error: true when running without carbon data is unacceptable.
  5. record_plan (io.kestra.plugin.core.kv.Set) stores the forecast, decision reason, selected window, and estimated impact under carbon_plan_<planner execution id>.
  6. route_execution (io.kestra.plugin.core.flow.If) either invokes carbon-aware-batch-job-executor now or uses io.kestra.plugin.core.flow.Subflow.scheduleDate with wait: false to create a durable future execution. The planner does not consume a worker while waiting.
  7. report_decision logs a compact, searchable decision summary. Flow outputs expose the decision, selected start, and complete plan to webhook callers and parent flows.

What you get

  • Deadline-aware scheduling rather than a naive "run at the daily greenest hour" cron.
  • Location-aware forecasts using any Great Britain Carbon Intensity API region id.
  • Explicit immediate, deferred, deadline-pressure, and forecast-outage paths.
  • Durable scheduled subflow executions with parent-child lineage and labels.
  • A KV audit record that separates forecast estimates from measured emissions.
  • SAMPLE and UNAVAILABLE_DEMO modes for repeatable demonstrations without manipulating the live grid.

Prerequisites

  • Kestra with the core HTTP and Python script plugins and Python 3 available on the worker.
  • Import carbon-aware-batch-job-executor.yaml into the same company.sustainability namespace before running this planner.
  • Outbound HTTPS access to api.carbonintensity.org.uk for LIVE mode.
  • Set a unique, strong webhook key as the CARBON_SCHEDULER_WEBHOOK_KEY namespace secret.
  • The public API needs no API token and covers Great Britain only. Replace the request and response adapter for another grid-data provider or geography.

Secrets

  • CARBON_SCHEDULER_WEBHOOK_KEY: protects the job-submission webhook URL. It is not sent to the carbon-intensity provider.

Inputs

  • job_id (STRING, default nightly-feature-build): unique job reference used in logs and executor records.
  • deadline (DATETIME, optional): hard completion deadline; manual runs default internally to 12 hours from now.
  • estimated_runtime_minutes (INT, default 30): duration reserved before the deadline.
  • estimated_energy_kwh (FLOAT, default 2.5): expected energy per run for impact estimates.
  • records_to_process (INT, default 100000): size of the included demonstration workload.
  • region_id (INT, default 13): Carbon Intensity API region id; 13 is London.
  • run_now_below_gco2_kwh (INT, default 100): intensity considered clean enough to start immediately.
  • minimum_savings_percent (FLOAT, default 10): minimum relative improvement required to defer.
  • minimum_deferral_minutes (INT, default 30): operational lead time for a future start.
  • forecast_mode (LIVE, SAMPLE, or UNAVAILABLE_DEMO; default LIVE): data source/test path.
  • fail_closed_on_forecast_error (BOOL, default false): fail rather than use the deadline-safe run-now fallback.

Quick start

  1. Import the executor flow first, then this scheduler flow, and add the webhook secret.
  2. Run manually with forecast_mode: LIVE; leave deadline empty to use a 12-hour window.
  3. Demonstrate deterministic deferral with forecast_mode: SAMPLE, run_now_below_gco2_kwh: 100, a deadline at least 4 hours away, and the default 30-minute runtime. The sample moves from 250 to 80 gCO2/kWh after two hours.
  4. Demonstrate deadline pressure with forecast_mode: SAMPLE and a deadline less than estimated_runtime_minutes + minimum_deferral_minutes away; the result is RUN_NOW / DEADLINE_APPROACHING.
  5. Demonstrate provider failure with forecast_mode: UNAVAILABLE_DEMO; the default result is RUN_NOW / FORECAST_UNAVAILABLE_FALLBACK.
  6. Submit a production-shaped request:
curl -X POST "https://kestra.example.com/api/v1/main/executions/webhook/company.sustainability/carbon-aware-batch-job-scheduler/<webhook-key>" \
  -H "Content-Type: application/json" \
  -d '{"job_id":"feature-store-2026-10-10","deadline":"2026-10-10T20:00:00Z","estimated_runtime_minutes":45,"estimated_energy_kwh":4.2,"region_id":13}'

Expected outputs

  • outputs.decision: RUN_NOW or DEFER.
  • outputs.schedule_at: selected UTC start such as 2026-10-10T14:30:00Z.
  • outputs.plan: JSON containing the reason, current and selected forecast intensities, savings percentage, deadline, and estimated kgCO2e values.
  • KV key carbon_plan_<execution id>: persisted copy of the full plan.
  • Deferred jobs immediately expose the child execution id at outputs.schedule_for_cleaner_window.executionId; immediate jobs expose the executor's flow outputs under outputs.execute_now.outputs.

Production adaptation

  • Replace the demonstration Python task in the executor with a dbt, Spark, Kubernetes, cloud batch, or ML-training subflow; keep the scheduling contract unchanged.
  • Feed measured kWh from server telemetry, cloud carbon tools, or model-training trackers back into the result record. Only then compare estimated and measured impact.
  • Add a second forecast provider and a freshness check for higher resilience.
  • Add request idempotency keyed by your queue message id before accepting at-least-once webhook deliveries.
  • Add Slack or incident notifications only for fallback, expired-deadline, and executor-failure paths.

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.