如何在另一份工作后 运行 Postgres pg_cron 工作?

How to run Postgres pg_cron Job AFTER another Job?

我 运行 晚上使用 pg_cron 扩展在我的 postgres 数据库上执行一些自动化任务。我正在将某些旧记录移动到存档数据库表中。我在 5 个不同的后台工作人员上 运行 同时 5 个存储过程,所以他们都同时开始 运行 在不同的工作人员上(我假设这个类似于 运行 在 Java 中在不同的线程上设置不同的任务)。这5个Stored Procedures是独立的(移动记录到归档表),所以它们可以同时运行。我使用

这样的命令安排它们
cron.schedule (myJob1,
    '* * * * *',
    'call my_stored_proc_1()'
);

cron.schedule (myJob2,
    '* * * * *',
    'call my_stored_proc_2()'
);

.
..
...

cron.schedule (myJob5,
    '* * * * *',
    'call my_stored_proc_5()'
);

现在,我还有一些 dependent 我想要的存储过程 运行。但是他们需要 运行 在这 5 个作业 finish/complete 之后,因为他们正在做一些 DELETE...sql 操作。

如何在我的前 5 个存储过程作业完成后获得第二个存储过程(执行 DELETE 查询的那个)作业运行?我不想为执行 DELETES 的第二个存储过程设置 CRON 表达式,因为我什至不知道前 5 个存储过程什么时候完成...

下面我提供了一个小示意图,说明当前如何触发作业以及我希望它如何工作(如果可能):

前言:我是如何理解问题的

希望我理解OP描述的问题。

如果我错了,那么下面的所有内容都会无效。

我想这是关于 CPU and/or IO 中繁重的周期性夜间任务。

例如:

  • 有任务 A-C 用于归档数据
  • 也许任务 D-E 用于重建聚合/刷新垫视图
  • 最后是 运行s reindexing/analyze 在整个 DB
  • 上的任务 F

所以只有在任务 A-E 完成后 运行 任务 F 才有意义。

每个任务都需要运行在一段时间内只需要一次:

  • 一天或一小时或一周一次,或仅在周末晚上一次
  • 最好不要运行服务器负载过大

是否符合 OP 要求 - IDK。

为了简单起见,我们假设每项任务 运行 每晚只有一次。很容易扩展到其他 periods/requirements.

数据驱动方法

1。添加日志table

例如

CREATE TABLE job_log (
  log_id bigint,
  job_name text,
  log_date timestamptz
) 

任务 A-E

开始时

对每个工作职能进行检查:

IF  EXISTS(
  SELECT 1 FROM job_log 
    WHERE
      job_name = 'TaskA' # TaskB-TaskE for each functiont
      AND log_date::DATE = NOW()::DATE # check that function already executed  this night
) OR  EXISTS(
   SELECT 1 FROM pg_stat_activity 
     WHERE 
       query like 'SELECT * FROM jobA_function();'  # check that job not executing right now
) THEN RETURN;
END IF;

可能会添加其他条件:查找连接数量、是否存在锁等等。

这样可以保证函数的执行频率不会超出需要。

完成时

INSERT INTO job_log
SELECT
   (SELECT MAX(log_id) FROM job_log) + 1 # or use sequences/other autoincrements
  ,'TaskA'
  ,NOW()

Cronjob 计划

意思变了

现在是:“尝试开始执行任务”。

在所选时间段之间的每个小时甚至更频繁地安排它是安全的。

Cronjob 无法知道服务器是否处于负载状态,table 是否有锁,或者有人手动开始执行任务。

工作职能在这方面可能会更聪明。

任务 F

与上面相同,但在开始时检查是否已完成其他任务。

例如

IF NOT EXISTS(
  SELECT 1 FROM job_log 
     WHERE 
       job_name = 'TaskA'
       AND log_date::DATE = NOW()::DATE
) OR NOT EXISTS(  
  SELECT 1 FROM job_log 
     WHERE 
       job_name = 'TaskB'  
       AND log_date::DATE = NOW()::DATE
)
....  # checks for completions of other tasks
OR EXISTS(
  SELECT 1 FROM job_log 
    WHERE
      job_name = 'TaskF' # TaskB-TaskE for each functiont
      AND log_date::DATE = NOW()::DATE # check that function already executed  this night
) OR  EXISTS(
   SELECT 1 FROM pg_stat_activity 
     WHERE 
       query like 'SELECT * FROM jobF_function();'  # check that job not executing right now
) THEN RETURN;

完成时

写入job_log与其他函数相同

更新。 Cronjob 计划

在 cronjob 中创建多个计划。

例如

假设任务 A-E 将 运行 大约 10-15 分钟。

并且其中一两个可以工作 30-45-60 分钟。

为任务 F 创建一个计划,每 5 分钟尝试启动一次。

如何运作:

  • 尝试 1:任务 A 完成,其他仍在工作 -> 退出
  • 尝试 2:任务 A-C 完成 -> 退出
  • 尝试 3:任务 A-E 完成 -> 开始任务 F
  • 尝试 4:任务 A-E 已完成,但在 pg_stat_activity 中有一个正在执行的任务 F -> 退出
  • 尝试 5:任务 A-E 完成,pg_stat_activity 为空但在日志中我们看到任务 F 已经执行 -> 无需工作 -> 退出
  • ...所有其他尝试将在明晚之前相同

总结

很容易扩展此方法以满足任何要求:

  • 另一个周期
  • 或者完全不定期。例如。使用触发器创建 table 并在更改时开始执行
  • 任何深度的依赖关系and/or“模糊”依赖关系
  • ...字面上的一切

构想不变:

  • cronjob 计划意味着“尝试 运行”
  • 决定是否 运行 是数据驱动的

我很高兴听到任何形式的批评 - 谁知道我可能忽略了什么。

您可以使用 pg_stat_activity view 来确保没有像您的职位 1-5 这样的活动查询。

注:

Superusers and members of the built-in role pg_read_all_stats (see also Section 21.5) can see all the information about all sessions

...
while (
    select count(*) > 0 
    from pg_stat_activity 
    where query in ('call my_stored_proc_1()', 'call my_stored_proc_2()', ...))
loop
    perform pg_sleep(1);
    perform pg_stat_clear_snapshot(); -- needs to retrieve the fresh data
end loop;
...

只需在 stored proc 6 的开头插入此代码,并在作业 1-5 之后调用它几秒钟。

注一:

可以使用正则表达式简化和概括条件:

when query ~ 'my_stored_proc_1|my_stored_proc_2|...'

注2:

您可以使用 clock_timestamp() 函数实现超时:

...
is_timedout := false;
timeout := '10 min'::interval; -- stop waiting after 10 minutes
start_time := clock_timestamp();
while (...)
loop
    perform pg_sleep(1);
    perform pg_stat_clear_snapshot(); -- needs to retrieve the fresh data
    if clock_timestamp() - start_time > timeout then
        is_timedout := true;
        break;
    end if;
end loop;

if is_timedout then
    ...
else
    ...
end if;
...

注3:

查看pg_stat_activity的其他栏目。您可能也需要使用它们。