@pgpm/database-jobs 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -6,6 +6,53 @@ GRANT USAGE ON SCHEMA app_jobs TO administrator;
|
|
|
6
6
|
ALTER DEFAULT PRIVILEGES IN SCHEMA app_jobs
|
|
7
7
|
GRANT EXECUTE ON FUNCTIONS TO administrator;
|
|
8
8
|
|
|
9
|
+
CREATE FUNCTION app_jobs.tg_update_timestamps() RETURNS trigger AS $EOFCODE$
|
|
10
|
+
BEGIN
|
|
11
|
+
IF TG_OP = 'INSERT' THEN
|
|
12
|
+
NEW.created_at = NOW();
|
|
13
|
+
NEW.updated_at = NOW();
|
|
14
|
+
ELSIF TG_OP = 'UPDATE' THEN
|
|
15
|
+
NEW.created_at = OLD.created_at;
|
|
16
|
+
NEW.updated_at = greatest (now(), OLD.updated_at + interval '1 millisecond');
|
|
17
|
+
END IF;
|
|
18
|
+
RETURN NEW;
|
|
19
|
+
END;
|
|
20
|
+
$EOFCODE$ LANGUAGE plpgsql;
|
|
21
|
+
|
|
22
|
+
CREATE FUNCTION app_jobs.tg_add_job_with_row_id() RETURNS trigger AS $EOFCODE$
|
|
23
|
+
BEGIN
|
|
24
|
+
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
|
25
|
+
PERFORM
|
|
26
|
+
app_jobs.add_job (jwt_private.current_database_id(), tg_argv[0], json_build_object('id', NEW.id));
|
|
27
|
+
RETURN NEW;
|
|
28
|
+
END IF;
|
|
29
|
+
IF (TG_OP = 'DELETE') THEN
|
|
30
|
+
PERFORM
|
|
31
|
+
app_jobs.add_job (jwt_private.current_database_id(), tg_argv[0], json_build_object('id', OLD.id));
|
|
32
|
+
RETURN OLD;
|
|
33
|
+
END IF;
|
|
34
|
+
END;
|
|
35
|
+
$EOFCODE$ LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
|
|
36
|
+
|
|
37
|
+
COMMENT ON FUNCTION app_jobs.tg_add_job_with_row_id IS 'Useful shortcut to create a job on insert or update. Pass the task name as the trigger argument, and the record id will automatically be available on the JSON payload.';
|
|
38
|
+
|
|
39
|
+
CREATE FUNCTION app_jobs.tg_add_job_with_row() RETURNS trigger AS $EOFCODE$
|
|
40
|
+
BEGIN
|
|
41
|
+
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
|
42
|
+
PERFORM
|
|
43
|
+
app_jobs.add_job (jwt_private.current_database_id(), TG_ARGV[0], to_json(NEW));
|
|
44
|
+
RETURN NEW;
|
|
45
|
+
END IF;
|
|
46
|
+
IF (TG_OP = 'DELETE') THEN
|
|
47
|
+
PERFORM
|
|
48
|
+
app_jobs.add_job (jwt_private.current_database_id(), TG_ARGV[0], to_json(OLD));
|
|
49
|
+
RETURN OLD;
|
|
50
|
+
END IF;
|
|
51
|
+
END;
|
|
52
|
+
$EOFCODE$ LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
|
|
53
|
+
|
|
54
|
+
COMMENT ON FUNCTION app_jobs.tg_add_job_with_row IS 'Useful shortcut to create a job on insert or update. Pass the task name as the trigger argument, and the record data will automatically be available on the JSON payload.';
|
|
55
|
+
|
|
9
56
|
CREATE FUNCTION app_jobs.json_build_object_apply(arguments text[]) RETURNS pg_catalog.json AS $EOFCODE$
|
|
10
57
|
DECLARE
|
|
11
58
|
arg text;
|
|
@@ -26,20 +73,59 @@ BEGIN
|
|
|
26
73
|
END;
|
|
27
74
|
$EOFCODE$ LANGUAGE plpgsql;
|
|
28
75
|
|
|
29
|
-
CREATE
|
|
76
|
+
CREATE FUNCTION app_jobs.trigger_job_with_fields() RETURNS trigger AS $EOFCODE$
|
|
77
|
+
DECLARE
|
|
78
|
+
arg text;
|
|
79
|
+
fn text;
|
|
80
|
+
i int;
|
|
81
|
+
args text[];
|
|
82
|
+
BEGIN
|
|
83
|
+
FOR i IN
|
|
84
|
+
SELECT
|
|
85
|
+
*
|
|
86
|
+
FROM
|
|
87
|
+
generate_series(1, TG_NARGS) g (i)
|
|
88
|
+
LOOP
|
|
89
|
+
IF (i = 1) THEN
|
|
90
|
+
fn = TG_ARGV[i - 1];
|
|
91
|
+
ELSE
|
|
92
|
+
args = array_append(args, TG_ARGV[i - 1]);
|
|
93
|
+
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
|
94
|
+
EXECUTE format('SELECT ($1).%s::text', TG_ARGV[i - 1])
|
|
95
|
+
USING NEW INTO arg;
|
|
96
|
+
END IF;
|
|
97
|
+
IF (TG_OP = 'DELETE') THEN
|
|
98
|
+
EXECUTE format('SELECT ($1).%s::text', TG_ARGV[i - 1])
|
|
99
|
+
USING OLD INTO arg;
|
|
100
|
+
END IF;
|
|
101
|
+
args = array_append(args, arg);
|
|
102
|
+
END IF;
|
|
103
|
+
END LOOP;
|
|
104
|
+
PERFORM
|
|
105
|
+
app_jobs.add_job (jwt_private.current_database_id(), fn, app_jobs.json_build_object_apply (args));
|
|
106
|
+
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
|
107
|
+
RETURN NEW;
|
|
108
|
+
END IF;
|
|
109
|
+
IF (TG_OP = 'DELETE') THEN
|
|
110
|
+
RETURN OLD;
|
|
111
|
+
END IF;
|
|
112
|
+
END;
|
|
113
|
+
$EOFCODE$ LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
|
|
114
|
+
|
|
115
|
+
CREATE TABLE app_jobs.scheduled_jobs (
|
|
30
116
|
id bigserial PRIMARY KEY,
|
|
31
117
|
database_id uuid NOT NULL,
|
|
32
118
|
queue_name text DEFAULT public.gen_random_uuid()::text,
|
|
33
119
|
task_identifier text NOT NULL,
|
|
34
120
|
payload pg_catalog.json DEFAULT '{}'::json NOT NULL,
|
|
35
121
|
priority int DEFAULT 0 NOT NULL,
|
|
36
|
-
run_at timestamptz DEFAULT now() NOT NULL,
|
|
37
|
-
attempts int DEFAULT 0 NOT NULL,
|
|
38
122
|
max_attempts int DEFAULT 25 NOT NULL,
|
|
39
123
|
key text,
|
|
40
|
-
last_error text,
|
|
41
124
|
locked_at timestamptz,
|
|
42
125
|
locked_by text,
|
|
126
|
+
schedule_info pg_catalog.json NOT NULL,
|
|
127
|
+
last_scheduled timestamptz,
|
|
128
|
+
last_scheduled_id bigint,
|
|
43
129
|
CHECK (length(key) < 513),
|
|
44
130
|
CHECK (length(task_identifier) < 127),
|
|
45
131
|
CHECK (max_attempts > 0),
|
|
@@ -48,110 +134,40 @@ CREATE TABLE app_jobs.jobs (
|
|
|
48
134
|
UNIQUE (key)
|
|
49
135
|
);
|
|
50
136
|
|
|
51
|
-
CREATE
|
|
52
|
-
queue_name text NOT NULL PRIMARY KEY,
|
|
53
|
-
job_count int DEFAULT 0 NOT NULL,
|
|
54
|
-
locked_at timestamptz,
|
|
55
|
-
locked_by text
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
CREATE FUNCTION app_jobs.add_job(db_id uuid, identifier text, payload pg_catalog.json DEFAULT '{}'::json, job_key text DEFAULT NULL, queue_name text DEFAULT NULL, run_at timestamptz DEFAULT now(), max_attempts int DEFAULT 25, priority int DEFAULT 0) RETURNS app_jobs.jobs AS $EOFCODE$
|
|
59
|
-
DECLARE
|
|
60
|
-
v_job app_jobs.jobs;
|
|
137
|
+
CREATE FUNCTION app_jobs.do_notify() RETURNS trigger AS $EOFCODE$
|
|
61
138
|
BEGIN
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
payload,
|
|
68
|
-
queue_name,
|
|
69
|
-
run_at,
|
|
70
|
-
max_attempts,
|
|
71
|
-
key,
|
|
72
|
-
priority
|
|
73
|
-
) VALUES (
|
|
74
|
-
db_id,
|
|
75
|
-
identifier,
|
|
76
|
-
coalesce(payload,
|
|
77
|
-
'{}'::json),
|
|
78
|
-
queue_name,
|
|
79
|
-
coalesce(run_at, now()),
|
|
80
|
-
coalesce(max_attempts, 25),
|
|
81
|
-
job_key,
|
|
82
|
-
coalesce(priority, 0)
|
|
83
|
-
)
|
|
84
|
-
ON CONFLICT (key)
|
|
85
|
-
DO UPDATE SET
|
|
86
|
-
task_identifier = EXCLUDED.task_identifier,
|
|
87
|
-
payload = EXCLUDED.payload,
|
|
88
|
-
queue_name = EXCLUDED.queue_name,
|
|
89
|
-
max_attempts = EXCLUDED.max_attempts,
|
|
90
|
-
run_at = EXCLUDED.run_at,
|
|
91
|
-
priority = EXCLUDED.priority,
|
|
92
|
-
-- always reset error/retry state
|
|
93
|
-
attempts = 0, last_error = NULL
|
|
94
|
-
WHERE
|
|
95
|
-
jobs.locked_at IS NULL
|
|
96
|
-
RETURNING
|
|
97
|
-
* INTO v_job;
|
|
98
|
-
|
|
99
|
-
-- If upsert succeeded (insert or update), return early
|
|
100
|
-
|
|
101
|
-
IF NOT (v_job IS NULL) THEN
|
|
102
|
-
RETURN v_job;
|
|
103
|
-
END IF;
|
|
139
|
+
PERFORM
|
|
140
|
+
pg_notify(TG_ARGV[0], '');
|
|
141
|
+
RETURN NEW;
|
|
142
|
+
END;
|
|
143
|
+
$EOFCODE$ LANGUAGE plpgsql;
|
|
104
144
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
145
|
+
CREATE TRIGGER _900_notify_scheduled_job
|
|
146
|
+
AFTER INSERT
|
|
147
|
+
ON app_jobs.scheduled_jobs
|
|
148
|
+
FOR EACH ROW
|
|
149
|
+
EXECUTE PROCEDURE app_jobs.do_notify('scheduled_jobs:insert');
|
|
108
150
|
|
|
109
|
-
|
|
110
|
-
app_jobs.jobs
|
|
111
|
-
SET
|
|
112
|
-
KEY = NULL,
|
|
113
|
-
attempts = jobs.max_attempts
|
|
114
|
-
WHERE
|
|
115
|
-
KEY = job_key;
|
|
116
|
-
END IF;
|
|
151
|
+
CREATE INDEX scheduled_jobs_priority_id_idx ON app_jobs.scheduled_jobs (priority, id);
|
|
117
152
|
|
|
118
|
-
|
|
119
|
-
database_id,
|
|
120
|
-
task_identifier,
|
|
121
|
-
payload,
|
|
122
|
-
queue_name,
|
|
123
|
-
run_at,
|
|
124
|
-
max_attempts,
|
|
125
|
-
priority
|
|
126
|
-
) VALUES (
|
|
127
|
-
db_id,
|
|
128
|
-
identifier,
|
|
129
|
-
payload,
|
|
130
|
-
queue_name,
|
|
131
|
-
run_at,
|
|
132
|
-
max_attempts,
|
|
133
|
-
priority
|
|
134
|
-
)
|
|
135
|
-
RETURNING * INTO v_job;
|
|
153
|
+
CREATE INDEX scheduled_jobs_locked_by_idx ON app_jobs.scheduled_jobs (locked_by);
|
|
136
154
|
|
|
137
|
-
|
|
138
|
-
END;
|
|
139
|
-
$EOFCODE$ LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
|
|
155
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON app_jobs.scheduled_jobs TO administrator;
|
|
140
156
|
|
|
141
|
-
CREATE TABLE app_jobs.
|
|
157
|
+
CREATE TABLE app_jobs.jobs (
|
|
142
158
|
id bigserial PRIMARY KEY,
|
|
143
159
|
database_id uuid NOT NULL,
|
|
144
160
|
queue_name text DEFAULT public.gen_random_uuid()::text,
|
|
145
161
|
task_identifier text NOT NULL,
|
|
146
162
|
payload pg_catalog.json DEFAULT '{}'::json NOT NULL,
|
|
147
163
|
priority int DEFAULT 0 NOT NULL,
|
|
164
|
+
run_at timestamptz DEFAULT now() NOT NULL,
|
|
165
|
+
attempts int DEFAULT 0 NOT NULL,
|
|
148
166
|
max_attempts int DEFAULT 25 NOT NULL,
|
|
149
167
|
key text,
|
|
168
|
+
last_error text,
|
|
150
169
|
locked_at timestamptz,
|
|
151
170
|
locked_by text,
|
|
152
|
-
schedule_info pg_catalog.json NOT NULL,
|
|
153
|
-
last_scheduled timestamptz,
|
|
154
|
-
last_scheduled_id bigint,
|
|
155
171
|
CHECK (length(key) < 513),
|
|
156
172
|
CHECK (length(task_identifier) < 127),
|
|
157
173
|
CHECK (max_attempts > 0),
|
|
@@ -160,366 +176,122 @@ CREATE TABLE app_jobs.scheduled_jobs (
|
|
|
160
176
|
UNIQUE (key)
|
|
161
177
|
);
|
|
162
178
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
v_job app_jobs.scheduled_jobs;
|
|
166
|
-
BEGIN
|
|
167
|
-
IF job_key IS NOT NULL THEN
|
|
179
|
+
ALTER TABLE app_jobs.jobs
|
|
180
|
+
ADD COLUMN created_at timestamptz;
|
|
168
181
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
database_id,
|
|
172
|
-
task_identifier,
|
|
173
|
-
payload,
|
|
174
|
-
queue_name,
|
|
175
|
-
schedule_info,
|
|
176
|
-
max_attempts,
|
|
177
|
-
key,
|
|
178
|
-
priority
|
|
179
|
-
) VALUES (
|
|
180
|
-
db_id,
|
|
181
|
-
identifier,
|
|
182
|
-
coalesce(payload, '{}'::json),
|
|
183
|
-
queue_name,
|
|
184
|
-
schedule_info,
|
|
185
|
-
coalesce(max_attempts, 25),
|
|
186
|
-
job_key,
|
|
187
|
-
coalesce(priority, 0)
|
|
188
|
-
)
|
|
189
|
-
ON CONFLICT (key)
|
|
190
|
-
DO UPDATE SET
|
|
191
|
-
task_identifier = EXCLUDED.task_identifier,
|
|
192
|
-
payload = EXCLUDED.payload,
|
|
193
|
-
queue_name = EXCLUDED.queue_name,
|
|
194
|
-
max_attempts = EXCLUDED.max_attempts,
|
|
195
|
-
schedule_info = EXCLUDED.schedule_info,
|
|
196
|
-
priority = EXCLUDED.priority
|
|
197
|
-
WHERE
|
|
198
|
-
scheduled_jobs.locked_at IS NULL
|
|
199
|
-
RETURNING
|
|
200
|
-
* INTO v_job;
|
|
182
|
+
ALTER TABLE app_jobs.jobs
|
|
183
|
+
ALTER COLUMN created_at SET DEFAULT now();
|
|
201
184
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
IF NOT (v_job IS NULL) THEN
|
|
205
|
-
RETURN v_job;
|
|
206
|
-
END IF;
|
|
185
|
+
ALTER TABLE app_jobs.jobs
|
|
186
|
+
ADD COLUMN updated_at timestamptz;
|
|
207
187
|
|
|
208
|
-
|
|
209
|
-
|
|
188
|
+
ALTER TABLE app_jobs.jobs
|
|
189
|
+
ALTER COLUMN updated_at SET DEFAULT now();
|
|
210
190
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
INSERT INTO app_jobs.scheduled_jobs (
|
|
218
|
-
database_id,
|
|
219
|
-
task_identifier,
|
|
220
|
-
payload,
|
|
221
|
-
queue_name,
|
|
222
|
-
schedule_info,
|
|
223
|
-
max_attempts,
|
|
224
|
-
priority
|
|
225
|
-
) VALUES (
|
|
226
|
-
db_id,
|
|
227
|
-
identifier,
|
|
228
|
-
payload,
|
|
229
|
-
queue_name,
|
|
230
|
-
schedule_info,
|
|
231
|
-
max_attempts,
|
|
232
|
-
priority
|
|
233
|
-
) RETURNING * INTO v_job;
|
|
234
|
-
RETURN v_job;
|
|
235
|
-
END;
|
|
236
|
-
$EOFCODE$ LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
|
|
191
|
+
CREATE TRIGGER _100_update_jobs_modtime_tg
|
|
192
|
+
BEFORE INSERT OR UPDATE
|
|
193
|
+
ON app_jobs.jobs
|
|
194
|
+
FOR EACH ROW
|
|
195
|
+
EXECUTE PROCEDURE app_jobs.tg_update_timestamps();
|
|
237
196
|
|
|
238
|
-
CREATE FUNCTION app_jobs.
|
|
239
|
-
DECLARE
|
|
240
|
-
v_row app_jobs.jobs;
|
|
197
|
+
CREATE FUNCTION app_jobs.tg_increase_job_queue_count() RETURNS trigger AS $EOFCODE$
|
|
241
198
|
BEGIN
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
app_jobs.job_queues
|
|
249
|
-
SET
|
|
250
|
-
locked_by = NULL,
|
|
251
|
-
locked_at = NULL
|
|
252
|
-
WHERE
|
|
253
|
-
queue_name = v_row.queue_name
|
|
254
|
-
AND locked_by = worker_id;
|
|
255
|
-
END IF;
|
|
256
|
-
RETURN v_row;
|
|
199
|
+
INSERT INTO app_jobs.job_queues (queue_name, job_count)
|
|
200
|
+
VALUES (NEW.queue_name, 1)
|
|
201
|
+
ON CONFLICT (queue_name)
|
|
202
|
+
DO UPDATE SET
|
|
203
|
+
job_count = job_queues.job_count + 1;
|
|
204
|
+
RETURN NEW;
|
|
257
205
|
END;
|
|
258
|
-
$EOFCODE
|
|
206
|
+
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
|
|
259
207
|
|
|
260
|
-
CREATE
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
*;
|
|
267
|
-
$EOFCODE$;
|
|
208
|
+
CREATE TRIGGER _500_increase_job_queue_count_on_insert
|
|
209
|
+
AFTER INSERT
|
|
210
|
+
ON app_jobs.jobs
|
|
211
|
+
FOR EACH ROW
|
|
212
|
+
WHEN (new.queue_name IS NOT NULL)
|
|
213
|
+
EXECUTE PROCEDURE app_jobs.tg_increase_job_queue_count();
|
|
268
214
|
|
|
269
|
-
CREATE
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
215
|
+
CREATE TRIGGER _500_increase_job_queue_count_on_update
|
|
216
|
+
AFTER UPDATE OF queue_name
|
|
217
|
+
ON app_jobs.jobs
|
|
218
|
+
FOR EACH ROW
|
|
219
|
+
WHEN (new.queue_name IS DISTINCT FROM old.queue_name
|
|
220
|
+
AND new.queue_name IS NOT NULL)
|
|
221
|
+
EXECUTE PROCEDURE app_jobs.tg_increase_job_queue_count();
|
|
276
222
|
|
|
277
|
-
CREATE
|
|
223
|
+
CREATE TRIGGER _900_notify_worker
|
|
224
|
+
AFTER INSERT
|
|
225
|
+
ON app_jobs.jobs
|
|
226
|
+
FOR EACH ROW
|
|
227
|
+
EXECUTE PROCEDURE app_jobs.do_notify('jobs:insert');
|
|
228
|
+
|
|
229
|
+
CREATE FUNCTION app_jobs.tg_decrease_job_queue_count() RETURNS trigger AS $EOFCODE$
|
|
278
230
|
DECLARE
|
|
279
|
-
|
|
231
|
+
v_new_job_count int;
|
|
280
232
|
BEGIN
|
|
281
233
|
UPDATE
|
|
282
|
-
app_jobs.
|
|
234
|
+
app_jobs.job_queues
|
|
283
235
|
SET
|
|
284
|
-
|
|
285
|
-
run_at = greatest (now(), run_at) + (exp(least (attempts, 10))::text || ' seconds')::interval,
|
|
286
|
-
locked_by = NULL,
|
|
287
|
-
locked_at = NULL
|
|
236
|
+
job_count = job_queues.job_count - 1
|
|
288
237
|
WHERE
|
|
289
|
-
|
|
290
|
-
AND locked_by = worker_id
|
|
238
|
+
queue_name = OLD.queue_name
|
|
291
239
|
RETURNING
|
|
292
|
-
|
|
293
|
-
IF
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
locked_by = NULL,
|
|
298
|
-
locked_at = NULL
|
|
299
|
-
WHERE
|
|
300
|
-
queue_name = v_row.queue_name
|
|
301
|
-
AND locked_by = worker_id;
|
|
240
|
+
job_count INTO v_new_job_count;
|
|
241
|
+
IF v_new_job_count <= 0 THEN
|
|
242
|
+
DELETE FROM app_jobs.job_queues
|
|
243
|
+
WHERE queue_name = OLD.queue_name
|
|
244
|
+
AND job_count <= 0;
|
|
302
245
|
END IF;
|
|
303
|
-
RETURN
|
|
246
|
+
RETURN OLD;
|
|
304
247
|
END;
|
|
305
|
-
$EOFCODE
|
|
306
|
-
|
|
307
|
-
CREATE FUNCTION app_jobs.get_job(worker_id text, task_identifiers text[] DEFAULT NULL, job_expiry interval DEFAULT '4 hours') RETURNS app_jobs.jobs LANGUAGE plpgsql AS $EOFCODE$
|
|
308
|
-
DECLARE
|
|
309
|
-
v_job_id bigint;
|
|
310
|
-
v_queue_name text;
|
|
311
|
-
v_row app_jobs.jobs;
|
|
312
|
-
v_now timestamptz = now();
|
|
313
|
-
BEGIN
|
|
314
|
-
|
|
315
|
-
IF worker_id IS NULL THEN
|
|
316
|
-
RAISE exception 'INVALID_WORKER_ID';
|
|
317
|
-
END IF;
|
|
318
|
-
|
|
319
|
-
--
|
|
248
|
+
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
|
|
320
249
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
WHERE (jobs.locked_at IS NULL
|
|
328
|
-
OR jobs.locked_at < (v_now - job_expiry))
|
|
329
|
-
AND (jobs.queue_name IS NULL
|
|
330
|
-
OR EXISTS (
|
|
331
|
-
SELECT
|
|
332
|
-
1
|
|
333
|
-
FROM
|
|
334
|
-
app_jobs.job_queues
|
|
335
|
-
WHERE
|
|
336
|
-
job_queues.queue_name = jobs.queue_name
|
|
337
|
-
AND (job_queues.locked_at IS NULL
|
|
338
|
-
OR job_queues.locked_at < (v_now - job_expiry))
|
|
339
|
-
FOR UPDATE
|
|
340
|
-
SKIP LOCKED))
|
|
341
|
-
AND run_at <= v_now
|
|
342
|
-
AND attempts < max_attempts
|
|
343
|
-
AND (task_identifiers IS NULL
|
|
344
|
-
OR task_identifier = ANY (task_identifiers))
|
|
345
|
-
ORDER BY
|
|
346
|
-
priority ASC,
|
|
347
|
-
run_at ASC,
|
|
348
|
-
id ASC
|
|
349
|
-
LIMIT 1
|
|
350
|
-
FOR UPDATE
|
|
351
|
-
SKIP LOCKED;
|
|
250
|
+
CREATE TRIGGER decrease_job_queue_count_on_delete
|
|
251
|
+
AFTER DELETE
|
|
252
|
+
ON app_jobs.jobs
|
|
253
|
+
FOR EACH ROW
|
|
254
|
+
WHEN (old.queue_name IS NOT NULL)
|
|
255
|
+
EXECUTE PROCEDURE app_jobs.tg_decrease_job_queue_count();
|
|
352
256
|
|
|
353
|
-
|
|
257
|
+
CREATE TRIGGER decrease_job_queue_count_on_update
|
|
258
|
+
AFTER UPDATE OF queue_name
|
|
259
|
+
ON app_jobs.jobs
|
|
260
|
+
FOR EACH ROW
|
|
261
|
+
WHEN (new.queue_name IS DISTINCT FROM old.queue_name
|
|
262
|
+
AND old.queue_name IS NOT NULL)
|
|
263
|
+
EXECUTE PROCEDURE app_jobs.tg_decrease_job_queue_count();
|
|
354
264
|
|
|
355
|
-
|
|
356
|
-
RETURN NULL;
|
|
357
|
-
END IF;
|
|
265
|
+
CREATE INDEX priority_run_at_id_idx ON app_jobs.jobs (priority, run_at, id);
|
|
358
266
|
|
|
359
|
-
|
|
267
|
+
CREATE INDEX jobs_locked_by_idx ON app_jobs.jobs (locked_by);
|
|
360
268
|
|
|
361
|
-
|
|
362
|
-
UPDATE
|
|
363
|
-
app_jobs.job_queues
|
|
364
|
-
SET
|
|
365
|
-
locked_by = worker_id,
|
|
366
|
-
locked_at = v_now
|
|
367
|
-
WHERE
|
|
368
|
-
job_queues.queue_name = v_queue_name;
|
|
369
|
-
END IF;
|
|
269
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON app_jobs.jobs TO administrator;
|
|
370
270
|
|
|
371
|
-
|
|
271
|
+
CREATE TABLE app_jobs.job_queues (
|
|
272
|
+
queue_name text NOT NULL PRIMARY KEY,
|
|
273
|
+
job_count int DEFAULT 0 NOT NULL,
|
|
274
|
+
locked_at timestamptz,
|
|
275
|
+
locked_by text
|
|
276
|
+
);
|
|
372
277
|
|
|
373
|
-
|
|
374
|
-
app_jobs.jobs
|
|
375
|
-
SET
|
|
376
|
-
attempts = attempts + 1,
|
|
377
|
-
locked_by = worker_id,
|
|
378
|
-
locked_at = v_now
|
|
379
|
-
WHERE
|
|
380
|
-
id = v_job_id
|
|
381
|
-
RETURNING
|
|
382
|
-
* INTO v_row;
|
|
278
|
+
CREATE INDEX job_queues_locked_by_idx ON app_jobs.job_queues (locked_by);
|
|
383
279
|
|
|
384
|
-
|
|
385
|
-
RETURN v_row;
|
|
386
|
-
END;
|
|
387
|
-
$EOFCODE$;
|
|
280
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON app_jobs.job_queues TO administrator;
|
|
388
281
|
|
|
389
|
-
CREATE FUNCTION app_jobs.
|
|
282
|
+
CREATE FUNCTION app_jobs.run_scheduled_job(id bigint, job_expiry interval DEFAULT '1 hours') RETURNS app_jobs.jobs AS $EOFCODE$
|
|
390
283
|
DECLARE
|
|
391
|
-
|
|
392
|
-
|
|
284
|
+
j app_jobs.jobs;
|
|
285
|
+
last_id bigint;
|
|
286
|
+
lkd_by text;
|
|
393
287
|
BEGIN
|
|
394
|
-
|
|
395
|
-
--
|
|
396
|
-
|
|
397
|
-
IF worker_id IS NULL THEN
|
|
398
|
-
RAISE exception 'INVALID_WORKER_ID';
|
|
399
|
-
END IF;
|
|
400
|
-
|
|
401
|
-
--
|
|
402
|
-
|
|
288
|
+
-- check last scheduled
|
|
403
289
|
SELECT
|
|
404
|
-
|
|
290
|
+
last_scheduled_id
|
|
405
291
|
FROM
|
|
406
|
-
app_jobs.scheduled_jobs
|
|
407
|
-
WHERE
|
|
408
|
-
|
|
409
|
-
OR task_identifier = ANY (task_identifiers))
|
|
410
|
-
ORDER BY
|
|
411
|
-
priority ASC,
|
|
412
|
-
id ASC
|
|
413
|
-
LIMIT 1
|
|
414
|
-
FOR UPDATE
|
|
415
|
-
SKIP LOCKED;
|
|
416
|
-
|
|
417
|
-
--
|
|
418
|
-
|
|
419
|
-
IF v_job_id IS NULL THEN
|
|
420
|
-
RETURN NULL;
|
|
421
|
-
END IF;
|
|
422
|
-
|
|
423
|
-
--
|
|
424
|
-
|
|
425
|
-
UPDATE
|
|
426
|
-
app_jobs.scheduled_jobs
|
|
427
|
-
SET
|
|
428
|
-
locked_by = worker_id,
|
|
429
|
-
locked_at = NOW()
|
|
430
|
-
WHERE
|
|
431
|
-
id = v_job_id
|
|
432
|
-
RETURNING
|
|
433
|
-
* INTO v_row;
|
|
434
|
-
|
|
435
|
-
--
|
|
436
|
-
|
|
437
|
-
RETURN v_row;
|
|
438
|
-
END;
|
|
439
|
-
$EOFCODE$;
|
|
440
|
-
|
|
441
|
-
CREATE FUNCTION app_jobs.permanently_fail_jobs(job_ids bigint[], error_message text DEFAULT NULL) RETURNS SETOF app_jobs.jobs LANGUAGE sql AS $EOFCODE$
|
|
442
|
-
UPDATE
|
|
443
|
-
app_jobs.jobs
|
|
444
|
-
SET
|
|
445
|
-
last_error = coalesce(error_message, 'Manually marked as failed'),
|
|
446
|
-
attempts = max_attempts
|
|
447
|
-
WHERE
|
|
448
|
-
id = ANY (job_ids)
|
|
449
|
-
AND (locked_by IS NULL
|
|
450
|
-
OR locked_at < NOW() - interval '4 hours')
|
|
451
|
-
RETURNING
|
|
452
|
-
*;
|
|
453
|
-
$EOFCODE$;
|
|
454
|
-
|
|
455
|
-
CREATE FUNCTION app_jobs.release_jobs(worker_id text) RETURNS void AS $EOFCODE$
|
|
456
|
-
DECLARE
|
|
457
|
-
BEGIN
|
|
458
|
-
-- clear the job
|
|
459
|
-
UPDATE
|
|
460
|
-
app_jobs.jobs
|
|
461
|
-
SET
|
|
462
|
-
locked_at = NULL,
|
|
463
|
-
locked_by = NULL,
|
|
464
|
-
attempts = GREATEST (attempts - 1, 0)
|
|
465
|
-
WHERE
|
|
466
|
-
locked_by = worker_id;
|
|
467
|
-
-- clear the queue
|
|
468
|
-
UPDATE
|
|
469
|
-
app_jobs.job_queues
|
|
470
|
-
SET
|
|
471
|
-
locked_at = NULL,
|
|
472
|
-
locked_by = NULL
|
|
473
|
-
WHERE
|
|
474
|
-
locked_by = worker_id;
|
|
475
|
-
END;
|
|
476
|
-
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
|
|
477
|
-
|
|
478
|
-
CREATE FUNCTION app_jobs.release_scheduled_jobs(worker_id text, ids bigint[] DEFAULT NULL) RETURNS void AS $EOFCODE$
|
|
479
|
-
DECLARE
|
|
480
|
-
BEGIN
|
|
481
|
-
-- clear the scheduled job
|
|
482
|
-
UPDATE
|
|
483
|
-
app_jobs.scheduled_jobs s
|
|
484
|
-
SET
|
|
485
|
-
locked_at = NULL,
|
|
486
|
-
locked_by = NULL
|
|
487
|
-
WHERE
|
|
488
|
-
locked_by = worker_id
|
|
489
|
-
AND (ids IS NULL
|
|
490
|
-
OR s.id = ANY (ids));
|
|
491
|
-
END;
|
|
492
|
-
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
|
|
493
|
-
|
|
494
|
-
CREATE FUNCTION app_jobs.reschedule_jobs(job_ids bigint[], run_at timestamptz DEFAULT NULL, priority int DEFAULT NULL, attempts int DEFAULT NULL, max_attempts int DEFAULT NULL) RETURNS SETOF app_jobs.jobs LANGUAGE sql AS $EOFCODE$
|
|
495
|
-
UPDATE
|
|
496
|
-
app_jobs.jobs
|
|
497
|
-
SET
|
|
498
|
-
run_at = coalesce(reschedule_jobs.run_at, jobs.run_at),
|
|
499
|
-
priority = coalesce(reschedule_jobs.priority, jobs.priority),
|
|
500
|
-
attempts = coalesce(reschedule_jobs.attempts, jobs.attempts),
|
|
501
|
-
max_attempts = coalesce(reschedule_jobs.max_attempts, jobs.max_attempts)
|
|
502
|
-
WHERE
|
|
503
|
-
id = ANY (job_ids)
|
|
504
|
-
AND (locked_by IS NULL
|
|
505
|
-
OR locked_at < NOW() - interval '4 hours')
|
|
506
|
-
RETURNING
|
|
507
|
-
*;
|
|
508
|
-
$EOFCODE$;
|
|
509
|
-
|
|
510
|
-
CREATE FUNCTION app_jobs.run_scheduled_job(id bigint, job_expiry interval DEFAULT '1 hours') RETURNS app_jobs.jobs AS $EOFCODE$
|
|
511
|
-
DECLARE
|
|
512
|
-
j app_jobs.jobs;
|
|
513
|
-
last_id bigint;
|
|
514
|
-
lkd_by text;
|
|
515
|
-
BEGIN
|
|
516
|
-
-- check last scheduled
|
|
517
|
-
SELECT
|
|
518
|
-
last_scheduled_id
|
|
519
|
-
FROM
|
|
520
|
-
app_jobs.scheduled_jobs s
|
|
521
|
-
WHERE
|
|
522
|
-
s.id = run_scheduled_job.id INTO last_id;
|
|
292
|
+
app_jobs.scheduled_jobs s
|
|
293
|
+
WHERE
|
|
294
|
+
s.id = run_scheduled_job.id INTO last_id;
|
|
523
295
|
|
|
524
296
|
-- if it's been scheduled check if it's been run
|
|
525
297
|
|
|
@@ -574,196 +346,424 @@ BEGIN
|
|
|
574
346
|
END;
|
|
575
347
|
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
|
|
576
348
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
349
|
+
CREATE FUNCTION app_jobs.reschedule_jobs(job_ids bigint[], run_at timestamptz DEFAULT NULL, priority int DEFAULT NULL, attempts int DEFAULT NULL, max_attempts int DEFAULT NULL) RETURNS SETOF app_jobs.jobs LANGUAGE sql AS $EOFCODE$
|
|
350
|
+
UPDATE
|
|
351
|
+
app_jobs.jobs
|
|
352
|
+
SET
|
|
353
|
+
run_at = coalesce(reschedule_jobs.run_at, jobs.run_at),
|
|
354
|
+
priority = coalesce(reschedule_jobs.priority, jobs.priority),
|
|
355
|
+
attempts = coalesce(reschedule_jobs.attempts, jobs.attempts),
|
|
356
|
+
max_attempts = coalesce(reschedule_jobs.max_attempts, jobs.max_attempts)
|
|
357
|
+
WHERE
|
|
358
|
+
id = ANY (job_ids)
|
|
359
|
+
AND (locked_by IS NULL
|
|
360
|
+
OR locked_at < NOW() - interval '4 hours')
|
|
361
|
+
RETURNING
|
|
362
|
+
*;
|
|
363
|
+
$EOFCODE$;
|
|
586
364
|
|
|
587
|
-
CREATE FUNCTION app_jobs.
|
|
365
|
+
CREATE FUNCTION app_jobs.release_scheduled_jobs(worker_id text, ids bigint[] DEFAULT NULL) RETURNS void AS $EOFCODE$
|
|
588
366
|
DECLARE
|
|
589
|
-
v_new_job_count int;
|
|
590
367
|
BEGIN
|
|
368
|
+
-- clear the scheduled job
|
|
591
369
|
UPDATE
|
|
592
|
-
app_jobs.
|
|
370
|
+
app_jobs.scheduled_jobs s
|
|
593
371
|
SET
|
|
594
|
-
|
|
372
|
+
locked_at = NULL,
|
|
373
|
+
locked_by = NULL
|
|
595
374
|
WHERE
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
IF v_new_job_count <= 0 THEN
|
|
600
|
-
DELETE FROM app_jobs.job_queues
|
|
601
|
-
WHERE queue_name = OLD.queue_name
|
|
602
|
-
AND job_count <= 0;
|
|
603
|
-
END IF;
|
|
604
|
-
RETURN OLD;
|
|
375
|
+
locked_by = worker_id
|
|
376
|
+
AND (ids IS NULL
|
|
377
|
+
OR s.id = ANY (ids));
|
|
605
378
|
END;
|
|
606
379
|
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
|
|
607
380
|
|
|
608
|
-
CREATE
|
|
609
|
-
|
|
610
|
-
ON app_jobs.jobs
|
|
611
|
-
FOR EACH ROW
|
|
612
|
-
WHEN (old.queue_name IS NOT NULL)
|
|
613
|
-
EXECUTE PROCEDURE app_jobs.tg_decrease_job_queue_count();
|
|
614
|
-
|
|
615
|
-
CREATE TRIGGER decrease_job_queue_count_on_update
|
|
616
|
-
AFTER UPDATE OF queue_name
|
|
617
|
-
ON app_jobs.jobs
|
|
618
|
-
FOR EACH ROW
|
|
619
|
-
WHEN (new.queue_name IS DISTINCT FROM old.queue_name
|
|
620
|
-
AND old.queue_name IS NOT NULL)
|
|
621
|
-
EXECUTE PROCEDURE app_jobs.tg_decrease_job_queue_count();
|
|
622
|
-
|
|
623
|
-
CREATE FUNCTION app_jobs.tg_increase_job_queue_count() RETURNS trigger AS $EOFCODE$
|
|
381
|
+
CREATE FUNCTION app_jobs.release_jobs(worker_id text) RETURNS void AS $EOFCODE$
|
|
382
|
+
DECLARE
|
|
624
383
|
BEGIN
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
384
|
+
-- clear the job
|
|
385
|
+
UPDATE
|
|
386
|
+
app_jobs.jobs
|
|
387
|
+
SET
|
|
388
|
+
locked_at = NULL,
|
|
389
|
+
locked_by = NULL,
|
|
390
|
+
attempts = GREATEST (attempts - 1, 0)
|
|
391
|
+
WHERE
|
|
392
|
+
locked_by = worker_id;
|
|
393
|
+
-- clear the queue
|
|
394
|
+
UPDATE
|
|
395
|
+
app_jobs.job_queues
|
|
396
|
+
SET
|
|
397
|
+
locked_at = NULL,
|
|
398
|
+
locked_by = NULL
|
|
399
|
+
WHERE
|
|
400
|
+
locked_by = worker_id;
|
|
631
401
|
END;
|
|
632
402
|
$EOFCODE$ LANGUAGE plpgsql VOLATILE;
|
|
633
403
|
|
|
634
|
-
CREATE
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
404
|
+
CREATE FUNCTION app_jobs.permanently_fail_jobs(job_ids bigint[], error_message text DEFAULT NULL) RETURNS SETOF app_jobs.jobs LANGUAGE sql AS $EOFCODE$
|
|
405
|
+
UPDATE
|
|
406
|
+
app_jobs.jobs
|
|
407
|
+
SET
|
|
408
|
+
last_error = coalesce(error_message, 'Manually marked as failed'),
|
|
409
|
+
attempts = max_attempts
|
|
410
|
+
WHERE
|
|
411
|
+
id = ANY (job_ids)
|
|
412
|
+
AND (locked_by IS NULL
|
|
413
|
+
OR locked_at < NOW() - interval '4 hours')
|
|
414
|
+
RETURNING
|
|
415
|
+
*;
|
|
416
|
+
$EOFCODE$;
|
|
640
417
|
|
|
641
|
-
CREATE
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
AND new.queue_name IS NOT NULL)
|
|
647
|
-
EXECUTE PROCEDURE app_jobs.tg_increase_job_queue_count();
|
|
418
|
+
CREATE FUNCTION app_jobs.get_scheduled_job(worker_id text, task_identifiers text[] DEFAULT NULL) RETURNS app_jobs.scheduled_jobs LANGUAGE plpgsql AS $EOFCODE$
|
|
419
|
+
DECLARE
|
|
420
|
+
v_job_id bigint;
|
|
421
|
+
v_row app_jobs.scheduled_jobs;
|
|
422
|
+
BEGIN
|
|
648
423
|
|
|
649
|
-
|
|
650
|
-
AFTER INSERT
|
|
651
|
-
ON app_jobs.jobs
|
|
652
|
-
FOR EACH ROW
|
|
653
|
-
EXECUTE PROCEDURE app_jobs.do_notify('jobs:insert');
|
|
424
|
+
--
|
|
654
425
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
IF TG_OP = 'INSERT' THEN
|
|
658
|
-
NEW.created_at = NOW();
|
|
659
|
-
NEW.updated_at = NOW();
|
|
660
|
-
ELSIF TG_OP = 'UPDATE' THEN
|
|
661
|
-
NEW.created_at = OLD.created_at;
|
|
662
|
-
NEW.updated_at = greatest (now(), OLD.updated_at + interval '1 millisecond');
|
|
426
|
+
IF worker_id IS NULL THEN
|
|
427
|
+
RAISE exception 'INVALID_WORKER_ID';
|
|
663
428
|
END IF;
|
|
664
|
-
RETURN NEW;
|
|
665
|
-
END;
|
|
666
|
-
$EOFCODE$ LANGUAGE plpgsql;
|
|
667
429
|
|
|
668
|
-
|
|
669
|
-
ADD COLUMN created_at timestamptz;
|
|
430
|
+
--
|
|
670
431
|
|
|
671
|
-
|
|
672
|
-
|
|
432
|
+
SELECT
|
|
433
|
+
scheduled_jobs.id INTO v_job_id
|
|
434
|
+
FROM
|
|
435
|
+
app_jobs.scheduled_jobs
|
|
436
|
+
WHERE (scheduled_jobs.locked_at IS NULL)
|
|
437
|
+
AND (task_identifiers IS NULL
|
|
438
|
+
OR task_identifier = ANY (task_identifiers))
|
|
439
|
+
ORDER BY
|
|
440
|
+
priority ASC,
|
|
441
|
+
id ASC
|
|
442
|
+
LIMIT 1
|
|
443
|
+
FOR UPDATE
|
|
444
|
+
SKIP LOCKED;
|
|
673
445
|
|
|
674
|
-
|
|
675
|
-
ADD COLUMN updated_at timestamptz;
|
|
676
|
-
|
|
677
|
-
ALTER TABLE app_jobs.jobs
|
|
678
|
-
ALTER COLUMN updated_at SET DEFAULT now();
|
|
446
|
+
--
|
|
679
447
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
FOR EACH ROW
|
|
684
|
-
EXECUTE PROCEDURE app_jobs.tg_update_timestamps();
|
|
448
|
+
IF v_job_id IS NULL THEN
|
|
449
|
+
RETURN NULL;
|
|
450
|
+
END IF;
|
|
685
451
|
|
|
686
|
-
|
|
452
|
+
--
|
|
687
453
|
|
|
688
|
-
|
|
454
|
+
UPDATE
|
|
455
|
+
app_jobs.scheduled_jobs
|
|
456
|
+
SET
|
|
457
|
+
locked_by = worker_id,
|
|
458
|
+
locked_at = NOW()
|
|
459
|
+
WHERE
|
|
460
|
+
id = v_job_id
|
|
461
|
+
RETURNING
|
|
462
|
+
* INTO v_row;
|
|
689
463
|
|
|
690
|
-
|
|
464
|
+
--
|
|
691
465
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
FOR EACH ROW
|
|
696
|
-
EXECUTE PROCEDURE app_jobs.do_notify('scheduled_jobs:insert');
|
|
466
|
+
RETURN v_row;
|
|
467
|
+
END;
|
|
468
|
+
$EOFCODE$;
|
|
697
469
|
|
|
698
|
-
CREATE FUNCTION app_jobs.
|
|
470
|
+
CREATE FUNCTION app_jobs.get_job(worker_id text, task_identifiers text[] DEFAULT NULL, job_expiry interval DEFAULT '4 hours') RETURNS app_jobs.jobs LANGUAGE plpgsql AS $EOFCODE$
|
|
699
471
|
DECLARE
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
472
|
+
v_job_id bigint;
|
|
473
|
+
v_queue_name text;
|
|
474
|
+
v_row app_jobs.jobs;
|
|
475
|
+
v_now timestamptz = now();
|
|
704
476
|
BEGIN
|
|
705
|
-
|
|
477
|
+
|
|
478
|
+
IF worker_id IS NULL THEN
|
|
479
|
+
RAISE exception 'INVALID_WORKER_ID';
|
|
480
|
+
END IF;
|
|
481
|
+
|
|
482
|
+
--
|
|
483
|
+
|
|
706
484
|
SELECT
|
|
707
|
-
|
|
485
|
+
jobs.queue_name,
|
|
486
|
+
jobs.id INTO v_queue_name,
|
|
487
|
+
v_job_id
|
|
708
488
|
FROM
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
489
|
+
app_jobs.jobs
|
|
490
|
+
WHERE (jobs.locked_at IS NULL
|
|
491
|
+
OR jobs.locked_at < (v_now - job_expiry))
|
|
492
|
+
AND (jobs.queue_name IS NULL
|
|
493
|
+
OR EXISTS (
|
|
494
|
+
SELECT
|
|
495
|
+
1
|
|
496
|
+
FROM
|
|
497
|
+
app_jobs.job_queues
|
|
498
|
+
WHERE
|
|
499
|
+
job_queues.queue_name = jobs.queue_name
|
|
500
|
+
AND (job_queues.locked_at IS NULL
|
|
501
|
+
OR job_queues.locked_at < (v_now - job_expiry))
|
|
502
|
+
FOR UPDATE
|
|
503
|
+
SKIP LOCKED))
|
|
504
|
+
AND run_at <= v_now
|
|
505
|
+
AND attempts < max_attempts
|
|
506
|
+
AND (task_identifiers IS NULL
|
|
507
|
+
OR task_identifier = ANY (task_identifiers))
|
|
508
|
+
ORDER BY
|
|
509
|
+
priority ASC,
|
|
510
|
+
run_at ASC,
|
|
511
|
+
id ASC
|
|
512
|
+
LIMIT 1
|
|
513
|
+
FOR UPDATE
|
|
514
|
+
SKIP LOCKED;
|
|
515
|
+
|
|
516
|
+
--
|
|
517
|
+
|
|
518
|
+
IF v_job_id IS NULL THEN
|
|
519
|
+
RETURN NULL;
|
|
730
520
|
END IF;
|
|
731
|
-
|
|
732
|
-
|
|
521
|
+
|
|
522
|
+
--
|
|
523
|
+
|
|
524
|
+
IF v_queue_name IS NOT NULL THEN
|
|
525
|
+
UPDATE
|
|
526
|
+
app_jobs.job_queues
|
|
527
|
+
SET
|
|
528
|
+
locked_by = worker_id,
|
|
529
|
+
locked_at = v_now
|
|
530
|
+
WHERE
|
|
531
|
+
job_queues.queue_name = v_queue_name;
|
|
733
532
|
END IF;
|
|
533
|
+
|
|
534
|
+
--
|
|
535
|
+
|
|
536
|
+
UPDATE
|
|
537
|
+
app_jobs.jobs
|
|
538
|
+
SET
|
|
539
|
+
attempts = attempts + 1,
|
|
540
|
+
locked_by = worker_id,
|
|
541
|
+
locked_at = v_now
|
|
542
|
+
WHERE
|
|
543
|
+
id = v_job_id
|
|
544
|
+
RETURNING
|
|
545
|
+
* INTO v_row;
|
|
546
|
+
|
|
547
|
+
--
|
|
548
|
+
RETURN v_row;
|
|
734
549
|
END;
|
|
735
|
-
$EOFCODE
|
|
550
|
+
$EOFCODE$;
|
|
736
551
|
|
|
737
|
-
CREATE FUNCTION app_jobs.
|
|
552
|
+
CREATE FUNCTION app_jobs.fail_job(worker_id text, job_id bigint, error_message text) RETURNS app_jobs.jobs LANGUAGE plpgsql STRICT AS $EOFCODE$
|
|
553
|
+
DECLARE
|
|
554
|
+
v_row app_jobs.jobs;
|
|
738
555
|
BEGIN
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
556
|
+
UPDATE
|
|
557
|
+
app_jobs.jobs
|
|
558
|
+
SET
|
|
559
|
+
last_error = error_message,
|
|
560
|
+
run_at = greatest (now(), run_at) + (exp(least (attempts, 10))::text || ' seconds')::interval,
|
|
561
|
+
locked_by = NULL,
|
|
562
|
+
locked_at = NULL
|
|
563
|
+
WHERE
|
|
564
|
+
id = job_id
|
|
565
|
+
AND locked_by = worker_id
|
|
566
|
+
RETURNING
|
|
567
|
+
* INTO v_row;
|
|
568
|
+
IF v_row.queue_name IS NOT NULL THEN
|
|
569
|
+
UPDATE
|
|
570
|
+
app_jobs.job_queues
|
|
571
|
+
SET
|
|
572
|
+
locked_by = NULL,
|
|
573
|
+
locked_at = NULL
|
|
574
|
+
WHERE
|
|
575
|
+
queue_name = v_row.queue_name
|
|
576
|
+
AND locked_by = worker_id;
|
|
748
577
|
END IF;
|
|
578
|
+
RETURN v_row;
|
|
749
579
|
END;
|
|
750
|
-
$EOFCODE
|
|
580
|
+
$EOFCODE$;
|
|
751
581
|
|
|
752
|
-
|
|
582
|
+
CREATE FUNCTION app_jobs.complete_jobs(job_ids bigint[]) RETURNS SETOF app_jobs.jobs LANGUAGE sql AS $EOFCODE$
|
|
583
|
+
DELETE FROM app_jobs.jobs
|
|
584
|
+
WHERE id = ANY (job_ids)
|
|
585
|
+
AND (locked_by IS NULL
|
|
586
|
+
OR locked_at < NOW() - interval '4 hours')
|
|
587
|
+
RETURNING
|
|
588
|
+
*;
|
|
589
|
+
$EOFCODE$;
|
|
753
590
|
|
|
754
|
-
CREATE FUNCTION app_jobs.
|
|
591
|
+
CREATE FUNCTION app_jobs.complete_job(worker_id text, job_id bigint) RETURNS app_jobs.jobs LANGUAGE plpgsql AS $EOFCODE$
|
|
592
|
+
DECLARE
|
|
593
|
+
v_row app_jobs.jobs;
|
|
755
594
|
BEGIN
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
595
|
+
DELETE FROM app_jobs.jobs
|
|
596
|
+
WHERE id = job_id
|
|
597
|
+
RETURNING
|
|
598
|
+
* INTO v_row;
|
|
599
|
+
IF v_row.queue_name IS NOT NULL THEN
|
|
600
|
+
UPDATE
|
|
601
|
+
app_jobs.job_queues
|
|
602
|
+
SET
|
|
603
|
+
locked_by = NULL,
|
|
604
|
+
locked_at = NULL
|
|
605
|
+
WHERE
|
|
606
|
+
queue_name = v_row.queue_name
|
|
607
|
+
AND locked_by = worker_id;
|
|
760
608
|
END IF;
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
609
|
+
RETURN v_row;
|
|
610
|
+
END;
|
|
611
|
+
$EOFCODE$;
|
|
612
|
+
|
|
613
|
+
CREATE FUNCTION app_jobs.add_scheduled_job(db_id uuid, identifier text, payload pg_catalog.json DEFAULT '{}'::json, schedule_info pg_catalog.json DEFAULT '{}'::json, job_key text DEFAULT NULL, queue_name text DEFAULT NULL, max_attempts int DEFAULT 25, priority int DEFAULT 0) RETURNS app_jobs.scheduled_jobs AS $EOFCODE$
|
|
614
|
+
DECLARE
|
|
615
|
+
v_job app_jobs.scheduled_jobs;
|
|
616
|
+
BEGIN
|
|
617
|
+
IF job_key IS NOT NULL THEN
|
|
618
|
+
|
|
619
|
+
-- Upsert job
|
|
620
|
+
INSERT INTO app_jobs.scheduled_jobs (
|
|
621
|
+
database_id,
|
|
622
|
+
task_identifier,
|
|
623
|
+
payload,
|
|
624
|
+
queue_name,
|
|
625
|
+
schedule_info,
|
|
626
|
+
max_attempts,
|
|
627
|
+
key,
|
|
628
|
+
priority
|
|
629
|
+
) VALUES (
|
|
630
|
+
db_id,
|
|
631
|
+
identifier,
|
|
632
|
+
coalesce(payload, '{}'::json),
|
|
633
|
+
queue_name,
|
|
634
|
+
schedule_info,
|
|
635
|
+
coalesce(max_attempts, 25),
|
|
636
|
+
job_key,
|
|
637
|
+
coalesce(priority, 0)
|
|
638
|
+
)
|
|
639
|
+
ON CONFLICT (key)
|
|
640
|
+
DO UPDATE SET
|
|
641
|
+
task_identifier = EXCLUDED.task_identifier,
|
|
642
|
+
payload = EXCLUDED.payload,
|
|
643
|
+
queue_name = EXCLUDED.queue_name,
|
|
644
|
+
max_attempts = EXCLUDED.max_attempts,
|
|
645
|
+
schedule_info = EXCLUDED.schedule_info,
|
|
646
|
+
priority = EXCLUDED.priority
|
|
647
|
+
WHERE
|
|
648
|
+
scheduled_jobs.locked_at IS NULL
|
|
649
|
+
RETURNING
|
|
650
|
+
* INTO v_job;
|
|
651
|
+
|
|
652
|
+
-- If upsert succeeded (insert or update), return early
|
|
653
|
+
|
|
654
|
+
IF NOT (v_job IS NULL) THEN
|
|
655
|
+
RETURN v_job;
|
|
656
|
+
END IF;
|
|
657
|
+
|
|
658
|
+
-- Upsert failed -> there must be an existing scheduled job that is locked. Remove
|
|
659
|
+
-- and allow a new one to be inserted
|
|
660
|
+
|
|
661
|
+
DELETE FROM
|
|
662
|
+
app_jobs.scheduled_jobs
|
|
663
|
+
WHERE
|
|
664
|
+
KEY = job_key;
|
|
765
665
|
END IF;
|
|
666
|
+
|
|
667
|
+
INSERT INTO app_jobs.scheduled_jobs (
|
|
668
|
+
database_id,
|
|
669
|
+
task_identifier,
|
|
670
|
+
payload,
|
|
671
|
+
queue_name,
|
|
672
|
+
schedule_info,
|
|
673
|
+
max_attempts,
|
|
674
|
+
priority
|
|
675
|
+
) VALUES (
|
|
676
|
+
db_id,
|
|
677
|
+
identifier,
|
|
678
|
+
payload,
|
|
679
|
+
queue_name,
|
|
680
|
+
schedule_info,
|
|
681
|
+
max_attempts,
|
|
682
|
+
priority
|
|
683
|
+
) RETURNING * INTO v_job;
|
|
684
|
+
RETURN v_job;
|
|
766
685
|
END;
|
|
767
686
|
$EOFCODE$ LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
|
|
768
687
|
|
|
769
|
-
|
|
688
|
+
CREATE FUNCTION app_jobs.add_job(db_id uuid, identifier text, payload pg_catalog.json DEFAULT '{}'::json, job_key text DEFAULT NULL, queue_name text DEFAULT NULL, run_at timestamptz DEFAULT now(), max_attempts int DEFAULT 25, priority int DEFAULT 0) RETURNS app_jobs.jobs AS $EOFCODE$
|
|
689
|
+
DECLARE
|
|
690
|
+
v_job app_jobs.jobs;
|
|
691
|
+
BEGIN
|
|
692
|
+
IF job_key IS NOT NULL THEN
|
|
693
|
+
-- Upsert job
|
|
694
|
+
INSERT INTO app_jobs.jobs (
|
|
695
|
+
database_id,
|
|
696
|
+
task_identifier,
|
|
697
|
+
payload,
|
|
698
|
+
queue_name,
|
|
699
|
+
run_at,
|
|
700
|
+
max_attempts,
|
|
701
|
+
key,
|
|
702
|
+
priority
|
|
703
|
+
) VALUES (
|
|
704
|
+
db_id,
|
|
705
|
+
identifier,
|
|
706
|
+
coalesce(payload,
|
|
707
|
+
'{}'::json),
|
|
708
|
+
queue_name,
|
|
709
|
+
coalesce(run_at, now()),
|
|
710
|
+
coalesce(max_attempts, 25),
|
|
711
|
+
job_key,
|
|
712
|
+
coalesce(priority, 0)
|
|
713
|
+
)
|
|
714
|
+
ON CONFLICT (key)
|
|
715
|
+
DO UPDATE SET
|
|
716
|
+
task_identifier = EXCLUDED.task_identifier,
|
|
717
|
+
payload = EXCLUDED.payload,
|
|
718
|
+
queue_name = EXCLUDED.queue_name,
|
|
719
|
+
max_attempts = EXCLUDED.max_attempts,
|
|
720
|
+
run_at = EXCLUDED.run_at,
|
|
721
|
+
priority = EXCLUDED.priority,
|
|
722
|
+
-- always reset error/retry state
|
|
723
|
+
attempts = 0, last_error = NULL
|
|
724
|
+
WHERE
|
|
725
|
+
jobs.locked_at IS NULL
|
|
726
|
+
RETURNING
|
|
727
|
+
* INTO v_job;
|
|
728
|
+
|
|
729
|
+
-- If upsert succeeded (insert or update), return early
|
|
730
|
+
|
|
731
|
+
IF NOT (v_job IS NULL) THEN
|
|
732
|
+
RETURN v_job;
|
|
733
|
+
END IF;
|
|
734
|
+
|
|
735
|
+
-- Upsert failed -> there must be an existing job that is locked. Remove
|
|
736
|
+
-- existing key to allow a new one to be inserted, and prevent any
|
|
737
|
+
-- subsequent retries by bumping attempts to the max allowed.
|
|
738
|
+
|
|
739
|
+
UPDATE
|
|
740
|
+
app_jobs.jobs
|
|
741
|
+
SET
|
|
742
|
+
KEY = NULL,
|
|
743
|
+
attempts = jobs.max_attempts
|
|
744
|
+
WHERE
|
|
745
|
+
KEY = job_key;
|
|
746
|
+
END IF;
|
|
747
|
+
|
|
748
|
+
INSERT INTO app_jobs.jobs (
|
|
749
|
+
database_id,
|
|
750
|
+
task_identifier,
|
|
751
|
+
payload,
|
|
752
|
+
queue_name,
|
|
753
|
+
run_at,
|
|
754
|
+
max_attempts,
|
|
755
|
+
priority
|
|
756
|
+
) VALUES (
|
|
757
|
+
db_id,
|
|
758
|
+
identifier,
|
|
759
|
+
payload,
|
|
760
|
+
queue_name,
|
|
761
|
+
run_at,
|
|
762
|
+
max_attempts,
|
|
763
|
+
priority
|
|
764
|
+
)
|
|
765
|
+
RETURNING * INTO v_job;
|
|
766
|
+
|
|
767
|
+
RETURN v_job;
|
|
768
|
+
END;
|
|
769
|
+
$EOFCODE$ LANGUAGE plpgsql VOLATILE SECURITY DEFINER;
|