Skip to content

Add the addCronTask helper to make registering cron tasks easier#982

Merged
thgreasi merged 1 commit into
masterfrom
addScheduledTask
Feb 26, 2026
Merged

Add the addCronTask helper to make registering cron tasks easier#982
thgreasi merged 1 commit into
masterfrom
addScheduledTask

Conversation

@thgreasi
Copy link
Copy Markdown
Member

@thgreasi thgreasi commented Feb 2, 2026

@thgreasi thgreasi force-pushed the addScheduledTask branch 4 times, most recently from f3b7f73 to 099e1d2 Compare February 5, 2026 08:45
@thgreasi thgreasi changed the title Add the addScheduledTask helper to make registering cron tasks easier Add the addCronTask helper to make registering cron tasks easier Feb 5, 2026
@thgreasi thgreasi force-pushed the addScheduledTask branch 2 times, most recently from eb3f6ff to 8fc5ae7 Compare February 5, 2026 10:10
@thgreasi thgreasi requested a review from a team February 5, 2026 10:18
@thgreasi thgreasi force-pushed the addScheduledTask branch 6 times, most recently from 6d1b20a to e48eb3b Compare February 5, 2026 13:40
@thgreasi thgreasi marked this pull request as ready for review February 5, 2026 15:02
@thgreasi thgreasi marked this pull request as draft February 5, 2026 15:02
Comment thread src/tasks/index.ts
Comment thread src/tasks/index.ts
@Page-
Copy link
Copy Markdown
Contributor

Page- commented Feb 12, 2026

/rebase

Comment thread src/tasks/tasks.sbvr
Necessity: each task has exactly one attempt limit
Necessity: each task has an attempt limit that is greater than or equal to 1

Rule: It is necessary that each handler that executes a task that is scheduled with a cron expression and has a status that is equal to "queued", executes at most one task that is scheduled with a cron expression and has a status that is equal to "queued".
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this can be simplified to:

Suggested change
Rule: It is necessary that each handler that executes a task that is scheduled with a cron expression and has a status that is equal to "queued", executes at most one task that is scheduled with a cron expression and has a status that is equal to "queued".
Rule: It is necessary that each handler executes at most one task that is scheduled with a cron expression and has a status that is equal to "queued".

Copy link
Copy Markdown
Member Author

@thgreasi thgreasi Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Page- It seems that the affected IDs optimization doesn't like it that rule (I also tried adding a comma, but that lead to "no match" errors):

Error: Only queries in the form of:
	SELECT (SELECT COUNT(*) ...) = 0
	SELECT NOT EXISTS (SELECT ...)
are supported for affected IDs insertion.

I also realized that this should also work (the sentence makes sense) but
a) Afaict filtering on both sides generates much faster queries, since allows the db to use indexes & cut down rows much sooner.
If we fake the generated sql (by filtering on the fields and then commenting them out), the generated query is prohibitively slow:

SELECT NOT EXISTS (
	SELECT "task.1"."is executed by-handler"
	FROM "task" AS "task.1"
	WHERE TRUE
-- 	AND "task.1"."is scheduled with-cron expression" IS NOT NULL
-- 	AND 'queued' = "task.1"."status"
-- 	AND "task.1"."status" IS NOT NULL
	AND (
		SELECT COUNT(*)
		FROM "task" AS "task.4"
		WHERE "task.4"."is scheduled with-cron expression" IS NOT NULL
		AND 'queued' = "task.4"."status"
		AND "task.4"."status" IS NOT NULL
		AND "task.4"."is executed by-handler" = "task.1"."is executed by-handler"
	) >= 2
) AS "result";
QUERY PLAN
Result  (cost=4.90..4.91 rows=1 width=1) (actual time=25883.233..25883.235 rows=1 loops=1)
  Output: (NOT (InitPlan 2).col1)
  InitPlan 2
    ->  Seq Scan on public.task task_1  (cost=0.00..6711290.02 rows=1368708 width=0) (actual time=25883.231..25883.231 rows=0 loops=1)
          Filter: ((SubPlan 1) >= 2)
          Rows Removed by Filter: 4112852
          SubPlan 1
            ->  Aggregate  (cost=1.59..1.60 rows=1 width=8) (actual time=0.006..0.006 rows=1 loops=4112852)
                  Output: count(*)
                  ->  Index Scan using idx_task_poll on public.task  (cost=0.12..1.58 rows=1 width=0) (actual time=0.005..0.005 rows=0 loops=4112852)
                        Index Cond: ((task."is executed by-handler")::text = (task_1."is executed by-handler")::text)
                        Filter: (task."is scheduled with-cron expression" IS NOT NULL)
Planning Time: 0.326 ms
Execution Time: 25883.270 ms

on the other hand w/ the filters present on both sides the query is much faster:

QUERY PLAN
Result  (cost=3.18..3.19 rows=1 width=1) (actual time=5.364..5.365 rows=1 loops=1)
  Output: (NOT (InitPlan 2).col1)
  InitPlan 2
    ->  Index Scan using idx_task_poll on public.task task_1  (cost=0.12..3.18 rows=1 width=0) (actual time=5.362..5.363 rows=0 loops=1)
          Filter: ((task_1."is scheduled with-cron expression" IS NOT NULL) AND ((SubPlan 1) >= 2))
          SubPlan 1
            ->  Aggregate  (cost=1.59..1.60 rows=1 width=8) (never executed)
                  Output: count(*)
                  ->  Index Scan using idx_task_poll on public.task  (cost=0.12..1.58 rows=1 width=0) (never executed)
                        Index Cond: ((task."is executed by-handler")::text = (task_1."is executed by-handler")::text)
                        Filter: (task."is scheduled with-cron expression" IS NOT NULL)
Planning Time: 0.305 ms
Execution Time: 5.393 ms

b) I followed the pattern that we use in all other cases where we mirror the where clauses in both sides.

Necessity: each name (Auth) that is of an application membership role that belongs to an organization, is of at most one application membership role that belongs to the organization.
Rule: It is necessary that each application that owns a release1 that has a status that is equal to "success" and is not invalidated and has a release version, owns at most one release2 that has a status that is equal to "success" and is not invalidated and has a release version that is of the release1.
Rule: It is necessary that each image that is produced by a delta1 that has a status that is equal to "success" and originates from an image1, is produced by at most one delta that originates from the image1 and has a version that is of the delta1 and has a status that is equal to "success".

fwiw, given the benefits of the pattern of having the "where clauses" on both sides & that we use it in all such rules atm, it's the pattern that my partial unique index auto generation PR v is attempting to detect:
balena-io-modules/abstract-sql-compiler#316

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I realized that it's specifically the pattern of each $column that is of ... $table ..., is of ... $table ... that needs to happen, since in this case handler isn't really something that exists in its own right and so not having the that predicate means things get particularly weird with the sql generation

@thgreasi thgreasi requested a review from Page- February 16, 2026 09:41
@thgreasi
Copy link
Copy Markdown
Member Author

/rebase

@thgreasi
Copy link
Copy Markdown
Member Author

/rebase

Comment thread src/tasks/tasks.sbvr
Necessity: each task has exactly one attempt limit
Necessity: each task has an attempt limit that is greater than or equal to 1

Rule: It is necessary that each handler that executes a task that is scheduled with a cron expression and has a status that is equal to "queued", executes at most one task that is scheduled with a cron expression and has a status that is equal to "queued".
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I realized that it's specifically the pattern of each $column that is of ... $table ..., is of ... $table ... that needs to happen, since in this case handler isn't really something that exists in its own right and so not having the that predicate means things get particularly weird with the sql generation

@thgreasi
Copy link
Copy Markdown
Member Author

/rebae

@thgreasi
Copy link
Copy Markdown
Member Author

/rebase

@thgreasi thgreasi marked this pull request as ready for review February 26, 2026 16:51
@thgreasi thgreasi merged commit 95b04e0 into master Feb 26, 2026
55 checks passed
@thgreasi thgreasi deleted the addScheduledTask branch February 26, 2026 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants