Ensuring that only one instance of a job runs at a time is important for tasks such as database cleanup, report generation, deployments, backups, and scheduled data processing. If multiple instances execute simultaneously, they can create duplicate work, race conditions, inconsistent data, or resource contention.
The best solution depends on where the job is running.
Use Kubernetes Concurrency Policy
For Kubernetes CronJobs, the simplest approach is to set concurrencyPolicy: Forbid. Kubernetes allows concurrent executions by default, while Forbid prevents a new Job from starting when the previous execution is still running.
apiVersion: batch/v1
kind: CronJob
metadata:
name: data-processing
spec:
schedule: "*/10 * * * *"
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
containers:
- name: worker
image: my-job:latest
restartPolicy: Never
Replace is another option when the new execution should replace the currently running one. However, for jobs where the existing execution must finish safely, Forbid is generally more appropriate.
Use a Distributed Lock
If the job can be triggered by multiple application instances, Kubernetes CronJobs may not be enough. In that situation, use a distributed lock backed by a shared system such as a database or Redis.
The basic flow is:
Acquire Lock β Run Job β Release Lock
If another instance tries to acquire the same lock while the job is running, it should exit or wait rather than execute the job. Distributed locking libraries such as ShedLock use external stores for this type of coordination.
Make the Job Idempotent
A lock should not be the only protection. Jobs should ideally be idempotent, meaning running the same operation again does not corrupt data or produce unwanted duplicate results.
This becomes particularly important in distributed systems because failures can occur between job execution and lock release. Kubernetes itself recommends designing CronJob workloads to be idempotent because scheduling situations can occasionally result in unexpected executions.
Choose Based on Your Environment
For a Kubernetes CronJob, start with concurrencyPolicy: Forbid.
For a multi-instance application, use a distributed lock with a reliable shared store.
For a CI/CD pipeline, use the platform's concurrency or deployment-locking mechanism so that two deployments of the same environment cannot execute simultaneously.
For cloud schedulers, look for native concurrency controls; otherwise, implement a distributed lock at the application or data layer.
Final Thought
The safest architecture is usually a combination of scheduler-level concurrency control, distributed locking where required, and idempotent job design. This prevents simple overlaps while also protecting the system when workers crash, networks fail, or jobs take longer than expected. The important goal is not just βone job at a time,β but ensuring that job execution remains predictable and safe during failures.