Blog JSystems - uwalniamy wiedzę!

Szukaj







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.

Komentarze (0)

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

Brak komentarzy...