如何在另一个作业之后运行 Postgres pg_cron 作业?

时间:2021-07-31 03:30:10

标签: postgresql cron plpgsql pg-cron

我在晚上使用 pg_cron 扩展在我的 postgres 数据库上运行一些自动化任务。我正在将某些旧记录移动到存档数据库表中。我在 5 个不同的后台工作者上同时运行 5 个存储过程,因此它们都同时启动并在不同的工作者上运行(我假设这类似于在 Java 中的不同线程上运行不同的任务) .这 5 个存储过程是独立的(将记录移动到存档表),因此它们可以同时运行。我使用像

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

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

.
..
...

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

现在,我有更多依赖存储过程要运行。但是他们需要在这 5 个作业完成/完成后运行,因为他们正在执行一些DELETE... sql 操作。

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

下面我包含了一个关于当前如何触发作业以及我希望它如何工作的小示意图(如果可能): enter image description here

2 个答案:

答案 0 :(得分:1)

前言:我如何理解问题

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

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

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

例如:

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

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

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

  • 一天一次、一小时一次或一周一次,或者只在周末夜间一次
  • 最好不要在服务器负载不足的时候运行

是否符合 OP 要求 - IDK。

为了简单起见,我们假设每个任务在一个晚上只运行一次。很容易扩展到其他时期/要求。

数据驱动的方法

1.添加日志表

例如

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 无法知道服务器是否处于负载状态,表上是否有锁,或者有人手动开始执行任务。

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

任务 F

同上,但检查 start 以查找其他任务的完成情况。

例如

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 中创建多个计划。

例如

假设任务 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 已执行 -> 无需工作 -> 退出
  • ...所有其他尝试直到第二天晚上都是一样的

总结

很容易将这种方法扩展到任何需求:

  • 另一个周期
  • 或者让它不定期。例如。使用触发器创建一个表并在更改时开始执行
  • 任何深度的依赖和/或“模糊”依赖
  • ...几乎所有的东西

概念保持不变:

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

我很高兴听到任何形式的批评——谁知道我可能忽略了一些东西。

答案 1 :(得分:0)

您可以使用 pg_stat_activity view 来确保没有像您的作业 1-5 那样的活动查询。

注意:

<块引用>

超级用户和内置角色 pg_read_all_stats 的成员(另见第 21.5 节)可以查看所有会话的所有信息

...
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 之后调用它几秒钟。

注 1:

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

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 的其他列。您可能还需要使用它们。