Blog JSystems - uwalniamy wiedzę!

Szukaj
Pg cron: PG_CRON


Rozszerzenie PG_CRON pozwala na automatyczne uruchamianie cyklicznych zadań w serwerze PostgreSQL. Możesz to wykorzystać np. do przeładowywania danych w hurtownii danych, czy uruchamiania procesów konserwacyjnych.

Z tego artykułu dowiesz się:
  • czym jest pg_cron i w jakim celu się go stosuje,
  • jak zainstalować pg_cron na Ubuntu,
  • jak zainstalować pg_cron na systemach z rodziny Red Hat,
  • w jaki sposób konfigurować automatyczne uruchamianie zadań w PostgreSQL za pomocą pg_cron,
  • jak przeglądać zaplanowane zadania,
  • jak modyfikować zaplanowane zadania,
  • jak wyłączać zaplanowane zadania,
  • jak przeglądać historię wykonanych zaplanowanych zdań wraz z ewentualnymi błędami i ich przyczynami.

Zaczynając swoją przygodę z Postgresem, często jedną z pierwszych przeszkód, z którą się stykamy, jest problem planowania cyklicznych zadań w bazie danych. Postgres domyślnie nie posiada takiej funkcjonalności (jak na przykład DBMS_JOB w Oracle), a jedyną możliwością jest zaplanowanie zadań w cronie systemowym. Istnieje jednak rozszerzenie pg_cron, dostępne w oficjalnych repozytoriach PGDG oraz do pobrania i własnej kompilacji ze strony projektu na github (https://github.com/citusdata/pg_cron), umożliwiające zaplanowanie powtarzalnych zadań bezpośrednio w Postgresie z wykorzystaniem składni znanej ze wspomnianego wcześniej unixowego crona.

Instalacja i konfiguracja

Instalacja zależnie od systemu, z którego korzystamy, odbywa się za pomocą yum na systemach z rodziny RedHat lub apt na pochodnych Debiana.

## RHEL, CentOS, Fedora, Rocky

1) wget https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm

2) sudo yum install pgdg-redhat-repo-latest.noarch.rpm

3) sudo yum install -y pg_cron_15



## Debian, Ubuntu

1) sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'

2) wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | \

sudo apt-key add -

3) sudo apt-get update

4) sudo apt-get -y install postgresql-15-cron

Inną możliwością jest pobranie projektu bezpośrednio z githuba i własnoręczna kompilacja.

git clone https://github.com/citusdata/pg_cron.git

cd pg_cron

# upewnij się, że zmienna PATH zawiera ścieżkę do pg_config

# przykładowo: /usr/lib/postgresql/15/bin dla ubuntu, albo /usr/pgsql-15/bin dla redhata

export PATH=/usr/pgsql-15/bin:$PATH

make && sudo PATH=$PATH make install

Następnie dodajemy ‘pg_cron’ do parametru shared_preload_libraries w pliku "postgresql.conf", albo przez ALTER SYSTEM i restartujemy Postgresa, aby zmiany zostały wczytane podczas startu Postgresa.

#Sprawdź czy nie masz już ustawionych jakichś bibliotek w "shared_preload_libraries"

show shared_preload_libraries;


#Zmiana parametru w postgresql.conf lub przez ALTER SYSTEM: alter system set shared_preload_libraries='pg_cron'; #można po przecinku: 'pg_prewarm,pg_cron'
# na Ubuntu /usr/lib/postgresql/15/bin/pg_ctl -D /katalog/pg/data restart -m fast # na RHEL /usr/pgsql-15/bin/pg_ctl -D /katalog/pg/data restart -m fast # ścieżkę do katalogu z danymi znajdziemy za pomocą postgres=# show data_directory; data_directory ----------------------------- /var/lib/postgresql/15/main (1 row)

Rozszerzenie tworzymy zawsze tylko w jednej bazie, którą później wykorzystujemy jako pojedynczy punkt do zarządzania wszystkimi zadaniami. Jeżeli planujemy stworzenie rozszerzenia w bazie innej niż domyślna Postgres, powinniśmy dodać do pliku konfiguracyjnego nowy parametr cron.database_name, na przykład:

shared_preload_libraries = 'pg_cron'

cron.database_name = 'apka'

Po ponownym uruchomieniu możemy dodać rozszerzenie do jednej wybranej bazy, w której będziemy planować zadania. Opcjonalnie możemy również nadać uprawnienia dla schematu “cron” użytkownikom, którzy powinni mieć możliwość planowania zadań.

postgres@vagrant:~$ psql -c "create extension pg_cron;" apka

CREATE EXTENSION

postgres@vagrant:~$ psql -c "grant usage on schema cron to planer;" apka

GRANT

Jak skorzystamy z domyślnych wartości i stworzymy rozszerzenie oraz zaplanujemy zadania jako użytkownicy Postgres w bazie Postgres.

postgres@vagrant:~$ psql -c "create extension pg_cron;"

CREATE EXTENSION

Sposoby wykonywania zadań w pg_cron

Domyślnie pg_cron otwiera lokalnie połączenia do bazy, aby wykonywać zaplanowane zadania, dlatego musimy zadbać o odpowiednią konfigurację pg_hba.conf orac .pgpass, jeżeli logowanie wymaga hasła. Korzystając z użytkownika Postgres oraz bazy Postgres, wystarczy zastosować wpis dla połączeń lokalnych za pomocą socketa z metodą autentykacji “peer”.

local - postgres      postgres - peer

Jeżeli chcielibyśmy korzystać z innego użytkownika, lub z hasła, musimy odpowiednio zmodyfikować oba pliki konfiguracyjne.

Drugim sposobem wykorzystania zadań jest korzystanie z procesów tła do wykonywania zadań, zamiast nawiązywania lokalnych połączeń do bazy. Aby pg_cron przygotować do działania w ten sposób, musimy zmienić dwa parametry, oba wymagają restartu Postgresa, aby zmiany weszły w życie:

cron.use_background_workers = on

max_worker_processes = 20

cron.use_background_workers aby pg_cron korzystał z procesów tła oraz za pomocą parametru max_worker_processes określić ile zadań będzie mogło się wykonywać jednocześnie, domyślnie liczba procesów ograniczona jest do 8.

Warto też pamiętać, że pg_cron wykonuje zadania tylko na instancji głównej klastra. Rozszerzenie może być zainstalowane i skonfigurowane na instancjach standby, jednak zadania nie będą wykonywane na serwerach standby do momentu awansowania ich na instancję primary i otwarciu na zapis.

Planowanie zadań

Zadania możemy zaplanować za pomocą funkcji cron.schedule(), która przyjmuje dwie lub trzy wartości. W pierwszej wersji- harmonogram oraz polecenie do wykonania, w drugiej- nazwę zadania, harmonogram oraz polecenie. Harmonogram ustalamy za pomocą składni znanej z systemowego crona.

 ┌───────────── minuty (0 - 59)

 │ ┌────────────── godziny (0 - 23)

 │ │ ┌─────────────── dzień miesiąca (1 - 31)

 │ │ │ ┌──────────────── miesiąc (1 - 12)

 │ │ │ │ ┌───────────────── dzień tygodnia (0 - 7) (0 - 6, niedziela do soboty, 7 to

 │ │ │ │ │                            również niedziela)

 * * * * *

Przykładowe zaplanowanie zadania

SELECT - cron.schedule('select','* * * * *', 'SELECT 1');

Wszystkie zaplanowane zadania możemy znaleźć w tabeli cron.job.
Jeżeli korzystamy z większej liczby użytkowników do planowania zadań, każdy z nich może zobaczyć tylko swoje zadania. Ograniczenie to nie dotyczy użytkownika Postgres, który jest superuserem i może zobaczyć wszystkie zadania. Należy również pamiętać, że użytkownik, który jest “właścicielem” zadania, powinien mieć uprawnienia do obiektów, na których będzie pracował.

postgres=# set role=test;

SET                                                                 ^

postgres=> SELECT - cron.schedule('select','* * * * *', 'SELECT 1');

 schedule 

----------

        1

(1 row)



postgres=> select * from cron.job;

 jobid | schedule - | command - | nodename - | nodeport | database | username | active | jobname 

-------+-----------+----------+-----------+----------+----------+----------+--------+---------

     1 | * * * * * | SELECT 1 | localhost |      5432 | postgres | test      | t        | select

(1 row)



postgres=> set role=planer;

SET

postgres=> select * from cron.job;

 jobid | schedule | command | nodename | nodeport | database | username | active | jobname 

-------+----------+---------+----------+----------+----------+----------+--------+---------

(0 rows)



postgres=> set role=postgres;

SET

postgres=# select * from cron.job;

 jobid | schedule - | command - | nodename - | nodeport | database | username | active | jobname 

-------+-----------+----------+-----------+----------+----------+----------+--------+---------

     1 | * * * * * | SELECT 1 | localhost |      5432 | postgres | test      | t        | select

(1 row)

Wszystkie zadania, które tworzymy za pomocą funkcji cron.schedule(), są domyślnie tworzone w bazie Postgres, chyba że ustawiliśmy inaczej w parametrze cron.database_name oraz w innej bazie stworzyliśmy rozszerzenie. Chcąc tworzyć zadania dla innych baz, powinniśmy skorzystać z funkcji cron.schedule_in_database(). Przyjmuje ona wartości: nazwa zadania, harmonogram, polecenie, nazwa bazy, nazwa użytkownika oraz informację, czy zadanie powinno być aktywne. Jeżeli pominiemy ostatni parametr, domyślnie utworzy się jako aktywny.

postgres=# SELECT cron.schedule_in_database('zadanie w innej bazie','0 6 * * *','SELECT 1', 'app_db','planer','f');

 schedule_in_database 

----------------------

                    2

(1 row)



postgres=# select * from cron.job;

 jobid | schedule - | command - | nodename - | nodeport | database | username | active |          jobname          

-------+-----------+----------+-----------+----------+----------+----------+--------+-----------------------

     1 | * * * * * | SELECT 1 | localhost |      5432 | postgres | test      | t        | select



     2 | 0 6 * * * | SELECT 1 | localhost |      5432 | app_db - | planer - | f        | zadanie w innej bazie



(2 rows)

Tak stworzone zadanie nie będzie się wykonywało do momentu zmiany flagi “active” na “t” za pomocą funkcji cron.alter_job() przyjmującej wartości: jobid, schedule, command, database, username, active. Jedynie pierwsza wartość jest obowiązkowa, pozostałe wartości możemy zmienić, aby zmodyfikować zadanie lub podać dla nich wartość NULL, aby zachować oryginalne wartości.

postgres=# select cron.alter_job(2,null,null,null,null,'t');

 alter_job 

-----------



(1 row)



postgres=# select * from cron.job;

 jobid | schedule - | command - | nodename - | nodeport | database | username | active |          jobname          

-------+-----------+----------+-----------+----------+----------+----------+--------+-----------------------

     1 | * * * * * | SELECT 1 | localhost |      5432 | postgres | test      | t        | select

     2 | 0 6 * * * | SELECT 1 | localhost |      5432 | app_db - | planer - | t        | zadanie w innej bazie



(3 rows)

Zadania, które są już niepotrzebne, można usunąć za pomocą funkcji cron.unschedule(), która przyjmuje jako parametr jobid lub jobname.

select cron.unschedule('zadanie w innej bazie');

select cron.unschedule(2);

Oba powyższe wywołania mają ten sam efekt, spowodują usunięcie drugiego zadania w bazie “app_db”. Jeżeli mielibyśmy więcej niż jedno zadanie z taką samą nazwą, zostanie usunięte tylko jedno, z najniższym jobid.

Historię wykonanych zadań możemy przejrzeć w tabeli cron.job_run_details.

postgres=# select * from cron.job_run_details ; 

 jobid | runid | job_pid | database | username | command - | status | - return_message - | start_time | end_time 

-------+-------+---------+----------+----------+----------+--------+-------------------+------------+----------

     1 |     27 |           | postgres | test      | SELECT 1 | failed | connection failed |               | 

     1 |      1 |           | postgres | test      | SELECT 1 | failed | connection failed |               | 

Bardziej szczegółowe informacje odnośnie rezultatu zadania, lub jego błędu, znajdziemy w postgresowym pliku loga. Przykładowo:

postgres@vagrant:/var/log/postgresql$ tail -f postgresql-15-main.log

2023-05-11 19:37:00.007 UTC [35712] LOG: - cron job 1 connection failed

2023-05-11 19:38:00.065 UTC [35712] LOG: - cron job 1 starting: SELECT 1

2023-05-11 19:38:00.070 UTC [45866] test@postgres FATAL: - password authentication failed for user "test"

2023-05-11 19:38:00.070 UTC [45866] test@postgres DETAIL: - User "test" has no password assigned.

        Connection matched pg_hba.conf line 97: "host     all                all                127.0.0.1/32               scram-sha-256"

W powyższym przykładzie widzimy, że pierwsze wykonania zadania nie powiodły się, ponieważ konfiguracja pg_hba.conf wymagała od użytkownika “test” hasła przy logowaniu. Po nadaniu mu hasła oraz stworzeniu pliku .pgpass w katalogu domowym użytkownika Postgres zadanie zaczęło się wykonywać poprawnie.

postgres=# alter user test with password '123';

ALTER ROLE



postgres@vagrant:/var/log/postgresql$ cat ~/.pgpass

*:*:*:test:123



postgres@vagrant:/var/log/postgresql$ tail -f postgresql-15-main.log

2023-05-11 19:40:00.003 UTC [35712] LOG: - cron job 1 starting: SELECT 1

2023-05-11 19:40:00.011 UTC [35712] LOG: - cron job 1 completed: 1 row



postgres=# select * from cron.job_run_details where status = 'succeeded';

 jobid | runid | job_pid | database | username | command - | - status - | return_message |             start_time             |             end_time               

-------+-------+---------+----------+----------+----------+-----------+----------------+-------------------------------+-------------------------------

     1 | - 29 - | - 50703 | postgres | test      | SELECT 1 | succeeded | 1 row                | 2023-05-11 19:40:00.010307+00 | 2023-05-11 19:40:00.010722+00

Powyższy przykład obrazuje, jak w prosty sposób pg_cron może ułatwić planowanie oraz zarządzanie zadaniami cyklicznymi w Postgresie. Dzięki temu, że rozszerzenia oraz ich obiekty są zachowywane w kopiach zapasowych, możemy mieć pewność, iż wszystkie zaplanowane zadania zostaną zachowane nawet w przypadku odtwarzania klastra z backupu na innym serwerze.

Baner szkolenia Administracja, replikacja i tuning baz danych PostgreSQL w JSystems, terminy gwarantowane

Szkolenie Administracja, replikacja i tuning baz danych PostgreSQL →

To szkolenie może być dofinansowane dla Ciebie z KFS lub BUR.

★★★★★Średnia ocena naszych szkoleń w Google: 5/5

Najczęściej zadawane pytania

Do czego służy rozszerzenie pg_cron?
pg_cron pozwala automatycznie uruchamiać cykliczne zadania bezpośrednio w serwerze PostgreSQL, korzystając ze składni harmonogramu znanej z systemowego crona. Przydaje się na przykład do przeładowywania danych w hurtowni danych czy uruchamiania procesów konserwacyjnych, bez sięgania po crona systemowego.
Czy PostgreSQL ma wbudowany harmonogram zadań?
Domyślnie PostgreSQL nie ma takiej funkcjonalności, jaką w Oracle pełni DBMS_JOB. Bez dodatków jedyną opcją jest zaplanowanie zadań w cronie systemowym. Rozszerzenie pg_cron uzupełnia ten brak, umożliwiając planowanie powtarzalnych zadań wprost w bazie.
Co trzeba zrobić po instalacji, aby pg_cron zadziałało?
Trzeba dodać pg_cron do parametru shared_preload_libraries w pliku postgresql.conf lub przez ALTER SYSTEM, a następnie zrestartować PostgreSQL, aby biblioteka wczytała się przy starcie. Samo rozszerzenie tworzymy tylko w jednej wybranej bazie, która staje się punktem zarządzania zadaniami.
Jak zaplanować zadanie w pg_cron?
Używamy funkcji cron.schedule, która przyjmuje harmonogram i polecenie, a w drugim wariancie dodatkowo nazwę zadania. Harmonogram zapisujemy składnią crona: minuty, godziny, dzień miesiąca, miesiąc i dzień tygodnia. Wszystkie zaplanowane zadania znajdziemy potem w tabeli cron.job.
Czy pg_cron działa na replikach standby?
Rozszerzenie można zainstalować i skonfigurować na instancji standby, jednak zadania nie będą tam wykonywane. pg_cron uruchamia zadania tylko na instancji głównej klastra. Standby zacznie wykonywać zadania dopiero po awansowaniu na instancję główną i otwarciu na zapis.
Jak pozwolić wykonywać kilka zadań pg_cron jednocześnie?
Trzeba włączyć pracę na procesach tła, ustawiając cron.use_background_workers na on, oraz zwiększyć max_worker_processes, który określa, ile zadań może wykonywać się naraz. Oba parametry wymagają restartu PostgreSQL. Domyślnie liczba procesów ograniczona jest do ośmiu.

Komentarze (0)

Musisz być zalogowany by móc dodać komentarz. Zaloguj się przez Google

Brak komentarzy...