【问题标题】:How to run Postgres pg_cron Job AFTER another Job?如何在另一个作业之后运行 Postgres pg_cron 作业?
【发布时间】:2021-07-31 03:30:10
【问题描述】:

我晚上使用 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 操作。

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

下面我提供了一个关于当前如何触发作业以及我希望它如何工作的示意图(如果可能的话):

【问题讨论】:

  • 我会让 cron 函数依赖于数据。所以该函数启动,检查条件是否运行,如果条件不满足则退出,如果条件满足则执行。这样就可以实现任何复杂的依赖逻辑。您可以检查服务器的状况:是否已加载,是否有锁,是否有其他进程在服务器上运行。 Cron 计划本身变得不那么重要了。为这些任务创建日志。遗憾的是,postgresql 中没有完全针对日志的自治事务。
  • 但是在前 5 个并发运行之后,我将如何为我想要运行的第二个 Cron 作业执行此操作???我有 Cron 设置每天晚上运行@1am...所以即使我在 SECOND Cron 作业中添加一个条件语句,这样它在前 5 个完成之前不会运行...它甚至会如何重新自己触发???
  • 啊哈。假设前 5 个任务每个都必须在日志中写入一条记录:{log_id, task_type, start_time, finish_time}。然后,启动时的 SECOND cron 作业检查他们所做的所有 5 个任务中是否存在 5 条记录。虽然它没有看到 - 退出,但当它看到所有以前的任务都完成工作时 - 开始工作
  • 所以它就像函数 6 开始时的 SELECT [condition 1] AND [condition 2] AND [condition 3] .. [condition 5] AS do_work。然后如果 do_work 为 True - 你开始执行

标签: postgresql cron plpgsql pg-cron


【解决方案1】:

前言:我如何理解问题

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

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

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

例如:

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

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

例如

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 已执行 -> 无需工作 -> 退出
  • ...所有其他尝试将保持不变,直到第二天晚上

总结

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

  • 另一个周期性
  • 或使其完全不定期。例如。用触发器制作一个表并在更改时开始执行
  • 任何深度的依赖和/或“模糊”依赖
  • ...几乎所有内容

概念保持不变:

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

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

【讨论】:

  • 这看起来不错,我认为它类似于我的问题。但是对于任务 F,如果日志不存在怎么办,那么这意味着作业没有完成......并且任务 F 无法启动。这是预期的行为,但任务 F 怎么知道何时重试?而且我不能让任务 F 重试多次
  • 在 cron 中进行多次重试绝对没问题。 Cron schedule 是一个尝试启动作业的时间表,它是否会真正启动 - 由作业功能决定。请注意,签入pg_stat_activity 至关重要——这样TaskF 就不会启动多次。
  • @ennth 看看答案的更新。我澄清了任务F的调度
  • 对于TaskF,你不必检查pg_stat_activity,因为如果它当前正在执行,pg_cron 不会重新启动/启动作业,对吧???
  • @ennth 是的。但是,如果有人手动启动它怎么办?最好检查然后不检查。如果您决定从其他调度程序运行它会怎样?
【解决方案2】:

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

【讨论】:

  • 我看到您提出的解决方案与我的有些相似。核心是:使用pg_stat_activity的非事务性(也没有MVCC)行为
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-09-27
  • 1970-01-01
  • 2019-03-26
  • 1970-01-01
  • 1970-01-01
  • 2015-12-03
  • 2020-07-28
相关资源
最近更新 更多