Key takeaways
- Decide the consequence of failure and name an owner before automating.
- Pass the reporting period as a parameter so retries process the right data.
- Make the job idempotent and guard against overlapping runs.
- Test the schedule in the real environment and document how to rerun it.
On this page
Start with the consequence of failure
A scheduled script is a small production system. Before choosing a scheduler, decide what happens if the run fails, finishes late, or produces a wrong number. A daily sales email that must arrive by 09:00 needs different monitoring from a monthly archive nobody reads.
Name the owner, the deadline, and the acceptable recovery time. If nobody would notice a failure for a week, fix ownership before adding automation. A job with no owner is not automated; it is unattended.
Use a scheduler the team already operates
The best scheduler is the one the organisation can monitor, restart, and hand over. A cron entry on a server is simple and transparent, but it has no built-in alerting or dependency management. A workflow orchestrator adds retries, history, and visibility, at the cost of another system to learn.
Whichever you choose, run the job with the same interpreter, environment, and working directory every time. A job that works in a terminal can still fail under a scheduler because one of those settings differs.
| Option | Good fit | Watch out for |
|---|---|---|
| cron or systemd timer | A few jobs on one server | No alerting by default; environment differs from your shell |
| Windows Task Scheduler | A Windows reporting stack | Credentials, working directory, and interpreter path |
| Cloud scheduler | Jobs already running in a cloud platform | Timeouts, permissions, and log retention |
| Workflow orchestrator | Dependent jobs and backfills | Setup cost; unnecessary for one simple script |
Make the run repeatable
A scheduled job should produce the same result from the same input, and it should be safe to run twice. Pass the reporting period as a parameter rather than reading the current date inside the script. If a run fails and is retried the next day, it must still process the intended period.
Write outputs to a temporary file and replace the target only after the write succeeds. Where the output is a database table, delete and insert the affected period or use a key-based upsert inside a transaction. A partially loaded table is harder to detect than a missing file.
Guard against overlapping runs
A job that takes longer than its interval will start again while the previous run is still working. Use the scheduler's concurrency setting where it exists, and a lock file as a second line of defence. The lock should fail fast with a clear message rather than queue silently. The example uses fcntl, which is available on Linux and macOS; on Windows, use an exclusive lock file or the scheduler's concurrency control.
import fcntl
from pathlib import Path
lock_path = Path('run/monthly_report.lock')
lock_file = lock_path.open('w')
try:
fcntl.flock(lock_file, fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError:
raise SystemExit('Another run is still active')
try:
run_report()
finally:
fcntl.flock(lock_file, fcntl.LOCK_UN)
lock_file.close()Log enough to diagnose a failure
Write a log line at the start and end of the run with the period, input count, output count, and duration. Log validation failures with enough context to identify the offending record. A stack trace alone rarely tells the business owner what to do.
- Start and finish timestamps, plus the reporting period processed.
- Input and output counts, with any difference explained.
- Validation failures, quarantined rows, and the source file.
- The output path or table name, so the result can be found.
- A non-zero exit code whenever the run is not safe to publish.
Test the schedule, not only the script
Run the job manually with the same user, environment, and working directory the scheduler uses. Check permissions on the output directory, the availability of the interpreter, and the location of configuration files. A script that works in a terminal but fails under cron usually depends on an interactive shell setting.
Test a failure: remove the input file or point the script at an empty folder, then confirm the alert arrives. Document how to rerun a period, where the logs live, and who to contact. The runbook is part of the automation.
Frequently asked questions
Is cron still appropriate for reporting jobs?+
Yes for simple jobs on a single server, provided you add logging, alerting, and a lock. Move to an orchestrator when jobs depend on each other, need backfills, or must be visible to people who do not use the server.
How do I stop a job running twice?+
Use the scheduler's concurrency setting and a lock file, and make the output idempotent so a second run cannot duplicate rows. The lock should fail fast rather than wait for the first run to finish.
How should I handle time zones and daylight saving?+
Schedule in a fixed, documented time zone, store timestamps in UTC, and check how the scheduler behaves at a daylight-saving transition. Avoid scheduling critical jobs in the repeated or skipped hour.
What should happen if a run fails?+
Exit non-zero, alert the owner, keep the previous good output in place, and record the failure in the log. Rerun the intended period after the cause is fixed rather than letting the next scheduled run overwrite the gap.
Need help applying this?
Improve your data cleanup & pipelines.
Review the service scope or tell us what you are trying to improve. We will reply with a clear next step.
Free KPI definition templateRelated reading
Git for analysts: versioning SQL and scripts
How analysts can use Git to version SQL, Python, and reporting logic: repositories, commits, branches, review, and secrets.
Reading Excel files with pandas and openpyxl
How to read Excel files reliably with pandas and openpyxl: choose the engine, target the right sheet and header, protect types, and validate.