Feat/2393 durable automation runs - #2457
Open
BelhsanHmida wants to merge 8 commits into
Open
Conversation
An automation occurrence currently leaves no trace of its own: the only durable marker is the automation's scheduling cursor, which says an occurrence was attempted, not what happened to it. Record each occurrence a runner picks up as an AutomationRun, with the attempts made on it (AutomationRunAttempt) and the jobs it intends to create (AutomationRunJob). Dispatch progress and worker execution outcome are tracked separately, because 'queued everything' and 'the jobs succeeded' are different questions an operator needs answered. The database enforces one run per automation, occurrence and schedule revision, and one job intent per run and logical job key. The new schedule revision on Automation keeps runs of an edited or reactivated schedule apart from the runs of the schedule it replaced, even at the same scheduled UTC time. All run timestamps are validated to be timezone-aware and stored as UTC. Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
…partial dispatch The runner used to advance and commit the scheduling cursor before queueing anything, guarded only by a Redis key with a two-minute TTL. A failure before the first enqueue therefore lost the occurrence for good, while retrying a partial enqueue could have duplicated work. Claim each due occurrence into an AutomationRun instead, write down the plan for the run before the first enqueue, and give every intended job a logical key and, from it, a deterministic RQ job ID. A retry replays that stored plan: jobs whose IDs are already in Redis are recognised and left alone, and only the missing ones are queued. Because the plan holds the parameters and timings the occurrence was planned with, a retry hours later still dispatches the occurrence as originally intended, even if the automation has been edited since. Ownership is a database lease, not a Redis key. An occurrence is only picked up by another runner once the lease of the runner holding it has expired, which is how a runner that died mid-queueing hands its work over. A runner that fails releases its own lease, so its run is retryable at once. Dispatch is finished only when it is marked complete, so a crash between the last enqueue and that mark is finalized by the next runner rather than left hanging. Forecast cycle and wrap-up jobs now carry their run identity and report their own start, success and failure back to it, so the execution outcome outlives the Redis jobs. Queueing the jobs of a pipeline run moved out of the already long run() into its own methods, which also removes the duplicate queueing path. Editing an automation's cron string or timezone, or reactivating it, counts up its schedule revision, so runs of the old and new schedule stay distinct. Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Job counts came from Redis alone, so once its jobs expired there was no way to tell whether an occurrence had failed before queueing anything, queued only part of its work, or queued everything and then failed while computing. Add a run_stats object to the automation detail response, summarizing the automation's durable runs and describing the recent ones: their occurrence, dispatch and execution state, attempt count, intended and queued job counts, timestamps, last error, latest attempt, and the jobs they created. Automation responses also expose schedule_revision. The automation details panel shows the latest run alongside the recent Redis jobs. Both additions are additive: no existing field changed. Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
The forecasting docs and the CLI change log stated that a failed or partially completed queueing attempt is never retried, which no longer holds. Describe how an occurrence is claimed, planned, dispatched and retried instead, and what the dispatch and execution states mean, along with the changelog entries for the new run status in the API and the UI. Those two stale CLI change log lines are corrected rather than left standing: they describe an unreleased release, so leaving them would ship a change log that contradicts itself. Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
The tuple of statuses that count a job intent as dispatched was written out in the model and, unused, in the service. Define it once next to the model that asks the question, and drop the copy nobody read. Also apply black to the migration and drop an unused import from the new test module. Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
…he transaction Running an automation against a real database and a real Redis turned up two ways the durable execution record lied about what happened. A cycle job that failed on the database itself left the session in an aborted transaction, so recording its failure was refused and the job stayed 'running' forever. Roll back before recording, the way dispatch failures already do; the job's uncommitted work is lost either way, since it is failing. A later job succeeding also reset the run to 'running', burying an earlier failure: the wrap-up job succeeds whatever became of the cycle jobs it reports on. Derive the run's execution state from all of its jobs instead, so a failed job keeps the run failed. Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
The entry linked the issue, because no PR existed when it was written. Point it at PR #2457 instead, which is what the changelog convention asks for. Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
Documentation build overview
11 files changed ·
|
…s test test_post_sensor_data_twice registers a 'handle_error' listener on the Engine class, which is global and process-wide, and never removes it. Every database error raised by any later test therefore ran its assertion that the error is an IntegrityError, so a test which provokes a different error fails inside SQLAlchemy rather than where it looks. Remove the listener in a finally block, so it only covers the posts it is about. Signed-off-by: Mohamed Belhsan Hmida <mohamedbelhsanhmida@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
automation_run(one row per scheduled run, unique on(automation_id, scheduled_at, schedule_revision)),automation_run_job(the jobs a run intends to create, with deterministic RQ job IDs) andautomation_run_attempt(one row per dispatch attempt).Automation.schedule_revision, counted up wheneverflexmeasures edit automationrebases the cursor, so runs of the old and the new schedule stay distinct.SET NXguard to the database, keeping the same cursor compare-and-swap. Removesclaim_due_automation()and the Redis guard.pending,claimed,partially_queued,queued,failed) separately from execution state (pending,running,succeeded,failed,canceled), the latter derived from all job intents so a late success cannot hide an earlier failure.schedule_revisionand arun_statsobject to the automations API, and the same run status to the asset's Automations page. Backward compatible: no existing field changed.flexmeasures jobs run-automationsreport the run and attempt it works on, and retry runs whose queueing did not finish.documentation/changelog.rstLook & Feel
flexmeasures jobs run-automations, on a run whose previous attempt queued only part of its jobs:and when an attempt fails, naming the run and the attempt rather than just the automation:
[GET] /assets/(id)/automations/(automation_id)gainsrun_stats:The automation details modal on the asset's Automations page shows the schedule revision and a Durable runs block with the same totals, dispatch and execution breakdown, and latest run — next to the existing Recently created jobs counts, which come from Redis and disappear as jobs expire, whereas the run records do not.
How to test
Automated coverage:
Related Items
Closes #2393.
Sign-off