Chapter 11Lesson 03~170 minutes

Job Dependencies, needs, Concurrency, Cancellation, and Deployment Serialization: Configuration, Design Patterns, and Trade-Offs

Once the primitives are clear, the design question becomes which invariant belongs in the DAG, which belongs in concurrency arbitration, and which belongs in an external deployment or governance system. Over-serialization wastes capacity; under-serialization creates race conditions.

Design patternsWorkflow vs job lockQueue vs cancelEnvironment lockReusable workflows

Learning objectives

  • Choose parallelism or dependency based on data/control requirements rather than visual YAML order.
  • Choose workflow-level or job-level concurrency according to the smallest critical section that must be mutually exclusive.
  • Choose queue, pending replacement or cancel-old semantics according to whether older work is disposable.
  • Scope deployment locks by target/environment to avoid unnecessary global blocking.
  • Design cleanup and reusable-workflow concurrency keys so they cannot accidentally bypass cancellation or cancel the caller.

1. Parallelism versus dependency

A dependency is justified by a causal requirement: one job needs a prior job’s result/output or must not start until that job finishes. If two checks only inspect the same revision independently, adding needs creates latency without adding correctness.

Scenario Preferred graph Reason
lint + unit test same SHA parallel independent evidence
package needs tested build ID package needs test/build explicit data/control dependency
deploy needs signed release manifest fan-in before deploy deployment invariant depends on verified evidence
postmortem summary should run after failures read-only fan-in with guarded always() evidence preservation

2. Workflow-level versus job-level concurrency

Workflow-level concurrency blocks or cancels the entire run. That is appropriate when the whole run is stale or conflicts with another run. Job-level concurrency is narrower: validation can run in parallel while only a deployment, migration, publication or other critical section is serialized.

# Narrow lock: expensive CI remains parallel; only staging mutation is serialized.
jobs:
  deploy_staging:
    concurrency:
      group: deploy-staging
      queue: max

Narrow locks usually improve feedback latency and runner utilization while keeping the actual external invariant protected.

3. Cancel-old versus queue

Question If yes If no
Is older work completely stale? consider cancel-in-progress queue or allow parallel
Can the work have committed an external side effect? prefer queue/compensation-aware design cancellation is easier to justify
Must every accepted request execute? queue: max may fit pending replacement may be acceptable
Is ordering itself a business invariant? verify queue order and external version checks mutual exclusion may be sufficient

Even a FIFO concurrency queue is not a substitute for an external optimistic-lock/version check when target state can also be changed outside GitHub Actions.

4. One global lock versus target-scoped locks

group: production serializes everything that uses that key. A more precise key often includes workflow, application and target:

concurrency:
  group: deploy-${{ github.repository }}-${{ inputs.service }}-${{ inputs.environment }}
  queue: max

Do not include high-cardinality values such as github.run_id when you intend mutual exclusion: a unique group disables sharing. Conversely, do not omit the workflow/application dimension if unrelated workflows could then cancel or block one another.

5. Concurrency and environments solve different problems

A concurrency group provides mutual exclusion. A GitHub environment can add reviewers, secrets/variables and deployment protections depending on repository plan/configuration. Neither implies the other. A deployment can be approved yet still race another deployment unless serialization exists; a serialized deployment can still be unauthorized if environment governance is missing.

6. Cleanup: evidence-first, privilege-last

There are three distinct “cleanup” categories:

  • read-only evidence finalization — summaries/log correlation; may use always() if it cannot mutate external state.
  • ephemeral local cleanup — removing temp files on the current disposable runner; bounded and low privilege.
  • external compensation/revocation — cloud resources, deployments, credentials; should be explicit, authorized and cancellation-aware.

A blanket if: always() on a privileged external cleanup job can keep destructive work running after cancellation. Prefer explicit conditions such as failure(), cancelled(), target guards and idempotent external APIs, and document whether compensation is safe to repeat.

7. Reusable workflow concurrency can cancel its caller

Called workflows see the caller workflow name through github.workflow. If both caller and called workflow derive the same group from that context and set cancel-in-progress: true, the called workflow can enter the same group and cancel the caller that invoked it.

# Safer namespace for a called deployment workflow.
concurrency:
  group: reusable-deploy-${{ inputs.environment }}
  queue: max

Treat the concurrency key as part of a reusable workflow’s public interface. Document who else uses the group and what cancellation/queue contract callers should expect.

8. Worked decision table

Workload Graph Concurrency Cancellation Evidence
feature CI parallel lint/test workflow per branch cancel old run/attempt/SHA + failure logs
staging deploy validation fan-in → deploy job group per service+staging queue resolved group + target revision
production release signed evidence → approval → deploy job group per production target normally queue, external version guard approval/deployment + external health
read-only nightly report independent/fan-in as needed optional latest-only may cancel stale report report version and source SHA

9. Trade-offs to document

  • Maintainability: stable group naming and explicit DAGs are easier to review than hidden shell polling.
  • Least privilege: put credentials only in the serialized mutation job, not upstream parallel checks.
  • Portability: GitHub concurrency governs GitHub runs, not changes performed by other CI/CD systems.
  • Auditability: record resolved group key, run IDs and target state.
  • Latency/cost: over-broad locks leave runners idle and lengthen queues.
  • Rollback: cancellation is not rollback; compensation must be designed at the external target layer.

Knowledge check

When is job-level concurrency preferable to workflow-level concurrency?

Why is github.run_id usually a bad component in a lock key?

What risk exists if a caller and called workflow share the same cancel-in-progress group?

Does an environment approval rule serialize deployments?

Why is always() acceptable for a read-only summary but risky for external cleanup?

Next chapter concept

Diagnose the graph before rerunning

Lesson 4 intentionally breaks dependency and concurrency designs and traces failures through run, queue, runner and external-state evidence.

Official references and version notes

Version and compatibility note

Version-sensitive behavior was rechecked against current GitHub-maintained documentation on 2026-09-09. Current documentation states that needs contains only direct dependencies and exposes result as success, failure, cancelled or skipped. A failed or skipped dependency normally skips downstream jobs unless an explicit job condition permits continuation. Concurrency groups are repository-wide and case-insensitive. By default at most one item may be running and one pending in a group; a newer pending item replaces an older pending item. Current GitHub Actions also supports queue: max to allow up to 100 pending items, processed FIFO by time waiting on the group; this mode cannot be combined with cancel-in-progress: true. Cancellation re-evaluates job/step conditions, so always() can keep work running during cancellation and must not be used casually for privileged or irreversible operations. Mandatory labs use ubuntu-24.04, permissions: {}, no Marketplace action and no real credential.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.