{"id":107,"date":"2020-11-14T09:52:04","date_gmt":"2020-11-14T08:52:04","guid":{"rendered":"https:\/\/ptdb.ch\/?p=107"},"modified":"2020-11-14T09:52:04","modified_gmt":"2020-11-14T08:52:04","slug":"postgresql-how-to-schedule-jobs-with-the-pg_cron-extension-in-multiple-databases","status":"publish","type":"post","link":"https:\/\/ptdb.ch\/?p=107","title":{"rendered":"PostgreSQL: How To Schedule Jobs With The pg_cron Extension In Multiple Databases"},"content":{"rendered":"<p>A new release of the famous pg_cron (v1.3.0) came out on the 6th of October with a lot of cool new features, and a few bug fixes as well. The PostgreSQL 13 support might be the most important one, however, some other very cool stuff was implemented as well. Especially the audit table support might a great one for a lot of people out there.<\/p>\n<ul>\n<li>PostgreSQL 13 support<\/li>\n<li>Background worker support<\/li>\n<li>Audit table support<\/li>\n<li>Schedule jobs by name<\/li>\n<li>Fixes a bug that could cause cron.schedule to crash with long schedules<\/li>\n<li>Fixes a bug that could cause cron.schedule to get into an infinite loop<\/li>\n<li>Fixes a bug that caused overlapping runs not to start<\/li>\n<\/ul>\n<p>For more information, check out pg_cron on github.<\/p>\n<p><a href=\"https:\/\/github.com\/citusdata\/pg_cron\">https:\/\/github.com\/citusdata\/pg_cron<\/a><\/p>\n<p>A feature I was looking for was pg_cron support for multiple databases, which is not the case with pg_cron v1.3.0. What I mean, is that the parameter cron.database_name still supports one database only. So the scheduling framework is attached to one database only.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\ncron.database_name = 'pg_cron'\n\n<\/pre>\n<p>So the question is, how do I schedule jobs with pg_cron in multiple databases? And does it work at all? The short answer is yes. With a little workaround. \ud83d\ude09<\/p>\n<p>Let&#8217;s say you have two databases &#8220;ptdb&#8221; and &#8220;slvdb&#8221; and you want to schedule jobs in both of them. The trick is to create a third database &#8220;pg_cron&#8221;, and to adjust the job metadata manually with some update commands.<\/p>\n<p>But let&#8217;s go through it step by step. First of all, lets create our environment. A few users and a few databases.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n(postgres@&#x5B;local]:55001)&#x5B;postgres] create user cron_admin password 'cron_admin';\nCREATE ROLE\n(postgres@&#x5B;local]:55001)&#x5B;postgres] create user ptdb_admin password 'ptdb_admin';\nCREATE ROLE\n(postgres@&#x5B;local]:55001)&#x5B;postgres] create user slvdb_admin password 'slvdb_admin';\nCREATE ROLE\n(postgres@&#x5B;local]:55001)&#x5B;postgres] create database pg_cron owner=cron_admin;\nCREATE DATABASE\n(postgres@&#x5B;local]:55001)&#x5B;postgres] create database ptdb owner=ptdb_admin;\nCREATE DATABASE\n(postgres@&#x5B;local]:55001)&#x5B;postgres] create database slvdb owner=slvdb_admin;\nCREATE DATABASE\n<\/pre>\n<p>Afterwards we compile the new pg_cron software against PostgreSQL 13.1. Simply by running make and make install.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n&#x5B;postgres@ocm199 1.3.0]$ unzip pg_cron-main.1.3.0.zip\nArchive: pg_cron-main.1.3.0.zip\n4fdb4676781c56ea837058d336287f41909e56c6\ncreating: pg_cron-main\/\ninflating: pg_cron-main\/.gitignore\ninflating: pg_cron-main\/CHANGELOG.md\ninflating: pg_cron-main\/LICENSE\ninflating: pg_cron-main\/META.json\ninflating: pg_cron-main\/Makefile\n...\n...\ninflating: pg_cron-main\/src\/entry.c\ninflating: pg_cron-main\/src\/job_metadata.c\ninflating: pg_cron-main\/src\/misc.c\ninflating: pg_cron-main\/src\/pg_cron.c\ninflating: pg_cron-main\/src\/task_states.c\n\n&#x5B;postgres@ocm199 1.3.0]$ cd pg_cron-main\/\n&#x5B;postgres@ocm199 pg_cron-main]$\n\n&#x5B;postgres@ocm199 pg_cron-main]$ make\ngcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wendif-labels -Wmissing-format-attribute -Wimplicit-fallthrough=3\n-Wformat-security -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation -O2 -fPIC -std=c99 -Wall\n-Wextra -Werror -Wno-unused-parameter -Wno-implicit-fallthrough -Iinclude -I\/u01\/app\/postgres\/pgproduct\/pg-13.1\/include -I. -I.\/\n-I\/u01\/app\/postgres\/pgproduct\/pg-13.1\/include\/server -I\/u01\/app\/postgres\/pgproduct\/pg-13.1\/include\/internal -D_GNU_SOURCE -I\/usr\/include\/libxml2\n-c -o src\/pg_cron.o src\/pg_cron.c\n...\n...\n\/usr\/bin\/clang -Wno-ignored-attributes -fno-strict-aliasing -fwrapv -O2 -std=c99 -Wall -Wextra -Werror -Wno-unused-parameter -Wno-implicit-fallthrough\n-Iinclude -I\/u01\/app\/postgres\/pgproduct\/pg-13.1\/include -I. -I.\/ -I\/u01\/app\/postgres\/pgproduct\/pg-13.1\/include\/server -I\/u01\/app\/postgres\/pgproduct\/pg-13.1\/include\/internal\n-D_GNU_SOURCE -I\/usr\/include\/libxml2 -flto=thin -emit-llvm -c -o src\/entry.bc src\/entry.c\n\n&#x5B;postgres@ocm199 pg_cron-main]$ make install\n\/usr\/bin\/mkdir -p '\/u01\/app\/postgres\/pgproduct\/pg-13.1\/lib'\n\/usr\/bin\/mkdir -p '\/u01\/app\/postgres\/pgproduct\/pg-13.1\/share\/extension'\n\/usr\/bin\/mkdir -p '\/u01\/app\/postgres\/pgproduct\/pg-13.1\/share\/extension'\n\/usr\/bin\/install -c -m 755 pg_cron.so '\/u01\/app\/postgres\/pgproduct\/pg-13.1\/lib\/pg_cron.so'\n\/usr\/bin\/install -c -m 644 .\/\/pg_cron.control '\/u01\/app\/postgres\/pgproduct\/pg-13.1\/share\/extension\/'\n\/usr\/bin\/install -c -m 644 .\/\/pg_cron--1.2--1.3.sql .\/\/pg_cron--1.0--1.1.sql .\/\/pg_cron--1.1--1.2.sql pg_cron--1.0.sql '\/u01\/app\/postgres\/pgproduct\/pg-13.1\/share\/extension\/'\n\/usr\/bin\/mkdir -p '\/u01\/app\/postgres\/pgproduct\/pg-13.1\/lib\/bitcode\/pg_cron'\n\/usr\/bin\/mkdir -p '\/u01\/app\/postgres\/pgproduct\/pg-13.1\/lib\/bitcode'\/pg_cron\/src\/\n\/usr\/bin\/install -c -m 644 src\/pg_cron.bc '\/u01\/app\/postgres\/pgproduct\/pg-13.1\/lib\/bitcode'\/pg_cron\/src\/\n\/usr\/bin\/install -c -m 644 src\/job_metadata.bc '\/u01\/app\/postgres\/pgproduct\/pg-13.1\/lib\/bitcode'\/pg_cron\/src\/\n\/usr\/bin\/install -c -m 644 src\/misc.bc '\/u01\/app\/postgres\/pgproduct\/pg-13.1\/lib\/bitcode'\/pg_cron\/src\/\n\/usr\/bin\/install -c -m 644 src\/task_states.bc '\/u01\/app\/postgres\/pgproduct\/pg-13.1\/lib\/bitcode'\/pg_cron\/src\/\n\/usr\/bin\/install -c -m 644 src\/entry.bc '\/u01\/app\/postgres\/pgproduct\/pg-13.1\/lib\/bitcode'\/pg_cron\/src\/\ncd '\/u01\/app\/postgres\/pgproduct\/pg-13.1\/lib\/bitcode' &amp;amp;&amp;amp; \/usr\/bin\/llvm-lto -thinlto -thinlto-action=thinlink -o pg_cron.index.bc pg_cron\/src\/pg_cron.bc pg_cron\/src\/job_metadata.bc pg_cron\/src\/misc.bc pg_cron\/src\/task_states.bc pg_cron\/src\/entry.bc\n&#x5B;postgres@ocm199 pg_cron-main]$\n<\/pre>\n<p>After a successful compilation, you should see a few pg_cron files in the $PGHOME\/share\/extension directory.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n&#x5B;postgres@ocm199 pg_cron-main]$ cd $PGHOME\/share\/extension\n&#x5B;postgres@ocm199 extension]$\n\n&#x5B;postgres@ocm199 extension]$ ls -l | grep cron\n-rw-r--r--. 1 postgres postgres 101 Nov 13 03:17 pg_cron--1.0--1.1.sql\n-rw-r--r--. 1 postgres postgres 2034 Nov 13 03:17 pg_cron--1.0.sql\n-rw-r--r--. 1 postgres postgres 155 Nov 13 03:17 pg_cron--1.1--1.2.sql\n-rw-r--r--. 1 postgres postgres 1407 Nov 13 03:17 pg_cron--1.2--1.3.sql\n-rw-r--r--. 1 postgres postgres 121 Nov 13 03:17 pg_cron.control\n<\/pre>\n<p>After we have compiled pg_cron successfully, we need to adjust the postgresql.conf and load the extension into the pg_cron database.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\n$ cat postgresql.conf\n...\n...\nshared_preload_libraries = 'pg_cron'\n\n# pg_cron extension\ncron.database_name='pg_cron'\n<\/pre>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n&#x5B;postgres@ocm199 PTDB01A]$ psql\npsql (13.1 - Pt(78)DB PostgreSQL 13.1 JIT Build)\nType &quot;help&quot; for help.\n\n(postgres@&#x5B;local]:55001)&#x5B;postgres]&gt; \\l\n                                   List of databases\n   Name    |    Owner    | Encoding |   Collate   |    Ctype    |   Access privileges\n-----------+-------------+----------+-------------+-------------+-----------------------\n pg_cron   | cron_admin  | UTF8     | en_US.UTF-8 | en_US.UTF-8 |\n postgres  | postgres    | UTF8     | en_US.UTF-8 | en_US.UTF-8 |\n ptdb      | ptdb_admin  | UTF8     | en_US.UTF-8 | en_US.UTF-8 |\n slvdb     | slvdb_admin | UTF8     | en_US.UTF-8 | en_US.UTF-8 |\n template0 | postgres    | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c\/postgres          +\n           |             |          |             |             | postgres=CTc\/postgres\n template1 | postgres    | UTF8     | en_US.UTF-8 | en_US.UTF-8 | =c\/postgres          +\n           |             |          |             |             | postgres=CTc\/postgres\n(6 rows)\n\n(postgres@&#x5B;local]:55001)&#x5B;postgres]&gt; \\c pg_cron\nYou are now connected to database &quot;pg_cron&quot; as user &quot;postgres&quot;.\n(postgres@&#x5B;local]:55001)&#x5B;pg_cron]&gt;\n(postgres@&#x5B;local]:55001)&#x5B;pg_cron]&gt; create extension pg_cron;\nCREATE EXTENSION\n(postgres@&#x5B;local]:55001)&#x5B;pg_cron]&gt;\n<\/pre>\n<p>After creating the extension, you should see a new schema called cron and few new objects in it.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n(postgres@&#x5B;local]:55001)&#x5B;pg_cron]&gt; \\dn\n  List of schemas\n  Name  |  Owner\n--------+----------\n cron   | postgres\n public | postgres\n(2 rows)\n\n(postgres@&#x5B;local]:55001)&#x5B;pg_cron]&gt; set search_path=cron;\nSET\n(postgres@&#x5B;local]:55001)&#x5B;pg_cron]&gt; \\dt+\n                                  List of relations\n Schema |      Name       | Type  |  Owner   | Persistence |    Size    | Description\n--------+-----------------+-------+----------+-------------+------------+-------------\n cron   | job             | table | postgres | permanent   | 8192 bytes |\n cron   | job_run_details | table | postgres | permanent   | 8192 bytes |\n(2 rows)\n<\/pre>\n<p>Change the ownership of the cron objects to user cron_admin<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n\n(postgres@&#x5B;local]:55001)&#x5B;postgres]&gt; \\c pg_cron\nYou are now connected to database &quot;pg_cron&quot; as user &quot;postgres&quot;.\n(postgres@&#x5B;local]:55001)&#x5B;pg_cron]&gt; set search_path=cron;\nSET\n(postgres@&#x5B;local]:55001)&#x5B;pg_cron]&gt; \\dt+\n                                  List of relations\n Schema |      Name       | Type  |  Owner   | Persistence |    Size    | Description\n--------+-----------------+-------+----------+-------------+------------+-------------\n cron   | job             | table | postgres | permanent   | 8192 bytes |\n cron   | job_run_details | table | postgres | permanent   | 8192 bytes |\n(2 rows)\n\n(postgres@&#x5B;local]:55001)&#x5B;pg_cron]&gt; GRANT USAGE ON SCHEMA cron TO cron_admin;\nGRANT\n(postgres@&#x5B;local]:55001)&#x5B;pg_cron]&gt; ALTER TABLE cron.job OWNER TO cron_admin;\nALTER TABLE\n(postgres@&#x5B;local]:55001)&#x5B;pg_cron]&gt; ALTER TABLE cron.job_run_details OWNER TO cron_admin;\nALTER TABLE\n(postgres@&#x5B;local]:55001)&#x5B;pg_cron]&gt; ALTER SEQUENCE cron.jobid_seq OWNER TO cron_admin;\nALTER SEQUENCE\n(postgres@&#x5B;local]:55001)&#x5B;pg_cron]&gt; ALTER SEQUENCE cron.runid_seq OWNER TO cron_admin;\nALTER SEQUENCE\n(postgres@&#x5B;local]:55001)&#x5B;pg_cron]&gt; ALTER FUNCTION cron.job_cache_invalidate() OWNER TO cron_admin;\nALTER FUNCTION\n(postgres@&#x5B;local]:55001)&#x5B;pg_cron]&gt; ALTER FUNCTION cron.schedule (job_name name, schedule text, command text) OWNER TO cron_admin;\nALTER FUNCTION\n(postgres@&#x5B;local]:55001)&#x5B;pg_cron]&gt; ALTER FUNCTION cron.schedule (schedule text, command text) OWNER TO cron_admin;\nALTER FUNCTION\n(postgres@&#x5B;local]:55001)&#x5B;pg_cron]&gt; ALTER FUNCTION cron.unschedule (job_id bigint) OWNER TO cron_admin;\nALTER FUNCTION\n(postgres@&#x5B;local]:55001)&#x5B;pg_cron]&gt; ALTER FUNCTION cron.unschedule (job_name name) OWNER TO cron_admin;\nALTER FUNCTION\n\n<\/pre>\n<p>Now we create a big table in each database. This has nothing to do with pg_cron. This is only to have some data to play with.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n&#x5B;postgres@ocm199 PTDB01A]$ psql --username=ptdb_admin --dbname=ptdb\nPassword for user ptdb_admin:\npsql (13.1 - Pt(78)DB PostgreSQL 13.1 JIT Build)\nType &quot;help&quot; for help.\n(ptdb_admin@&#x5B;local]:55001)&#x5B;ptdb]&gt; create schema ptdb;\nCREATE SCHEMA\n(ptdb_admin@&#x5B;local]:55001)&#x5B;ptdb]&gt; set search_path=ptdb;\nSET\n(ptdb_admin@&#x5B;local]:55001)&#x5B;ptdb]&gt; CREATE TABLE ptdb_big (n1 bigint, n2 double precision, n3 double precision);\nCREATE TABLE\n(ptdb_admin@&#x5B;local]:55001)&#x5B;ptdb]&gt; INSERT INTO ptdb_big (n1, n2, n3)\n&gt;   SELECT round(random()*10), random(), random()*142\n&gt;   FROM generate_series(1, 20000000) s(i);\nINSERT 0 20000000\n(ptdb_admin@&#x5B;local]:55001)&#x5B;ptdb]&gt; CREATE INDEX i1 ON ptdb_big (n1);\nCREATE INDEX\n(ptdb_admin@&#x5B;local]:55001)&#x5B;ptdb]&gt; ANALYZE VERBOSE ptdb_big;\nINFO:  analyzing &quot;ptdb.ptdb_big&quot;\nINFO:  &quot;ptdb_big&quot;: scanned 30000 of 127389 pages, containing 4710000 live rows and 0 dead rows; 30000 rows in sample, 20000073 estimated total rows\nANALYZE\n(ptdb_admin@&#x5B;local]:55001)&#x5B;ptdb]&gt; \\dt+\n                              List of relations\n Schema |   Name   | Type  |   Owner    | Persistence |  Size  | Description\n--------+----------+-------+------------+-------------+--------+-------------\n ptdb   | ptdb_big | table | ptdb_admin | permanent   | 996 MB |\n(1 row)\n\n(ptdb_admin@&#x5B;local]:55001)&#x5B;ptdb]&gt;\n<\/pre>\n<p>Do the same for database slvdb<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n&#x5B;postgres@ocm199 PTDB01A]$ psql --username=slvdb_admin --dbname=slvdb\nPassword for user slvdb_admin:\npsql (13.1 - Pt(78)DB PostgreSQL 13.1 JIT Build)\nType &quot;help&quot; for help.\n\n(slvdb_admin@&#x5B;local]:55001)&#x5B;slvdb]&gt; create schema slvdb;\nCREATE SCHEMA\n(slvdb_admin@&#x5B;local]:55001)&#x5B;slvdb]&gt; set search_path=slvdb;\nSET\n(slvdb_admin@&#x5B;local]:55001)&#x5B;slvdb]&gt; CREATE TABLE slvdb_big (n1 bigint, n2 double precision, n3 double precision);\nCREATE TABLE\n(slvdb_admin@&#x5B;local]:55001)&#x5B;slvdb]&gt; INSERT INTO slvdb_big (n1, n2, n3)\n&gt;   SELECT round(random()*10), random(), random()*142\n&gt;   FROM generate_series(1, 20000000) s(i);\nINSERT 0 20000000\n(slvdb_admin@&#x5B;local]:55001)&#x5B;slvdb]&gt; CREATE INDEX i1 ON slvdb_big (n1);\nCREATE INDEX\n(slvdb_admin@&#x5B;local]:55001)&#x5B;slvdb]&gt; ANALYZE VERBOSE slvdb_big;\nINFO:  analyzing &quot;slvdb.slvdb_big&quot;\nINFO:  &quot;slvdb_big&quot;: scanned 30000 of 127389 pages, containing 4710000 live rows and 0 dead rows; 30000 rows in sample, 20000073 estimated total rows\nANALYZE\n(slvdb_admin@&#x5B;local]:55001)&#x5B;slvdb]&gt; \\dt+\n                               List of relations\n Schema |   Name    | Type  |    Owner    | Persistence |  Size  | Description\n--------+-----------+-------+-------------+-------------+--------+-------------\n slvdb  | slvdb_big | table | slvdb_admin | permanent   | 996 MB |\n(1 row)\n\n(slvdb_admin@&#x5B;local]:55001)&#x5B;slvdb]&gt;\n<\/pre>\n<p>Now we can start scheduling jobs as user cron_admin for database ptdb<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n(cron_admin@&#x5B;local]:55001)&#x5B;pg_cron] SELECT cron.schedule('ptdb-minute-analyze', '* * * * *', 'ANALYZE ptdb.ptdb_big');\nschedule\n----------\n1\n(1 row)\n<\/pre>\n<p>From the alert.log<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n2020-11-14 03:06:33.399 EST &#x5B;11014] LOG: pg_cron scheduler started\n2020-11-14 03:09:00.005 EST &#x5B;11014] LOG: cron job 1 starting: ANALYZE ANALYZE ptdb.ptdb_big\n2020-11-14 03:09:00.033 EST &#x5B;11014] LOG: cron job 1 connection failed\n<\/pre>\n<p>As you might notice, the wrong database and username are in the job table<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n(cron_admin@&#x5B;local]:55001)&#x5B;pg_cron]&gt; select jobid, command, nodename, nodeport, database, username from job;\n jobid |        command        | nodename  | nodeport | database |  username\n-------+-----------------------+-----------+----------+----------+------------\n     2 | ANALYZE ptdb.ptdb_big | localhost |    55001 | pg_cron  | cron_admin\n(1 row)\n<\/pre>\n<p>Looks much better<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n(cron_admin@&#x5B;local]:55001)&#x5B;pg_cron]&gt; update job set database='ptdb' where jobid = 2;\nUPDATE 1\n(cron_admin@&#x5B;local]:55001)&#x5B;pg_cron]&gt; update job set username='ptdb_admin' where jobid = 2;\nUPDATE 1\n(cron_admin@&#x5B;local]:55001)&#x5B;pg_cron]&gt; select jobid, command, nodename, nodeport, database, username from job;\n jobid |        command        | nodename  | nodeport | database |  username\n-------+-----------------------+-----------+----------+----------+------------\n     2 | ANALYZE ptdb.ptdb_big | localhost |    55001 | ptdb     | ptdb_admin\n(1 row)\n<\/pre>\n<p>The job still failed<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n2020-11-14 03:19:00.003 EST &#x5B;11014] LOG: cron job 2 starting: ANALYZE ptdb.ptdb_big\n2020-11-14 03:19:00.007 EST &#x5B;11014] LOG: cron job 2 connection failed\n<\/pre>\n<p>Adjust the .pgpass file<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n&#x5B;postgres@ocm199 ~]$ cat .pgpass\n#hostname:port:database:username:password\nlocalhost:55001:ptdb:ptdb_admin:ptdb_admin\n<\/pre>\n<p>Test if it works<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n&#x5B;postgres@ocm199 ~]$ psql --username=ptdb_admin --dbname=ptdb\npsql (13.1 - Pt(78)DB PostgreSQL 13.1 JIT Build)\nType &quot;help&quot; for help.\n\n(ptdb_admin@&#x5B;local]:55001)&#x5B;ptdb] exit\n&#x5B;postgres@ocm199 ~]$\n<\/pre>\n<p>From the alert.log<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n2020-11-14 03:20:00.003 EST &#x5B;11014] LOG: cron job 2 starting: ANALYZE ptdb.ptdb_big\n2020-11-14 03:20:05.678 EST &#x5B;11014] LOG: cron job 2 COMMAND completed: ANALYZE\n<\/pre>\n<p>Cool. It worked. Now we can repeat the same steps for the slvdb and start scheduling jobs as user cron_admin for database slvdb<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n&#x5B;postgres@ocm199 ~]$ cat .pgpass\n#hostname:port:database:username:password\nlocalhost:55001:ptdb:ptdb_admin:ptdb_admin\nlocalhost:55001:slvdb:slvdb_admin:slvdb_admin\n<\/pre>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n(cron_admin@&#x5B;local]:55001)&#x5B;pg_cron]&gt; SELECT cron.schedule('slvdb-minute-analyze', '* * * * *', 'ANALYZE slvdb.slvdb_big');\n schedule\n----------\n        3\n(1 row)\n\n(cron_admin@&#x5B;local]:55001)&#x5B;pg_cron]&gt; select jobid, command, nodename, nodeport, database, username from job;\n jobid |         command         | nodename  | nodeport | database |  username\n-------+-------------------------+-----------+----------+----------+------------\n     2 | ANALYZE ptdb.ptdb_big   | localhost |    55001 | ptdb     | ptdb_admin\n     3 | ANALYZE slvdb.slvdb_big | localhost |    55001 | pg_cron  | cron_admin\n(2 rows)\n\n\n(cron_admin@&#x5B;local]:55001)&#x5B;pg_cron]&gt; update job set database='slvdb' where jobid = 3;\nUPDATE 1\n(cron_admin@&#x5B;local]:55001)&#x5B;pg_cron]&gt; update job set username='slvdb_admin' where jobid = 3;\nUPDATE 1\n(cron_admin@&#x5B;local]:55001)&#x5B;pg_cron]&gt; select jobid, command, nodename, nodeport, database, username from job;\n jobid |         command         | nodename  | nodeport | database |  username\n-------+-------------------------+-----------+----------+----------+-------------\n     2 | ANALYZE ptdb.ptdb_big   | localhost |    55001 | ptdb     | ptdb_admin\n     3 | ANALYZE slvdb.slvdb_big | localhost |    55001 | slvdb    | slvdb_admin\n(2 rows)\n\n(cron_admin@&#x5B;local]:55001)&#x5B;pg_cron]&gt;\n<\/pre>\n<p>From the alert.log<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\n2020-11-14 03:23:00.002 EST &#x5B;11014] LOG: cron job 2 starting: ANALYZE ptdb.ptdb_big\n2020-11-14 03:23:00.006 EST &#x5B;11014] LOG: cron job 3 starting: ANALYZE slvdb.slvdb_big\n2020-11-14 03:23:00.439 EST &#x5B;11014] LOG: cron job 2 COMMAND completed: ANALYZE\n2020-11-14 03:23:05.886 EST &#x5B;11014] LOG: cron job 3 COMMAND completed: ANALYZE\n<\/pre>\n<p>Ready.<\/p>\n<h3>Conclusion<\/h3>\n<p>With a few workarounds, you can schedule jobs for different databases and different users with pg_cron. The trick essentially is to create a separate pg_cron database which holds only the pg_cron extension. Besides that, you are very flexible as well. For example, if you need to drop one of the databases, the pg_cron jobs for all others still work.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A new release of the famous pg_cron (v1.3.0) came out on the 6th of October with a lot of cool new features, and a few bug fixes as well. The PostgreSQL 13 support might be the most important one, however, some other very cool stuff was implemented as well. Especially the audit table support might [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ocean_post_layout":"","ocean_both_sidebars_style":"","ocean_both_sidebars_content_width":0,"ocean_both_sidebars_sidebars_width":0,"ocean_sidebar":"","ocean_second_sidebar":"","ocean_disable_margins":"enable","ocean_add_body_class":"","ocean_shortcode_before_top_bar":"","ocean_shortcode_after_top_bar":"","ocean_shortcode_before_header":"","ocean_shortcode_after_header":"","ocean_has_shortcode":"","ocean_shortcode_after_title":"","ocean_shortcode_before_footer_widgets":"","ocean_shortcode_after_footer_widgets":"","ocean_shortcode_before_footer_bottom":"","ocean_shortcode_after_footer_bottom":"","ocean_display_top_bar":"default","ocean_display_header":"default","ocean_header_style":"","ocean_center_header_left_menu":"","ocean_custom_header_template":"","ocean_custom_logo":0,"ocean_custom_retina_logo":0,"ocean_custom_logo_max_width":0,"ocean_custom_logo_tablet_max_width":0,"ocean_custom_logo_mobile_max_width":0,"ocean_custom_logo_max_height":0,"ocean_custom_logo_tablet_max_height":0,"ocean_custom_logo_mobile_max_height":0,"ocean_header_custom_menu":"","ocean_menu_typo_font_family":"","ocean_menu_typo_font_subset":"","ocean_menu_typo_font_size":0,"ocean_menu_typo_font_size_tablet":0,"ocean_menu_typo_font_size_mobile":0,"ocean_menu_typo_font_size_unit":"px","ocean_menu_typo_font_weight":"","ocean_menu_typo_font_weight_tablet":"","ocean_menu_typo_font_weight_mobile":"","ocean_menu_typo_transform":"","ocean_menu_typo_transform_tablet":"","ocean_menu_typo_transform_mobile":"","ocean_menu_typo_line_height":0,"ocean_menu_typo_line_height_tablet":0,"ocean_menu_typo_line_height_mobile":0,"ocean_menu_typo_line_height_unit":"","ocean_menu_typo_spacing":0,"ocean_menu_typo_spacing_tablet":0,"ocean_menu_typo_spacing_mobile":0,"ocean_menu_typo_spacing_unit":"","ocean_menu_link_color":"","ocean_menu_link_color_hover":"","ocean_menu_link_color_active":"","ocean_menu_link_background":"","ocean_menu_link_hover_background":"","ocean_menu_link_active_background":"","ocean_menu_social_links_bg":"","ocean_menu_social_hover_links_bg":"","ocean_menu_social_links_color":"","ocean_menu_social_hover_links_color":"","ocean_disable_title":"default","ocean_disable_heading":"default","ocean_post_title":"","ocean_post_subheading":"","ocean_post_title_style":"","ocean_post_title_background_color":"","ocean_post_title_background":0,"ocean_post_title_bg_image_position":"","ocean_post_title_bg_image_attachment":"","ocean_post_title_bg_image_repeat":"","ocean_post_title_bg_image_size":"","ocean_post_title_height":0,"ocean_post_title_bg_overlay":0.5,"ocean_post_title_bg_overlay_color":"","ocean_disable_breadcrumbs":"default","ocean_breadcrumbs_color":"","ocean_breadcrumbs_separator_color":"","ocean_breadcrumbs_links_color":"","ocean_breadcrumbs_links_hover_color":"","ocean_display_footer_widgets":"default","ocean_display_footer_bottom":"default","ocean_custom_footer_template":"","ocean_post_oembed":"","ocean_post_self_hosted_media":"","ocean_post_video_embed":"","ocean_link_format":"","ocean_link_format_target":"self","ocean_quote_format":"","ocean_quote_format_link":"post","ocean_gallery_link_images":"on","ocean_gallery_id":[],"footnotes":""},"categories":[6],"tags":[19,21],"class_list":["post-107","post","type-post","status-publish","format-standard","hentry","category-postgresql","tag-pg_cron","tag-postgresql","entry"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.7 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>PostgreSQL: How To Schedule Jobs With The pg_cron Extension In Multiple Databases - ptdb - Platinum DB<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/ptdb.ch\/?p=107\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"PostgreSQL: How To Schedule Jobs With The pg_cron Extension In Multiple Databases - ptdb - Platinum DB\" \/>\n<meta property=\"og:description\" content=\"A new release of the famous pg_cron (v1.3.0) came out on the 6th of October with a lot of cool new features, and a few bug fixes as well. The PostgreSQL 13 support might be the most important one, however, some other very cool stuff was implemented as well. Especially the audit table support might [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/ptdb.ch\/?p=107\" \/>\n<meta property=\"og:site_name\" content=\"ptdb - Platinum DB\" \/>\n<meta property=\"article:published_time\" content=\"2020-11-14T08:52:04+00:00\" \/>\n<meta name=\"author\" content=\"admin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"admin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"13 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/ptdb.ch\\\/?p=107#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/ptdb.ch\\\/?p=107\"},\"author\":{\"name\":\"admin\",\"@id\":\"https:\\\/\\\/ptdb.ch\\\/#\\\/schema\\\/person\\\/0b7baf52d23e71d85e1c95442306090b\"},\"headline\":\"PostgreSQL: How To Schedule Jobs With The pg_cron Extension In Multiple Databases\",\"datePublished\":\"2020-11-14T08:52:04+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/ptdb.ch\\\/?p=107\"},\"wordCount\":2515,\"keywords\":[\"pg_cron\",\"PostgreSQL\"],\"articleSection\":[\"PostgreSQL\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/ptdb.ch\\\/?p=107\",\"url\":\"https:\\\/\\\/ptdb.ch\\\/?p=107\",\"name\":\"PostgreSQL: How To Schedule Jobs With The pg_cron Extension In Multiple Databases - ptdb - Platinum DB\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/ptdb.ch\\\/#website\"},\"datePublished\":\"2020-11-14T08:52:04+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/ptdb.ch\\\/#\\\/schema\\\/person\\\/0b7baf52d23e71d85e1c95442306090b\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/ptdb.ch\\\/?p=107#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/ptdb.ch\\\/?p=107\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/ptdb.ch\\\/?p=107#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/ptdb.ch\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"PostgreSQL: How To Schedule Jobs With The pg_cron Extension In Multiple Databases\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/ptdb.ch\\\/#website\",\"url\":\"https:\\\/\\\/ptdb.ch\\\/\",\"name\":\"ptdb - Platinum DB\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/ptdb.ch\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/ptdb.ch\\\/#\\\/schema\\\/person\\\/0b7baf52d23e71d85e1c95442306090b\",\"name\":\"admin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/1a3dffc48c5f6bae0b88a9f0b2a986d48d322673fbc2880c5abbfab96e45da8a?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/1a3dffc48c5f6bae0b88a9f0b2a986d48d322673fbc2880c5abbfab96e45da8a?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/1a3dffc48c5f6bae0b88a9f0b2a986d48d322673fbc2880c5abbfab96e45da8a?s=96&d=mm&r=g\",\"caption\":\"admin\"},\"sameAs\":[\"https:\\\/\\\/ptdb.ch\"],\"url\":\"https:\\\/\\\/ptdb.ch\\\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"PostgreSQL: How To Schedule Jobs With The pg_cron Extension In Multiple Databases - ptdb - Platinum DB","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/ptdb.ch\/?p=107","og_locale":"en_US","og_type":"article","og_title":"PostgreSQL: How To Schedule Jobs With The pg_cron Extension In Multiple Databases - ptdb - Platinum DB","og_description":"A new release of the famous pg_cron (v1.3.0) came out on the 6th of October with a lot of cool new features, and a few bug fixes as well. The PostgreSQL 13 support might be the most important one, however, some other very cool stuff was implemented as well. Especially the audit table support might [&hellip;]","og_url":"https:\/\/ptdb.ch\/?p=107","og_site_name":"ptdb - Platinum DB","article_published_time":"2020-11-14T08:52:04+00:00","author":"admin","twitter_card":"summary_large_image","twitter_misc":{"Written by":"admin","Est. reading time":"13 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/ptdb.ch\/?p=107#article","isPartOf":{"@id":"https:\/\/ptdb.ch\/?p=107"},"author":{"name":"admin","@id":"https:\/\/ptdb.ch\/#\/schema\/person\/0b7baf52d23e71d85e1c95442306090b"},"headline":"PostgreSQL: How To Schedule Jobs With The pg_cron Extension In Multiple Databases","datePublished":"2020-11-14T08:52:04+00:00","mainEntityOfPage":{"@id":"https:\/\/ptdb.ch\/?p=107"},"wordCount":2515,"keywords":["pg_cron","PostgreSQL"],"articleSection":["PostgreSQL"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/ptdb.ch\/?p=107","url":"https:\/\/ptdb.ch\/?p=107","name":"PostgreSQL: How To Schedule Jobs With The pg_cron Extension In Multiple Databases - ptdb - Platinum DB","isPartOf":{"@id":"https:\/\/ptdb.ch\/#website"},"datePublished":"2020-11-14T08:52:04+00:00","author":{"@id":"https:\/\/ptdb.ch\/#\/schema\/person\/0b7baf52d23e71d85e1c95442306090b"},"breadcrumb":{"@id":"https:\/\/ptdb.ch\/?p=107#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/ptdb.ch\/?p=107"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/ptdb.ch\/?p=107#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/ptdb.ch\/"},{"@type":"ListItem","position":2,"name":"PostgreSQL: How To Schedule Jobs With The pg_cron Extension In Multiple Databases"}]},{"@type":"WebSite","@id":"https:\/\/ptdb.ch\/#website","url":"https:\/\/ptdb.ch\/","name":"ptdb - Platinum DB","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/ptdb.ch\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/ptdb.ch\/#\/schema\/person\/0b7baf52d23e71d85e1c95442306090b","name":"admin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/1a3dffc48c5f6bae0b88a9f0b2a986d48d322673fbc2880c5abbfab96e45da8a?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/1a3dffc48c5f6bae0b88a9f0b2a986d48d322673fbc2880c5abbfab96e45da8a?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/1a3dffc48c5f6bae0b88a9f0b2a986d48d322673fbc2880c5abbfab96e45da8a?s=96&d=mm&r=g","caption":"admin"},"sameAs":["https:\/\/ptdb.ch"],"url":"https:\/\/ptdb.ch\/?author=1"}]}},"_links":{"self":[{"href":"https:\/\/ptdb.ch\/index.php?rest_route=\/wp\/v2\/posts\/107","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/ptdb.ch\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/ptdb.ch\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/ptdb.ch\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/ptdb.ch\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=107"}],"version-history":[{"count":0,"href":"https:\/\/ptdb.ch\/index.php?rest_route=\/wp\/v2\/posts\/107\/revisions"}],"wp:attachment":[{"href":"https:\/\/ptdb.ch\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=107"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/ptdb.ch\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=107"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/ptdb.ch\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=107"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}