<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://iandoob.net/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Ian.readman</id>
	<title>Ian Doob Technology - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://iandoob.net/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Ian.readman"/>
	<link rel="alternate" type="text/html" href="https://iandoob.net/index.php/Special:Contributions/Ian.readman"/>
	<updated>2026-08-09T08:06:23Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.42.7</generator>
	<entry>
		<id>https://iandoob.net/index.php?title=Main_Page&amp;diff=125</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Main_Page&amp;diff=125"/>
		<updated>2026-01-20T12:03:29Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Useful Links ==&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User&#039;s Guide]&lt;br /&gt;
* [https://www.postgresql.org/download PostgreSQL Downloads]&lt;br /&gt;
* [https://learn.microsoft.com/en-us/azure/postgresql PostgreSQL Flexible Server]&lt;br /&gt;
* [https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_PostgreSQL.html PostgreSQL RDS]&lt;br /&gt;
* [https://docs.timescale.com/self-hosted/latest TimescaleDB Self-Hosted]&lt;br /&gt;
* [https://patroni.readthedocs.io/en/latest Patroni Documentation]&lt;br /&gt;
* [https://hub.docker.com/_/postgres Postgres Docker Hub]&lt;br /&gt;
&lt;br /&gt;
== PostgreSQL ==&lt;br /&gt;
* [[Patroni Cluster]]&lt;br /&gt;
* [[TimeScale Notes]]&lt;br /&gt;
* [[TimeScale Training]]&lt;br /&gt;
* [[Oracle PostgreSQL Migration]]&lt;br /&gt;
&lt;br /&gt;
== Getting started ==&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]&lt;br /&gt;
* [https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/ MediaWiki release mailing list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Ora2pg&amp;diff=124</id>
		<title>Ora2pg</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Ora2pg&amp;diff=124"/>
		<updated>2025-12-08T12:24:10Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: /* Installation */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Documentation ===&lt;br /&gt;
https://ora2pg.darold.net/documentation.html&lt;br /&gt;
&lt;br /&gt;
=== Pre-Requisites ===&lt;br /&gt;
Install a full Oracle client or  and instant client &amp;lt;/br&amp;gt;&lt;br /&gt;
An instant client must include the below 4 zip files&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-basic-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sqlplus-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sdk-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-jdbc-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
sudo apt install libaio1t64&lt;br /&gt;
sudo ln -s /usr/lib/x86_64-linux-gnu/libaio.so.1t64 /usr/lib/x86_64-linux-gnu/libaio.so.1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Check that perl is installed and at version 5.10+&lt;br /&gt;
# Set environment variables, install required packages and choose &#039;sudo&#039; when asked &#039;What approach&#039;&lt;br /&gt;
perl -v&lt;br /&gt;
export ORACLE_HOME=/home/ian/oracle/instantclient_23_7&lt;br /&gt;
export LD_LIBRARY_PATH=$ORACLE_HOME:$LD_LIBRARY_PATH&lt;br /&gt;
export PATH=$ORACLE_HOME:$PATH&lt;br /&gt;
sudo apt install libdbi-perl make gcc&lt;br /&gt;
perl -MCPAN -e &#039;install DBD::Oracle&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Installation ===&lt;br /&gt;
The latest version can be downloaded here ... &amp;lt;/br&amp;gt;&lt;br /&gt;
https://github.com/darold/ora2pg/releases&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
tar xvf ora2pg-25.0.tar.gz&lt;br /&gt;
cd ora2pg-25.0/&lt;br /&gt;
perl Makefile.PL&lt;br /&gt;
make&lt;br /&gt;
sudo make install&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuration ===&lt;br /&gt;
Example conf file (/etc/ora2pg/ora2pg.conf.dist) &amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Important Parameters ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Set the Oracle home directory&lt;br /&gt;
ORACLE_HOME     /home/ian/oracle/instantclient_23_7&lt;br /&gt;
&lt;br /&gt;
# Set Oracle database connection (datasource, user, password)&lt;br /&gt;
ORACLE_DSN      dbi:Oracle:host=localhost;service_name=XEPDB1;port=1523&lt;br /&gt;
ORACLE_USER     system&lt;br /&gt;
ORACLE_PWD      ipswich&lt;br /&gt;
&lt;br /&gt;
# Export Oracle schema to PostgreSQL schema&lt;br /&gt;
EXPORT_SCHEMA   1&lt;br /&gt;
&lt;br /&gt;
# Oracle schema/owner to use&lt;br /&gt;
SCHEMA          HR&lt;br /&gt;
&lt;br /&gt;
# Enable/disable the CREATE SCHEMA SQL order at starting of the output file.&lt;br /&gt;
# It is enable by default and concern on TABLE export type.&lt;br /&gt;
CREATE_SCHEMA   1&lt;br /&gt;
COMPILE_SCHEMA  1&lt;br /&gt;
&lt;br /&gt;
# Type of export. Values can be the following keyword:&lt;br /&gt;
#       TABLE           Export tables, constraints, indexes, ...&lt;br /&gt;
#       PACKAGE         Export packages&lt;br /&gt;
#       INSERT          Export data from table as INSERT statement&lt;br /&gt;
#       COPY            Export data from table as COPY statement&lt;br /&gt;
#       VIEW            Export views&lt;br /&gt;
#       GRANT           Export grants&lt;br /&gt;
#       SEQUENCE        Export sequences&lt;br /&gt;
#       TRIGGER         Export triggers&lt;br /&gt;
#       FUNCTION        Export functions&lt;br /&gt;
#       PROCEDURE       Export procedures&lt;br /&gt;
#       TABLESPACE      Export tablespace (PostgreSQL &amp;gt;= 8 only)&lt;br /&gt;
#       TYPE            Export user defined Oracle types&lt;br /&gt;
#       PARTITION       Export range or list partition (PostgreSQL &amp;gt;= v8.4)&lt;br /&gt;
#       FDW             Export table as foreign data wrapper tables&lt;br /&gt;
#       MVIEW           Export materialized view as snapshot refresh view&lt;br /&gt;
#       QUERY           Convert Oracle SQL queries from a file.&lt;br /&gt;
#       KETTLE          Generate XML ktr template files to be used by Kettle.&lt;br /&gt;
#       DBLINK          Generate oracle foreign data wrapper server to use as dblink.&lt;br /&gt;
#       SYNONYM         Export Oracle&#039;s synonyms as views on other schema&#039;s objects.&lt;br /&gt;
#       DIRECTORY       Export Oracle&#039;s directories as external_file extension objects.&lt;br /&gt;
#       LOAD            Dispatch a list of queries over multiple PostgreSQl connections.&lt;br /&gt;
#       TEST            perform a diff between Oracle and PostgreSQL database.&lt;br /&gt;
#       TEST_COUNT      perform only a row count between Oracle and PostgreSQL tables.&lt;br /&gt;
#       TEST_VIEW       perform a count on both side of number of rows returned by views&lt;br /&gt;
#       TEST_DATA       perform data validation check on rows at both sides.&lt;br /&gt;
#       SEQUENCE_VALUES export DDL to set the last values of sequences&lt;br /&gt;
&lt;br /&gt;
TYPE            TABLE&lt;br /&gt;
&lt;br /&gt;
# Define the following directive to send export directly to a PostgreSQL&lt;br /&gt;
# database, this will disable file output. Note that these directives are only&lt;br /&gt;
# used for data export, other export need to be imported manually through the&lt;br /&gt;
# use of psql or any other PostgreSQL client.&lt;br /&gt;
PG_DSN          dbi:Pg:dbname=postgres;host=host.docker.internal;port=5433&lt;br /&gt;
PG_USER         postgres&lt;br /&gt;
PG_PWD          ipswich&lt;br /&gt;
&lt;br /&gt;
# By default all output is dump to STDOUT if not send directly to postgresql&lt;br /&gt;
# database (see above). Give a filename to save export to it. If you want&lt;br /&gt;
# a Gzip&#039;d compressed file just add the extension .gz to the filename (you&lt;br /&gt;
# need perl module Compress::Zlib from CPAN). Add extension .bz2 to use Bzip2&lt;br /&gt;
# compression.&lt;br /&gt;
OUTPUT          output.sql&lt;br /&gt;
&lt;br /&gt;
# Base directory where all dumped files must be written&lt;br /&gt;
OUTPUT_DIR      /home/ian/tmp&lt;br /&gt;
&lt;br /&gt;
# Enable PLSQL to PLPSQL conversion. This is a work in progress, feel&lt;br /&gt;
# free modify/add you own code and send me patches. The code is under&lt;br /&gt;
# function plsql_toplpgsql in Ora2PG/PLSQL.pm. Default enabled.&lt;br /&gt;
PLSQL_PGSQL     1&lt;br /&gt;
&lt;br /&gt;
# If you want to use functions defined in the Orafce library and prevent&lt;br /&gt;
# Ora2Pg to translate call to these function, enable this directive.&lt;br /&gt;
# The Orafce library can be found here: https://github.com/orafce/orafce&lt;br /&gt;
# By default Ora2pg rewrite add_month(), add_year(), date_trunc() and&lt;br /&gt;
# to_char() functions, but you may prefer to use the orafce version of&lt;br /&gt;
# these function that do not need any code transformation.&lt;br /&gt;
USE_ORAFCE      0&lt;br /&gt;
&lt;br /&gt;
# By default when using SHOW_REPORT the migration report is generated as&lt;br /&gt;
# simple text, enabling this directive will force ora2pg to create a report&lt;br /&gt;
# in HTML format.&lt;br /&gt;
DUMP_AS_HTML    0&lt;br /&gt;
&lt;br /&gt;
# Set the PostgreSQL major version number of the target database. Ex: 9.6 or 10&lt;br /&gt;
# Default is current major version at time of a new release. This replace the&lt;br /&gt;
# old PG_SUPPORTS_* configuration directives.&lt;br /&gt;
PG_VERSION      17&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Test Connectivity ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ora2pg -t SHOW_VERSION -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Other Example Commands ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ora2pg -t SHOW_SCHEMA -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -t SHOW_ENCODING -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_TABLE -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_COLUMN -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_REPORT -c /etc/ora2pg/ora2pg.conf (--dump_as_html &amp;gt; migration.html)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Migration Template ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mkdir -p /home/ian/oracle/ora2pg/migration&lt;br /&gt;
ora2pg --project_base /home/ian/oracle/ora2pg/migration --init_project test_project&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Copy ora2pg.conf file to the config directory &amp;lt;/br&amp;gt;&lt;br /&gt;
Following types will be exported: &amp;lt;/br&amp;gt;&lt;br /&gt;
SEQUENCE; SEQUENCE_VALUES; TABLE; PACKAGE; VIEW; GRANT; TRIGGER; FUNCTION; &amp;lt;/br&amp;gt;&lt;br /&gt;
PROCEDURE; TABLESPACE; PARTITION; TYPE; MVIEW; DBLINK; SYNONYM; DIRECTORY&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
./export_schema.sh&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Run highlighted command at end to export the copy the data. &amp;lt;/br&amp;gt;&lt;br /&gt;
Create password file and samples database.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
./import_all.sh -h localhost -p 5433 -d samples -U postgres -o hr&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Ora2pg&amp;diff=123</id>
		<title>Ora2pg</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Ora2pg&amp;diff=123"/>
		<updated>2025-12-08T11:47:47Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Documentation ===&lt;br /&gt;
https://ora2pg.darold.net/documentation.html&lt;br /&gt;
&lt;br /&gt;
=== Pre-Requisites ===&lt;br /&gt;
Install a full Oracle client or  and instant client &amp;lt;/br&amp;gt;&lt;br /&gt;
An instant client must include the below 4 zip files&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-basic-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sqlplus-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sdk-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-jdbc-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
sudo apt install libaio1t64&lt;br /&gt;
sudo ln -s /usr/lib/x86_64-linux-gnu/libaio.so.1t64 /usr/lib/x86_64-linux-gnu/libaio.so.1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Check that perl is installed and at version 5.10+&lt;br /&gt;
# Set environment variables, install required packages and choose &#039;sudo&#039; when asked &#039;What approach&#039;&lt;br /&gt;
perl -v&lt;br /&gt;
export ORACLE_HOME=/home/ian/oracle/instantclient_23_7&lt;br /&gt;
export LD_LIBRARY_PATH=$ORACLE_HOME:$LD_LIBRARY_PATH&lt;br /&gt;
export PATH=$ORACLE_HOME:$PATH&lt;br /&gt;
sudo apt install libdbi-perl make gcc&lt;br /&gt;
perl -MCPAN -e &#039;install DBD::Oracle&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Installation ===&lt;br /&gt;
The latest version can be downloaded here ... &amp;lt;/br&amp;gt;&lt;br /&gt;
https://github.com/darold/ora2pg/releases&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
tar xvf ora2pg-25.0.tar.gz&lt;br /&gt;
cd ora2pg-25.0/&lt;br /&gt;
perl Makefile.PL&lt;br /&gt;
sudo make &amp;amp;&amp;amp; make install&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuration ===&lt;br /&gt;
Example conf file (/etc/ora2pg/ora2pg.conf.dist) &amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Important Parameters ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Set the Oracle home directory&lt;br /&gt;
ORACLE_HOME     /home/ian/oracle/instantclient_23_7&lt;br /&gt;
&lt;br /&gt;
# Set Oracle database connection (datasource, user, password)&lt;br /&gt;
ORACLE_DSN      dbi:Oracle:host=localhost;service_name=XEPDB1;port=1523&lt;br /&gt;
ORACLE_USER     system&lt;br /&gt;
ORACLE_PWD      ipswich&lt;br /&gt;
&lt;br /&gt;
# Export Oracle schema to PostgreSQL schema&lt;br /&gt;
EXPORT_SCHEMA   1&lt;br /&gt;
&lt;br /&gt;
# Oracle schema/owner to use&lt;br /&gt;
SCHEMA          HR&lt;br /&gt;
&lt;br /&gt;
# Enable/disable the CREATE SCHEMA SQL order at starting of the output file.&lt;br /&gt;
# It is enable by default and concern on TABLE export type.&lt;br /&gt;
CREATE_SCHEMA   1&lt;br /&gt;
COMPILE_SCHEMA  1&lt;br /&gt;
&lt;br /&gt;
# Type of export. Values can be the following keyword:&lt;br /&gt;
#       TABLE           Export tables, constraints, indexes, ...&lt;br /&gt;
#       PACKAGE         Export packages&lt;br /&gt;
#       INSERT          Export data from table as INSERT statement&lt;br /&gt;
#       COPY            Export data from table as COPY statement&lt;br /&gt;
#       VIEW            Export views&lt;br /&gt;
#       GRANT           Export grants&lt;br /&gt;
#       SEQUENCE        Export sequences&lt;br /&gt;
#       TRIGGER         Export triggers&lt;br /&gt;
#       FUNCTION        Export functions&lt;br /&gt;
#       PROCEDURE       Export procedures&lt;br /&gt;
#       TABLESPACE      Export tablespace (PostgreSQL &amp;gt;= 8 only)&lt;br /&gt;
#       TYPE            Export user defined Oracle types&lt;br /&gt;
#       PARTITION       Export range or list partition (PostgreSQL &amp;gt;= v8.4)&lt;br /&gt;
#       FDW             Export table as foreign data wrapper tables&lt;br /&gt;
#       MVIEW           Export materialized view as snapshot refresh view&lt;br /&gt;
#       QUERY           Convert Oracle SQL queries from a file.&lt;br /&gt;
#       KETTLE          Generate XML ktr template files to be used by Kettle.&lt;br /&gt;
#       DBLINK          Generate oracle foreign data wrapper server to use as dblink.&lt;br /&gt;
#       SYNONYM         Export Oracle&#039;s synonyms as views on other schema&#039;s objects.&lt;br /&gt;
#       DIRECTORY       Export Oracle&#039;s directories as external_file extension objects.&lt;br /&gt;
#       LOAD            Dispatch a list of queries over multiple PostgreSQl connections.&lt;br /&gt;
#       TEST            perform a diff between Oracle and PostgreSQL database.&lt;br /&gt;
#       TEST_COUNT      perform only a row count between Oracle and PostgreSQL tables.&lt;br /&gt;
#       TEST_VIEW       perform a count on both side of number of rows returned by views&lt;br /&gt;
#       TEST_DATA       perform data validation check on rows at both sides.&lt;br /&gt;
#       SEQUENCE_VALUES export DDL to set the last values of sequences&lt;br /&gt;
&lt;br /&gt;
TYPE            TABLE&lt;br /&gt;
&lt;br /&gt;
# Define the following directive to send export directly to a PostgreSQL&lt;br /&gt;
# database, this will disable file output. Note that these directives are only&lt;br /&gt;
# used for data export, other export need to be imported manually through the&lt;br /&gt;
# use of psql or any other PostgreSQL client.&lt;br /&gt;
PG_DSN          dbi:Pg:dbname=postgres;host=host.docker.internal;port=5433&lt;br /&gt;
PG_USER         postgres&lt;br /&gt;
PG_PWD          ipswich&lt;br /&gt;
&lt;br /&gt;
# By default all output is dump to STDOUT if not send directly to postgresql&lt;br /&gt;
# database (see above). Give a filename to save export to it. If you want&lt;br /&gt;
# a Gzip&#039;d compressed file just add the extension .gz to the filename (you&lt;br /&gt;
# need perl module Compress::Zlib from CPAN). Add extension .bz2 to use Bzip2&lt;br /&gt;
# compression.&lt;br /&gt;
OUTPUT          output.sql&lt;br /&gt;
&lt;br /&gt;
# Base directory where all dumped files must be written&lt;br /&gt;
OUTPUT_DIR      /home/ian/tmp&lt;br /&gt;
&lt;br /&gt;
# Enable PLSQL to PLPSQL conversion. This is a work in progress, feel&lt;br /&gt;
# free modify/add you own code and send me patches. The code is under&lt;br /&gt;
# function plsql_toplpgsql in Ora2PG/PLSQL.pm. Default enabled.&lt;br /&gt;
PLSQL_PGSQL     1&lt;br /&gt;
&lt;br /&gt;
# If you want to use functions defined in the Orafce library and prevent&lt;br /&gt;
# Ora2Pg to translate call to these function, enable this directive.&lt;br /&gt;
# The Orafce library can be found here: https://github.com/orafce/orafce&lt;br /&gt;
# By default Ora2pg rewrite add_month(), add_year(), date_trunc() and&lt;br /&gt;
# to_char() functions, but you may prefer to use the orafce version of&lt;br /&gt;
# these function that do not need any code transformation.&lt;br /&gt;
USE_ORAFCE      0&lt;br /&gt;
&lt;br /&gt;
# By default when using SHOW_REPORT the migration report is generated as&lt;br /&gt;
# simple text, enabling this directive will force ora2pg to create a report&lt;br /&gt;
# in HTML format.&lt;br /&gt;
DUMP_AS_HTML    0&lt;br /&gt;
&lt;br /&gt;
# Set the PostgreSQL major version number of the target database. Ex: 9.6 or 10&lt;br /&gt;
# Default is current major version at time of a new release. This replace the&lt;br /&gt;
# old PG_SUPPORTS_* configuration directives.&lt;br /&gt;
PG_VERSION      17&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Test Connectivity ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ora2pg -t SHOW_VERSION -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Other Example Commands ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ora2pg -t SHOW_SCHEMA -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -t SHOW_ENCODING -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_TABLE -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_COLUMN -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_REPORT -c /etc/ora2pg/ora2pg.conf (--dump_as_html &amp;gt; migration.html)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Migration Template ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mkdir -p /home/ian/oracle/ora2pg/migration&lt;br /&gt;
ora2pg --project_base /home/ian/oracle/ora2pg/migration --init_project test_project&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Copy ora2pg.conf file to the config directory &amp;lt;/br&amp;gt;&lt;br /&gt;
Following types will be exported: &amp;lt;/br&amp;gt;&lt;br /&gt;
SEQUENCE; SEQUENCE_VALUES; TABLE; PACKAGE; VIEW; GRANT; TRIGGER; FUNCTION; &amp;lt;/br&amp;gt;&lt;br /&gt;
PROCEDURE; TABLESPACE; PARTITION; TYPE; MVIEW; DBLINK; SYNONYM; DIRECTORY&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
./export_schema.sh&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Run highlighted command at end to export the copy the data. &amp;lt;/br&amp;gt;&lt;br /&gt;
Create password file and samples database.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
./import_all.sh -h localhost -p 5433 -d samples -U postgres -o hr&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=122</id>
		<title>Patroni Cluster</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=122"/>
		<updated>2025-08-03T15:39:07Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: /* PGBackRest */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Setup ==&lt;br /&gt;
&lt;br /&gt;
* Debian1: 192.168.59.101 (Debian 12)&lt;br /&gt;
* Debian2: 192.168.59.102 (Debian 12)&lt;br /&gt;
* Debian3: 192.168.59.103 (Debian 12)&lt;br /&gt;
&lt;br /&gt;
The servers were created using VirtualBox. &amp;lt;/br&amp;gt;&lt;br /&gt;
Network setup was 2 adapters (Bridged Adapter and NAT).&lt;br /&gt;
&lt;br /&gt;
== Installation ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo apt install postgresql patroni&lt;br /&gt;
sudo systemctl stop patroni postgresql&lt;br /&gt;
sudo systemctl disable postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
etcd was installed from prebuilt binaries &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ETCD_RELEASE=$(curl -s https://api.github.com/repos/etcd-io/etcd/releases/latest|grep tag_name | cut -d &#039;&amp;quot;&#039; -f 4)&lt;br /&gt;
curl -sL https://github.com/etcd-io/etcd/releases/download/${ETCD_RELEASE}/etcd-${ETCD_RELEASE}-linux-amd64.tar.gz \&lt;br /&gt;
 | sudo tar xz -C /usr/bin --strip=1 --wildcards --no-anchored etcdctl etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuration ==&lt;br /&gt;
=== etcd ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo mkdir -p /var/lib/etcd/&lt;br /&gt;
sudo mkdir -p /etc/etcd&lt;br /&gt;
sudo groupadd --system etcd&lt;br /&gt;
sudo useradd -s /sbin/nologin --system -g etcd etcd&lt;br /&gt;
sudo chown -R etcd:etcd /var/lib/etcd/&lt;br /&gt;
sudo chmod -R a+rw /var/lib/etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the configuration file at /etcd/etcd/etcd.yaml &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
name: &#039;debian1&#039; &lt;br /&gt;
data-dir: &#039;/var/lib/etcd/data.etcd&#039;&lt;br /&gt;
initial-advertise-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
listen-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
advertise-client-urls: http://192.168.59.101:2379 &lt;br /&gt;
listen-client-urls: http://192.168.59.101:2379,http://127.0.0.1:2379 &lt;br /&gt;
initial-cluster: &amp;quot;debian1=http://192.168.59.101:2380,debian2=http://192.168.59.102:2380,debian3=http://192.168.59.103:2380&amp;quot; &lt;br /&gt;
initial-cluster-state: &#039;new&#039; &lt;br /&gt;
initial-cluster-token: &#039;token-01&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Configure the systemd unit file &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=etcd key-value store&lt;br /&gt;
Documentation=https://github.com/etcd-io/etcd&lt;br /&gt;
After=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
Wants=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
User=etcd&lt;br /&gt;
Type=notify&lt;br /&gt;
Environment=ETCD_DATA_DIR=/var/lib/etcd&lt;br /&gt;
Environment=ETCD_NAME=%H&lt;br /&gt;
ExecStart=/usr/bin/etcd --config-file /etc/etcd/etcd.yml&lt;br /&gt;
Restart=always&lt;br /&gt;
RestartSec=10s&lt;br /&gt;
LimitNOFILE=40000&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl enable etcd.service&lt;br /&gt;
sudo systemctl start etcd.service&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test that everything is working &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export ETCDCTL_ENDPOINTS=&amp;quot;http://192.168.59.103:2379,http://192.168.59.101:2379,http://192.168.59.102:2379&amp;quot;&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
etcdctl endpoint health -w table&lt;br /&gt;
etcdctl endpoint status -w table &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
|          ENDPOINT          |        ID        | VERSION | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX | ERRORS |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
| http://192.168.59.102:2379 | 3cdde56ec82a0ca0 |  3.5.16 |   53 kB |      true |        13 |        145 |        |&lt;br /&gt;
| http://192.168.59.101:2379 | 406189a3bb67b8bc |  3.5.16 |   53 kB |     false |        13 |        145 |        |&lt;br /&gt;
| http://192.168.59.103:2379 | 939ed44c2e6d5e5b |  3.5.16 |   53 kB |     false |        13 |        145 |        |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Change the leader if necessary &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
etcdctl move-leader 939ed44c2e6d5e5b --endpoints=$ETCDCTL_ENDPOINTS&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Issues ====&lt;br /&gt;
&lt;br /&gt;
One of the members would not connect to the cluster. &amp;lt;/br&amp;gt;&lt;br /&gt;
The error message on the leader was “Prober detected unhealthy status …” &amp;lt;/br&amp;gt;&lt;br /&gt;
On the problem member the message was “Failed to publish local member to cluster through raft” &amp;lt;/br&amp;gt;&lt;br /&gt;
No network issues, solved by removing and adding the member. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
sudo systemctl stop etcd (On server being removed)&lt;br /&gt;
etcdctl member remove &amp;lt;ID&amp;gt;&lt;br /&gt;
etcdctl member add debian2 --peer-urls=http://192.168.59.102:2380&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Edit the etcd.yml files copying all information displayed, including the new order for ETCD_INITIAL_CLUSTER &amp;lt;/br&amp;gt;&lt;br /&gt;
On the server being added ..&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo systemctl start etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Patroni ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo mkdir -p /etc/patroni/&lt;br /&gt;
sudo chown -R  postgres:postgres /etc/patroni/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
As the postgres user, create a configuration file. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
vi /etc/patroni/patroni.yml&lt;br /&gt;
&lt;br /&gt;
namespace: /db/&lt;br /&gt;
scope: cluster_1&lt;br /&gt;
name: $THIS_NAME&lt;br /&gt;
&lt;br /&gt;
log:&lt;br /&gt;
  format: &#039;%(asctime)s %(levelname)s: %(message)s&#039;&lt;br /&gt;
  level: INFO&lt;br /&gt;
  max_queue_size: 1000&lt;br /&gt;
  traceback_level: ERROR&lt;br /&gt;
  type: plain&lt;br /&gt;
&lt;br /&gt;
restapi:&lt;br /&gt;
  listen: 0.0.0.0:8008&lt;br /&gt;
  connect_address: $THIS_IP:8008&lt;br /&gt;
&lt;br /&gt;
etcd3:&lt;br /&gt;
  hosts: &lt;br /&gt;
  - 192.168.59.101:2379&lt;br /&gt;
  - 192.168.59.102:2379&lt;br /&gt;
  - 192.168.59.103:2379&lt;br /&gt;
&lt;br /&gt;
bootstrap:&lt;br /&gt;
  dcs:&lt;br /&gt;
    ttl: 30&lt;br /&gt;
    loop_wait: 10&lt;br /&gt;
    retry_timeout: 10&lt;br /&gt;
    maximum_lag_on_failover: 1048576&lt;br /&gt;
    postgresql:&lt;br /&gt;
      use_pg_rewind: true&lt;br /&gt;
      use_slots: true&lt;br /&gt;
      parameters:&lt;br /&gt;
        wal_level: replica&lt;br /&gt;
        hot_standby: &amp;quot;on&amp;quot;&lt;br /&gt;
        wal_keep_segments: 10&lt;br /&gt;
        max_wal_senders: 5&lt;br /&gt;
        max_replication_slots: 10&lt;br /&gt;
        wal_log_hints: &amp;quot;on&amp;quot;&lt;br /&gt;
        logging_collector: &#039;on&#039;&lt;br /&gt;
        max_wal_size: &#039;10GB&#039;&lt;br /&gt;
        archive_mode: &amp;quot;on&amp;quot;&lt;br /&gt;
        archive_timeout: 600s&lt;br /&gt;
        archive_command: &amp;quot;/bin/true&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  initdb: # Note: It needs to be a list (some options need values, others are switches)&lt;br /&gt;
  - encoding: UTF8&lt;br /&gt;
  - data-checksums&lt;br /&gt;
&lt;br /&gt;
  pg_hba: # Add following lines to pg_hba.conf after running &#039;initdb&#039;&lt;br /&gt;
  - host replication replicator 127.0.0.1/32 trust&lt;br /&gt;
  - host replication replicator 0.0.0.0/0 md5&lt;br /&gt;
  - host all all 0.0.0.0/0 md5&lt;br /&gt;
  - host all all ::0/0 md5&lt;br /&gt;
&lt;br /&gt;
  # Some additional users which needs to be created after initializing new cluster&lt;br /&gt;
  users:&lt;br /&gt;
    admin:&lt;br /&gt;
      password: qaz123&lt;br /&gt;
      options:&lt;br /&gt;
        - createrole&lt;br /&gt;
        - createdb&lt;br /&gt;
&lt;br /&gt;
postgresql:&lt;br /&gt;
  listen: 0.0.0.0:5432&lt;br /&gt;
  connect_address: $THIS_IP:5432&lt;br /&gt;
  data_dir: /var/lib/postgresql/17&lt;br /&gt;
  bin_dir: /usr/lib/postgresql/17/bin&lt;br /&gt;
  pgpass: /tmp/pgpass0&lt;br /&gt;
  authentication:&lt;br /&gt;
    replication:&lt;br /&gt;
      username: replicator&lt;br /&gt;
      password: replPasswd&lt;br /&gt;
    superuser:&lt;br /&gt;
      username: postgres&lt;br /&gt;
      password: qaz123&lt;br /&gt;
  parameters:&lt;br /&gt;
    unix_socket_directories: &amp;quot;/var/run/postgresql/&amp;quot;&lt;br /&gt;
&lt;br /&gt;
tags:&lt;br /&gt;
  nofailover: false&lt;br /&gt;
  noloadbalance: false&lt;br /&gt;
  clonefrom: false&lt;br /&gt;
  nosync: false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Validate the configuration file &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
patroni --validate-config /etc/patroni/patroni.yml&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Service ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/patroni.service&lt;br /&gt;
&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Runners to orchestrate a high-availability PostgreSQL&lt;br /&gt;
After=network.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
&lt;br /&gt;
User=postgres&lt;br /&gt;
Group=postgres&lt;br /&gt;
&lt;br /&gt;
# Start the patroni process&lt;br /&gt;
ExecStart=/usr/bin/patroni /etc/patroni/patroni.yml&lt;br /&gt;
&lt;br /&gt;
# Send HUP to reload from patroni.yml&lt;br /&gt;
ExecReload=/bin/kill -s HUP $MAINPID&lt;br /&gt;
&lt;br /&gt;
# Only kill the patroni process, not it&#039;s children, so it will gracefully stop postgres&lt;br /&gt;
KillMode=process&lt;br /&gt;
&lt;br /&gt;
# Give a reasonable amount of time for the server to start up/shut down&lt;br /&gt;
TimeoutSec=30&lt;br /&gt;
&lt;br /&gt;
# Restart the service if it crashed&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Set permissions of the data directory to 700 &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
chmod 700 /var/lib/postgresql/17&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Start the patroni service &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start patroni&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Check status&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml list&lt;br /&gt;
&lt;br /&gt;
+ Cluster: cluster_1 (7439347493790530098) ------+----+-----------+&lt;br /&gt;
| Member  | Host           | Role    | State     | TL | Lag in MB |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
| debian1 | 192.168.59.101 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian2 | 192.168.59.102 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian3 | 192.168.59.103 | Leader  | running   |  1 |           |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Change configuration &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest --stanza=ian archive-push %p&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Issues ====&lt;br /&gt;
&lt;br /&gt;
Replication showed the following error&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ERROR:  Requested starting point 0/4000000 is ahead of the WAL flush &lt;br /&gt;
        position of this server 0/3000060&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The replica instance needed to be re-initialised &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml reinit &amp;lt;scope&amp;gt; &amp;lt;name&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== VIP Manager ===&lt;br /&gt;
Copy the latest release to your server.&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
sudo dpkg -i vip-manager_2.8.0_Linux_x86_64.deb&lt;br /&gt;
sudo systemctl stop vip-manager&lt;br /&gt;
sudo vi /etc/patroni/vip-manager.yml&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trigger key below contains the namespace and scope of the Patroni service. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ip: 192.168.59.100&lt;br /&gt;
netmask: 24&lt;br /&gt;
interface: enp0s3&lt;br /&gt;
trigger-key: &amp;quot;/db/cluster_1/leader&amp;quot;&lt;br /&gt;
dcs-type: etcd&lt;br /&gt;
dcs-endpoints: http://192.168.59.101:2379&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/vip-manager.service&lt;br /&gt;
&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Manages Virtual IP for Patroni&lt;br /&gt;
After=network-online.target&lt;br /&gt;
Before=patroni.service&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
ExecStart=/usr/bin/vip-manager --config=/etc/patroni/vip-manager.yml&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start vip-manager&lt;br /&gt;
sudo journalctl -u vip-manager.service -n 100 -f&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== PGBackRest ===&lt;br /&gt;
&lt;br /&gt;
Setup the repository on Debian3. &amp;lt;/br&amp;gt;&lt;br /&gt;
Install pgbackrest on all the servers … &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo apt install pgbackrest&lt;br /&gt;
sudo mkdir -p -m 770 /var/log/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/log/pgbackrest&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest&lt;br /&gt;
sudo touch /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chmod 640 /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chown postgres:postgres /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On debian3, create the directory that will hold the backups … &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest/conf.d&lt;br /&gt;
sudo mkdir -p /var/lib/pgbackrest&lt;br /&gt;
sudo chmod 750 /var/lib/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/lib/pgbackrest&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Setup SSH ====&lt;br /&gt;
We will need a passwordless SSH connection between the repository and the database servers. &amp;lt;/br&amp;gt;&lt;br /&gt;
For this setup I was using postgres as the user that controls pgbackrest. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# debian1&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp id_ras.pub postgres@debian3&lt;br /&gt;
ssh postgres@debian3&lt;br /&gt;
# debian3&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp ./.ssh/id_ras.pub postgres@debian1&lt;br /&gt;
ssh postgres@debian1&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Configuration ====&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the repository. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
vi /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-host=debian1&lt;br /&gt;
pg1-path=/var/lib/postgresql/17 &lt;br /&gt;
&lt;br /&gt;
[global]&lt;br /&gt;
repo1-cipher-pass=zWaf6XtpjIVZC5444yXB+cgFDFl7MxGlgkZSaoPv&lt;br /&gt;
repo1-cipher-type=aes-256-cbc&lt;br /&gt;
repo1-path=/var/lib/pgbackrest&lt;br /&gt;
repo1-retention-full=2&lt;br /&gt;
start-fast=y&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the database server. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-path=/var/lib/postgresql/17&lt;br /&gt;
 &lt;br /&gt;
[global]&lt;br /&gt;
log-level-file=detail&lt;br /&gt;
repo1-host=debian3 &lt;br /&gt;
repo1-host-user=postgres&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Alter the archive_command parameter so that it uses the pgbackrest executable. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest --stanza=cybertec archive-push %p&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create a Stanza on the repository server and confirm it is working. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pgbackrest --stanza=cybertec stanza-create&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Also check if the Stanza is correct on the database servers. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=121</id>
		<title>Patroni Cluster</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=121"/>
		<updated>2025-08-03T15:34:37Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: /* VIP Manager */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Setup ==&lt;br /&gt;
&lt;br /&gt;
* Debian1: 192.168.59.101 (Debian 12)&lt;br /&gt;
* Debian2: 192.168.59.102 (Debian 12)&lt;br /&gt;
* Debian3: 192.168.59.103 (Debian 12)&lt;br /&gt;
&lt;br /&gt;
The servers were created using VirtualBox. &amp;lt;/br&amp;gt;&lt;br /&gt;
Network setup was 2 adapters (Bridged Adapter and NAT).&lt;br /&gt;
&lt;br /&gt;
== Installation ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo apt install postgresql patroni&lt;br /&gt;
sudo systemctl stop patroni postgresql&lt;br /&gt;
sudo systemctl disable postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
etcd was installed from prebuilt binaries &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ETCD_RELEASE=$(curl -s https://api.github.com/repos/etcd-io/etcd/releases/latest|grep tag_name | cut -d &#039;&amp;quot;&#039; -f 4)&lt;br /&gt;
curl -sL https://github.com/etcd-io/etcd/releases/download/${ETCD_RELEASE}/etcd-${ETCD_RELEASE}-linux-amd64.tar.gz \&lt;br /&gt;
 | sudo tar xz -C /usr/bin --strip=1 --wildcards --no-anchored etcdctl etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuration ==&lt;br /&gt;
=== etcd ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo mkdir -p /var/lib/etcd/&lt;br /&gt;
sudo mkdir -p /etc/etcd&lt;br /&gt;
sudo groupadd --system etcd&lt;br /&gt;
sudo useradd -s /sbin/nologin --system -g etcd etcd&lt;br /&gt;
sudo chown -R etcd:etcd /var/lib/etcd/&lt;br /&gt;
sudo chmod -R a+rw /var/lib/etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the configuration file at /etcd/etcd/etcd.yaml &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
name: &#039;debian1&#039; &lt;br /&gt;
data-dir: &#039;/var/lib/etcd/data.etcd&#039;&lt;br /&gt;
initial-advertise-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
listen-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
advertise-client-urls: http://192.168.59.101:2379 &lt;br /&gt;
listen-client-urls: http://192.168.59.101:2379,http://127.0.0.1:2379 &lt;br /&gt;
initial-cluster: &amp;quot;debian1=http://192.168.59.101:2380,debian2=http://192.168.59.102:2380,debian3=http://192.168.59.103:2380&amp;quot; &lt;br /&gt;
initial-cluster-state: &#039;new&#039; &lt;br /&gt;
initial-cluster-token: &#039;token-01&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Configure the systemd unit file &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=etcd key-value store&lt;br /&gt;
Documentation=https://github.com/etcd-io/etcd&lt;br /&gt;
After=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
Wants=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
User=etcd&lt;br /&gt;
Type=notify&lt;br /&gt;
Environment=ETCD_DATA_DIR=/var/lib/etcd&lt;br /&gt;
Environment=ETCD_NAME=%H&lt;br /&gt;
ExecStart=/usr/bin/etcd --config-file /etc/etcd/etcd.yml&lt;br /&gt;
Restart=always&lt;br /&gt;
RestartSec=10s&lt;br /&gt;
LimitNOFILE=40000&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl enable etcd.service&lt;br /&gt;
sudo systemctl start etcd.service&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test that everything is working &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export ETCDCTL_ENDPOINTS=&amp;quot;http://192.168.59.103:2379,http://192.168.59.101:2379,http://192.168.59.102:2379&amp;quot;&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
etcdctl endpoint health -w table&lt;br /&gt;
etcdctl endpoint status -w table &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
|          ENDPOINT          |        ID        | VERSION | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX | ERRORS |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
| http://192.168.59.102:2379 | 3cdde56ec82a0ca0 |  3.5.16 |   53 kB |      true |        13 |        145 |        |&lt;br /&gt;
| http://192.168.59.101:2379 | 406189a3bb67b8bc |  3.5.16 |   53 kB |     false |        13 |        145 |        |&lt;br /&gt;
| http://192.168.59.103:2379 | 939ed44c2e6d5e5b |  3.5.16 |   53 kB |     false |        13 |        145 |        |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Change the leader if necessary &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
etcdctl move-leader 939ed44c2e6d5e5b --endpoints=$ETCDCTL_ENDPOINTS&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Issues ====&lt;br /&gt;
&lt;br /&gt;
One of the members would not connect to the cluster. &amp;lt;/br&amp;gt;&lt;br /&gt;
The error message on the leader was “Prober detected unhealthy status …” &amp;lt;/br&amp;gt;&lt;br /&gt;
On the problem member the message was “Failed to publish local member to cluster through raft” &amp;lt;/br&amp;gt;&lt;br /&gt;
No network issues, solved by removing and adding the member. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
sudo systemctl stop etcd (On server being removed)&lt;br /&gt;
etcdctl member remove &amp;lt;ID&amp;gt;&lt;br /&gt;
etcdctl member add debian2 --peer-urls=http://192.168.59.102:2380&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Edit the etcd.yml files copying all information displayed, including the new order for ETCD_INITIAL_CLUSTER &amp;lt;/br&amp;gt;&lt;br /&gt;
On the server being added ..&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo systemctl start etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Patroni ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo mkdir -p /etc/patroni/&lt;br /&gt;
sudo chown -R  postgres:postgres /etc/patroni/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
As the postgres user, create a configuration file. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
vi /etc/patroni/patroni.yml&lt;br /&gt;
&lt;br /&gt;
namespace: /db/&lt;br /&gt;
scope: cluster_1&lt;br /&gt;
name: $THIS_NAME&lt;br /&gt;
&lt;br /&gt;
log:&lt;br /&gt;
  format: &#039;%(asctime)s %(levelname)s: %(message)s&#039;&lt;br /&gt;
  level: INFO&lt;br /&gt;
  max_queue_size: 1000&lt;br /&gt;
  traceback_level: ERROR&lt;br /&gt;
  type: plain&lt;br /&gt;
&lt;br /&gt;
restapi:&lt;br /&gt;
  listen: 0.0.0.0:8008&lt;br /&gt;
  connect_address: $THIS_IP:8008&lt;br /&gt;
&lt;br /&gt;
etcd3:&lt;br /&gt;
  hosts: &lt;br /&gt;
  - 192.168.59.101:2379&lt;br /&gt;
  - 192.168.59.102:2379&lt;br /&gt;
  - 192.168.59.103:2379&lt;br /&gt;
&lt;br /&gt;
bootstrap:&lt;br /&gt;
  dcs:&lt;br /&gt;
    ttl: 30&lt;br /&gt;
    loop_wait: 10&lt;br /&gt;
    retry_timeout: 10&lt;br /&gt;
    maximum_lag_on_failover: 1048576&lt;br /&gt;
    postgresql:&lt;br /&gt;
      use_pg_rewind: true&lt;br /&gt;
      use_slots: true&lt;br /&gt;
      parameters:&lt;br /&gt;
        wal_level: replica&lt;br /&gt;
        hot_standby: &amp;quot;on&amp;quot;&lt;br /&gt;
        wal_keep_segments: 10&lt;br /&gt;
        max_wal_senders: 5&lt;br /&gt;
        max_replication_slots: 10&lt;br /&gt;
        wal_log_hints: &amp;quot;on&amp;quot;&lt;br /&gt;
        logging_collector: &#039;on&#039;&lt;br /&gt;
        max_wal_size: &#039;10GB&#039;&lt;br /&gt;
        archive_mode: &amp;quot;on&amp;quot;&lt;br /&gt;
        archive_timeout: 600s&lt;br /&gt;
        archive_command: &amp;quot;/bin/true&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  initdb: # Note: It needs to be a list (some options need values, others are switches)&lt;br /&gt;
  - encoding: UTF8&lt;br /&gt;
  - data-checksums&lt;br /&gt;
&lt;br /&gt;
  pg_hba: # Add following lines to pg_hba.conf after running &#039;initdb&#039;&lt;br /&gt;
  - host replication replicator 127.0.0.1/32 trust&lt;br /&gt;
  - host replication replicator 0.0.0.0/0 md5&lt;br /&gt;
  - host all all 0.0.0.0/0 md5&lt;br /&gt;
  - host all all ::0/0 md5&lt;br /&gt;
&lt;br /&gt;
  # Some additional users which needs to be created after initializing new cluster&lt;br /&gt;
  users:&lt;br /&gt;
    admin:&lt;br /&gt;
      password: qaz123&lt;br /&gt;
      options:&lt;br /&gt;
        - createrole&lt;br /&gt;
        - createdb&lt;br /&gt;
&lt;br /&gt;
postgresql:&lt;br /&gt;
  listen: 0.0.0.0:5432&lt;br /&gt;
  connect_address: $THIS_IP:5432&lt;br /&gt;
  data_dir: /var/lib/postgresql/17&lt;br /&gt;
  bin_dir: /usr/lib/postgresql/17/bin&lt;br /&gt;
  pgpass: /tmp/pgpass0&lt;br /&gt;
  authentication:&lt;br /&gt;
    replication:&lt;br /&gt;
      username: replicator&lt;br /&gt;
      password: replPasswd&lt;br /&gt;
    superuser:&lt;br /&gt;
      username: postgres&lt;br /&gt;
      password: qaz123&lt;br /&gt;
  parameters:&lt;br /&gt;
    unix_socket_directories: &amp;quot;/var/run/postgresql/&amp;quot;&lt;br /&gt;
&lt;br /&gt;
tags:&lt;br /&gt;
  nofailover: false&lt;br /&gt;
  noloadbalance: false&lt;br /&gt;
  clonefrom: false&lt;br /&gt;
  nosync: false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Validate the configuration file &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
patroni --validate-config /etc/patroni/patroni.yml&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Service ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/patroni.service&lt;br /&gt;
&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Runners to orchestrate a high-availability PostgreSQL&lt;br /&gt;
After=network.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
&lt;br /&gt;
User=postgres&lt;br /&gt;
Group=postgres&lt;br /&gt;
&lt;br /&gt;
# Start the patroni process&lt;br /&gt;
ExecStart=/usr/bin/patroni /etc/patroni/patroni.yml&lt;br /&gt;
&lt;br /&gt;
# Send HUP to reload from patroni.yml&lt;br /&gt;
ExecReload=/bin/kill -s HUP $MAINPID&lt;br /&gt;
&lt;br /&gt;
# Only kill the patroni process, not it&#039;s children, so it will gracefully stop postgres&lt;br /&gt;
KillMode=process&lt;br /&gt;
&lt;br /&gt;
# Give a reasonable amount of time for the server to start up/shut down&lt;br /&gt;
TimeoutSec=30&lt;br /&gt;
&lt;br /&gt;
# Restart the service if it crashed&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Set permissions of the data directory to 700 &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
chmod 700 /var/lib/postgresql/17&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Start the patroni service &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start patroni&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Check status&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml list&lt;br /&gt;
&lt;br /&gt;
+ Cluster: cluster_1 (7439347493790530098) ------+----+-----------+&lt;br /&gt;
| Member  | Host           | Role    | State     | TL | Lag in MB |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
| debian1 | 192.168.59.101 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian2 | 192.168.59.102 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian3 | 192.168.59.103 | Leader  | running   |  1 |           |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Change configuration &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest --stanza=ian archive-push %p&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Issues ====&lt;br /&gt;
&lt;br /&gt;
Replication showed the following error&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ERROR:  Requested starting point 0/4000000 is ahead of the WAL flush &lt;br /&gt;
        position of this server 0/3000060&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The replica instance needed to be re-initialised &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml reinit &amp;lt;scope&amp;gt; &amp;lt;name&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== VIP Manager ===&lt;br /&gt;
Copy the latest release to your server.&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
sudo dpkg -i vip-manager_2.8.0_Linux_x86_64.deb&lt;br /&gt;
sudo systemctl stop vip-manager&lt;br /&gt;
sudo vi /etc/patroni/vip-manager.yml&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trigger key below contains the namespace and scope of the Patroni service. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ip: 192.168.59.100&lt;br /&gt;
netmask: 24&lt;br /&gt;
interface: enp0s3&lt;br /&gt;
trigger-key: &amp;quot;/db/cluster_1/leader&amp;quot;&lt;br /&gt;
dcs-type: etcd&lt;br /&gt;
dcs-endpoints: http://192.168.59.101:2379&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/vip-manager.service&lt;br /&gt;
&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Manages Virtual IP for Patroni&lt;br /&gt;
After=network-online.target&lt;br /&gt;
Before=patroni.service&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
ExecStart=/usr/bin/vip-manager --config=/etc/patroni/vip-manager.yml&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start vip-manager&lt;br /&gt;
sudo journalctl -u vip-manager.service -n 100 -f&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== PGBackRest ===&lt;br /&gt;
&lt;br /&gt;
Setup the repository on Debian3&lt;br /&gt;
&lt;br /&gt;
Install pgbackrest on all the servers …&lt;br /&gt;
sudo apt install pgbackrest&lt;br /&gt;
sudo mkdir -p -m 770 /var/log/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/log/pgbackrest&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest&lt;br /&gt;
sudo touch /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chmod 640 /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chown postgres:postgres /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
&lt;br /&gt;
On debian3, create the directory that will hold the backups …&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest/conf.d&lt;br /&gt;
sudo mkdir -p /var/lib/pgbackrest&lt;br /&gt;
sudo chmod 750 /var/lib/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/lib/pgbackrest&lt;br /&gt;
Setup SSH&lt;br /&gt;
&lt;br /&gt;
We will need a passwordless SSH connection between the repository and the database servers.&lt;br /&gt;
For this setup I was using postgres as the user that controls pgbackrest.&lt;br /&gt;
# debian1&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp id_ras.pub postgres@debian3&lt;br /&gt;
ssh postgres@debian3&lt;br /&gt;
# debian3&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp ./.ssh/id_ras.pub postgres@debian1&lt;br /&gt;
ssh postgres@debian1&lt;br /&gt;
Configuration&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the repository&lt;br /&gt;
vi /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-host=debian1&lt;br /&gt;
pg1-path=/var/lib/postgresql/17 &lt;br /&gt;
&lt;br /&gt;
[global]&lt;br /&gt;
repo1-cipher-pass=zWaf6XtpjIVZC5444yXB+cgFDFl7MxGlgkZSaoPv&lt;br /&gt;
repo1-cipher-type=aes-256-cbc&lt;br /&gt;
repo1-path=/var/lib/pgbackrest&lt;br /&gt;
repo1-retention-full=2&lt;br /&gt;
start-fast=y&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the database server&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-path=/var/lib/postgresql/17&lt;br /&gt;
 &lt;br /&gt;
[global]&lt;br /&gt;
log-level-file=detail&lt;br /&gt;
repo1-host=debian3 &lt;br /&gt;
repo1-host-user=postgres&lt;br /&gt;
&lt;br /&gt;
Alter the archive_command parameter so that it uses the pgbackrest executable&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest \&lt;br /&gt;
                       --stanza=cybertec archive-push %p&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Create a Stanza on the repository server and confirm it is working&lt;br /&gt;
pgbackrest --stanza=cybertec stanza-create&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;br /&gt;
&lt;br /&gt;
Also check if the Stanza is correct on the database server.&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=120</id>
		<title>Patroni Cluster</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=120"/>
		<updated>2025-08-03T15:34:29Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: /* VIP Manager */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Setup ==&lt;br /&gt;
&lt;br /&gt;
* Debian1: 192.168.59.101 (Debian 12)&lt;br /&gt;
* Debian2: 192.168.59.102 (Debian 12)&lt;br /&gt;
* Debian3: 192.168.59.103 (Debian 12)&lt;br /&gt;
&lt;br /&gt;
The servers were created using VirtualBox. &amp;lt;/br&amp;gt;&lt;br /&gt;
Network setup was 2 adapters (Bridged Adapter and NAT).&lt;br /&gt;
&lt;br /&gt;
== Installation ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo apt install postgresql patroni&lt;br /&gt;
sudo systemctl stop patroni postgresql&lt;br /&gt;
sudo systemctl disable postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
etcd was installed from prebuilt binaries &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ETCD_RELEASE=$(curl -s https://api.github.com/repos/etcd-io/etcd/releases/latest|grep tag_name | cut -d &#039;&amp;quot;&#039; -f 4)&lt;br /&gt;
curl -sL https://github.com/etcd-io/etcd/releases/download/${ETCD_RELEASE}/etcd-${ETCD_RELEASE}-linux-amd64.tar.gz \&lt;br /&gt;
 | sudo tar xz -C /usr/bin --strip=1 --wildcards --no-anchored etcdctl etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuration ==&lt;br /&gt;
=== etcd ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo mkdir -p /var/lib/etcd/&lt;br /&gt;
sudo mkdir -p /etc/etcd&lt;br /&gt;
sudo groupadd --system etcd&lt;br /&gt;
sudo useradd -s /sbin/nologin --system -g etcd etcd&lt;br /&gt;
sudo chown -R etcd:etcd /var/lib/etcd/&lt;br /&gt;
sudo chmod -R a+rw /var/lib/etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the configuration file at /etcd/etcd/etcd.yaml &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
name: &#039;debian1&#039; &lt;br /&gt;
data-dir: &#039;/var/lib/etcd/data.etcd&#039;&lt;br /&gt;
initial-advertise-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
listen-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
advertise-client-urls: http://192.168.59.101:2379 &lt;br /&gt;
listen-client-urls: http://192.168.59.101:2379,http://127.0.0.1:2379 &lt;br /&gt;
initial-cluster: &amp;quot;debian1=http://192.168.59.101:2380,debian2=http://192.168.59.102:2380,debian3=http://192.168.59.103:2380&amp;quot; &lt;br /&gt;
initial-cluster-state: &#039;new&#039; &lt;br /&gt;
initial-cluster-token: &#039;token-01&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Configure the systemd unit file &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=etcd key-value store&lt;br /&gt;
Documentation=https://github.com/etcd-io/etcd&lt;br /&gt;
After=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
Wants=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
User=etcd&lt;br /&gt;
Type=notify&lt;br /&gt;
Environment=ETCD_DATA_DIR=/var/lib/etcd&lt;br /&gt;
Environment=ETCD_NAME=%H&lt;br /&gt;
ExecStart=/usr/bin/etcd --config-file /etc/etcd/etcd.yml&lt;br /&gt;
Restart=always&lt;br /&gt;
RestartSec=10s&lt;br /&gt;
LimitNOFILE=40000&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl enable etcd.service&lt;br /&gt;
sudo systemctl start etcd.service&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test that everything is working &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export ETCDCTL_ENDPOINTS=&amp;quot;http://192.168.59.103:2379,http://192.168.59.101:2379,http://192.168.59.102:2379&amp;quot;&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
etcdctl endpoint health -w table&lt;br /&gt;
etcdctl endpoint status -w table &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
|          ENDPOINT          |        ID        | VERSION | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX | ERRORS |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
| http://192.168.59.102:2379 | 3cdde56ec82a0ca0 |  3.5.16 |   53 kB |      true |        13 |        145 |        |&lt;br /&gt;
| http://192.168.59.101:2379 | 406189a3bb67b8bc |  3.5.16 |   53 kB |     false |        13 |        145 |        |&lt;br /&gt;
| http://192.168.59.103:2379 | 939ed44c2e6d5e5b |  3.5.16 |   53 kB |     false |        13 |        145 |        |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Change the leader if necessary &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
etcdctl move-leader 939ed44c2e6d5e5b --endpoints=$ETCDCTL_ENDPOINTS&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Issues ====&lt;br /&gt;
&lt;br /&gt;
One of the members would not connect to the cluster. &amp;lt;/br&amp;gt;&lt;br /&gt;
The error message on the leader was “Prober detected unhealthy status …” &amp;lt;/br&amp;gt;&lt;br /&gt;
On the problem member the message was “Failed to publish local member to cluster through raft” &amp;lt;/br&amp;gt;&lt;br /&gt;
No network issues, solved by removing and adding the member. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
sudo systemctl stop etcd (On server being removed)&lt;br /&gt;
etcdctl member remove &amp;lt;ID&amp;gt;&lt;br /&gt;
etcdctl member add debian2 --peer-urls=http://192.168.59.102:2380&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Edit the etcd.yml files copying all information displayed, including the new order for ETCD_INITIAL_CLUSTER &amp;lt;/br&amp;gt;&lt;br /&gt;
On the server being added ..&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo systemctl start etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Patroni ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo mkdir -p /etc/patroni/&lt;br /&gt;
sudo chown -R  postgres:postgres /etc/patroni/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
As the postgres user, create a configuration file. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
vi /etc/patroni/patroni.yml&lt;br /&gt;
&lt;br /&gt;
namespace: /db/&lt;br /&gt;
scope: cluster_1&lt;br /&gt;
name: $THIS_NAME&lt;br /&gt;
&lt;br /&gt;
log:&lt;br /&gt;
  format: &#039;%(asctime)s %(levelname)s: %(message)s&#039;&lt;br /&gt;
  level: INFO&lt;br /&gt;
  max_queue_size: 1000&lt;br /&gt;
  traceback_level: ERROR&lt;br /&gt;
  type: plain&lt;br /&gt;
&lt;br /&gt;
restapi:&lt;br /&gt;
  listen: 0.0.0.0:8008&lt;br /&gt;
  connect_address: $THIS_IP:8008&lt;br /&gt;
&lt;br /&gt;
etcd3:&lt;br /&gt;
  hosts: &lt;br /&gt;
  - 192.168.59.101:2379&lt;br /&gt;
  - 192.168.59.102:2379&lt;br /&gt;
  - 192.168.59.103:2379&lt;br /&gt;
&lt;br /&gt;
bootstrap:&lt;br /&gt;
  dcs:&lt;br /&gt;
    ttl: 30&lt;br /&gt;
    loop_wait: 10&lt;br /&gt;
    retry_timeout: 10&lt;br /&gt;
    maximum_lag_on_failover: 1048576&lt;br /&gt;
    postgresql:&lt;br /&gt;
      use_pg_rewind: true&lt;br /&gt;
      use_slots: true&lt;br /&gt;
      parameters:&lt;br /&gt;
        wal_level: replica&lt;br /&gt;
        hot_standby: &amp;quot;on&amp;quot;&lt;br /&gt;
        wal_keep_segments: 10&lt;br /&gt;
        max_wal_senders: 5&lt;br /&gt;
        max_replication_slots: 10&lt;br /&gt;
        wal_log_hints: &amp;quot;on&amp;quot;&lt;br /&gt;
        logging_collector: &#039;on&#039;&lt;br /&gt;
        max_wal_size: &#039;10GB&#039;&lt;br /&gt;
        archive_mode: &amp;quot;on&amp;quot;&lt;br /&gt;
        archive_timeout: 600s&lt;br /&gt;
        archive_command: &amp;quot;/bin/true&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  initdb: # Note: It needs to be a list (some options need values, others are switches)&lt;br /&gt;
  - encoding: UTF8&lt;br /&gt;
  - data-checksums&lt;br /&gt;
&lt;br /&gt;
  pg_hba: # Add following lines to pg_hba.conf after running &#039;initdb&#039;&lt;br /&gt;
  - host replication replicator 127.0.0.1/32 trust&lt;br /&gt;
  - host replication replicator 0.0.0.0/0 md5&lt;br /&gt;
  - host all all 0.0.0.0/0 md5&lt;br /&gt;
  - host all all ::0/0 md5&lt;br /&gt;
&lt;br /&gt;
  # Some additional users which needs to be created after initializing new cluster&lt;br /&gt;
  users:&lt;br /&gt;
    admin:&lt;br /&gt;
      password: qaz123&lt;br /&gt;
      options:&lt;br /&gt;
        - createrole&lt;br /&gt;
        - createdb&lt;br /&gt;
&lt;br /&gt;
postgresql:&lt;br /&gt;
  listen: 0.0.0.0:5432&lt;br /&gt;
  connect_address: $THIS_IP:5432&lt;br /&gt;
  data_dir: /var/lib/postgresql/17&lt;br /&gt;
  bin_dir: /usr/lib/postgresql/17/bin&lt;br /&gt;
  pgpass: /tmp/pgpass0&lt;br /&gt;
  authentication:&lt;br /&gt;
    replication:&lt;br /&gt;
      username: replicator&lt;br /&gt;
      password: replPasswd&lt;br /&gt;
    superuser:&lt;br /&gt;
      username: postgres&lt;br /&gt;
      password: qaz123&lt;br /&gt;
  parameters:&lt;br /&gt;
    unix_socket_directories: &amp;quot;/var/run/postgresql/&amp;quot;&lt;br /&gt;
&lt;br /&gt;
tags:&lt;br /&gt;
  nofailover: false&lt;br /&gt;
  noloadbalance: false&lt;br /&gt;
  clonefrom: false&lt;br /&gt;
  nosync: false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Validate the configuration file &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
patroni --validate-config /etc/patroni/patroni.yml&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Service ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/patroni.service&lt;br /&gt;
&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Runners to orchestrate a high-availability PostgreSQL&lt;br /&gt;
After=network.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
&lt;br /&gt;
User=postgres&lt;br /&gt;
Group=postgres&lt;br /&gt;
&lt;br /&gt;
# Start the patroni process&lt;br /&gt;
ExecStart=/usr/bin/patroni /etc/patroni/patroni.yml&lt;br /&gt;
&lt;br /&gt;
# Send HUP to reload from patroni.yml&lt;br /&gt;
ExecReload=/bin/kill -s HUP $MAINPID&lt;br /&gt;
&lt;br /&gt;
# Only kill the patroni process, not it&#039;s children, so it will gracefully stop postgres&lt;br /&gt;
KillMode=process&lt;br /&gt;
&lt;br /&gt;
# Give a reasonable amount of time for the server to start up/shut down&lt;br /&gt;
TimeoutSec=30&lt;br /&gt;
&lt;br /&gt;
# Restart the service if it crashed&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Set permissions of the data directory to 700 &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
chmod 700 /var/lib/postgresql/17&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Start the patroni service &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start patroni&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Check status&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml list&lt;br /&gt;
&lt;br /&gt;
+ Cluster: cluster_1 (7439347493790530098) ------+----+-----------+&lt;br /&gt;
| Member  | Host           | Role    | State     | TL | Lag in MB |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
| debian1 | 192.168.59.101 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian2 | 192.168.59.102 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian3 | 192.168.59.103 | Leader  | running   |  1 |           |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Change configuration &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest --stanza=ian archive-push %p&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Issues ====&lt;br /&gt;
&lt;br /&gt;
Replication showed the following error&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ERROR:  Requested starting point 0/4000000 is ahead of the WAL flush &lt;br /&gt;
        position of this server 0/3000060&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The replica instance needed to be re-initialised &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml reinit &amp;lt;scope&amp;gt; &amp;lt;name&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== VIP Manager ===&lt;br /&gt;
Copy the latest release to your server. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
sudo dpkg -i vip-manager_2.8.0_Linux_x86_64.deb&lt;br /&gt;
sudo systemctl stop vip-manager&lt;br /&gt;
sudo vi /etc/patroni/vip-manager.yml&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trigger key below contains the namespace and scope of the Patroni service. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ip: 192.168.59.100&lt;br /&gt;
netmask: 24&lt;br /&gt;
interface: enp0s3&lt;br /&gt;
trigger-key: &amp;quot;/db/cluster_1/leader&amp;quot;&lt;br /&gt;
dcs-type: etcd&lt;br /&gt;
dcs-endpoints: http://192.168.59.101:2379&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/vip-manager.service&lt;br /&gt;
&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Manages Virtual IP for Patroni&lt;br /&gt;
After=network-online.target&lt;br /&gt;
Before=patroni.service&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
ExecStart=/usr/bin/vip-manager --config=/etc/patroni/vip-manager.yml&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start vip-manager&lt;br /&gt;
sudo journalctl -u vip-manager.service -n 100 -f&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== PGBackRest ===&lt;br /&gt;
&lt;br /&gt;
Setup the repository on Debian3&lt;br /&gt;
&lt;br /&gt;
Install pgbackrest on all the servers …&lt;br /&gt;
sudo apt install pgbackrest&lt;br /&gt;
sudo mkdir -p -m 770 /var/log/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/log/pgbackrest&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest&lt;br /&gt;
sudo touch /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chmod 640 /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chown postgres:postgres /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
&lt;br /&gt;
On debian3, create the directory that will hold the backups …&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest/conf.d&lt;br /&gt;
sudo mkdir -p /var/lib/pgbackrest&lt;br /&gt;
sudo chmod 750 /var/lib/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/lib/pgbackrest&lt;br /&gt;
Setup SSH&lt;br /&gt;
&lt;br /&gt;
We will need a passwordless SSH connection between the repository and the database servers.&lt;br /&gt;
For this setup I was using postgres as the user that controls pgbackrest.&lt;br /&gt;
# debian1&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp id_ras.pub postgres@debian3&lt;br /&gt;
ssh postgres@debian3&lt;br /&gt;
# debian3&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp ./.ssh/id_ras.pub postgres@debian1&lt;br /&gt;
ssh postgres@debian1&lt;br /&gt;
Configuration&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the repository&lt;br /&gt;
vi /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-host=debian1&lt;br /&gt;
pg1-path=/var/lib/postgresql/17 &lt;br /&gt;
&lt;br /&gt;
[global]&lt;br /&gt;
repo1-cipher-pass=zWaf6XtpjIVZC5444yXB+cgFDFl7MxGlgkZSaoPv&lt;br /&gt;
repo1-cipher-type=aes-256-cbc&lt;br /&gt;
repo1-path=/var/lib/pgbackrest&lt;br /&gt;
repo1-retention-full=2&lt;br /&gt;
start-fast=y&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the database server&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-path=/var/lib/postgresql/17&lt;br /&gt;
 &lt;br /&gt;
[global]&lt;br /&gt;
log-level-file=detail&lt;br /&gt;
repo1-host=debian3 &lt;br /&gt;
repo1-host-user=postgres&lt;br /&gt;
&lt;br /&gt;
Alter the archive_command parameter so that it uses the pgbackrest executable&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest \&lt;br /&gt;
                       --stanza=cybertec archive-push %p&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Create a Stanza on the repository server and confirm it is working&lt;br /&gt;
pgbackrest --stanza=cybertec stanza-create&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;br /&gt;
&lt;br /&gt;
Also check if the Stanza is correct on the database server.&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=119</id>
		<title>Patroni Cluster</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=119"/>
		<updated>2025-08-03T15:34:16Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: /* VIP Manager */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Setup ==&lt;br /&gt;
&lt;br /&gt;
* Debian1: 192.168.59.101 (Debian 12)&lt;br /&gt;
* Debian2: 192.168.59.102 (Debian 12)&lt;br /&gt;
* Debian3: 192.168.59.103 (Debian 12)&lt;br /&gt;
&lt;br /&gt;
The servers were created using VirtualBox. &amp;lt;/br&amp;gt;&lt;br /&gt;
Network setup was 2 adapters (Bridged Adapter and NAT).&lt;br /&gt;
&lt;br /&gt;
== Installation ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo apt install postgresql patroni&lt;br /&gt;
sudo systemctl stop patroni postgresql&lt;br /&gt;
sudo systemctl disable postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
etcd was installed from prebuilt binaries &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ETCD_RELEASE=$(curl -s https://api.github.com/repos/etcd-io/etcd/releases/latest|grep tag_name | cut -d &#039;&amp;quot;&#039; -f 4)&lt;br /&gt;
curl -sL https://github.com/etcd-io/etcd/releases/download/${ETCD_RELEASE}/etcd-${ETCD_RELEASE}-linux-amd64.tar.gz \&lt;br /&gt;
 | sudo tar xz -C /usr/bin --strip=1 --wildcards --no-anchored etcdctl etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuration ==&lt;br /&gt;
=== etcd ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo mkdir -p /var/lib/etcd/&lt;br /&gt;
sudo mkdir -p /etc/etcd&lt;br /&gt;
sudo groupadd --system etcd&lt;br /&gt;
sudo useradd -s /sbin/nologin --system -g etcd etcd&lt;br /&gt;
sudo chown -R etcd:etcd /var/lib/etcd/&lt;br /&gt;
sudo chmod -R a+rw /var/lib/etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the configuration file at /etcd/etcd/etcd.yaml &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
name: &#039;debian1&#039; &lt;br /&gt;
data-dir: &#039;/var/lib/etcd/data.etcd&#039;&lt;br /&gt;
initial-advertise-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
listen-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
advertise-client-urls: http://192.168.59.101:2379 &lt;br /&gt;
listen-client-urls: http://192.168.59.101:2379,http://127.0.0.1:2379 &lt;br /&gt;
initial-cluster: &amp;quot;debian1=http://192.168.59.101:2380,debian2=http://192.168.59.102:2380,debian3=http://192.168.59.103:2380&amp;quot; &lt;br /&gt;
initial-cluster-state: &#039;new&#039; &lt;br /&gt;
initial-cluster-token: &#039;token-01&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Configure the systemd unit file &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=etcd key-value store&lt;br /&gt;
Documentation=https://github.com/etcd-io/etcd&lt;br /&gt;
After=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
Wants=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
User=etcd&lt;br /&gt;
Type=notify&lt;br /&gt;
Environment=ETCD_DATA_DIR=/var/lib/etcd&lt;br /&gt;
Environment=ETCD_NAME=%H&lt;br /&gt;
ExecStart=/usr/bin/etcd --config-file /etc/etcd/etcd.yml&lt;br /&gt;
Restart=always&lt;br /&gt;
RestartSec=10s&lt;br /&gt;
LimitNOFILE=40000&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl enable etcd.service&lt;br /&gt;
sudo systemctl start etcd.service&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test that everything is working &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export ETCDCTL_ENDPOINTS=&amp;quot;http://192.168.59.103:2379,http://192.168.59.101:2379,http://192.168.59.102:2379&amp;quot;&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
etcdctl endpoint health -w table&lt;br /&gt;
etcdctl endpoint status -w table &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
|          ENDPOINT          |        ID        | VERSION | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX | ERRORS |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
| http://192.168.59.102:2379 | 3cdde56ec82a0ca0 |  3.5.16 |   53 kB |      true |        13 |        145 |        |&lt;br /&gt;
| http://192.168.59.101:2379 | 406189a3bb67b8bc |  3.5.16 |   53 kB |     false |        13 |        145 |        |&lt;br /&gt;
| http://192.168.59.103:2379 | 939ed44c2e6d5e5b |  3.5.16 |   53 kB |     false |        13 |        145 |        |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Change the leader if necessary &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
etcdctl move-leader 939ed44c2e6d5e5b --endpoints=$ETCDCTL_ENDPOINTS&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Issues ====&lt;br /&gt;
&lt;br /&gt;
One of the members would not connect to the cluster. &amp;lt;/br&amp;gt;&lt;br /&gt;
The error message on the leader was “Prober detected unhealthy status …” &amp;lt;/br&amp;gt;&lt;br /&gt;
On the problem member the message was “Failed to publish local member to cluster through raft” &amp;lt;/br&amp;gt;&lt;br /&gt;
No network issues, solved by removing and adding the member. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
sudo systemctl stop etcd (On server being removed)&lt;br /&gt;
etcdctl member remove &amp;lt;ID&amp;gt;&lt;br /&gt;
etcdctl member add debian2 --peer-urls=http://192.168.59.102:2380&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Edit the etcd.yml files copying all information displayed, including the new order for ETCD_INITIAL_CLUSTER &amp;lt;/br&amp;gt;&lt;br /&gt;
On the server being added ..&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo systemctl start etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Patroni ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo mkdir -p /etc/patroni/&lt;br /&gt;
sudo chown -R  postgres:postgres /etc/patroni/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
As the postgres user, create a configuration file. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
vi /etc/patroni/patroni.yml&lt;br /&gt;
&lt;br /&gt;
namespace: /db/&lt;br /&gt;
scope: cluster_1&lt;br /&gt;
name: $THIS_NAME&lt;br /&gt;
&lt;br /&gt;
log:&lt;br /&gt;
  format: &#039;%(asctime)s %(levelname)s: %(message)s&#039;&lt;br /&gt;
  level: INFO&lt;br /&gt;
  max_queue_size: 1000&lt;br /&gt;
  traceback_level: ERROR&lt;br /&gt;
  type: plain&lt;br /&gt;
&lt;br /&gt;
restapi:&lt;br /&gt;
  listen: 0.0.0.0:8008&lt;br /&gt;
  connect_address: $THIS_IP:8008&lt;br /&gt;
&lt;br /&gt;
etcd3:&lt;br /&gt;
  hosts: &lt;br /&gt;
  - 192.168.59.101:2379&lt;br /&gt;
  - 192.168.59.102:2379&lt;br /&gt;
  - 192.168.59.103:2379&lt;br /&gt;
&lt;br /&gt;
bootstrap:&lt;br /&gt;
  dcs:&lt;br /&gt;
    ttl: 30&lt;br /&gt;
    loop_wait: 10&lt;br /&gt;
    retry_timeout: 10&lt;br /&gt;
    maximum_lag_on_failover: 1048576&lt;br /&gt;
    postgresql:&lt;br /&gt;
      use_pg_rewind: true&lt;br /&gt;
      use_slots: true&lt;br /&gt;
      parameters:&lt;br /&gt;
        wal_level: replica&lt;br /&gt;
        hot_standby: &amp;quot;on&amp;quot;&lt;br /&gt;
        wal_keep_segments: 10&lt;br /&gt;
        max_wal_senders: 5&lt;br /&gt;
        max_replication_slots: 10&lt;br /&gt;
        wal_log_hints: &amp;quot;on&amp;quot;&lt;br /&gt;
        logging_collector: &#039;on&#039;&lt;br /&gt;
        max_wal_size: &#039;10GB&#039;&lt;br /&gt;
        archive_mode: &amp;quot;on&amp;quot;&lt;br /&gt;
        archive_timeout: 600s&lt;br /&gt;
        archive_command: &amp;quot;/bin/true&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  initdb: # Note: It needs to be a list (some options need values, others are switches)&lt;br /&gt;
  - encoding: UTF8&lt;br /&gt;
  - data-checksums&lt;br /&gt;
&lt;br /&gt;
  pg_hba: # Add following lines to pg_hba.conf after running &#039;initdb&#039;&lt;br /&gt;
  - host replication replicator 127.0.0.1/32 trust&lt;br /&gt;
  - host replication replicator 0.0.0.0/0 md5&lt;br /&gt;
  - host all all 0.0.0.0/0 md5&lt;br /&gt;
  - host all all ::0/0 md5&lt;br /&gt;
&lt;br /&gt;
  # Some additional users which needs to be created after initializing new cluster&lt;br /&gt;
  users:&lt;br /&gt;
    admin:&lt;br /&gt;
      password: qaz123&lt;br /&gt;
      options:&lt;br /&gt;
        - createrole&lt;br /&gt;
        - createdb&lt;br /&gt;
&lt;br /&gt;
postgresql:&lt;br /&gt;
  listen: 0.0.0.0:5432&lt;br /&gt;
  connect_address: $THIS_IP:5432&lt;br /&gt;
  data_dir: /var/lib/postgresql/17&lt;br /&gt;
  bin_dir: /usr/lib/postgresql/17/bin&lt;br /&gt;
  pgpass: /tmp/pgpass0&lt;br /&gt;
  authentication:&lt;br /&gt;
    replication:&lt;br /&gt;
      username: replicator&lt;br /&gt;
      password: replPasswd&lt;br /&gt;
    superuser:&lt;br /&gt;
      username: postgres&lt;br /&gt;
      password: qaz123&lt;br /&gt;
  parameters:&lt;br /&gt;
    unix_socket_directories: &amp;quot;/var/run/postgresql/&amp;quot;&lt;br /&gt;
&lt;br /&gt;
tags:&lt;br /&gt;
  nofailover: false&lt;br /&gt;
  noloadbalance: false&lt;br /&gt;
  clonefrom: false&lt;br /&gt;
  nosync: false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Validate the configuration file &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
patroni --validate-config /etc/patroni/patroni.yml&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Service ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/patroni.service&lt;br /&gt;
&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Runners to orchestrate a high-availability PostgreSQL&lt;br /&gt;
After=network.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
&lt;br /&gt;
User=postgres&lt;br /&gt;
Group=postgres&lt;br /&gt;
&lt;br /&gt;
# Start the patroni process&lt;br /&gt;
ExecStart=/usr/bin/patroni /etc/patroni/patroni.yml&lt;br /&gt;
&lt;br /&gt;
# Send HUP to reload from patroni.yml&lt;br /&gt;
ExecReload=/bin/kill -s HUP $MAINPID&lt;br /&gt;
&lt;br /&gt;
# Only kill the patroni process, not it&#039;s children, so it will gracefully stop postgres&lt;br /&gt;
KillMode=process&lt;br /&gt;
&lt;br /&gt;
# Give a reasonable amount of time for the server to start up/shut down&lt;br /&gt;
TimeoutSec=30&lt;br /&gt;
&lt;br /&gt;
# Restart the service if it crashed&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Set permissions of the data directory to 700 &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
chmod 700 /var/lib/postgresql/17&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Start the patroni service &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start patroni&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Check status&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml list&lt;br /&gt;
&lt;br /&gt;
+ Cluster: cluster_1 (7439347493790530098) ------+----+-----------+&lt;br /&gt;
| Member  | Host           | Role    | State     | TL | Lag in MB |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
| debian1 | 192.168.59.101 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian2 | 192.168.59.102 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian3 | 192.168.59.103 | Leader  | running   |  1 |           |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Change configuration &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest --stanza=ian archive-push %p&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Issues ====&lt;br /&gt;
&lt;br /&gt;
Replication showed the following error&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ERROR:  Requested starting point 0/4000000 is ahead of the WAL flush &lt;br /&gt;
        position of this server 0/3000060&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The replica instance needed to be re-initialised &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml reinit &amp;lt;scope&amp;gt; &amp;lt;name&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== VIP Manager ===&lt;br /&gt;
Copy the latest release to your server : &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
sudo dpkg -i vip-manager_2.8.0_Linux_x86_64.deb&lt;br /&gt;
sudo systemctl stop vip-manager&lt;br /&gt;
sudo vi /etc/patroni/vip-manager.yml&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trigger key below contains the namespace and scope of the Patroni service. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ip: 192.168.59.100&lt;br /&gt;
netmask: 24&lt;br /&gt;
interface: enp0s3&lt;br /&gt;
trigger-key: &amp;quot;/db/cluster_1/leader&amp;quot;&lt;br /&gt;
dcs-type: etcd&lt;br /&gt;
dcs-endpoints: http://192.168.59.101:2379&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/vip-manager.service&lt;br /&gt;
&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Manages Virtual IP for Patroni&lt;br /&gt;
After=network-online.target&lt;br /&gt;
Before=patroni.service&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
ExecStart=/usr/bin/vip-manager --config=/etc/patroni/vip-manager.yml&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start vip-manager&lt;br /&gt;
sudo journalctl -u vip-manager.service -n 100 -f&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== PGBackRest ===&lt;br /&gt;
&lt;br /&gt;
Setup the repository on Debian3&lt;br /&gt;
&lt;br /&gt;
Install pgbackrest on all the servers …&lt;br /&gt;
sudo apt install pgbackrest&lt;br /&gt;
sudo mkdir -p -m 770 /var/log/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/log/pgbackrest&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest&lt;br /&gt;
sudo touch /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chmod 640 /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chown postgres:postgres /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
&lt;br /&gt;
On debian3, create the directory that will hold the backups …&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest/conf.d&lt;br /&gt;
sudo mkdir -p /var/lib/pgbackrest&lt;br /&gt;
sudo chmod 750 /var/lib/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/lib/pgbackrest&lt;br /&gt;
Setup SSH&lt;br /&gt;
&lt;br /&gt;
We will need a passwordless SSH connection between the repository and the database servers.&lt;br /&gt;
For this setup I was using postgres as the user that controls pgbackrest.&lt;br /&gt;
# debian1&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp id_ras.pub postgres@debian3&lt;br /&gt;
ssh postgres@debian3&lt;br /&gt;
# debian3&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp ./.ssh/id_ras.pub postgres@debian1&lt;br /&gt;
ssh postgres@debian1&lt;br /&gt;
Configuration&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the repository&lt;br /&gt;
vi /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-host=debian1&lt;br /&gt;
pg1-path=/var/lib/postgresql/17 &lt;br /&gt;
&lt;br /&gt;
[global]&lt;br /&gt;
repo1-cipher-pass=zWaf6XtpjIVZC5444yXB+cgFDFl7MxGlgkZSaoPv&lt;br /&gt;
repo1-cipher-type=aes-256-cbc&lt;br /&gt;
repo1-path=/var/lib/pgbackrest&lt;br /&gt;
repo1-retention-full=2&lt;br /&gt;
start-fast=y&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the database server&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-path=/var/lib/postgresql/17&lt;br /&gt;
 &lt;br /&gt;
[global]&lt;br /&gt;
log-level-file=detail&lt;br /&gt;
repo1-host=debian3 &lt;br /&gt;
repo1-host-user=postgres&lt;br /&gt;
&lt;br /&gt;
Alter the archive_command parameter so that it uses the pgbackrest executable&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest \&lt;br /&gt;
                       --stanza=cybertec archive-push %p&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Create a Stanza on the repository server and confirm it is working&lt;br /&gt;
pgbackrest --stanza=cybertec stanza-create&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;br /&gt;
&lt;br /&gt;
Also check if the Stanza is correct on the database server.&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=118</id>
		<title>Patroni Cluster</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=118"/>
		<updated>2025-08-03T15:33:55Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Setup ==&lt;br /&gt;
&lt;br /&gt;
* Debian1: 192.168.59.101 (Debian 12)&lt;br /&gt;
* Debian2: 192.168.59.102 (Debian 12)&lt;br /&gt;
* Debian3: 192.168.59.103 (Debian 12)&lt;br /&gt;
&lt;br /&gt;
The servers were created using VirtualBox. &amp;lt;/br&amp;gt;&lt;br /&gt;
Network setup was 2 adapters (Bridged Adapter and NAT).&lt;br /&gt;
&lt;br /&gt;
== Installation ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo apt install postgresql patroni&lt;br /&gt;
sudo systemctl stop patroni postgresql&lt;br /&gt;
sudo systemctl disable postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
etcd was installed from prebuilt binaries &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ETCD_RELEASE=$(curl -s https://api.github.com/repos/etcd-io/etcd/releases/latest|grep tag_name | cut -d &#039;&amp;quot;&#039; -f 4)&lt;br /&gt;
curl -sL https://github.com/etcd-io/etcd/releases/download/${ETCD_RELEASE}/etcd-${ETCD_RELEASE}-linux-amd64.tar.gz \&lt;br /&gt;
 | sudo tar xz -C /usr/bin --strip=1 --wildcards --no-anchored etcdctl etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuration ==&lt;br /&gt;
=== etcd ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo mkdir -p /var/lib/etcd/&lt;br /&gt;
sudo mkdir -p /etc/etcd&lt;br /&gt;
sudo groupadd --system etcd&lt;br /&gt;
sudo useradd -s /sbin/nologin --system -g etcd etcd&lt;br /&gt;
sudo chown -R etcd:etcd /var/lib/etcd/&lt;br /&gt;
sudo chmod -R a+rw /var/lib/etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the configuration file at /etcd/etcd/etcd.yaml &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
name: &#039;debian1&#039; &lt;br /&gt;
data-dir: &#039;/var/lib/etcd/data.etcd&#039;&lt;br /&gt;
initial-advertise-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
listen-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
advertise-client-urls: http://192.168.59.101:2379 &lt;br /&gt;
listen-client-urls: http://192.168.59.101:2379,http://127.0.0.1:2379 &lt;br /&gt;
initial-cluster: &amp;quot;debian1=http://192.168.59.101:2380,debian2=http://192.168.59.102:2380,debian3=http://192.168.59.103:2380&amp;quot; &lt;br /&gt;
initial-cluster-state: &#039;new&#039; &lt;br /&gt;
initial-cluster-token: &#039;token-01&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Configure the systemd unit file &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=etcd key-value store&lt;br /&gt;
Documentation=https://github.com/etcd-io/etcd&lt;br /&gt;
After=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
Wants=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
User=etcd&lt;br /&gt;
Type=notify&lt;br /&gt;
Environment=ETCD_DATA_DIR=/var/lib/etcd&lt;br /&gt;
Environment=ETCD_NAME=%H&lt;br /&gt;
ExecStart=/usr/bin/etcd --config-file /etc/etcd/etcd.yml&lt;br /&gt;
Restart=always&lt;br /&gt;
RestartSec=10s&lt;br /&gt;
LimitNOFILE=40000&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl enable etcd.service&lt;br /&gt;
sudo systemctl start etcd.service&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test that everything is working &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export ETCDCTL_ENDPOINTS=&amp;quot;http://192.168.59.103:2379,http://192.168.59.101:2379,http://192.168.59.102:2379&amp;quot;&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
etcdctl endpoint health -w table&lt;br /&gt;
etcdctl endpoint status -w table &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
|          ENDPOINT          |        ID        | VERSION | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX | ERRORS |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
| http://192.168.59.102:2379 | 3cdde56ec82a0ca0 |  3.5.16 |   53 kB |      true |        13 |        145 |        |&lt;br /&gt;
| http://192.168.59.101:2379 | 406189a3bb67b8bc |  3.5.16 |   53 kB |     false |        13 |        145 |        |&lt;br /&gt;
| http://192.168.59.103:2379 | 939ed44c2e6d5e5b |  3.5.16 |   53 kB |     false |        13 |        145 |        |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Change the leader if necessary &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
etcdctl move-leader 939ed44c2e6d5e5b --endpoints=$ETCDCTL_ENDPOINTS&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Issues ====&lt;br /&gt;
&lt;br /&gt;
One of the members would not connect to the cluster. &amp;lt;/br&amp;gt;&lt;br /&gt;
The error message on the leader was “Prober detected unhealthy status …” &amp;lt;/br&amp;gt;&lt;br /&gt;
On the problem member the message was “Failed to publish local member to cluster through raft” &amp;lt;/br&amp;gt;&lt;br /&gt;
No network issues, solved by removing and adding the member. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
sudo systemctl stop etcd (On server being removed)&lt;br /&gt;
etcdctl member remove &amp;lt;ID&amp;gt;&lt;br /&gt;
etcdctl member add debian2 --peer-urls=http://192.168.59.102:2380&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Edit the etcd.yml files copying all information displayed, including the new order for ETCD_INITIAL_CLUSTER &amp;lt;/br&amp;gt;&lt;br /&gt;
On the server being added ..&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo systemctl start etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Patroni ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo mkdir -p /etc/patroni/&lt;br /&gt;
sudo chown -R  postgres:postgres /etc/patroni/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
As the postgres user, create a configuration file. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
vi /etc/patroni/patroni.yml&lt;br /&gt;
&lt;br /&gt;
namespace: /db/&lt;br /&gt;
scope: cluster_1&lt;br /&gt;
name: $THIS_NAME&lt;br /&gt;
&lt;br /&gt;
log:&lt;br /&gt;
  format: &#039;%(asctime)s %(levelname)s: %(message)s&#039;&lt;br /&gt;
  level: INFO&lt;br /&gt;
  max_queue_size: 1000&lt;br /&gt;
  traceback_level: ERROR&lt;br /&gt;
  type: plain&lt;br /&gt;
&lt;br /&gt;
restapi:&lt;br /&gt;
  listen: 0.0.0.0:8008&lt;br /&gt;
  connect_address: $THIS_IP:8008&lt;br /&gt;
&lt;br /&gt;
etcd3:&lt;br /&gt;
  hosts: &lt;br /&gt;
  - 192.168.59.101:2379&lt;br /&gt;
  - 192.168.59.102:2379&lt;br /&gt;
  - 192.168.59.103:2379&lt;br /&gt;
&lt;br /&gt;
bootstrap:&lt;br /&gt;
  dcs:&lt;br /&gt;
    ttl: 30&lt;br /&gt;
    loop_wait: 10&lt;br /&gt;
    retry_timeout: 10&lt;br /&gt;
    maximum_lag_on_failover: 1048576&lt;br /&gt;
    postgresql:&lt;br /&gt;
      use_pg_rewind: true&lt;br /&gt;
      use_slots: true&lt;br /&gt;
      parameters:&lt;br /&gt;
        wal_level: replica&lt;br /&gt;
        hot_standby: &amp;quot;on&amp;quot;&lt;br /&gt;
        wal_keep_segments: 10&lt;br /&gt;
        max_wal_senders: 5&lt;br /&gt;
        max_replication_slots: 10&lt;br /&gt;
        wal_log_hints: &amp;quot;on&amp;quot;&lt;br /&gt;
        logging_collector: &#039;on&#039;&lt;br /&gt;
        max_wal_size: &#039;10GB&#039;&lt;br /&gt;
        archive_mode: &amp;quot;on&amp;quot;&lt;br /&gt;
        archive_timeout: 600s&lt;br /&gt;
        archive_command: &amp;quot;/bin/true&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  initdb: # Note: It needs to be a list (some options need values, others are switches)&lt;br /&gt;
  - encoding: UTF8&lt;br /&gt;
  - data-checksums&lt;br /&gt;
&lt;br /&gt;
  pg_hba: # Add following lines to pg_hba.conf after running &#039;initdb&#039;&lt;br /&gt;
  - host replication replicator 127.0.0.1/32 trust&lt;br /&gt;
  - host replication replicator 0.0.0.0/0 md5&lt;br /&gt;
  - host all all 0.0.0.0/0 md5&lt;br /&gt;
  - host all all ::0/0 md5&lt;br /&gt;
&lt;br /&gt;
  # Some additional users which needs to be created after initializing new cluster&lt;br /&gt;
  users:&lt;br /&gt;
    admin:&lt;br /&gt;
      password: qaz123&lt;br /&gt;
      options:&lt;br /&gt;
        - createrole&lt;br /&gt;
        - createdb&lt;br /&gt;
&lt;br /&gt;
postgresql:&lt;br /&gt;
  listen: 0.0.0.0:5432&lt;br /&gt;
  connect_address: $THIS_IP:5432&lt;br /&gt;
  data_dir: /var/lib/postgresql/17&lt;br /&gt;
  bin_dir: /usr/lib/postgresql/17/bin&lt;br /&gt;
  pgpass: /tmp/pgpass0&lt;br /&gt;
  authentication:&lt;br /&gt;
    replication:&lt;br /&gt;
      username: replicator&lt;br /&gt;
      password: replPasswd&lt;br /&gt;
    superuser:&lt;br /&gt;
      username: postgres&lt;br /&gt;
      password: qaz123&lt;br /&gt;
  parameters:&lt;br /&gt;
    unix_socket_directories: &amp;quot;/var/run/postgresql/&amp;quot;&lt;br /&gt;
&lt;br /&gt;
tags:&lt;br /&gt;
  nofailover: false&lt;br /&gt;
  noloadbalance: false&lt;br /&gt;
  clonefrom: false&lt;br /&gt;
  nosync: false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Validate the configuration file &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
patroni --validate-config /etc/patroni/patroni.yml&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Service ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/patroni.service&lt;br /&gt;
&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Runners to orchestrate a high-availability PostgreSQL&lt;br /&gt;
After=network.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
&lt;br /&gt;
User=postgres&lt;br /&gt;
Group=postgres&lt;br /&gt;
&lt;br /&gt;
# Start the patroni process&lt;br /&gt;
ExecStart=/usr/bin/patroni /etc/patroni/patroni.yml&lt;br /&gt;
&lt;br /&gt;
# Send HUP to reload from patroni.yml&lt;br /&gt;
ExecReload=/bin/kill -s HUP $MAINPID&lt;br /&gt;
&lt;br /&gt;
# Only kill the patroni process, not it&#039;s children, so it will gracefully stop postgres&lt;br /&gt;
KillMode=process&lt;br /&gt;
&lt;br /&gt;
# Give a reasonable amount of time for the server to start up/shut down&lt;br /&gt;
TimeoutSec=30&lt;br /&gt;
&lt;br /&gt;
# Restart the service if it crashed&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Set permissions of the data directory to 700 &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
chmod 700 /var/lib/postgresql/17&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Start the patroni service &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start patroni&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Check status&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml list&lt;br /&gt;
&lt;br /&gt;
+ Cluster: cluster_1 (7439347493790530098) ------+----+-----------+&lt;br /&gt;
| Member  | Host           | Role    | State     | TL | Lag in MB |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
| debian1 | 192.168.59.101 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian2 | 192.168.59.102 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian3 | 192.168.59.103 | Leader  | running   |  1 |           |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Change configuration &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest --stanza=ian archive-push %p&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Issues ====&lt;br /&gt;
&lt;br /&gt;
Replication showed the following error&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ERROR:  Requested starting point 0/4000000 is ahead of the WAL flush &lt;br /&gt;
        position of this server 0/3000060&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The replica instance needed to be re-initialised &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml reinit &amp;lt;scope&amp;gt; &amp;lt;name&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== VIP Manager ===&lt;br /&gt;
&lt;br /&gt;
Copy the latest release to your server : &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt; &lt;br /&gt;
sudo dpkg -i vip-manager_2.8.0_Linux_x86_64.deb&lt;br /&gt;
sudo systemctl stop vip-manager&lt;br /&gt;
sudo vi /etc/patroni/vip-manager.yml&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The trigger key below contains the namespace and scope of the Patroni service. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ip: 192.168.59.100&lt;br /&gt;
netmask: 24&lt;br /&gt;
interface: enp0s3&lt;br /&gt;
trigger-key: &amp;quot;/db/cluster_1/leader&amp;quot;&lt;br /&gt;
dcs-type: etcd&lt;br /&gt;
dcs-endpoints: http://192.168.59.101:2379&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/vip-manager.service&lt;br /&gt;
&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Manages Virtual IP for Patroni&lt;br /&gt;
After=network-online.target&lt;br /&gt;
Before=patroni.service&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
ExecStart=/usr/bin/vip-manager --config=/etc/patroni/vip-manager.yml&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start vip-manager&lt;br /&gt;
sudo journalctl -u vip-manager.service -n 100 -f&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== PGBackRest ===&lt;br /&gt;
&lt;br /&gt;
Setup the repository on Debian3&lt;br /&gt;
&lt;br /&gt;
Install pgbackrest on all the servers …&lt;br /&gt;
sudo apt install pgbackrest&lt;br /&gt;
sudo mkdir -p -m 770 /var/log/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/log/pgbackrest&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest&lt;br /&gt;
sudo touch /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chmod 640 /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chown postgres:postgres /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
&lt;br /&gt;
On debian3, create the directory that will hold the backups …&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest/conf.d&lt;br /&gt;
sudo mkdir -p /var/lib/pgbackrest&lt;br /&gt;
sudo chmod 750 /var/lib/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/lib/pgbackrest&lt;br /&gt;
Setup SSH&lt;br /&gt;
&lt;br /&gt;
We will need a passwordless SSH connection between the repository and the database servers.&lt;br /&gt;
For this setup I was using postgres as the user that controls pgbackrest.&lt;br /&gt;
# debian1&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp id_ras.pub postgres@debian3&lt;br /&gt;
ssh postgres@debian3&lt;br /&gt;
# debian3&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp ./.ssh/id_ras.pub postgres@debian1&lt;br /&gt;
ssh postgres@debian1&lt;br /&gt;
Configuration&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the repository&lt;br /&gt;
vi /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-host=debian1&lt;br /&gt;
pg1-path=/var/lib/postgresql/17 &lt;br /&gt;
&lt;br /&gt;
[global]&lt;br /&gt;
repo1-cipher-pass=zWaf6XtpjIVZC5444yXB+cgFDFl7MxGlgkZSaoPv&lt;br /&gt;
repo1-cipher-type=aes-256-cbc&lt;br /&gt;
repo1-path=/var/lib/pgbackrest&lt;br /&gt;
repo1-retention-full=2&lt;br /&gt;
start-fast=y&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the database server&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-path=/var/lib/postgresql/17&lt;br /&gt;
 &lt;br /&gt;
[global]&lt;br /&gt;
log-level-file=detail&lt;br /&gt;
repo1-host=debian3 &lt;br /&gt;
repo1-host-user=postgres&lt;br /&gt;
&lt;br /&gt;
Alter the archive_command parameter so that it uses the pgbackrest executable&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest \&lt;br /&gt;
                       --stanza=cybertec archive-push %p&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Create a Stanza on the repository server and confirm it is working&lt;br /&gt;
pgbackrest --stanza=cybertec stanza-create&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;br /&gt;
&lt;br /&gt;
Also check if the Stanza is correct on the database server.&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=117</id>
		<title>Patroni Cluster</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=117"/>
		<updated>2025-08-03T15:21:35Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Setup ==&lt;br /&gt;
&lt;br /&gt;
* Debian1: 192.168.59.101 (Debian 12)&lt;br /&gt;
* Debian2: 192.168.59.102 (Debian 12)&lt;br /&gt;
* Debian3: 192.168.59.103 (Debian 12)&lt;br /&gt;
&lt;br /&gt;
The servers were created using VirtualBox. &amp;lt;/br&amp;gt;&lt;br /&gt;
Network setup was 2 adapters (Bridged Adapter and NAT).&lt;br /&gt;
&lt;br /&gt;
== Installation ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo apt install postgresql patroni&lt;br /&gt;
sudo systemctl stop patroni postgresql&lt;br /&gt;
sudo systemctl disable postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
etcd was installed from prebuilt binaries &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ETCD_RELEASE=$(curl -s https://api.github.com/repos/etcd-io/etcd/releases/latest|grep tag_name | cut -d &#039;&amp;quot;&#039; -f 4)&lt;br /&gt;
curl -sL https://github.com/etcd-io/etcd/releases/download/${ETCD_RELEASE}/etcd-${ETCD_RELEASE}-linux-amd64.tar.gz \&lt;br /&gt;
 | sudo tar xz -C /usr/bin --strip=1 --wildcards --no-anchored etcdctl etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuration ==&lt;br /&gt;
=== etcd ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo mkdir -p /var/lib/etcd/&lt;br /&gt;
sudo mkdir -p /etc/etcd&lt;br /&gt;
sudo groupadd --system etcd&lt;br /&gt;
sudo useradd -s /sbin/nologin --system -g etcd etcd&lt;br /&gt;
sudo chown -R etcd:etcd /var/lib/etcd/&lt;br /&gt;
sudo chmod -R a+rw /var/lib/etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the configuration file at /etcd/etcd/etcd.yaml &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
name: &#039;debian1&#039; &lt;br /&gt;
data-dir: &#039;/var/lib/etcd/data.etcd&#039;&lt;br /&gt;
initial-advertise-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
listen-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
advertise-client-urls: http://192.168.59.101:2379 &lt;br /&gt;
listen-client-urls: http://192.168.59.101:2379,http://127.0.0.1:2379 &lt;br /&gt;
initial-cluster: &amp;quot;debian1=http://192.168.59.101:2380,debian2=http://192.168.59.102:2380,debian3=http://192.168.59.103:2380&amp;quot; &lt;br /&gt;
initial-cluster-state: &#039;new&#039; &lt;br /&gt;
initial-cluster-token: &#039;token-01&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Configure the systemd unit file &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=etcd key-value store&lt;br /&gt;
Documentation=https://github.com/etcd-io/etcd&lt;br /&gt;
After=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
Wants=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
User=etcd&lt;br /&gt;
Type=notify&lt;br /&gt;
Environment=ETCD_DATA_DIR=/var/lib/etcd&lt;br /&gt;
Environment=ETCD_NAME=%H&lt;br /&gt;
ExecStart=/usr/bin/etcd --config-file /etc/etcd/etcd.yml&lt;br /&gt;
Restart=always&lt;br /&gt;
RestartSec=10s&lt;br /&gt;
LimitNOFILE=40000&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl enable etcd.service&lt;br /&gt;
sudo systemctl start etcd.service&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test that everything is working &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export ETCDCTL_ENDPOINTS=&amp;quot;http://192.168.59.103:2379,http://192.168.59.101:2379,http://192.168.59.102:2379&amp;quot;&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
etcdctl endpoint health -w table&lt;br /&gt;
etcdctl endpoint status -w table &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
|          ENDPOINT          |        ID        | VERSION | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX | ERRORS |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
| http://192.168.59.102:2379 | 3cdde56ec82a0ca0 |  3.5.16 |   53 kB |      true |        13 |        145 |        |&lt;br /&gt;
| http://192.168.59.101:2379 | 406189a3bb67b8bc |  3.5.16 |   53 kB |     false |        13 |        145 |        |&lt;br /&gt;
| http://192.168.59.103:2379 | 939ed44c2e6d5e5b |  3.5.16 |   53 kB |     false |        13 |        145 |        |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Change the leader if necessary &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
etcdctl move-leader 939ed44c2e6d5e5b --endpoints=$ETCDCTL_ENDPOINTS&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Issues ====&lt;br /&gt;
&lt;br /&gt;
One of the members would not connect to the cluster. &amp;lt;/br&amp;gt;&lt;br /&gt;
The error message on the leader was “Prober detected unhealthy status …” &amp;lt;/br&amp;gt;&lt;br /&gt;
On the problem member the message was “Failed to publish local member to cluster through raft” &amp;lt;/br&amp;gt;&lt;br /&gt;
No network issues, solved by removing and adding the member. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
sudo systemctl stop etcd (On server being removed)&lt;br /&gt;
etcdctl member remove &amp;lt;ID&amp;gt;&lt;br /&gt;
etcdctl member add debian2 --peer-urls=http://192.168.59.102:2380&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Edit the etcd.yml files copying all information displayed, including the new order for ETCD_INITIAL_CLUSTER &amp;lt;/br&amp;gt;&lt;br /&gt;
On the server being added ..&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo systemctl start etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Patroni ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo mkdir -p /etc/patroni/&lt;br /&gt;
sudo chown -R  postgres:postgres /etc/patroni/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
As the postgres user, create a configuration file. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
vi /etc/patroni/patroni.yml&lt;br /&gt;
&lt;br /&gt;
namespace: /db/&lt;br /&gt;
scope: cluster_1&lt;br /&gt;
name: $THIS_NAME&lt;br /&gt;
&lt;br /&gt;
log:&lt;br /&gt;
  format: &#039;%(asctime)s %(levelname)s: %(message)s&#039;&lt;br /&gt;
  level: INFO&lt;br /&gt;
  max_queue_size: 1000&lt;br /&gt;
  traceback_level: ERROR&lt;br /&gt;
  type: plain&lt;br /&gt;
&lt;br /&gt;
restapi:&lt;br /&gt;
  listen: 0.0.0.0:8008&lt;br /&gt;
  connect_address: $THIS_IP:8008&lt;br /&gt;
&lt;br /&gt;
etcd3:&lt;br /&gt;
  hosts: &lt;br /&gt;
  - 192.168.59.101:2379&lt;br /&gt;
  - 192.168.59.102:2379&lt;br /&gt;
  - 192.168.59.103:2379&lt;br /&gt;
&lt;br /&gt;
bootstrap:&lt;br /&gt;
  dcs:&lt;br /&gt;
    ttl: 30&lt;br /&gt;
    loop_wait: 10&lt;br /&gt;
    retry_timeout: 10&lt;br /&gt;
    maximum_lag_on_failover: 1048576&lt;br /&gt;
    postgresql:&lt;br /&gt;
      use_pg_rewind: true&lt;br /&gt;
      use_slots: true&lt;br /&gt;
      parameters:&lt;br /&gt;
        wal_level: replica&lt;br /&gt;
        hot_standby: &amp;quot;on&amp;quot;&lt;br /&gt;
        wal_keep_segments: 10&lt;br /&gt;
        max_wal_senders: 5&lt;br /&gt;
        max_replication_slots: 10&lt;br /&gt;
        wal_log_hints: &amp;quot;on&amp;quot;&lt;br /&gt;
        logging_collector: &#039;on&#039;&lt;br /&gt;
        max_wal_size: &#039;10GB&#039;&lt;br /&gt;
        archive_mode: &amp;quot;on&amp;quot;&lt;br /&gt;
        archive_timeout: 600s&lt;br /&gt;
        archive_command: &amp;quot;/bin/true&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  initdb: # Note: It needs to be a list (some options need values, others are switches)&lt;br /&gt;
  - encoding: UTF8&lt;br /&gt;
  - data-checksums&lt;br /&gt;
&lt;br /&gt;
  pg_hba: # Add following lines to pg_hba.conf after running &#039;initdb&#039;&lt;br /&gt;
  - host replication replicator 127.0.0.1/32 trust&lt;br /&gt;
  - host replication replicator 0.0.0.0/0 md5&lt;br /&gt;
  - host all all 0.0.0.0/0 md5&lt;br /&gt;
  - host all all ::0/0 md5&lt;br /&gt;
&lt;br /&gt;
  # Some additional users which needs to be created after initializing new cluster&lt;br /&gt;
  users:&lt;br /&gt;
    admin:&lt;br /&gt;
      password: qaz123&lt;br /&gt;
      options:&lt;br /&gt;
        - createrole&lt;br /&gt;
        - createdb&lt;br /&gt;
&lt;br /&gt;
postgresql:&lt;br /&gt;
  listen: 0.0.0.0:5432&lt;br /&gt;
  connect_address: $THIS_IP:5432&lt;br /&gt;
  data_dir: /var/lib/postgresql/17&lt;br /&gt;
  bin_dir: /usr/lib/postgresql/17/bin&lt;br /&gt;
  pgpass: /tmp/pgpass0&lt;br /&gt;
  authentication:&lt;br /&gt;
    replication:&lt;br /&gt;
      username: replicator&lt;br /&gt;
      password: replPasswd&lt;br /&gt;
    superuser:&lt;br /&gt;
      username: postgres&lt;br /&gt;
      password: qaz123&lt;br /&gt;
  parameters:&lt;br /&gt;
    unix_socket_directories: &amp;quot;/var/run/postgresql/&amp;quot;&lt;br /&gt;
&lt;br /&gt;
tags:&lt;br /&gt;
  nofailover: false&lt;br /&gt;
  noloadbalance: false&lt;br /&gt;
  clonefrom: false&lt;br /&gt;
  nosync: false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Validate the configuration file &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
patroni --validate-config /etc/patroni/patroni.yml&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Service ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/patroni.service&lt;br /&gt;
&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Runners to orchestrate a high-availability PostgreSQL&lt;br /&gt;
After=network.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
&lt;br /&gt;
User=postgres&lt;br /&gt;
Group=postgres&lt;br /&gt;
&lt;br /&gt;
# Start the patroni process&lt;br /&gt;
ExecStart=/usr/bin/patroni /etc/patroni/patroni.yml&lt;br /&gt;
&lt;br /&gt;
# Send HUP to reload from patroni.yml&lt;br /&gt;
ExecReload=/bin/kill -s HUP $MAINPID&lt;br /&gt;
&lt;br /&gt;
# Only kill the patroni process, not it&#039;s children, so it will gracefully stop postgres&lt;br /&gt;
KillMode=process&lt;br /&gt;
&lt;br /&gt;
# Give a reasonable amount of time for the server to start up/shut down&lt;br /&gt;
TimeoutSec=30&lt;br /&gt;
&lt;br /&gt;
# Restart the service if it crashed&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Set permissions of the data directory to 700 &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
chmod 700 /var/lib/postgresql/17&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Start the patroni service &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start patroni&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Check status&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml list&lt;br /&gt;
&lt;br /&gt;
+ Cluster: cluster_1 (7439347493790530098) ------+----+-----------+&lt;br /&gt;
| Member  | Host           | Role    | State     | TL | Lag in MB |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
| debian1 | 192.168.59.101 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian2 | 192.168.59.102 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian3 | 192.168.59.103 | Leader  | running   |  1 |           |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Change configuration &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest --stanza=ian archive-push %p&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Issues ====&lt;br /&gt;
&lt;br /&gt;
Replication showed the following error&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ERROR:  Requested starting point 0/4000000 is ahead of the WAL flush &lt;br /&gt;
        position of this server 0/3000060&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The replica instance needed to be re-initialised &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml reinit &amp;lt;scope&amp;gt; &amp;lt;name&amp;gt;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== VIP Manager ===&lt;br /&gt;
&lt;br /&gt;
Copy the latest release to your server : &lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
sudo dpkg -i vip-manager_2.8.0_Linux_x86_64.deb&lt;br /&gt;
sudo systemctl stop vip-manager&lt;br /&gt;
sudo vi /etc/patroni/vip-manager.yml&lt;br /&gt;
&lt;br /&gt;
The trigger key below contains the namespace and scope of the Patroni service.&lt;br /&gt;
ip: 192.168.59.100&lt;br /&gt;
netmask: 24&lt;br /&gt;
interface: enp0s3&lt;br /&gt;
trigger-key: &amp;quot;/db/cluster_1/leader&amp;quot;&lt;br /&gt;
dcs-type: etcd&lt;br /&gt;
dcs-endpoints: http://192.168.59.101:2379&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/vip-manager.service&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Manages Virtual IP for Patroni&lt;br /&gt;
After=network-online.target&lt;br /&gt;
Before=patroni.service&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
ExecStart=/usr/bin/vip-manager --config=/etc/patroni/vip-manager.yml&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start vip-manager&lt;br /&gt;
sudo journalctl -u vip-manager.service -n 100 -f&lt;br /&gt;
PGBackRest&lt;br /&gt;
&lt;br /&gt;
Setup the repository on Debian3&lt;br /&gt;
&lt;br /&gt;
Install pgbackrest on all the servers …&lt;br /&gt;
sudo apt install pgbackrest&lt;br /&gt;
sudo mkdir -p -m 770 /var/log/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/log/pgbackrest&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest&lt;br /&gt;
sudo touch /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chmod 640 /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chown postgres:postgres /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
&lt;br /&gt;
On debian3, create the directory that will hold the backups …&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest/conf.d&lt;br /&gt;
sudo mkdir -p /var/lib/pgbackrest&lt;br /&gt;
sudo chmod 750 /var/lib/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/lib/pgbackrest&lt;br /&gt;
Setup SSH&lt;br /&gt;
&lt;br /&gt;
We will need a passwordless SSH connection between the repository and the database servers.&lt;br /&gt;
For this setup I was using postgres as the user that controls pgbackrest.&lt;br /&gt;
# debian1&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp id_ras.pub postgres@debian3&lt;br /&gt;
ssh postgres@debian3&lt;br /&gt;
# debian3&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp ./.ssh/id_ras.pub postgres@debian1&lt;br /&gt;
ssh postgres@debian1&lt;br /&gt;
Configuration&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the repository&lt;br /&gt;
vi /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-host=debian1&lt;br /&gt;
pg1-path=/var/lib/postgresql/17 &lt;br /&gt;
&lt;br /&gt;
[global]&lt;br /&gt;
repo1-cipher-pass=zWaf6XtpjIVZC5444yXB+cgFDFl7MxGlgkZSaoPv&lt;br /&gt;
repo1-cipher-type=aes-256-cbc&lt;br /&gt;
repo1-path=/var/lib/pgbackrest&lt;br /&gt;
repo1-retention-full=2&lt;br /&gt;
start-fast=y&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the database server&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-path=/var/lib/postgresql/17&lt;br /&gt;
 &lt;br /&gt;
[global]&lt;br /&gt;
log-level-file=detail&lt;br /&gt;
repo1-host=debian3 &lt;br /&gt;
repo1-host-user=postgres&lt;br /&gt;
&lt;br /&gt;
Alter the archive_command parameter so that it uses the pgbackrest executable&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest \&lt;br /&gt;
                       --stanza=cybertec archive-push %p&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Create a Stanza on the repository server and confirm it is working&lt;br /&gt;
pgbackrest --stanza=cybertec stanza-create&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;br /&gt;
&lt;br /&gt;
Also check if the Stanza is correct on the database server.&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=116</id>
		<title>Patroni Cluster</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=116"/>
		<updated>2025-08-03T15:18:54Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Setup ==&lt;br /&gt;
&lt;br /&gt;
* Debian1: 192.168.59.101 (Debian 12)&lt;br /&gt;
* Debian2: 192.168.59.102 (Debian 12)&lt;br /&gt;
* Debian3: 192.168.59.103 (Debian 12)&lt;br /&gt;
&lt;br /&gt;
The servers were created using VirtualBox. &amp;lt;/br&amp;gt;&lt;br /&gt;
Network setup was 2 adapters (Bridged Adapter and NAT).&lt;br /&gt;
&lt;br /&gt;
== Installation ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo apt install postgresql patroni&lt;br /&gt;
sudo systemctl stop patroni postgresql&lt;br /&gt;
sudo systemctl disable postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
etcd was installed from prebuilt binaries &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ETCD_RELEASE=$(curl -s https://api.github.com/repos/etcd-io/etcd/releases/latest|grep tag_name | cut -d &#039;&amp;quot;&#039; -f 4)&lt;br /&gt;
curl -sL https://github.com/etcd-io/etcd/releases/download/${ETCD_RELEASE}/etcd-${ETCD_RELEASE}-linux-amd64.tar.gz \&lt;br /&gt;
 | sudo tar xz -C /usr/bin --strip=1 --wildcards --no-anchored etcdctl etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuration ==&lt;br /&gt;
=== etcd ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo mkdir -p /var/lib/etcd/&lt;br /&gt;
sudo mkdir -p /etc/etcd&lt;br /&gt;
sudo groupadd --system etcd&lt;br /&gt;
sudo useradd -s /sbin/nologin --system -g etcd etcd&lt;br /&gt;
sudo chown -R etcd:etcd /var/lib/etcd/&lt;br /&gt;
sudo chmod -R a+rw /var/lib/etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the configuration file at /etcd/etcd/etcd.yaml &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
name: &#039;debian1&#039; &lt;br /&gt;
data-dir: &#039;/var/lib/etcd/data.etcd&#039;&lt;br /&gt;
initial-advertise-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
listen-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
advertise-client-urls: http://192.168.59.101:2379 &lt;br /&gt;
listen-client-urls: http://192.168.59.101:2379,http://127.0.0.1:2379 &lt;br /&gt;
initial-cluster: &amp;quot;debian1=http://192.168.59.101:2380,debian2=http://192.168.59.102:2380,debian3=http://192.168.59.103:2380&amp;quot; &lt;br /&gt;
initial-cluster-state: &#039;new&#039; &lt;br /&gt;
initial-cluster-token: &#039;token-01&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Configure the systemd unit file &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=etcd key-value store&lt;br /&gt;
Documentation=https://github.com/etcd-io/etcd&lt;br /&gt;
After=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
Wants=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
User=etcd&lt;br /&gt;
Type=notify&lt;br /&gt;
Environment=ETCD_DATA_DIR=/var/lib/etcd&lt;br /&gt;
Environment=ETCD_NAME=%H&lt;br /&gt;
ExecStart=/usr/bin/etcd --config-file /etc/etcd/etcd.yml&lt;br /&gt;
Restart=always&lt;br /&gt;
RestartSec=10s&lt;br /&gt;
LimitNOFILE=40000&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl enable etcd.service&lt;br /&gt;
sudo systemctl start etcd.service&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test that everything is working &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export ETCDCTL_ENDPOINTS=&amp;quot;http://192.168.59.103:2379,http://192.168.59.101:2379,http://192.168.59.102:2379&amp;quot;&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
etcdctl endpoint health -w table&lt;br /&gt;
etcdctl endpoint status -w table &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
|          ENDPOINT          |        ID        | VERSION | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX | ERRORS |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
| http://192.168.59.102:2379 | 3cdde56ec82a0ca0 |  3.5.16 |   53 kB |      true |        13 |        145 |        |&lt;br /&gt;
| http://192.168.59.101:2379 | 406189a3bb67b8bc |  3.5.16 |   53 kB |     false |        13 |        145 |        |&lt;br /&gt;
| http://192.168.59.103:2379 | 939ed44c2e6d5e5b |  3.5.16 |   53 kB |     false |        13 |        145 |        |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Change the leader if necessary &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
etcdctl move-leader 939ed44c2e6d5e5b --endpoints=$ETCDCTL_ENDPOINTS&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Issues ====&lt;br /&gt;
&lt;br /&gt;
One of the members would not connect to the cluster. &amp;lt;/br&amp;gt;&lt;br /&gt;
The error message on the leader was “Prober detected unhealthy status …” &amp;lt;/br&amp;gt;&lt;br /&gt;
On the problem member the message was “Failed to publish local member to cluster through raft” &amp;lt;/br&amp;gt;&lt;br /&gt;
No network issues, solved by removing and adding the member. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
sudo systemctl stop etcd (On server being removed)&lt;br /&gt;
etcdctl member remove &amp;lt;ID&amp;gt;&lt;br /&gt;
etcdctl member add debian2 --peer-urls=http://192.168.59.102:2380&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Edit the etcd.yml files copying all information displayed, including the new order for ETCD_INITIAL_CLUSTER &amp;lt;/br&amp;gt;&lt;br /&gt;
On the server being added ..&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo systemctl start etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Patroni ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo mkdir -p /etc/patroni/&lt;br /&gt;
sudo chown -R  postgres:postgres /etc/patroni/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
As the postgres user, create a configuration file. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
vi /etc/patroni/patroni.yml&lt;br /&gt;
&lt;br /&gt;
namespace: /db/&lt;br /&gt;
scope: cluster_1&lt;br /&gt;
name: $THIS_NAME&lt;br /&gt;
&lt;br /&gt;
log:&lt;br /&gt;
  format: &#039;%(asctime)s %(levelname)s: %(message)s&#039;&lt;br /&gt;
  level: INFO&lt;br /&gt;
  max_queue_size: 1000&lt;br /&gt;
  traceback_level: ERROR&lt;br /&gt;
  type: plain&lt;br /&gt;
&lt;br /&gt;
restapi:&lt;br /&gt;
  listen: 0.0.0.0:8008&lt;br /&gt;
  connect_address: $THIS_IP:8008&lt;br /&gt;
&lt;br /&gt;
etcd3:&lt;br /&gt;
  hosts: &lt;br /&gt;
  - 192.168.59.101:2379&lt;br /&gt;
  - 192.168.59.102:2379&lt;br /&gt;
  - 192.168.59.103:2379&lt;br /&gt;
&lt;br /&gt;
bootstrap:&lt;br /&gt;
  dcs:&lt;br /&gt;
    ttl: 30&lt;br /&gt;
    loop_wait: 10&lt;br /&gt;
    retry_timeout: 10&lt;br /&gt;
    maximum_lag_on_failover: 1048576&lt;br /&gt;
    postgresql:&lt;br /&gt;
      use_pg_rewind: true&lt;br /&gt;
      use_slots: true&lt;br /&gt;
      parameters:&lt;br /&gt;
        wal_level: replica&lt;br /&gt;
        hot_standby: &amp;quot;on&amp;quot;&lt;br /&gt;
        wal_keep_segments: 10&lt;br /&gt;
        max_wal_senders: 5&lt;br /&gt;
        max_replication_slots: 10&lt;br /&gt;
        wal_log_hints: &amp;quot;on&amp;quot;&lt;br /&gt;
        logging_collector: &#039;on&#039;&lt;br /&gt;
        max_wal_size: &#039;10GB&#039;&lt;br /&gt;
        archive_mode: &amp;quot;on&amp;quot;&lt;br /&gt;
        archive_timeout: 600s&lt;br /&gt;
        archive_command: &amp;quot;/bin/true&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  initdb: # Note: It needs to be a list (some options need values, others are switches)&lt;br /&gt;
  - encoding: UTF8&lt;br /&gt;
  - data-checksums&lt;br /&gt;
&lt;br /&gt;
  pg_hba: # Add following lines to pg_hba.conf after running &#039;initdb&#039;&lt;br /&gt;
  - host replication replicator 127.0.0.1/32 trust&lt;br /&gt;
  - host replication replicator 0.0.0.0/0 md5&lt;br /&gt;
  - host all all 0.0.0.0/0 md5&lt;br /&gt;
  - host all all ::0/0 md5&lt;br /&gt;
&lt;br /&gt;
  # Some additional users which needs to be created after initializing new cluster&lt;br /&gt;
  users:&lt;br /&gt;
    admin:&lt;br /&gt;
      password: qaz123&lt;br /&gt;
      options:&lt;br /&gt;
        - createrole&lt;br /&gt;
        - createdb&lt;br /&gt;
&lt;br /&gt;
postgresql:&lt;br /&gt;
  listen: 0.0.0.0:5432&lt;br /&gt;
  connect_address: $THIS_IP:5432&lt;br /&gt;
  data_dir: /var/lib/postgresql/17&lt;br /&gt;
  bin_dir: /usr/lib/postgresql/17/bin&lt;br /&gt;
  pgpass: /tmp/pgpass0&lt;br /&gt;
  authentication:&lt;br /&gt;
    replication:&lt;br /&gt;
      username: replicator&lt;br /&gt;
      password: replPasswd&lt;br /&gt;
    superuser:&lt;br /&gt;
      username: postgres&lt;br /&gt;
      password: qaz123&lt;br /&gt;
  parameters:&lt;br /&gt;
    unix_socket_directories: &amp;quot;/var/run/postgresql/&amp;quot;&lt;br /&gt;
&lt;br /&gt;
tags:&lt;br /&gt;
  nofailover: false&lt;br /&gt;
  noloadbalance: false&lt;br /&gt;
  clonefrom: false&lt;br /&gt;
  nosync: false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Validate the configuration file&lt;br /&gt;
patroni --validate-config /etc/patroni/patroni.yml&lt;br /&gt;
Service&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/patroni.service&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Runners to orchestrate a high-availability PostgreSQL&lt;br /&gt;
After=network.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
&lt;br /&gt;
User=postgres&lt;br /&gt;
Group=postgres&lt;br /&gt;
&lt;br /&gt;
# Start the patroni process&lt;br /&gt;
ExecStart=/usr/bin/patroni /etc/patroni/patroni.yml&lt;br /&gt;
&lt;br /&gt;
# Send HUP to reload from patroni.yml&lt;br /&gt;
ExecReload=/bin/kill -s HUP $MAINPID&lt;br /&gt;
&lt;br /&gt;
# Only kill the patroni process, not it&#039;s children, so it will gracefully stop postgres&lt;br /&gt;
KillMode=process&lt;br /&gt;
&lt;br /&gt;
# Give a reasonable amount of time for the server to start up/shut down&lt;br /&gt;
TimeoutSec=30&lt;br /&gt;
&lt;br /&gt;
# Restart the service if it crashed&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
&lt;br /&gt;
Set permissions of the data directory to 700&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
chmod 700 /var/lib/postgresql/17&lt;br /&gt;
&lt;br /&gt;
Start the patroni service&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start patroni&lt;br /&gt;
&lt;br /&gt;
Check status&lt;br /&gt;
export PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml list&lt;br /&gt;
&lt;br /&gt;
+ Cluster: cluster_1 (7439347493790530098) ------+----+-----------+&lt;br /&gt;
| Member  | Host           | Role    | State     | TL | Lag in MB |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
| debian1 | 192.168.59.101 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian2 | 192.168.59.102 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian3 | 192.168.59.103 | Leader  | running   |  1 |           |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
&lt;br /&gt;
Change configuration&lt;br /&gt;
PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest --stanza=ian archive-push %p&amp;quot;&lt;br /&gt;
Issues&lt;br /&gt;
&lt;br /&gt;
Replication showed the following error&lt;br /&gt;
ERROR:  Requested starting point 0/4000000 is ahead of the WAL flush &lt;br /&gt;
        position of this server 0/3000060&lt;br /&gt;
&lt;br /&gt;
The replica instance needed to be re-initialised&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml reinit &amp;lt;scope&amp;gt; &amp;lt;name&amp;gt;&lt;br /&gt;
VIP Manager&lt;br /&gt;
&lt;br /&gt;
Copy the latest release to your server : &lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
sudo dpkg -i vip-manager_2.8.0_Linux_x86_64.deb&lt;br /&gt;
sudo systemctl stop vip-manager&lt;br /&gt;
sudo vi /etc/patroni/vip-manager.yml&lt;br /&gt;
&lt;br /&gt;
The trigger key below contains the namespace and scope of the Patroni service.&lt;br /&gt;
ip: 192.168.59.100&lt;br /&gt;
netmask: 24&lt;br /&gt;
interface: enp0s3&lt;br /&gt;
trigger-key: &amp;quot;/db/cluster_1/leader&amp;quot;&lt;br /&gt;
dcs-type: etcd&lt;br /&gt;
dcs-endpoints: http://192.168.59.101:2379&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/vip-manager.service&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Manages Virtual IP for Patroni&lt;br /&gt;
After=network-online.target&lt;br /&gt;
Before=patroni.service&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
ExecStart=/usr/bin/vip-manager --config=/etc/patroni/vip-manager.yml&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start vip-manager&lt;br /&gt;
sudo journalctl -u vip-manager.service -n 100 -f&lt;br /&gt;
PGBackRest&lt;br /&gt;
&lt;br /&gt;
Setup the repository on Debian3&lt;br /&gt;
&lt;br /&gt;
Install pgbackrest on all the servers …&lt;br /&gt;
sudo apt install pgbackrest&lt;br /&gt;
sudo mkdir -p -m 770 /var/log/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/log/pgbackrest&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest&lt;br /&gt;
sudo touch /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chmod 640 /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chown postgres:postgres /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
&lt;br /&gt;
On debian3, create the directory that will hold the backups …&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest/conf.d&lt;br /&gt;
sudo mkdir -p /var/lib/pgbackrest&lt;br /&gt;
sudo chmod 750 /var/lib/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/lib/pgbackrest&lt;br /&gt;
Setup SSH&lt;br /&gt;
&lt;br /&gt;
We will need a passwordless SSH connection between the repository and the database servers.&lt;br /&gt;
For this setup I was using postgres as the user that controls pgbackrest.&lt;br /&gt;
# debian1&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp id_ras.pub postgres@debian3&lt;br /&gt;
ssh postgres@debian3&lt;br /&gt;
# debian3&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp ./.ssh/id_ras.pub postgres@debian1&lt;br /&gt;
ssh postgres@debian1&lt;br /&gt;
Configuration&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the repository&lt;br /&gt;
vi /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-host=debian1&lt;br /&gt;
pg1-path=/var/lib/postgresql/17 &lt;br /&gt;
&lt;br /&gt;
[global]&lt;br /&gt;
repo1-cipher-pass=zWaf6XtpjIVZC5444yXB+cgFDFl7MxGlgkZSaoPv&lt;br /&gt;
repo1-cipher-type=aes-256-cbc&lt;br /&gt;
repo1-path=/var/lib/pgbackrest&lt;br /&gt;
repo1-retention-full=2&lt;br /&gt;
start-fast=y&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the database server&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-path=/var/lib/postgresql/17&lt;br /&gt;
 &lt;br /&gt;
[global]&lt;br /&gt;
log-level-file=detail&lt;br /&gt;
repo1-host=debian3 &lt;br /&gt;
repo1-host-user=postgres&lt;br /&gt;
&lt;br /&gt;
Alter the archive_command parameter so that it uses the pgbackrest executable&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest \&lt;br /&gt;
                       --stanza=cybertec archive-push %p&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Create a Stanza on the repository server and confirm it is working&lt;br /&gt;
pgbackrest --stanza=cybertec stanza-create&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;br /&gt;
&lt;br /&gt;
Also check if the Stanza is correct on the database server.&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=115</id>
		<title>Patroni Cluster</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=115"/>
		<updated>2025-08-03T15:17:53Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Setup ==&lt;br /&gt;
&lt;br /&gt;
* Debian1: 192.168.59.101 (Debian 12)&lt;br /&gt;
* Debian2: 192.168.59.102 (Debian 12)&lt;br /&gt;
* Debian3: 192.168.59.103 (Debian 12)&lt;br /&gt;
&lt;br /&gt;
The servers were created using VirtualBox. &amp;lt;/br&amp;gt;&lt;br /&gt;
Network setup was 2 adapters (Bridged Adapter and NAT).&lt;br /&gt;
&lt;br /&gt;
== Installation ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo apt install postgresql patroni&lt;br /&gt;
sudo systemctl stop patroni postgresql&lt;br /&gt;
sudo systemctl disable postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
etcd was installed from prebuilt binaries &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ETCD_RELEASE=$(curl -s https://api.github.com/repos/etcd-io/etcd/releases/latest|grep tag_name | cut -d &#039;&amp;quot;&#039; -f 4)&lt;br /&gt;
curl -sL https://github.com/etcd-io/etcd/releases/download/${ETCD_RELEASE}/etcd-${ETCD_RELEASE}-linux-amd64.tar.gz \&lt;br /&gt;
 | sudo tar xz -C /usr/bin --strip=1 --wildcards --no-anchored etcdctl etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuration ==&lt;br /&gt;
=== etcd ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo mkdir -p /var/lib/etcd/&lt;br /&gt;
sudo mkdir -p /etc/etcd&lt;br /&gt;
sudo groupadd --system etcd&lt;br /&gt;
sudo useradd -s /sbin/nologin --system -g etcd etcd&lt;br /&gt;
sudo chown -R etcd:etcd /var/lib/etcd/&lt;br /&gt;
sudo chmod -R a+rw /var/lib/etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the configuration file at /etcd/etcd/etcd.yaml &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
name: &#039;debian1&#039; &lt;br /&gt;
data-dir: &#039;/var/lib/etcd/data.etcd&#039;&lt;br /&gt;
initial-advertise-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
listen-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
advertise-client-urls: http://192.168.59.101:2379 &lt;br /&gt;
listen-client-urls: http://192.168.59.101:2379,http://127.0.0.1:2379 &lt;br /&gt;
initial-cluster: &amp;quot;debian1=http://192.168.59.101:2380,debian2=http://192.168.59.102:2380,debian3=http://192.168.59.103:2380&amp;quot; &lt;br /&gt;
initial-cluster-state: &#039;new&#039; &lt;br /&gt;
initial-cluster-token: &#039;token-01&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Configure the systemd unit file &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=etcd key-value store&lt;br /&gt;
Documentation=https://github.com/etcd-io/etcd&lt;br /&gt;
After=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
Wants=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
User=etcd&lt;br /&gt;
Type=notify&lt;br /&gt;
Environment=ETCD_DATA_DIR=/var/lib/etcd&lt;br /&gt;
Environment=ETCD_NAME=%H&lt;br /&gt;
ExecStart=/usr/bin/etcd --config-file /etc/etcd/etcd.yml&lt;br /&gt;
Restart=always&lt;br /&gt;
RestartSec=10s&lt;br /&gt;
LimitNOFILE=40000&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl enable etcd.service&lt;br /&gt;
sudo systemctl start etcd.service&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test that everything is working &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export ETCDCTL_ENDPOINTS=&amp;quot;http://192.168.59.103:2379,http://192.168.59.101:2379,http://192.168.59.102:2379&amp;quot;&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
etcdctl endpoint health -w table&lt;br /&gt;
etcdctl endpoint status -w table &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
|          ENDPOINT          |        ID        | VERSION | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX | ERRORS |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
| http://192.168.59.102:2379 | 3cdde56ec82a0ca0 |  3.5.16 |   53 kB |      true |        13 |        145 |        |&lt;br /&gt;
| http://192.168.59.101:2379 | 406189a3bb67b8bc |  3.5.16 |   53 kB |     false |        13 |        145 |        |&lt;br /&gt;
| http://192.168.59.103:2379 | 939ed44c2e6d5e5b |  3.5.16 |   53 kB |     false |        13 |        145 |        |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Change the leader if necessary &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
etcdctl move-leader 939ed44c2e6d5e5b --endpoints=$ETCDCTL_ENDPOINTS&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Issues ====&lt;br /&gt;
&lt;br /&gt;
One of the members would not connect to the cluster.&lt;br /&gt;
The error message on the leader was “Prober detected unhealthy status …”&lt;br /&gt;
On the problem member the message was “Failed to publish local member to cluster through raft”&lt;br /&gt;
No network issues, solved by removing and adding the member&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
sudo systemctl stop etcd (On server being removed)&lt;br /&gt;
etcdctl member remove &amp;lt;ID&amp;gt;&lt;br /&gt;
etcdctl member add debian2 --peer-urls=http://192.168.59.102:2380&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Edit the etcd.yml files copying all information displayed, including the new order for ETCD_INITIAL_CLUSTER &amp;lt;/br&amp;gt;&lt;br /&gt;
On the server being added ..&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo systemctl start etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Patroni ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo mkdir -p /etc/patroni/&lt;br /&gt;
sudo chown -R  postgres:postgres /etc/patroni/&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
As the postgres user, create a configuration file. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
vi /etc/patroni/patroni.yml&lt;br /&gt;
&lt;br /&gt;
namespace: /db/&lt;br /&gt;
scope: cluster_1&lt;br /&gt;
name: $THIS_NAME&lt;br /&gt;
&lt;br /&gt;
log:&lt;br /&gt;
  format: &#039;%(asctime)s %(levelname)s: %(message)s&#039;&lt;br /&gt;
  level: INFO&lt;br /&gt;
  max_queue_size: 1000&lt;br /&gt;
  traceback_level: ERROR&lt;br /&gt;
  type: plain&lt;br /&gt;
&lt;br /&gt;
restapi:&lt;br /&gt;
  listen: 0.0.0.0:8008&lt;br /&gt;
  connect_address: $THIS_IP:8008&lt;br /&gt;
&lt;br /&gt;
etcd3:&lt;br /&gt;
  hosts: &lt;br /&gt;
  - 192.168.59.101:2379&lt;br /&gt;
  - 192.168.59.102:2379&lt;br /&gt;
  - 192.168.59.103:2379&lt;br /&gt;
&lt;br /&gt;
bootstrap:&lt;br /&gt;
  dcs:&lt;br /&gt;
    ttl: 30&lt;br /&gt;
    loop_wait: 10&lt;br /&gt;
    retry_timeout: 10&lt;br /&gt;
    maximum_lag_on_failover: 1048576&lt;br /&gt;
    postgresql:&lt;br /&gt;
      use_pg_rewind: true&lt;br /&gt;
      use_slots: true&lt;br /&gt;
      parameters:&lt;br /&gt;
        wal_level: replica&lt;br /&gt;
        hot_standby: &amp;quot;on&amp;quot;&lt;br /&gt;
        wal_keep_segments: 10&lt;br /&gt;
        max_wal_senders: 5&lt;br /&gt;
        max_replication_slots: 10&lt;br /&gt;
        wal_log_hints: &amp;quot;on&amp;quot;&lt;br /&gt;
        logging_collector: &#039;on&#039;&lt;br /&gt;
        max_wal_size: &#039;10GB&#039;&lt;br /&gt;
        archive_mode: &amp;quot;on&amp;quot;&lt;br /&gt;
        archive_timeout: 600s&lt;br /&gt;
        archive_command: &amp;quot;/bin/true&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  initdb: # Note: It needs to be a list (some options need values, others are switches)&lt;br /&gt;
  - encoding: UTF8&lt;br /&gt;
  - data-checksums&lt;br /&gt;
&lt;br /&gt;
  pg_hba: # Add following lines to pg_hba.conf after running &#039;initdb&#039;&lt;br /&gt;
  - host replication replicator 127.0.0.1/32 trust&lt;br /&gt;
  - host replication replicator 0.0.0.0/0 md5&lt;br /&gt;
  - host all all 0.0.0.0/0 md5&lt;br /&gt;
  - host all all ::0/0 md5&lt;br /&gt;
&lt;br /&gt;
  # Some additional users which needs to be created after initializing new cluster&lt;br /&gt;
  users:&lt;br /&gt;
    admin:&lt;br /&gt;
      password: qaz123&lt;br /&gt;
      options:&lt;br /&gt;
        - createrole&lt;br /&gt;
        - createdb&lt;br /&gt;
&lt;br /&gt;
postgresql:&lt;br /&gt;
  listen: 0.0.0.0:5432&lt;br /&gt;
  connect_address: $THIS_IP:5432&lt;br /&gt;
  data_dir: /var/lib/postgresql/17&lt;br /&gt;
  bin_dir: /usr/lib/postgresql/17/bin&lt;br /&gt;
  pgpass: /tmp/pgpass0&lt;br /&gt;
  authentication:&lt;br /&gt;
    replication:&lt;br /&gt;
      username: replicator&lt;br /&gt;
      password: replPasswd&lt;br /&gt;
    superuser:&lt;br /&gt;
      username: postgres&lt;br /&gt;
      password: qaz123&lt;br /&gt;
  parameters:&lt;br /&gt;
    unix_socket_directories: &amp;quot;/var/run/postgresql/&amp;quot;&lt;br /&gt;
&lt;br /&gt;
tags:&lt;br /&gt;
  nofailover: false&lt;br /&gt;
  noloadbalance: false&lt;br /&gt;
  clonefrom: false&lt;br /&gt;
  nosync: false&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Validate the configuration file&lt;br /&gt;
patroni --validate-config /etc/patroni/patroni.yml&lt;br /&gt;
Service&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/patroni.service&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Runners to orchestrate a high-availability PostgreSQL&lt;br /&gt;
After=network.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
&lt;br /&gt;
User=postgres&lt;br /&gt;
Group=postgres&lt;br /&gt;
&lt;br /&gt;
# Start the patroni process&lt;br /&gt;
ExecStart=/usr/bin/patroni /etc/patroni/patroni.yml&lt;br /&gt;
&lt;br /&gt;
# Send HUP to reload from patroni.yml&lt;br /&gt;
ExecReload=/bin/kill -s HUP $MAINPID&lt;br /&gt;
&lt;br /&gt;
# Only kill the patroni process, not it&#039;s children, so it will gracefully stop postgres&lt;br /&gt;
KillMode=process&lt;br /&gt;
&lt;br /&gt;
# Give a reasonable amount of time for the server to start up/shut down&lt;br /&gt;
TimeoutSec=30&lt;br /&gt;
&lt;br /&gt;
# Restart the service if it crashed&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
&lt;br /&gt;
Set permissions of the data directory to 700&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
chmod 700 /var/lib/postgresql/17&lt;br /&gt;
&lt;br /&gt;
Start the patroni service&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start patroni&lt;br /&gt;
&lt;br /&gt;
Check status&lt;br /&gt;
export PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml list&lt;br /&gt;
&lt;br /&gt;
+ Cluster: cluster_1 (7439347493790530098) ------+----+-----------+&lt;br /&gt;
| Member  | Host           | Role    | State     | TL | Lag in MB |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
| debian1 | 192.168.59.101 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian2 | 192.168.59.102 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian3 | 192.168.59.103 | Leader  | running   |  1 |           |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
&lt;br /&gt;
Change configuration&lt;br /&gt;
PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest --stanza=ian archive-push %p&amp;quot;&lt;br /&gt;
Issues&lt;br /&gt;
&lt;br /&gt;
Replication showed the following error&lt;br /&gt;
ERROR:  Requested starting point 0/4000000 is ahead of the WAL flush &lt;br /&gt;
        position of this server 0/3000060&lt;br /&gt;
&lt;br /&gt;
The replica instance needed to be re-initialised&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml reinit &amp;lt;scope&amp;gt; &amp;lt;name&amp;gt;&lt;br /&gt;
VIP Manager&lt;br /&gt;
&lt;br /&gt;
Copy the latest release to your server : &lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
sudo dpkg -i vip-manager_2.8.0_Linux_x86_64.deb&lt;br /&gt;
sudo systemctl stop vip-manager&lt;br /&gt;
sudo vi /etc/patroni/vip-manager.yml&lt;br /&gt;
&lt;br /&gt;
The trigger key below contains the namespace and scope of the Patroni service.&lt;br /&gt;
ip: 192.168.59.100&lt;br /&gt;
netmask: 24&lt;br /&gt;
interface: enp0s3&lt;br /&gt;
trigger-key: &amp;quot;/db/cluster_1/leader&amp;quot;&lt;br /&gt;
dcs-type: etcd&lt;br /&gt;
dcs-endpoints: http://192.168.59.101:2379&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/vip-manager.service&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Manages Virtual IP for Patroni&lt;br /&gt;
After=network-online.target&lt;br /&gt;
Before=patroni.service&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
ExecStart=/usr/bin/vip-manager --config=/etc/patroni/vip-manager.yml&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start vip-manager&lt;br /&gt;
sudo journalctl -u vip-manager.service -n 100 -f&lt;br /&gt;
PGBackRest&lt;br /&gt;
&lt;br /&gt;
Setup the repository on Debian3&lt;br /&gt;
&lt;br /&gt;
Install pgbackrest on all the servers …&lt;br /&gt;
sudo apt install pgbackrest&lt;br /&gt;
sudo mkdir -p -m 770 /var/log/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/log/pgbackrest&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest&lt;br /&gt;
sudo touch /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chmod 640 /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chown postgres:postgres /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
&lt;br /&gt;
On debian3, create the directory that will hold the backups …&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest/conf.d&lt;br /&gt;
sudo mkdir -p /var/lib/pgbackrest&lt;br /&gt;
sudo chmod 750 /var/lib/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/lib/pgbackrest&lt;br /&gt;
Setup SSH&lt;br /&gt;
&lt;br /&gt;
We will need a passwordless SSH connection between the repository and the database servers.&lt;br /&gt;
For this setup I was using postgres as the user that controls pgbackrest.&lt;br /&gt;
# debian1&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp id_ras.pub postgres@debian3&lt;br /&gt;
ssh postgres@debian3&lt;br /&gt;
# debian3&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp ./.ssh/id_ras.pub postgres@debian1&lt;br /&gt;
ssh postgres@debian1&lt;br /&gt;
Configuration&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the repository&lt;br /&gt;
vi /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-host=debian1&lt;br /&gt;
pg1-path=/var/lib/postgresql/17 &lt;br /&gt;
&lt;br /&gt;
[global]&lt;br /&gt;
repo1-cipher-pass=zWaf6XtpjIVZC5444yXB+cgFDFl7MxGlgkZSaoPv&lt;br /&gt;
repo1-cipher-type=aes-256-cbc&lt;br /&gt;
repo1-path=/var/lib/pgbackrest&lt;br /&gt;
repo1-retention-full=2&lt;br /&gt;
start-fast=y&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the database server&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-path=/var/lib/postgresql/17&lt;br /&gt;
 &lt;br /&gt;
[global]&lt;br /&gt;
log-level-file=detail&lt;br /&gt;
repo1-host=debian3 &lt;br /&gt;
repo1-host-user=postgres&lt;br /&gt;
&lt;br /&gt;
Alter the archive_command parameter so that it uses the pgbackrest executable&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest \&lt;br /&gt;
                       --stanza=cybertec archive-push %p&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Create a Stanza on the repository server and confirm it is working&lt;br /&gt;
pgbackrest --stanza=cybertec stanza-create&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;br /&gt;
&lt;br /&gt;
Also check if the Stanza is correct on the database server.&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=114</id>
		<title>Patroni Cluster</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=114"/>
		<updated>2025-08-03T15:16:36Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Setup ==&lt;br /&gt;
&lt;br /&gt;
* Debian1: 192.168.59.101 (Debian 12)&lt;br /&gt;
* Debian2: 192.168.59.102 (Debian 12)&lt;br /&gt;
* Debian3: 192.168.59.103 (Debian 12)&lt;br /&gt;
&lt;br /&gt;
The servers were created using VirtualBox. &amp;lt;/br&amp;gt;&lt;br /&gt;
Network setup was 2 adapters (Bridged Adapter and NAT).&lt;br /&gt;
&lt;br /&gt;
== Installation ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo apt install postgresql patroni&lt;br /&gt;
sudo systemctl stop patroni postgresql&lt;br /&gt;
sudo systemctl disable postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
etcd was installed from prebuilt binaries &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ETCD_RELEASE=$(curl -s https://api.github.com/repos/etcd-io/etcd/releases/latest|grep tag_name | cut -d &#039;&amp;quot;&#039; -f 4)&lt;br /&gt;
curl -sL https://github.com/etcd-io/etcd/releases/download/${ETCD_RELEASE}/etcd-${ETCD_RELEASE}-linux-amd64.tar.gz \&lt;br /&gt;
 | sudo tar xz -C /usr/bin --strip=1 --wildcards --no-anchored etcdctl etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuration ==&lt;br /&gt;
=== etcd ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo mkdir -p /var/lib/etcd/&lt;br /&gt;
sudo mkdir -p /etc/etcd&lt;br /&gt;
sudo groupadd --system etcd&lt;br /&gt;
sudo useradd -s /sbin/nologin --system -g etcd etcd&lt;br /&gt;
sudo chown -R etcd:etcd /var/lib/etcd/&lt;br /&gt;
sudo chmod -R a+rw /var/lib/etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the configuration file at /etcd/etcd/etcd.yaml &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
name: &#039;debian1&#039; &lt;br /&gt;
data-dir: &#039;/var/lib/etcd/data.etcd&#039;&lt;br /&gt;
initial-advertise-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
listen-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
advertise-client-urls: http://192.168.59.101:2379 &lt;br /&gt;
listen-client-urls: http://192.168.59.101:2379,http://127.0.0.1:2379 &lt;br /&gt;
initial-cluster: &amp;quot;debian1=http://192.168.59.101:2380,debian2=http://192.168.59.102:2380,debian3=http://192.168.59.103:2380&amp;quot; &lt;br /&gt;
initial-cluster-state: &#039;new&#039; &lt;br /&gt;
initial-cluster-token: &#039;token-01&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Configure the systemd unit file &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=etcd key-value store&lt;br /&gt;
Documentation=https://github.com/etcd-io/etcd&lt;br /&gt;
After=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
Wants=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
User=etcd&lt;br /&gt;
Type=notify&lt;br /&gt;
Environment=ETCD_DATA_DIR=/var/lib/etcd&lt;br /&gt;
Environment=ETCD_NAME=%H&lt;br /&gt;
ExecStart=/usr/bin/etcd --config-file /etc/etcd/etcd.yml&lt;br /&gt;
Restart=always&lt;br /&gt;
RestartSec=10s&lt;br /&gt;
LimitNOFILE=40000&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl enable etcd.service&lt;br /&gt;
sudo systemctl start etcd.service&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test that everything is working &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export ETCDCTL_ENDPOINTS=&amp;quot;http://192.168.59.103:2379,http://192.168.59.101:2379,http://192.168.59.102:2379&amp;quot;&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
etcdctl endpoint health -w table&lt;br /&gt;
etcdctl endpoint status -w table &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
|          ENDPOINT          |        ID        | VERSION | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX | ERRORS |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
| http://192.168.59.102:2379 | 3cdde56ec82a0ca0 |  3.5.16 |   53 kB |      true |        13 |        145 |        |&lt;br /&gt;
| http://192.168.59.101:2379 | 406189a3bb67b8bc |  3.5.16 |   53 kB |     false |        13 |        145 |        |&lt;br /&gt;
| http://192.168.59.103:2379 | 939ed44c2e6d5e5b |  3.5.16 |   53 kB |     false |        13 |        145 |        |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Change the leader if necessary &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
etcdctl move-leader 939ed44c2e6d5e5b --endpoints=$ETCDCTL_ENDPOINTS&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Issues ===&lt;br /&gt;
&lt;br /&gt;
One of the members would not connect to the cluster.&lt;br /&gt;
The error message on the leader was “Prober detected unhealthy status …”&lt;br /&gt;
On the problem member the message was “Failed to publish local member to cluster through raft”&lt;br /&gt;
No network issues, solved by removing and adding the member&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
sudo systemctl stop etcd (On server being removed)&lt;br /&gt;
etcdctl member remove &amp;lt;ID&amp;gt;&lt;br /&gt;
etcdctl member add debian2 --peer-urls=http://192.168.59.102:2380&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Edit the etcd.yml files copying all information displayed, including the new order for ETCD_INITIAL_CLUSTER &amp;lt;/br&amp;gt;&lt;br /&gt;
On the server being added ..&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo systemctl start etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Patroni ===&lt;br /&gt;
sudo mkdir -p /etc/patroni/&lt;br /&gt;
sudo chown -R  postgres:postgres /etc/patroni/&lt;br /&gt;
&lt;br /&gt;
As the postgres user, create a configuration file.&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
vi /etc/patroni/patroni.yml&lt;br /&gt;
namespace: /db/&lt;br /&gt;
scope: cluster_1&lt;br /&gt;
name: $THIS_NAME&lt;br /&gt;
&lt;br /&gt;
log:&lt;br /&gt;
  format: &#039;%(asctime)s %(levelname)s: %(message)s&#039;&lt;br /&gt;
  level: INFO&lt;br /&gt;
  max_queue_size: 1000&lt;br /&gt;
  traceback_level: ERROR&lt;br /&gt;
  type: plain&lt;br /&gt;
&lt;br /&gt;
restapi:&lt;br /&gt;
  listen: 0.0.0.0:8008&lt;br /&gt;
  connect_address: $THIS_IP:8008&lt;br /&gt;
&lt;br /&gt;
etcd3:&lt;br /&gt;
  hosts: &lt;br /&gt;
  - 192.168.59.101:2379&lt;br /&gt;
  - 192.168.59.102:2379&lt;br /&gt;
  - 192.168.59.103:2379&lt;br /&gt;
&lt;br /&gt;
bootstrap:&lt;br /&gt;
  dcs:&lt;br /&gt;
    ttl: 30&lt;br /&gt;
    loop_wait: 10&lt;br /&gt;
    retry_timeout: 10&lt;br /&gt;
    maximum_lag_on_failover: 1048576&lt;br /&gt;
    postgresql:&lt;br /&gt;
      use_pg_rewind: true&lt;br /&gt;
      use_slots: true&lt;br /&gt;
      parameters:&lt;br /&gt;
        wal_level: replica&lt;br /&gt;
        hot_standby: &amp;quot;on&amp;quot;&lt;br /&gt;
        wal_keep_segments: 10&lt;br /&gt;
        max_wal_senders: 5&lt;br /&gt;
        max_replication_slots: 10&lt;br /&gt;
        wal_log_hints: &amp;quot;on&amp;quot;&lt;br /&gt;
        logging_collector: &#039;on&#039;&lt;br /&gt;
        max_wal_size: &#039;10GB&#039;&lt;br /&gt;
        archive_mode: &amp;quot;on&amp;quot;&lt;br /&gt;
        archive_timeout: 600s&lt;br /&gt;
        archive_command: &amp;quot;/bin/true&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  initdb: # Note: It needs to be a list (some options need values, others are switches)&lt;br /&gt;
  - encoding: UTF8&lt;br /&gt;
  - data-checksums&lt;br /&gt;
&lt;br /&gt;
  pg_hba: # Add following lines to pg_hba.conf after running &#039;initdb&#039;&lt;br /&gt;
  - host replication replicator 127.0.0.1/32 trust&lt;br /&gt;
  - host replication replicator 0.0.0.0/0 md5&lt;br /&gt;
  - host all all 0.0.0.0/0 md5&lt;br /&gt;
  - host all all ::0/0 md5&lt;br /&gt;
&lt;br /&gt;
  # Some additional users which needs to be created after initializing new cluster&lt;br /&gt;
  users:&lt;br /&gt;
    admin:&lt;br /&gt;
      password: qaz123&lt;br /&gt;
      options:&lt;br /&gt;
        - createrole&lt;br /&gt;
        - createdb&lt;br /&gt;
&lt;br /&gt;
postgresql:&lt;br /&gt;
  listen: 0.0.0.0:5432&lt;br /&gt;
  connect_address: $THIS_IP:5432&lt;br /&gt;
  data_dir: /var/lib/postgresql/17&lt;br /&gt;
  bin_dir: /usr/lib/postgresql/17/bin&lt;br /&gt;
  pgpass: /tmp/pgpass0&lt;br /&gt;
  authentication:&lt;br /&gt;
    replication:&lt;br /&gt;
      username: replicator&lt;br /&gt;
      password: replPasswd&lt;br /&gt;
    superuser:&lt;br /&gt;
      username: postgres&lt;br /&gt;
      password: qaz123&lt;br /&gt;
  parameters:&lt;br /&gt;
    unix_socket_directories: &amp;quot;/var/run/postgresql/&amp;quot;&lt;br /&gt;
&lt;br /&gt;
tags:&lt;br /&gt;
  nofailover: false&lt;br /&gt;
  noloadbalance: false&lt;br /&gt;
  clonefrom: false&lt;br /&gt;
  nosync: false&lt;br /&gt;
&lt;br /&gt;
Validate the configuration file&lt;br /&gt;
patroni --validate-config /etc/patroni/patroni.yml&lt;br /&gt;
Service&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/patroni.service&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Runners to orchestrate a high-availability PostgreSQL&lt;br /&gt;
After=network.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
&lt;br /&gt;
User=postgres&lt;br /&gt;
Group=postgres&lt;br /&gt;
&lt;br /&gt;
# Start the patroni process&lt;br /&gt;
ExecStart=/usr/bin/patroni /etc/patroni/patroni.yml&lt;br /&gt;
&lt;br /&gt;
# Send HUP to reload from patroni.yml&lt;br /&gt;
ExecReload=/bin/kill -s HUP $MAINPID&lt;br /&gt;
&lt;br /&gt;
# Only kill the patroni process, not it&#039;s children, so it will gracefully stop postgres&lt;br /&gt;
KillMode=process&lt;br /&gt;
&lt;br /&gt;
# Give a reasonable amount of time for the server to start up/shut down&lt;br /&gt;
TimeoutSec=30&lt;br /&gt;
&lt;br /&gt;
# Restart the service if it crashed&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
&lt;br /&gt;
Set permissions of the data directory to 700&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
chmod 700 /var/lib/postgresql/17&lt;br /&gt;
&lt;br /&gt;
Start the patroni service&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start patroni&lt;br /&gt;
&lt;br /&gt;
Check status&lt;br /&gt;
export PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml list&lt;br /&gt;
&lt;br /&gt;
+ Cluster: cluster_1 (7439347493790530098) ------+----+-----------+&lt;br /&gt;
| Member  | Host           | Role    | State     | TL | Lag in MB |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
| debian1 | 192.168.59.101 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian2 | 192.168.59.102 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian3 | 192.168.59.103 | Leader  | running   |  1 |           |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
&lt;br /&gt;
Change configuration&lt;br /&gt;
PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest --stanza=ian archive-push %p&amp;quot;&lt;br /&gt;
Issues&lt;br /&gt;
&lt;br /&gt;
Replication showed the following error&lt;br /&gt;
ERROR:  Requested starting point 0/4000000 is ahead of the WAL flush &lt;br /&gt;
        position of this server 0/3000060&lt;br /&gt;
&lt;br /&gt;
The replica instance needed to be re-initialised&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml reinit &amp;lt;scope&amp;gt; &amp;lt;name&amp;gt;&lt;br /&gt;
VIP Manager&lt;br /&gt;
&lt;br /&gt;
Copy the latest release to your server : &lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
sudo dpkg -i vip-manager_2.8.0_Linux_x86_64.deb&lt;br /&gt;
sudo systemctl stop vip-manager&lt;br /&gt;
sudo vi /etc/patroni/vip-manager.yml&lt;br /&gt;
&lt;br /&gt;
The trigger key below contains the namespace and scope of the Patroni service.&lt;br /&gt;
ip: 192.168.59.100&lt;br /&gt;
netmask: 24&lt;br /&gt;
interface: enp0s3&lt;br /&gt;
trigger-key: &amp;quot;/db/cluster_1/leader&amp;quot;&lt;br /&gt;
dcs-type: etcd&lt;br /&gt;
dcs-endpoints: http://192.168.59.101:2379&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/vip-manager.service&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Manages Virtual IP for Patroni&lt;br /&gt;
After=network-online.target&lt;br /&gt;
Before=patroni.service&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
ExecStart=/usr/bin/vip-manager --config=/etc/patroni/vip-manager.yml&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start vip-manager&lt;br /&gt;
sudo journalctl -u vip-manager.service -n 100 -f&lt;br /&gt;
PGBackRest&lt;br /&gt;
&lt;br /&gt;
Setup the repository on Debian3&lt;br /&gt;
&lt;br /&gt;
Install pgbackrest on all the servers …&lt;br /&gt;
sudo apt install pgbackrest&lt;br /&gt;
sudo mkdir -p -m 770 /var/log/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/log/pgbackrest&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest&lt;br /&gt;
sudo touch /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chmod 640 /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chown postgres:postgres /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
&lt;br /&gt;
On debian3, create the directory that will hold the backups …&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest/conf.d&lt;br /&gt;
sudo mkdir -p /var/lib/pgbackrest&lt;br /&gt;
sudo chmod 750 /var/lib/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/lib/pgbackrest&lt;br /&gt;
Setup SSH&lt;br /&gt;
&lt;br /&gt;
We will need a passwordless SSH connection between the repository and the database servers.&lt;br /&gt;
For this setup I was using postgres as the user that controls pgbackrest.&lt;br /&gt;
# debian1&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp id_ras.pub postgres@debian3&lt;br /&gt;
ssh postgres@debian3&lt;br /&gt;
# debian3&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp ./.ssh/id_ras.pub postgres@debian1&lt;br /&gt;
ssh postgres@debian1&lt;br /&gt;
Configuration&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the repository&lt;br /&gt;
vi /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-host=debian1&lt;br /&gt;
pg1-path=/var/lib/postgresql/17 &lt;br /&gt;
&lt;br /&gt;
[global]&lt;br /&gt;
repo1-cipher-pass=zWaf6XtpjIVZC5444yXB+cgFDFl7MxGlgkZSaoPv&lt;br /&gt;
repo1-cipher-type=aes-256-cbc&lt;br /&gt;
repo1-path=/var/lib/pgbackrest&lt;br /&gt;
repo1-retention-full=2&lt;br /&gt;
start-fast=y&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the database server&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-path=/var/lib/postgresql/17&lt;br /&gt;
 &lt;br /&gt;
[global]&lt;br /&gt;
log-level-file=detail&lt;br /&gt;
repo1-host=debian3 &lt;br /&gt;
repo1-host-user=postgres&lt;br /&gt;
&lt;br /&gt;
Alter the archive_command parameter so that it uses the pgbackrest executable&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest \&lt;br /&gt;
                       --stanza=cybertec archive-push %p&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Create a Stanza on the repository server and confirm it is working&lt;br /&gt;
pgbackrest --stanza=cybertec stanza-create&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;br /&gt;
&lt;br /&gt;
Also check if the Stanza is correct on the database server.&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=113</id>
		<title>Patroni Cluster</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=113"/>
		<updated>2025-08-03T15:14:05Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Setup ==&lt;br /&gt;
&lt;br /&gt;
* Debian1: 192.168.59.101 (Debian 12)&lt;br /&gt;
* Debian2: 192.168.59.102 (Debian 12)&lt;br /&gt;
* Debian3: 192.168.59.103 (Debian 12)&lt;br /&gt;
&lt;br /&gt;
The servers were created using VirtualBox. &amp;lt;/br&amp;gt;&lt;br /&gt;
Network setup was 2 adapters (Bridged Adapter and NAT).&lt;br /&gt;
&lt;br /&gt;
== Installation ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo apt install postgresql patroni&lt;br /&gt;
sudo systemctl stop patroni postgresql&lt;br /&gt;
sudo systemctl disable postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
etcd was installed from prebuilt binaries &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ETCD_RELEASE=$(curl -s https://api.github.com/repos/etcd-io/etcd/releases/latest|grep tag_name | cut -d &#039;&amp;quot;&#039; -f 4)&lt;br /&gt;
curl -sL https://github.com/etcd-io/etcd/releases/download/${ETCD_RELEASE}/etcd-${ETCD_RELEASE}-linux-amd64.tar.gz \&lt;br /&gt;
 | sudo tar xz -C /usr/bin --strip=1 --wildcards --no-anchored etcdctl etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuration ==&lt;br /&gt;
=== etcd ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo mkdir -p /var/lib/etcd/&lt;br /&gt;
sudo mkdir -p /etc/etcd&lt;br /&gt;
sudo groupadd --system etcd&lt;br /&gt;
sudo useradd -s /sbin/nologin --system -g etcd etcd&lt;br /&gt;
sudo chown -R etcd:etcd /var/lib/etcd/&lt;br /&gt;
sudo chmod -R a+rw /var/lib/etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the configuration file at /etcd/etcd/etcd.yaml &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
name: &#039;debian1&#039; &lt;br /&gt;
data-dir: &#039;/var/lib/etcd/data.etcd&#039;&lt;br /&gt;
initial-advertise-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
listen-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
advertise-client-urls: http://192.168.59.101:2379 &lt;br /&gt;
listen-client-urls: http://192.168.59.101:2379,http://127.0.0.1:2379 &lt;br /&gt;
initial-cluster: &amp;quot;debian1=http://192.168.59.101:2380,debian2=http://192.168.59.102:2380,debian3=http://192.168.59.103:2380&amp;quot; &lt;br /&gt;
initial-cluster-state: &#039;new&#039; &lt;br /&gt;
initial-cluster-token: &#039;token-01&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Configure the systemd unit file &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=etcd key-value store&lt;br /&gt;
Documentation=https://github.com/etcd-io/etcd&lt;br /&gt;
After=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
Wants=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
User=etcd&lt;br /&gt;
Type=notify&lt;br /&gt;
Environment=ETCD_DATA_DIR=/var/lib/etcd&lt;br /&gt;
Environment=ETCD_NAME=%H&lt;br /&gt;
ExecStart=/usr/bin/etcd --config-file /etc/etcd/etcd.yml&lt;br /&gt;
Restart=always&lt;br /&gt;
RestartSec=10s&lt;br /&gt;
LimitNOFILE=40000&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl enable etcd.service&lt;br /&gt;
sudo systemctl start etcd.service&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test that everything is working &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export ETCDCTL_ENDPOINTS=&amp;quot;http://192.168.59.103:2379,http://192.168.59.101:2379,http://192.168.59.102:2379&amp;quot;&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
etcdctl endpoint health -w table&lt;br /&gt;
etcdctl endpoint status -w table &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
|          ENDPOINT          |        ID        | VERSION | DB SIZE | IS LEADER | RAFT TERM | RAFT INDEX | ERRORS |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
| http://192.168.59.102:2379 | 3cdde56ec82a0ca0 |  3.5.16 |   53 kB |      true |        13 |        145 |        |&lt;br /&gt;
| http://192.168.59.101:2379 | 406189a3bb67b8bc |  3.5.16 |   53 kB |     false |        13 |        145 |        |&lt;br /&gt;
| http://192.168.59.103:2379 | 939ed44c2e6d5e5b |  3.5.16 |   53 kB |     false |        13 |        145 |        |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+-----------+------------+--------+&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Change the leader if necessary &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
etcdctl move-leader 939ed44c2e6d5e5b --endpoints=$ENDPOINTS&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Issues ===&lt;br /&gt;
&lt;br /&gt;
One of the members would not connect to the cluster.&lt;br /&gt;
The error message on the leader was “Prober detected unhealthy status …”&lt;br /&gt;
On the problem member the message was “Failed to publish local member to cluster through raft”&lt;br /&gt;
&lt;br /&gt;
No network issues, solved by removing and adding the member&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
sudo systemctl stop etcd (On server being removed)&lt;br /&gt;
etcdctl member remove &amp;lt;ID&amp;gt;&lt;br /&gt;
&lt;br /&gt;
etcdctl member add debian2 --peer-urls=http://192.168.59.102:2380&lt;br /&gt;
# Edit the etcd.yml files copying all information displayed, &lt;br /&gt;
# including the new order for ETCD_INITIAL_CLUSTER&lt;br /&gt;
sudo systemctl start etcd (On server being added)&lt;br /&gt;
Patroni&lt;br /&gt;
sudo mkdir -p /etc/patroni/&lt;br /&gt;
sudo chown -R  postgres:postgres /etc/patroni/&lt;br /&gt;
&lt;br /&gt;
As the postgres user, create a configuration file.&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
vi /etc/patroni/patroni.yml&lt;br /&gt;
namespace: /db/&lt;br /&gt;
scope: cluster_1&lt;br /&gt;
name: $THIS_NAME&lt;br /&gt;
&lt;br /&gt;
log:&lt;br /&gt;
  format: &#039;%(asctime)s %(levelname)s: %(message)s&#039;&lt;br /&gt;
  level: INFO&lt;br /&gt;
  max_queue_size: 1000&lt;br /&gt;
  traceback_level: ERROR&lt;br /&gt;
  type: plain&lt;br /&gt;
&lt;br /&gt;
restapi:&lt;br /&gt;
  listen: 0.0.0.0:8008&lt;br /&gt;
  connect_address: $THIS_IP:8008&lt;br /&gt;
&lt;br /&gt;
etcd3:&lt;br /&gt;
  hosts: &lt;br /&gt;
  - 192.168.59.101:2379&lt;br /&gt;
  - 192.168.59.102:2379&lt;br /&gt;
  - 192.168.59.103:2379&lt;br /&gt;
&lt;br /&gt;
bootstrap:&lt;br /&gt;
  dcs:&lt;br /&gt;
    ttl: 30&lt;br /&gt;
    loop_wait: 10&lt;br /&gt;
    retry_timeout: 10&lt;br /&gt;
    maximum_lag_on_failover: 1048576&lt;br /&gt;
    postgresql:&lt;br /&gt;
      use_pg_rewind: true&lt;br /&gt;
      use_slots: true&lt;br /&gt;
      parameters:&lt;br /&gt;
        wal_level: replica&lt;br /&gt;
        hot_standby: &amp;quot;on&amp;quot;&lt;br /&gt;
        wal_keep_segments: 10&lt;br /&gt;
        max_wal_senders: 5&lt;br /&gt;
        max_replication_slots: 10&lt;br /&gt;
        wal_log_hints: &amp;quot;on&amp;quot;&lt;br /&gt;
        logging_collector: &#039;on&#039;&lt;br /&gt;
        max_wal_size: &#039;10GB&#039;&lt;br /&gt;
        archive_mode: &amp;quot;on&amp;quot;&lt;br /&gt;
        archive_timeout: 600s&lt;br /&gt;
        archive_command: &amp;quot;/bin/true&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  initdb: # Note: It needs to be a list (some options need values, others are switches)&lt;br /&gt;
  - encoding: UTF8&lt;br /&gt;
  - data-checksums&lt;br /&gt;
&lt;br /&gt;
  pg_hba: # Add following lines to pg_hba.conf after running &#039;initdb&#039;&lt;br /&gt;
  - host replication replicator 127.0.0.1/32 trust&lt;br /&gt;
  - host replication replicator 0.0.0.0/0 md5&lt;br /&gt;
  - host all all 0.0.0.0/0 md5&lt;br /&gt;
  - host all all ::0/0 md5&lt;br /&gt;
&lt;br /&gt;
  # Some additional users which needs to be created after initializing new cluster&lt;br /&gt;
  users:&lt;br /&gt;
    admin:&lt;br /&gt;
      password: qaz123&lt;br /&gt;
      options:&lt;br /&gt;
        - createrole&lt;br /&gt;
        - createdb&lt;br /&gt;
&lt;br /&gt;
postgresql:&lt;br /&gt;
  listen: 0.0.0.0:5432&lt;br /&gt;
  connect_address: $THIS_IP:5432&lt;br /&gt;
  data_dir: /var/lib/postgresql/17&lt;br /&gt;
  bin_dir: /usr/lib/postgresql/17/bin&lt;br /&gt;
  pgpass: /tmp/pgpass0&lt;br /&gt;
  authentication:&lt;br /&gt;
    replication:&lt;br /&gt;
      username: replicator&lt;br /&gt;
      password: replPasswd&lt;br /&gt;
    superuser:&lt;br /&gt;
      username: postgres&lt;br /&gt;
      password: qaz123&lt;br /&gt;
  parameters:&lt;br /&gt;
    unix_socket_directories: &amp;quot;/var/run/postgresql/&amp;quot;&lt;br /&gt;
&lt;br /&gt;
tags:&lt;br /&gt;
  nofailover: false&lt;br /&gt;
  noloadbalance: false&lt;br /&gt;
  clonefrom: false&lt;br /&gt;
  nosync: false&lt;br /&gt;
&lt;br /&gt;
Validate the configuration file&lt;br /&gt;
patroni --validate-config /etc/patroni/patroni.yml&lt;br /&gt;
Service&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/patroni.service&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Runners to orchestrate a high-availability PostgreSQL&lt;br /&gt;
After=network.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
&lt;br /&gt;
User=postgres&lt;br /&gt;
Group=postgres&lt;br /&gt;
&lt;br /&gt;
# Start the patroni process&lt;br /&gt;
ExecStart=/usr/bin/patroni /etc/patroni/patroni.yml&lt;br /&gt;
&lt;br /&gt;
# Send HUP to reload from patroni.yml&lt;br /&gt;
ExecReload=/bin/kill -s HUP $MAINPID&lt;br /&gt;
&lt;br /&gt;
# Only kill the patroni process, not it&#039;s children, so it will gracefully stop postgres&lt;br /&gt;
KillMode=process&lt;br /&gt;
&lt;br /&gt;
# Give a reasonable amount of time for the server to start up/shut down&lt;br /&gt;
TimeoutSec=30&lt;br /&gt;
&lt;br /&gt;
# Restart the service if it crashed&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
&lt;br /&gt;
Set permissions of the data directory to 700&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
chmod 700 /var/lib/postgresql/17&lt;br /&gt;
&lt;br /&gt;
Start the patroni service&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start patroni&lt;br /&gt;
&lt;br /&gt;
Check status&lt;br /&gt;
export PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml list&lt;br /&gt;
&lt;br /&gt;
+ Cluster: cluster_1 (7439347493790530098) ------+----+-----------+&lt;br /&gt;
| Member  | Host           | Role    | State     | TL | Lag in MB |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
| debian1 | 192.168.59.101 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian2 | 192.168.59.102 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian3 | 192.168.59.103 | Leader  | running   |  1 |           |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
&lt;br /&gt;
Change configuration&lt;br /&gt;
PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest --stanza=ian archive-push %p&amp;quot;&lt;br /&gt;
Issues&lt;br /&gt;
&lt;br /&gt;
Replication showed the following error&lt;br /&gt;
ERROR:  Requested starting point 0/4000000 is ahead of the WAL flush &lt;br /&gt;
        position of this server 0/3000060&lt;br /&gt;
&lt;br /&gt;
The replica instance needed to be re-initialised&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml reinit &amp;lt;scope&amp;gt; &amp;lt;name&amp;gt;&lt;br /&gt;
VIP Manager&lt;br /&gt;
&lt;br /&gt;
Copy the latest release to your server : &lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
sudo dpkg -i vip-manager_2.8.0_Linux_x86_64.deb&lt;br /&gt;
sudo systemctl stop vip-manager&lt;br /&gt;
sudo vi /etc/patroni/vip-manager.yml&lt;br /&gt;
&lt;br /&gt;
The trigger key below contains the namespace and scope of the Patroni service.&lt;br /&gt;
ip: 192.168.59.100&lt;br /&gt;
netmask: 24&lt;br /&gt;
interface: enp0s3&lt;br /&gt;
trigger-key: &amp;quot;/db/cluster_1/leader&amp;quot;&lt;br /&gt;
dcs-type: etcd&lt;br /&gt;
dcs-endpoints: http://192.168.59.101:2379&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/vip-manager.service&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Manages Virtual IP for Patroni&lt;br /&gt;
After=network-online.target&lt;br /&gt;
Before=patroni.service&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
ExecStart=/usr/bin/vip-manager --config=/etc/patroni/vip-manager.yml&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start vip-manager&lt;br /&gt;
sudo journalctl -u vip-manager.service -n 100 -f&lt;br /&gt;
PGBackRest&lt;br /&gt;
&lt;br /&gt;
Setup the repository on Debian3&lt;br /&gt;
&lt;br /&gt;
Install pgbackrest on all the servers …&lt;br /&gt;
sudo apt install pgbackrest&lt;br /&gt;
sudo mkdir -p -m 770 /var/log/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/log/pgbackrest&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest&lt;br /&gt;
sudo touch /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chmod 640 /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chown postgres:postgres /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
&lt;br /&gt;
On debian3, create the directory that will hold the backups …&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest/conf.d&lt;br /&gt;
sudo mkdir -p /var/lib/pgbackrest&lt;br /&gt;
sudo chmod 750 /var/lib/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/lib/pgbackrest&lt;br /&gt;
Setup SSH&lt;br /&gt;
&lt;br /&gt;
We will need a passwordless SSH connection between the repository and the database servers.&lt;br /&gt;
For this setup I was using postgres as the user that controls pgbackrest.&lt;br /&gt;
# debian1&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp id_ras.pub postgres@debian3&lt;br /&gt;
ssh postgres@debian3&lt;br /&gt;
# debian3&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp ./.ssh/id_ras.pub postgres@debian1&lt;br /&gt;
ssh postgres@debian1&lt;br /&gt;
Configuration&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the repository&lt;br /&gt;
vi /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-host=debian1&lt;br /&gt;
pg1-path=/var/lib/postgresql/17 &lt;br /&gt;
&lt;br /&gt;
[global]&lt;br /&gt;
repo1-cipher-pass=zWaf6XtpjIVZC5444yXB+cgFDFl7MxGlgkZSaoPv&lt;br /&gt;
repo1-cipher-type=aes-256-cbc&lt;br /&gt;
repo1-path=/var/lib/pgbackrest&lt;br /&gt;
repo1-retention-full=2&lt;br /&gt;
start-fast=y&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the database server&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-path=/var/lib/postgresql/17&lt;br /&gt;
 &lt;br /&gt;
[global]&lt;br /&gt;
log-level-file=detail&lt;br /&gt;
repo1-host=debian3 &lt;br /&gt;
repo1-host-user=postgres&lt;br /&gt;
&lt;br /&gt;
Alter the archive_command parameter so that it uses the pgbackrest executable&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest \&lt;br /&gt;
                       --stanza=cybertec archive-push %p&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Create a Stanza on the repository server and confirm it is working&lt;br /&gt;
pgbackrest --stanza=cybertec stanza-create&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;br /&gt;
&lt;br /&gt;
Also check if the Stanza is correct on the database server.&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=112</id>
		<title>Patroni Cluster</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=112"/>
		<updated>2025-08-03T15:12:53Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Setup ==&lt;br /&gt;
&lt;br /&gt;
* Debian1: 192.168.59.101 (Debian 12)&lt;br /&gt;
* Debian2: 192.168.59.102 (Debian 12)&lt;br /&gt;
* Debian3: 192.168.59.103 (Debian 12)&lt;br /&gt;
&lt;br /&gt;
The servers were created using VirtualBox. &amp;lt;/br&amp;gt;&lt;br /&gt;
Network setup was 2 adapters (Bridged Adapter and NAT).&lt;br /&gt;
&lt;br /&gt;
== Installation ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo apt install postgresql patroni&lt;br /&gt;
sudo systemctl stop patroni postgresql&lt;br /&gt;
sudo systemctl disable postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
etcd was installed from prebuilt binaries &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ETCD_RELEASE=$(curl -s https://api.github.com/repos/etcd-io/etcd/releases/latest|grep tag_name | cut -d &#039;&amp;quot;&#039; -f 4)&lt;br /&gt;
curl -sL https://github.com/etcd-io/etcd/releases/download/${ETCD_RELEASE}/etcd-${ETCD_RELEASE}-linux-amd64.tar.gz \&lt;br /&gt;
 | sudo tar xz -C /usr/bin --strip=1 --wildcards --no-anchored etcdctl etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuration ==&lt;br /&gt;
=== etcd ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo mkdir -p /var/lib/etcd/&lt;br /&gt;
sudo mkdir -p /etc/etcd&lt;br /&gt;
sudo groupadd --system etcd&lt;br /&gt;
sudo useradd -s /sbin/nologin --system -g etcd etcd&lt;br /&gt;
sudo chown -R etcd:etcd /var/lib/etcd/&lt;br /&gt;
sudo chmod -R a+rw /var/lib/etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the configuration file at /etcd/etcd/etcd.yaml &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
name: &#039;debian1&#039; &lt;br /&gt;
data-dir: &#039;/var/lib/etcd/data.etcd&#039;&lt;br /&gt;
initial-advertise-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
listen-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
advertise-client-urls: http://192.168.59.101:2379 &lt;br /&gt;
listen-client-urls: http://192.168.59.101:2379,http://127.0.0.1:2379 &lt;br /&gt;
initial-cluster: &amp;quot;debian1=http://192.168.59.101:2380,debian2=http://192.168.59.102:2380,debian3=http://192.168.59.103:2380&amp;quot; &lt;br /&gt;
initial-cluster-state: &#039;new&#039; &lt;br /&gt;
initial-cluster-token: &#039;token-01&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Configure the systemd unit file &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=etcd key-value store&lt;br /&gt;
Documentation=https://github.com/etcd-io/etcd&lt;br /&gt;
After=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
Wants=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
User=etcd&lt;br /&gt;
Type=notify&lt;br /&gt;
Environment=ETCD_DATA_DIR=/var/lib/etcd&lt;br /&gt;
Environment=ETCD_NAME=%H&lt;br /&gt;
ExecStart=/usr/bin/etcd --config-file /etc/etcd/etcd.yml&lt;br /&gt;
Restart=always&lt;br /&gt;
RestartSec=10s&lt;br /&gt;
LimitNOFILE=40000&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl enable etcd.service&lt;br /&gt;
sudo systemctl start etcd.service&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test that everything is working &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export ETCDCTL_ENDPOINTS=&amp;quot;http://192.168.59.103:2379,http://192.168.59.101:2379,http://192.168.59.102:2379&amp;quot;&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
etcdctl endpoint health -w table&lt;br /&gt;
etcdctl endpoint status -w table &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------+&lt;br /&gt;
|          ENDPOINT          |        ID        | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | ERRORS |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------+&lt;br /&gt;
| http://192.168.59.102:2379 | 3cdde56ec82a0ca0 |  3.5.16 |   53 kB |      true |      false |        13 |        145 |        |&lt;br /&gt;
| http://192.168.59.101:2379 | 406189a3bb67b8bc |  3.5.16 |   53 kB |     false |      false |        13 |        145 |        |&lt;br /&gt;
| http://192.168.59.103:2379 | 939ed44c2e6d5e5b |  3.5.16 |   53 kB |     false |      false |        13 |        145 |        |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------+&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Change the leader if necessary &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
etcdctl move-leader 939ed44c2e6d5e5b --endpoints=$ENDPOINTS&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Issues ===&lt;br /&gt;
&lt;br /&gt;
One of the members would not connect to the cluster.&lt;br /&gt;
The error message on the leader was “Prober detected unhealthy status …”&lt;br /&gt;
On the problem member the message was “Failed to publish local member to cluster through raft”&lt;br /&gt;
&lt;br /&gt;
No network issues, solved by removing and adding the member&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
sudo systemctl stop etcd (On server being removed)&lt;br /&gt;
etcdctl member remove &amp;lt;ID&amp;gt;&lt;br /&gt;
&lt;br /&gt;
etcdctl member add debian2 --peer-urls=http://192.168.59.102:2380&lt;br /&gt;
# Edit the etcd.yml files copying all information displayed, &lt;br /&gt;
# including the new order for ETCD_INITIAL_CLUSTER&lt;br /&gt;
sudo systemctl start etcd (On server being added)&lt;br /&gt;
Patroni&lt;br /&gt;
sudo mkdir -p /etc/patroni/&lt;br /&gt;
sudo chown -R  postgres:postgres /etc/patroni/&lt;br /&gt;
&lt;br /&gt;
As the postgres user, create a configuration file.&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
vi /etc/patroni/patroni.yml&lt;br /&gt;
namespace: /db/&lt;br /&gt;
scope: cluster_1&lt;br /&gt;
name: $THIS_NAME&lt;br /&gt;
&lt;br /&gt;
log:&lt;br /&gt;
  format: &#039;%(asctime)s %(levelname)s: %(message)s&#039;&lt;br /&gt;
  level: INFO&lt;br /&gt;
  max_queue_size: 1000&lt;br /&gt;
  traceback_level: ERROR&lt;br /&gt;
  type: plain&lt;br /&gt;
&lt;br /&gt;
restapi:&lt;br /&gt;
  listen: 0.0.0.0:8008&lt;br /&gt;
  connect_address: $THIS_IP:8008&lt;br /&gt;
&lt;br /&gt;
etcd3:&lt;br /&gt;
  hosts: &lt;br /&gt;
  - 192.168.59.101:2379&lt;br /&gt;
  - 192.168.59.102:2379&lt;br /&gt;
  - 192.168.59.103:2379&lt;br /&gt;
&lt;br /&gt;
bootstrap:&lt;br /&gt;
  dcs:&lt;br /&gt;
    ttl: 30&lt;br /&gt;
    loop_wait: 10&lt;br /&gt;
    retry_timeout: 10&lt;br /&gt;
    maximum_lag_on_failover: 1048576&lt;br /&gt;
    postgresql:&lt;br /&gt;
      use_pg_rewind: true&lt;br /&gt;
      use_slots: true&lt;br /&gt;
      parameters:&lt;br /&gt;
        wal_level: replica&lt;br /&gt;
        hot_standby: &amp;quot;on&amp;quot;&lt;br /&gt;
        wal_keep_segments: 10&lt;br /&gt;
        max_wal_senders: 5&lt;br /&gt;
        max_replication_slots: 10&lt;br /&gt;
        wal_log_hints: &amp;quot;on&amp;quot;&lt;br /&gt;
        logging_collector: &#039;on&#039;&lt;br /&gt;
        max_wal_size: &#039;10GB&#039;&lt;br /&gt;
        archive_mode: &amp;quot;on&amp;quot;&lt;br /&gt;
        archive_timeout: 600s&lt;br /&gt;
        archive_command: &amp;quot;/bin/true&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  initdb: # Note: It needs to be a list (some options need values, others are switches)&lt;br /&gt;
  - encoding: UTF8&lt;br /&gt;
  - data-checksums&lt;br /&gt;
&lt;br /&gt;
  pg_hba: # Add following lines to pg_hba.conf after running &#039;initdb&#039;&lt;br /&gt;
  - host replication replicator 127.0.0.1/32 trust&lt;br /&gt;
  - host replication replicator 0.0.0.0/0 md5&lt;br /&gt;
  - host all all 0.0.0.0/0 md5&lt;br /&gt;
  - host all all ::0/0 md5&lt;br /&gt;
&lt;br /&gt;
  # Some additional users which needs to be created after initializing new cluster&lt;br /&gt;
  users:&lt;br /&gt;
    admin:&lt;br /&gt;
      password: qaz123&lt;br /&gt;
      options:&lt;br /&gt;
        - createrole&lt;br /&gt;
        - createdb&lt;br /&gt;
&lt;br /&gt;
postgresql:&lt;br /&gt;
  listen: 0.0.0.0:5432&lt;br /&gt;
  connect_address: $THIS_IP:5432&lt;br /&gt;
  data_dir: /var/lib/postgresql/17&lt;br /&gt;
  bin_dir: /usr/lib/postgresql/17/bin&lt;br /&gt;
  pgpass: /tmp/pgpass0&lt;br /&gt;
  authentication:&lt;br /&gt;
    replication:&lt;br /&gt;
      username: replicator&lt;br /&gt;
      password: replPasswd&lt;br /&gt;
    superuser:&lt;br /&gt;
      username: postgres&lt;br /&gt;
      password: qaz123&lt;br /&gt;
  parameters:&lt;br /&gt;
    unix_socket_directories: &amp;quot;/var/run/postgresql/&amp;quot;&lt;br /&gt;
&lt;br /&gt;
tags:&lt;br /&gt;
  nofailover: false&lt;br /&gt;
  noloadbalance: false&lt;br /&gt;
  clonefrom: false&lt;br /&gt;
  nosync: false&lt;br /&gt;
&lt;br /&gt;
Validate the configuration file&lt;br /&gt;
patroni --validate-config /etc/patroni/patroni.yml&lt;br /&gt;
Service&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/patroni.service&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Runners to orchestrate a high-availability PostgreSQL&lt;br /&gt;
After=network.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
&lt;br /&gt;
User=postgres&lt;br /&gt;
Group=postgres&lt;br /&gt;
&lt;br /&gt;
# Start the patroni process&lt;br /&gt;
ExecStart=/usr/bin/patroni /etc/patroni/patroni.yml&lt;br /&gt;
&lt;br /&gt;
# Send HUP to reload from patroni.yml&lt;br /&gt;
ExecReload=/bin/kill -s HUP $MAINPID&lt;br /&gt;
&lt;br /&gt;
# Only kill the patroni process, not it&#039;s children, so it will gracefully stop postgres&lt;br /&gt;
KillMode=process&lt;br /&gt;
&lt;br /&gt;
# Give a reasonable amount of time for the server to start up/shut down&lt;br /&gt;
TimeoutSec=30&lt;br /&gt;
&lt;br /&gt;
# Restart the service if it crashed&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
&lt;br /&gt;
Set permissions of the data directory to 700&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
chmod 700 /var/lib/postgresql/17&lt;br /&gt;
&lt;br /&gt;
Start the patroni service&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start patroni&lt;br /&gt;
&lt;br /&gt;
Check status&lt;br /&gt;
export PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml list&lt;br /&gt;
&lt;br /&gt;
+ Cluster: cluster_1 (7439347493790530098) ------+----+-----------+&lt;br /&gt;
| Member  | Host           | Role    | State     | TL | Lag in MB |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
| debian1 | 192.168.59.101 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian2 | 192.168.59.102 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian3 | 192.168.59.103 | Leader  | running   |  1 |           |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
&lt;br /&gt;
Change configuration&lt;br /&gt;
PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest --stanza=ian archive-push %p&amp;quot;&lt;br /&gt;
Issues&lt;br /&gt;
&lt;br /&gt;
Replication showed the following error&lt;br /&gt;
ERROR:  Requested starting point 0/4000000 is ahead of the WAL flush &lt;br /&gt;
        position of this server 0/3000060&lt;br /&gt;
&lt;br /&gt;
The replica instance needed to be re-initialised&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml reinit &amp;lt;scope&amp;gt; &amp;lt;name&amp;gt;&lt;br /&gt;
VIP Manager&lt;br /&gt;
&lt;br /&gt;
Copy the latest release to your server : &lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
sudo dpkg -i vip-manager_2.8.0_Linux_x86_64.deb&lt;br /&gt;
sudo systemctl stop vip-manager&lt;br /&gt;
sudo vi /etc/patroni/vip-manager.yml&lt;br /&gt;
&lt;br /&gt;
The trigger key below contains the namespace and scope of the Patroni service.&lt;br /&gt;
ip: 192.168.59.100&lt;br /&gt;
netmask: 24&lt;br /&gt;
interface: enp0s3&lt;br /&gt;
trigger-key: &amp;quot;/db/cluster_1/leader&amp;quot;&lt;br /&gt;
dcs-type: etcd&lt;br /&gt;
dcs-endpoints: http://192.168.59.101:2379&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/vip-manager.service&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Manages Virtual IP for Patroni&lt;br /&gt;
After=network-online.target&lt;br /&gt;
Before=patroni.service&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
ExecStart=/usr/bin/vip-manager --config=/etc/patroni/vip-manager.yml&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start vip-manager&lt;br /&gt;
sudo journalctl -u vip-manager.service -n 100 -f&lt;br /&gt;
PGBackRest&lt;br /&gt;
&lt;br /&gt;
Setup the repository on Debian3&lt;br /&gt;
&lt;br /&gt;
Install pgbackrest on all the servers …&lt;br /&gt;
sudo apt install pgbackrest&lt;br /&gt;
sudo mkdir -p -m 770 /var/log/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/log/pgbackrest&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest&lt;br /&gt;
sudo touch /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chmod 640 /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chown postgres:postgres /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
&lt;br /&gt;
On debian3, create the directory that will hold the backups …&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest/conf.d&lt;br /&gt;
sudo mkdir -p /var/lib/pgbackrest&lt;br /&gt;
sudo chmod 750 /var/lib/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/lib/pgbackrest&lt;br /&gt;
Setup SSH&lt;br /&gt;
&lt;br /&gt;
We will need a passwordless SSH connection between the repository and the database servers.&lt;br /&gt;
For this setup I was using postgres as the user that controls pgbackrest.&lt;br /&gt;
# debian1&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp id_ras.pub postgres@debian3&lt;br /&gt;
ssh postgres@debian3&lt;br /&gt;
# debian3&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp ./.ssh/id_ras.pub postgres@debian1&lt;br /&gt;
ssh postgres@debian1&lt;br /&gt;
Configuration&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the repository&lt;br /&gt;
vi /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-host=debian1&lt;br /&gt;
pg1-path=/var/lib/postgresql/17 &lt;br /&gt;
&lt;br /&gt;
[global]&lt;br /&gt;
repo1-cipher-pass=zWaf6XtpjIVZC5444yXB+cgFDFl7MxGlgkZSaoPv&lt;br /&gt;
repo1-cipher-type=aes-256-cbc&lt;br /&gt;
repo1-path=/var/lib/pgbackrest&lt;br /&gt;
repo1-retention-full=2&lt;br /&gt;
start-fast=y&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the database server&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-path=/var/lib/postgresql/17&lt;br /&gt;
 &lt;br /&gt;
[global]&lt;br /&gt;
log-level-file=detail&lt;br /&gt;
repo1-host=debian3 &lt;br /&gt;
repo1-host-user=postgres&lt;br /&gt;
&lt;br /&gt;
Alter the archive_command parameter so that it uses the pgbackrest executable&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest \&lt;br /&gt;
                       --stanza=cybertec archive-push %p&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Create a Stanza on the repository server and confirm it is working&lt;br /&gt;
pgbackrest --stanza=cybertec stanza-create&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;br /&gt;
&lt;br /&gt;
Also check if the Stanza is correct on the database server.&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=111</id>
		<title>Patroni Cluster</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=111"/>
		<updated>2025-08-03T15:11:06Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Setup ==&lt;br /&gt;
&lt;br /&gt;
* Debian1: 192.168.59.101 (Debian 12)&lt;br /&gt;
* Debian2: 192.168.59.102 (Debian 12)&lt;br /&gt;
* Debian3: 192.168.59.103 (Debian 12)&lt;br /&gt;
&lt;br /&gt;
The servers were created using VirtualBox. &amp;lt;/br&amp;gt;&lt;br /&gt;
Network setup was 2 adapters (Bridged Adapter and NAT).&lt;br /&gt;
&lt;br /&gt;
== Installation ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo apt install postgresql patroni&lt;br /&gt;
sudo systemctl stop patroni postgresql&lt;br /&gt;
sudo systemctl disable postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
etcd was installed from prebuilt binaries &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ETCD_RELEASE=$(curl -s https://api.github.com/repos/etcd-io/etcd/releases/latest|grep tag_name | cut -d &#039;&amp;quot;&#039; -f 4)&lt;br /&gt;
curl -sL https://github.com/etcd-io/etcd/releases/download/${ETCD_RELEASE}/etcd-${ETCD_RELEASE}-linux-amd64.tar.gz \&lt;br /&gt;
 | sudo tar xz -C /usr/bin --strip=1 --wildcards --no-anchored etcdctl etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuration ==&lt;br /&gt;
=== etcd ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo mkdir -p /var/lib/etcd/&lt;br /&gt;
sudo mkdir -p /etc/etcd&lt;br /&gt;
sudo groupadd --system etcd&lt;br /&gt;
sudo useradd -s /sbin/nologin --system -g etcd etcd&lt;br /&gt;
sudo chown -R etcd:etcd /var/lib/etcd/&lt;br /&gt;
sudo chmod -R a+rw /var/lib/etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the configuration file at /etcd/etcd/etcd.yaml &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
name: &#039;debian1&#039; &lt;br /&gt;
data-dir: &#039;/var/lib/etcd/data.etcd&#039;&lt;br /&gt;
initial-advertise-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
listen-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
advertise-client-urls: http://192.168.59.101:2379 &lt;br /&gt;
listen-client-urls: http://192.168.59.101:2379,http://127.0.0.1:2379 &lt;br /&gt;
initial-cluster: &amp;quot;debian1=http://192.168.59.101:2380,debian2=http://192.168.59.102:2380,debian3=http://192.168.59.103:2380&amp;quot; &lt;br /&gt;
initial-cluster-state: &#039;new&#039; &lt;br /&gt;
initial-cluster-token: &#039;token-01&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Configure the systemd unit file &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=etcd key-value store&lt;br /&gt;
Documentation=https://github.com/etcd-io/etcd&lt;br /&gt;
After=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
Wants=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
User=etcd&lt;br /&gt;
Type=notify&lt;br /&gt;
Environment=ETCD_DATA_DIR=/var/lib/etcd&lt;br /&gt;
Environment=ETCD_NAME=%H&lt;br /&gt;
ExecStart=/usr/bin/etcd --config-file /etc/etcd/etcd.yml&lt;br /&gt;
Restart=always&lt;br /&gt;
RestartSec=10s&lt;br /&gt;
LimitNOFILE=40000&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl enable etcd.service&lt;br /&gt;
sudo systemctl start etcd.service&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test that everything is working &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export ETCDCTL_ENDPOINTS=&amp;quot;http://192.168.59.103:2379,http://192.168.59.101:2379,http://192.168.59.102:2379&amp;quot;&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
etcdctl endpoint health -w table&lt;br /&gt;
etcdctl endpoint status -w table &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+&lt;br /&gt;
|          ENDPOINT          |        ID        | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+&lt;br /&gt;
| http://192.168.59.102:2379 | 3cdde56ec82a0ca0 |  3.5.16 |   53 kB |      true |      false |        13 |        145 |                145 |        |&lt;br /&gt;
| http://192.168.59.101:2379 | 406189a3bb67b8bc |  3.5.16 |   53 kB |     false |      false |        13 |        145 |                145 |        |&lt;br /&gt;
| http://192.168.59.103:2379 | 939ed44c2e6d5e5b |  3.5.16 |   53 kB |     false |      false |        13 |        145 |                145 |        |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Change the leader if necessary &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
etcdctl move-leader 939ed44c2e6d5e5b --endpoints=$ENDPOINTS&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Issues ===&lt;br /&gt;
&lt;br /&gt;
One of the members would not connect to the cluster.&lt;br /&gt;
The error message on the leader was “Prober detected unhealthy status …”&lt;br /&gt;
On the problem member the message was “Failed to publish local member to cluster through raft”&lt;br /&gt;
&lt;br /&gt;
No network issues, solved by removing and adding the member&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
sudo systemctl stop etcd (On server being removed)&lt;br /&gt;
etcdctl member remove &amp;lt;ID&amp;gt;&lt;br /&gt;
&lt;br /&gt;
etcdctl member add debian2 --peer-urls=http://192.168.59.102:2380&lt;br /&gt;
# Edit the etcd.yml files copying all information displayed, &lt;br /&gt;
# including the new order for ETCD_INITIAL_CLUSTER&lt;br /&gt;
sudo systemctl start etcd (On server being added)&lt;br /&gt;
Patroni&lt;br /&gt;
sudo mkdir -p /etc/patroni/&lt;br /&gt;
sudo chown -R  postgres:postgres /etc/patroni/&lt;br /&gt;
&lt;br /&gt;
As the postgres user, create a configuration file.&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
vi /etc/patroni/patroni.yml&lt;br /&gt;
namespace: /db/&lt;br /&gt;
scope: cluster_1&lt;br /&gt;
name: $THIS_NAME&lt;br /&gt;
&lt;br /&gt;
log:&lt;br /&gt;
  format: &#039;%(asctime)s %(levelname)s: %(message)s&#039;&lt;br /&gt;
  level: INFO&lt;br /&gt;
  max_queue_size: 1000&lt;br /&gt;
  traceback_level: ERROR&lt;br /&gt;
  type: plain&lt;br /&gt;
&lt;br /&gt;
restapi:&lt;br /&gt;
  listen: 0.0.0.0:8008&lt;br /&gt;
  connect_address: $THIS_IP:8008&lt;br /&gt;
&lt;br /&gt;
etcd3:&lt;br /&gt;
  hosts: &lt;br /&gt;
  - 192.168.59.101:2379&lt;br /&gt;
  - 192.168.59.102:2379&lt;br /&gt;
  - 192.168.59.103:2379&lt;br /&gt;
&lt;br /&gt;
bootstrap:&lt;br /&gt;
  dcs:&lt;br /&gt;
    ttl: 30&lt;br /&gt;
    loop_wait: 10&lt;br /&gt;
    retry_timeout: 10&lt;br /&gt;
    maximum_lag_on_failover: 1048576&lt;br /&gt;
    postgresql:&lt;br /&gt;
      use_pg_rewind: true&lt;br /&gt;
      use_slots: true&lt;br /&gt;
      parameters:&lt;br /&gt;
        wal_level: replica&lt;br /&gt;
        hot_standby: &amp;quot;on&amp;quot;&lt;br /&gt;
        wal_keep_segments: 10&lt;br /&gt;
        max_wal_senders: 5&lt;br /&gt;
        max_replication_slots: 10&lt;br /&gt;
        wal_log_hints: &amp;quot;on&amp;quot;&lt;br /&gt;
        logging_collector: &#039;on&#039;&lt;br /&gt;
        max_wal_size: &#039;10GB&#039;&lt;br /&gt;
        archive_mode: &amp;quot;on&amp;quot;&lt;br /&gt;
        archive_timeout: 600s&lt;br /&gt;
        archive_command: &amp;quot;/bin/true&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  initdb: # Note: It needs to be a list (some options need values, others are switches)&lt;br /&gt;
  - encoding: UTF8&lt;br /&gt;
  - data-checksums&lt;br /&gt;
&lt;br /&gt;
  pg_hba: # Add following lines to pg_hba.conf after running &#039;initdb&#039;&lt;br /&gt;
  - host replication replicator 127.0.0.1/32 trust&lt;br /&gt;
  - host replication replicator 0.0.0.0/0 md5&lt;br /&gt;
  - host all all 0.0.0.0/0 md5&lt;br /&gt;
  - host all all ::0/0 md5&lt;br /&gt;
&lt;br /&gt;
  # Some additional users which needs to be created after initializing new cluster&lt;br /&gt;
  users:&lt;br /&gt;
    admin:&lt;br /&gt;
      password: qaz123&lt;br /&gt;
      options:&lt;br /&gt;
        - createrole&lt;br /&gt;
        - createdb&lt;br /&gt;
&lt;br /&gt;
postgresql:&lt;br /&gt;
  listen: 0.0.0.0:5432&lt;br /&gt;
  connect_address: $THIS_IP:5432&lt;br /&gt;
  data_dir: /var/lib/postgresql/17&lt;br /&gt;
  bin_dir: /usr/lib/postgresql/17/bin&lt;br /&gt;
  pgpass: /tmp/pgpass0&lt;br /&gt;
  authentication:&lt;br /&gt;
    replication:&lt;br /&gt;
      username: replicator&lt;br /&gt;
      password: replPasswd&lt;br /&gt;
    superuser:&lt;br /&gt;
      username: postgres&lt;br /&gt;
      password: qaz123&lt;br /&gt;
  parameters:&lt;br /&gt;
    unix_socket_directories: &amp;quot;/var/run/postgresql/&amp;quot;&lt;br /&gt;
&lt;br /&gt;
tags:&lt;br /&gt;
  nofailover: false&lt;br /&gt;
  noloadbalance: false&lt;br /&gt;
  clonefrom: false&lt;br /&gt;
  nosync: false&lt;br /&gt;
&lt;br /&gt;
Validate the configuration file&lt;br /&gt;
patroni --validate-config /etc/patroni/patroni.yml&lt;br /&gt;
Service&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/patroni.service&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Runners to orchestrate a high-availability PostgreSQL&lt;br /&gt;
After=network.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
&lt;br /&gt;
User=postgres&lt;br /&gt;
Group=postgres&lt;br /&gt;
&lt;br /&gt;
# Start the patroni process&lt;br /&gt;
ExecStart=/usr/bin/patroni /etc/patroni/patroni.yml&lt;br /&gt;
&lt;br /&gt;
# Send HUP to reload from patroni.yml&lt;br /&gt;
ExecReload=/bin/kill -s HUP $MAINPID&lt;br /&gt;
&lt;br /&gt;
# Only kill the patroni process, not it&#039;s children, so it will gracefully stop postgres&lt;br /&gt;
KillMode=process&lt;br /&gt;
&lt;br /&gt;
# Give a reasonable amount of time for the server to start up/shut down&lt;br /&gt;
TimeoutSec=30&lt;br /&gt;
&lt;br /&gt;
# Restart the service if it crashed&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
&lt;br /&gt;
Set permissions of the data directory to 700&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
chmod 700 /var/lib/postgresql/17&lt;br /&gt;
&lt;br /&gt;
Start the patroni service&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start patroni&lt;br /&gt;
&lt;br /&gt;
Check status&lt;br /&gt;
export PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml list&lt;br /&gt;
&lt;br /&gt;
+ Cluster: cluster_1 (7439347493790530098) ------+----+-----------+&lt;br /&gt;
| Member  | Host           | Role    | State     | TL | Lag in MB |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
| debian1 | 192.168.59.101 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian2 | 192.168.59.102 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian3 | 192.168.59.103 | Leader  | running   |  1 |           |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
&lt;br /&gt;
Change configuration&lt;br /&gt;
PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest --stanza=ian archive-push %p&amp;quot;&lt;br /&gt;
Issues&lt;br /&gt;
&lt;br /&gt;
Replication showed the following error&lt;br /&gt;
ERROR:  Requested starting point 0/4000000 is ahead of the WAL flush &lt;br /&gt;
        position of this server 0/3000060&lt;br /&gt;
&lt;br /&gt;
The replica instance needed to be re-initialised&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml reinit &amp;lt;scope&amp;gt; &amp;lt;name&amp;gt;&lt;br /&gt;
VIP Manager&lt;br /&gt;
&lt;br /&gt;
Copy the latest release to your server : &lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
sudo dpkg -i vip-manager_2.8.0_Linux_x86_64.deb&lt;br /&gt;
sudo systemctl stop vip-manager&lt;br /&gt;
sudo vi /etc/patroni/vip-manager.yml&lt;br /&gt;
&lt;br /&gt;
The trigger key below contains the namespace and scope of the Patroni service.&lt;br /&gt;
ip: 192.168.59.100&lt;br /&gt;
netmask: 24&lt;br /&gt;
interface: enp0s3&lt;br /&gt;
trigger-key: &amp;quot;/db/cluster_1/leader&amp;quot;&lt;br /&gt;
dcs-type: etcd&lt;br /&gt;
dcs-endpoints: http://192.168.59.101:2379&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/vip-manager.service&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Manages Virtual IP for Patroni&lt;br /&gt;
After=network-online.target&lt;br /&gt;
Before=patroni.service&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
ExecStart=/usr/bin/vip-manager --config=/etc/patroni/vip-manager.yml&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start vip-manager&lt;br /&gt;
sudo journalctl -u vip-manager.service -n 100 -f&lt;br /&gt;
PGBackRest&lt;br /&gt;
&lt;br /&gt;
Setup the repository on Debian3&lt;br /&gt;
&lt;br /&gt;
Install pgbackrest on all the servers …&lt;br /&gt;
sudo apt install pgbackrest&lt;br /&gt;
sudo mkdir -p -m 770 /var/log/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/log/pgbackrest&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest&lt;br /&gt;
sudo touch /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chmod 640 /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chown postgres:postgres /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
&lt;br /&gt;
On debian3, create the directory that will hold the backups …&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest/conf.d&lt;br /&gt;
sudo mkdir -p /var/lib/pgbackrest&lt;br /&gt;
sudo chmod 750 /var/lib/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/lib/pgbackrest&lt;br /&gt;
Setup SSH&lt;br /&gt;
&lt;br /&gt;
We will need a passwordless SSH connection between the repository and the database servers.&lt;br /&gt;
For this setup I was using postgres as the user that controls pgbackrest.&lt;br /&gt;
# debian1&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp id_ras.pub postgres@debian3&lt;br /&gt;
ssh postgres@debian3&lt;br /&gt;
# debian3&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp ./.ssh/id_ras.pub postgres@debian1&lt;br /&gt;
ssh postgres@debian1&lt;br /&gt;
Configuration&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the repository&lt;br /&gt;
vi /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-host=debian1&lt;br /&gt;
pg1-path=/var/lib/postgresql/17 &lt;br /&gt;
&lt;br /&gt;
[global]&lt;br /&gt;
repo1-cipher-pass=zWaf6XtpjIVZC5444yXB+cgFDFl7MxGlgkZSaoPv&lt;br /&gt;
repo1-cipher-type=aes-256-cbc&lt;br /&gt;
repo1-path=/var/lib/pgbackrest&lt;br /&gt;
repo1-retention-full=2&lt;br /&gt;
start-fast=y&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the database server&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-path=/var/lib/postgresql/17&lt;br /&gt;
 &lt;br /&gt;
[global]&lt;br /&gt;
log-level-file=detail&lt;br /&gt;
repo1-host=debian3 &lt;br /&gt;
repo1-host-user=postgres&lt;br /&gt;
&lt;br /&gt;
Alter the archive_command parameter so that it uses the pgbackrest executable&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest \&lt;br /&gt;
                       --stanza=cybertec archive-push %p&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Create a Stanza on the repository server and confirm it is working&lt;br /&gt;
pgbackrest --stanza=cybertec stanza-create&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;br /&gt;
&lt;br /&gt;
Also check if the Stanza is correct on the database server.&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=110</id>
		<title>Patroni Cluster</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=110"/>
		<updated>2025-08-03T15:09:39Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Setup ==&lt;br /&gt;
&lt;br /&gt;
* Debian1: 192.168.59.101 (Debian 12)&lt;br /&gt;
* Debian2: 192.168.59.102 (Debian 12)&lt;br /&gt;
* Debian3: 192.168.59.103 (Debian 12)&lt;br /&gt;
&lt;br /&gt;
The servers were created using VirtualBox. &amp;lt;/br&amp;gt;&lt;br /&gt;
Network setup was 2 adapters (Bridged Adapter and NAT).&lt;br /&gt;
&lt;br /&gt;
== Installation ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo apt install postgresql patroni&lt;br /&gt;
sudo systemctl stop patroni postgresql&lt;br /&gt;
sudo systemctl disable postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
etcd was installed from prebuilt binaries &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ETCD_RELEASE=$(curl -s https://api.github.com/repos/etcd-io/etcd/releases/latest|grep tag_name | cut -d &#039;&amp;quot;&#039; -f 4)&lt;br /&gt;
curl -sL https://github.com/etcd-io/etcd/releases/download/${ETCD_RELEASE}/etcd-${ETCD_RELEASE}-linux-amd64.tar.gz \&lt;br /&gt;
 | sudo tar xz -C /usr/bin --strip=1 --wildcards --no-anchored etcdctl etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Configuration ==&lt;br /&gt;
=== etcd ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo mkdir -p /var/lib/etcd/&lt;br /&gt;
sudo mkdir -p /etc/etcd&lt;br /&gt;
sudo groupadd --system etcd&lt;br /&gt;
sudo useradd -s /sbin/nologin --system -g etcd etcd&lt;br /&gt;
sudo chown -R etcd:etcd /var/lib/etcd/&lt;br /&gt;
sudo chmod -R a+rw /var/lib/etcd&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Create the configuration file at /etcd/etcd/etcd.yaml &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
name: &#039;debian1&#039; &lt;br /&gt;
data-dir: &#039;/var/lib/etcd/data.etcd&#039;&lt;br /&gt;
initial-advertise-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
listen-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
advertise-client-urls: http://192.168.59.101:2379 &lt;br /&gt;
listen-client-urls: http://192.168.59.101:2379,http://127.0.0.1:2379 &lt;br /&gt;
initial-cluster: &amp;quot;debian1=http://192.168.59.101:2380,debian2=http://192.168.59.102:2380,debian3=http://192.168.59.103:2380&amp;quot; &lt;br /&gt;
initial-cluster-state: &#039;new&#039; &lt;br /&gt;
initial-cluster-token: &#039;token-01&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Configure the systemd unit file &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=etcd key-value store&lt;br /&gt;
Documentation=https://github.com/etcd-io/etcd&lt;br /&gt;
After=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
Wants=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
User=etcd&lt;br /&gt;
Type=notify&lt;br /&gt;
Environment=ETCD_DATA_DIR=/var/lib/etcd&lt;br /&gt;
Environment=ETCD_NAME=%H&lt;br /&gt;
ExecStart=/usr/bin/etcd --config-file /etc/etcd/etcd.yml&lt;br /&gt;
Restart=always&lt;br /&gt;
RestartSec=10s&lt;br /&gt;
LimitNOFILE=40000&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl enable etcd.service&lt;br /&gt;
sudo systemctl start etcd.service&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Test that everything is working&lt;br /&gt;
export ETCDCTL_ENDPOINTS=&amp;quot;http://192.168.59.103:2379,http://192.168.59.101:2379,http://192.168.59.102:2379&amp;quot;&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
etcdctl endpoint health -w table&lt;br /&gt;
etcdctl endpoint status -w table &lt;br /&gt;
&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+&lt;br /&gt;
|          ENDPOINT          |        ID        | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+&lt;br /&gt;
| http://192.168.59.102:2379 | 3cdde56ec82a0ca0 |  3.5.16 |   53 kB |      true |      false |        13 |        145 |                145 |        |&lt;br /&gt;
| http://192.168.59.101:2379 | 406189a3bb67b8bc |  3.5.16 |   53 kB |     false |      false |        13 |        145 |                145 |        |&lt;br /&gt;
| http://192.168.59.103:2379 | 939ed44c2e6d5e5b |  3.5.16 |   53 kB |     false |      false |        13 |        145 |                145 |        |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+&lt;br /&gt;
&lt;br /&gt;
Change the leader if necessary&lt;br /&gt;
etcdctl move-leader 939ed44c2e6d5e5b --endpoints=$ENDPOINTS&lt;br /&gt;
Issues&lt;br /&gt;
&lt;br /&gt;
One of the members would not connect to the cluster.&lt;br /&gt;
The error message on the leader was “Prober detected unhealthy status …”&lt;br /&gt;
On the problem member the message was “Failed to publish local member to cluster through raft”&lt;br /&gt;
&lt;br /&gt;
No network issues, solved by removing and adding the member&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
sudo systemctl stop etcd (On server being removed)&lt;br /&gt;
etcdctl member remove &amp;lt;ID&amp;gt;&lt;br /&gt;
&lt;br /&gt;
etcdctl member add debian2 --peer-urls=http://192.168.59.102:2380&lt;br /&gt;
# Edit the etcd.yml files copying all information displayed, &lt;br /&gt;
# including the new order for ETCD_INITIAL_CLUSTER&lt;br /&gt;
sudo systemctl start etcd (On server being added)&lt;br /&gt;
Patroni&lt;br /&gt;
sudo mkdir -p /etc/patroni/&lt;br /&gt;
sudo chown -R  postgres:postgres /etc/patroni/&lt;br /&gt;
&lt;br /&gt;
As the postgres user, create a configuration file.&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
vi /etc/patroni/patroni.yml&lt;br /&gt;
namespace: /db/&lt;br /&gt;
scope: cluster_1&lt;br /&gt;
name: $THIS_NAME&lt;br /&gt;
&lt;br /&gt;
log:&lt;br /&gt;
  format: &#039;%(asctime)s %(levelname)s: %(message)s&#039;&lt;br /&gt;
  level: INFO&lt;br /&gt;
  max_queue_size: 1000&lt;br /&gt;
  traceback_level: ERROR&lt;br /&gt;
  type: plain&lt;br /&gt;
&lt;br /&gt;
restapi:&lt;br /&gt;
  listen: 0.0.0.0:8008&lt;br /&gt;
  connect_address: $THIS_IP:8008&lt;br /&gt;
&lt;br /&gt;
etcd3:&lt;br /&gt;
  hosts: &lt;br /&gt;
  - 192.168.59.101:2379&lt;br /&gt;
  - 192.168.59.102:2379&lt;br /&gt;
  - 192.168.59.103:2379&lt;br /&gt;
&lt;br /&gt;
bootstrap:&lt;br /&gt;
  dcs:&lt;br /&gt;
    ttl: 30&lt;br /&gt;
    loop_wait: 10&lt;br /&gt;
    retry_timeout: 10&lt;br /&gt;
    maximum_lag_on_failover: 1048576&lt;br /&gt;
    postgresql:&lt;br /&gt;
      use_pg_rewind: true&lt;br /&gt;
      use_slots: true&lt;br /&gt;
      parameters:&lt;br /&gt;
        wal_level: replica&lt;br /&gt;
        hot_standby: &amp;quot;on&amp;quot;&lt;br /&gt;
        wal_keep_segments: 10&lt;br /&gt;
        max_wal_senders: 5&lt;br /&gt;
        max_replication_slots: 10&lt;br /&gt;
        wal_log_hints: &amp;quot;on&amp;quot;&lt;br /&gt;
        logging_collector: &#039;on&#039;&lt;br /&gt;
        max_wal_size: &#039;10GB&#039;&lt;br /&gt;
        archive_mode: &amp;quot;on&amp;quot;&lt;br /&gt;
        archive_timeout: 600s&lt;br /&gt;
        archive_command: &amp;quot;/bin/true&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  initdb: # Note: It needs to be a list (some options need values, others are switches)&lt;br /&gt;
  - encoding: UTF8&lt;br /&gt;
  - data-checksums&lt;br /&gt;
&lt;br /&gt;
  pg_hba: # Add following lines to pg_hba.conf after running &#039;initdb&#039;&lt;br /&gt;
  - host replication replicator 127.0.0.1/32 trust&lt;br /&gt;
  - host replication replicator 0.0.0.0/0 md5&lt;br /&gt;
  - host all all 0.0.0.0/0 md5&lt;br /&gt;
  - host all all ::0/0 md5&lt;br /&gt;
&lt;br /&gt;
  # Some additional users which needs to be created after initializing new cluster&lt;br /&gt;
  users:&lt;br /&gt;
    admin:&lt;br /&gt;
      password: qaz123&lt;br /&gt;
      options:&lt;br /&gt;
        - createrole&lt;br /&gt;
        - createdb&lt;br /&gt;
&lt;br /&gt;
postgresql:&lt;br /&gt;
  listen: 0.0.0.0:5432&lt;br /&gt;
  connect_address: $THIS_IP:5432&lt;br /&gt;
  data_dir: /var/lib/postgresql/17&lt;br /&gt;
  bin_dir: /usr/lib/postgresql/17/bin&lt;br /&gt;
  pgpass: /tmp/pgpass0&lt;br /&gt;
  authentication:&lt;br /&gt;
    replication:&lt;br /&gt;
      username: replicator&lt;br /&gt;
      password: replPasswd&lt;br /&gt;
    superuser:&lt;br /&gt;
      username: postgres&lt;br /&gt;
      password: qaz123&lt;br /&gt;
  parameters:&lt;br /&gt;
    unix_socket_directories: &amp;quot;/var/run/postgresql/&amp;quot;&lt;br /&gt;
&lt;br /&gt;
tags:&lt;br /&gt;
  nofailover: false&lt;br /&gt;
  noloadbalance: false&lt;br /&gt;
  clonefrom: false&lt;br /&gt;
  nosync: false&lt;br /&gt;
&lt;br /&gt;
Validate the configuration file&lt;br /&gt;
patroni --validate-config /etc/patroni/patroni.yml&lt;br /&gt;
Service&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/patroni.service&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Runners to orchestrate a high-availability PostgreSQL&lt;br /&gt;
After=network.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
&lt;br /&gt;
User=postgres&lt;br /&gt;
Group=postgres&lt;br /&gt;
&lt;br /&gt;
# Start the patroni process&lt;br /&gt;
ExecStart=/usr/bin/patroni /etc/patroni/patroni.yml&lt;br /&gt;
&lt;br /&gt;
# Send HUP to reload from patroni.yml&lt;br /&gt;
ExecReload=/bin/kill -s HUP $MAINPID&lt;br /&gt;
&lt;br /&gt;
# Only kill the patroni process, not it&#039;s children, so it will gracefully stop postgres&lt;br /&gt;
KillMode=process&lt;br /&gt;
&lt;br /&gt;
# Give a reasonable amount of time for the server to start up/shut down&lt;br /&gt;
TimeoutSec=30&lt;br /&gt;
&lt;br /&gt;
# Restart the service if it crashed&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
&lt;br /&gt;
Set permissions of the data directory to 700&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
chmod 700 /var/lib/postgresql/17&lt;br /&gt;
&lt;br /&gt;
Start the patroni service&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start patroni&lt;br /&gt;
&lt;br /&gt;
Check status&lt;br /&gt;
export PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml list&lt;br /&gt;
&lt;br /&gt;
+ Cluster: cluster_1 (7439347493790530098) ------+----+-----------+&lt;br /&gt;
| Member  | Host           | Role    | State     | TL | Lag in MB |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
| debian1 | 192.168.59.101 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian2 | 192.168.59.102 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian3 | 192.168.59.103 | Leader  | running   |  1 |           |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
&lt;br /&gt;
Change configuration&lt;br /&gt;
PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest --stanza=ian archive-push %p&amp;quot;&lt;br /&gt;
Issues&lt;br /&gt;
&lt;br /&gt;
Replication showed the following error&lt;br /&gt;
ERROR:  Requested starting point 0/4000000 is ahead of the WAL flush &lt;br /&gt;
        position of this server 0/3000060&lt;br /&gt;
&lt;br /&gt;
The replica instance needed to be re-initialised&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml reinit &amp;lt;scope&amp;gt; &amp;lt;name&amp;gt;&lt;br /&gt;
VIP Manager&lt;br /&gt;
&lt;br /&gt;
Copy the latest release to your server : &lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
sudo dpkg -i vip-manager_2.8.0_Linux_x86_64.deb&lt;br /&gt;
sudo systemctl stop vip-manager&lt;br /&gt;
sudo vi /etc/patroni/vip-manager.yml&lt;br /&gt;
&lt;br /&gt;
The trigger key below contains the namespace and scope of the Patroni service.&lt;br /&gt;
ip: 192.168.59.100&lt;br /&gt;
netmask: 24&lt;br /&gt;
interface: enp0s3&lt;br /&gt;
trigger-key: &amp;quot;/db/cluster_1/leader&amp;quot;&lt;br /&gt;
dcs-type: etcd&lt;br /&gt;
dcs-endpoints: http://192.168.59.101:2379&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/vip-manager.service&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Manages Virtual IP for Patroni&lt;br /&gt;
After=network-online.target&lt;br /&gt;
Before=patroni.service&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
ExecStart=/usr/bin/vip-manager --config=/etc/patroni/vip-manager.yml&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start vip-manager&lt;br /&gt;
sudo journalctl -u vip-manager.service -n 100 -f&lt;br /&gt;
PGBackRest&lt;br /&gt;
&lt;br /&gt;
Setup the repository on Debian3&lt;br /&gt;
&lt;br /&gt;
Install pgbackrest on all the servers …&lt;br /&gt;
sudo apt install pgbackrest&lt;br /&gt;
sudo mkdir -p -m 770 /var/log/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/log/pgbackrest&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest&lt;br /&gt;
sudo touch /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chmod 640 /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chown postgres:postgres /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
&lt;br /&gt;
On debian3, create the directory that will hold the backups …&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest/conf.d&lt;br /&gt;
sudo mkdir -p /var/lib/pgbackrest&lt;br /&gt;
sudo chmod 750 /var/lib/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/lib/pgbackrest&lt;br /&gt;
Setup SSH&lt;br /&gt;
&lt;br /&gt;
We will need a passwordless SSH connection between the repository and the database servers.&lt;br /&gt;
For this setup I was using postgres as the user that controls pgbackrest.&lt;br /&gt;
# debian1&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp id_ras.pub postgres@debian3&lt;br /&gt;
ssh postgres@debian3&lt;br /&gt;
# debian3&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp ./.ssh/id_ras.pub postgres@debian1&lt;br /&gt;
ssh postgres@debian1&lt;br /&gt;
Configuration&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the repository&lt;br /&gt;
vi /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-host=debian1&lt;br /&gt;
pg1-path=/var/lib/postgresql/17 &lt;br /&gt;
&lt;br /&gt;
[global]&lt;br /&gt;
repo1-cipher-pass=zWaf6XtpjIVZC5444yXB+cgFDFl7MxGlgkZSaoPv&lt;br /&gt;
repo1-cipher-type=aes-256-cbc&lt;br /&gt;
repo1-path=/var/lib/pgbackrest&lt;br /&gt;
repo1-retention-full=2&lt;br /&gt;
start-fast=y&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the database server&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-path=/var/lib/postgresql/17&lt;br /&gt;
 &lt;br /&gt;
[global]&lt;br /&gt;
log-level-file=detail&lt;br /&gt;
repo1-host=debian3 &lt;br /&gt;
repo1-host-user=postgres&lt;br /&gt;
&lt;br /&gt;
Alter the archive_command parameter so that it uses the pgbackrest executable&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest \&lt;br /&gt;
                       --stanza=cybertec archive-push %p&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Create a Stanza on the repository server and confirm it is working&lt;br /&gt;
pgbackrest --stanza=cybertec stanza-create&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;br /&gt;
&lt;br /&gt;
Also check if the Stanza is correct on the database server.&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=109</id>
		<title>Patroni Cluster</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Patroni_Cluster&amp;diff=109"/>
		<updated>2025-08-03T15:05:58Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: Created page with &amp;quot;Setup  * Debian1: 192.168.59.101 (Debian 12) * Debian2: 192.168.59.102 (Debian 12) * Debian3: 192.168.59.103 (Debian 12)  The servers were created using VirtualBox. Network setup was 2 adapters (Bridged Adapter and NAT). Installation sudo apt install postgresql patroni sudo systemctl stop patroni postgresql sudo systemctl disable postgresql  etcd was installed from prebuilt binaries ETCD_RELEASE=$(curl -s https://api.github.com/repos/etcd-io/etcd/releases/latest|grep tag...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Setup&lt;br /&gt;
&lt;br /&gt;
* Debian1: 192.168.59.101 (Debian 12)&lt;br /&gt;
* Debian2: 192.168.59.102 (Debian 12)&lt;br /&gt;
* Debian3: 192.168.59.103 (Debian 12)&lt;br /&gt;
&lt;br /&gt;
The servers were created using VirtualBox.&lt;br /&gt;
Network setup was 2 adapters (Bridged Adapter and NAT).&lt;br /&gt;
Installation&lt;br /&gt;
sudo apt install postgresql patroni&lt;br /&gt;
sudo systemctl stop patroni postgresql&lt;br /&gt;
sudo systemctl disable postgresql&lt;br /&gt;
&lt;br /&gt;
etcd was installed from prebuilt binaries&lt;br /&gt;
ETCD_RELEASE=$(curl -s https://api.github.com/repos/etcd-io/etcd/releases/latest|grep tag_name | cut -d &#039;&amp;quot;&#039; -f 4)&lt;br /&gt;
curl -sL https://github.com/etcd-io/etcd/releases/download/${ETCD_RELEASE}/etcd-${ETCD_RELEASE}-linux-amd64.tar.gz \&lt;br /&gt;
 | sudo tar xz -C /usr/bin --strip=1 --wildcards --no-anchored etcdctl etcd&lt;br /&gt;
Configuration&lt;br /&gt;
etcd&lt;br /&gt;
sudo mkdir -p /var/lib/etcd/&lt;br /&gt;
sudo mkdir -p /etc/etcd&lt;br /&gt;
sudo groupadd --system etcd&lt;br /&gt;
sudo useradd -s /sbin/nologin --system -g etcd etcd&lt;br /&gt;
sudo chown -R etcd:etcd /var/lib/etcd/&lt;br /&gt;
sudo chmod -R a+rw /var/lib/etcd&lt;br /&gt;
&lt;br /&gt;
Create the configuration file at /etcd/etcd/etcd.yaml&lt;br /&gt;
name: &#039;debian1&#039; &lt;br /&gt;
data-dir: &#039;/var/lib/etcd/data.etcd&#039;&lt;br /&gt;
initial-advertise-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
listen-peer-urls: http://192.168.59.101:2380 &lt;br /&gt;
advertise-client-urls: http://192.168.59.101:2379 &lt;br /&gt;
listen-client-urls: http://192.168.59.101:2379,http://127.0.0.1:2379 &lt;br /&gt;
initial-cluster: &amp;quot;debian1=http://192.168.59.101:2380,debian2=http://192.168.59.102:2380,debian3=http://192.168.59.103:2380&amp;quot; &lt;br /&gt;
initial-cluster-state: &#039;new&#039; &lt;br /&gt;
initial-cluster-token: &#039;token-01&#039;&lt;br /&gt;
&lt;br /&gt;
Configure the systemd unit file&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=etcd key-value store&lt;br /&gt;
Documentation=https://github.com/etcd-io/etcd&lt;br /&gt;
After=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
Wants=network-online.target local-fs.target remote-fs.target time-sync.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
User=etcd&lt;br /&gt;
Type=notify&lt;br /&gt;
Environment=ETCD_DATA_DIR=/var/lib/etcd&lt;br /&gt;
Environment=ETCD_NAME=%H&lt;br /&gt;
ExecStart=/usr/bin/etcd --config-file /etc/etcd/etcd.yml&lt;br /&gt;
Restart=always&lt;br /&gt;
RestartSec=10s&lt;br /&gt;
LimitNOFILE=40000&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl enable etcd.service&lt;br /&gt;
sudo systemctl start etcd.service&lt;br /&gt;
&lt;br /&gt;
Test that everything is working&lt;br /&gt;
export ETCDCTL_ENDPOINTS=&amp;quot;http://192.168.59.103:2379,http://192.168.59.101:2379,http://192.168.59.102:2379&amp;quot;&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
etcdctl endpoint health -w table&lt;br /&gt;
etcdctl endpoint status -w table &lt;br /&gt;
&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+&lt;br /&gt;
|          ENDPOINT          |        ID        | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+&lt;br /&gt;
| http://192.168.59.102:2379 | 3cdde56ec82a0ca0 |  3.5.16 |   53 kB |      true |      false |        13 |        145 |                145 |        |&lt;br /&gt;
| http://192.168.59.101:2379 | 406189a3bb67b8bc |  3.5.16 |   53 kB |     false |      false |        13 |        145 |                145 |        |&lt;br /&gt;
| http://192.168.59.103:2379 | 939ed44c2e6d5e5b |  3.5.16 |   53 kB |     false |      false |        13 |        145 |                145 |        |&lt;br /&gt;
+----------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+&lt;br /&gt;
&lt;br /&gt;
Change the leader if necessary&lt;br /&gt;
etcdctl move-leader 939ed44c2e6d5e5b --endpoints=$ENDPOINTS&lt;br /&gt;
Issues&lt;br /&gt;
&lt;br /&gt;
One of the members would not connect to the cluster.&lt;br /&gt;
The error message on the leader was “Prober detected unhealthy status …”&lt;br /&gt;
On the problem member the message was “Failed to publish local member to cluster through raft”&lt;br /&gt;
&lt;br /&gt;
No network issues, solved by removing and adding the member&lt;br /&gt;
etcdctl member list -w table&lt;br /&gt;
sudo systemctl stop etcd (On server being removed)&lt;br /&gt;
etcdctl member remove &amp;lt;ID&amp;gt;&lt;br /&gt;
&lt;br /&gt;
etcdctl member add debian2 --peer-urls=http://192.168.59.102:2380&lt;br /&gt;
# Edit the etcd.yml files copying all information displayed, &lt;br /&gt;
# including the new order for ETCD_INITIAL_CLUSTER&lt;br /&gt;
sudo systemctl start etcd (On server being added)&lt;br /&gt;
Patroni&lt;br /&gt;
sudo mkdir -p /etc/patroni/&lt;br /&gt;
sudo chown -R  postgres:postgres /etc/patroni/&lt;br /&gt;
&lt;br /&gt;
As the postgres user, create a configuration file.&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
vi /etc/patroni/patroni.yml&lt;br /&gt;
namespace: /db/&lt;br /&gt;
scope: cluster_1&lt;br /&gt;
name: $THIS_NAME&lt;br /&gt;
&lt;br /&gt;
log:&lt;br /&gt;
  format: &#039;%(asctime)s %(levelname)s: %(message)s&#039;&lt;br /&gt;
  level: INFO&lt;br /&gt;
  max_queue_size: 1000&lt;br /&gt;
  traceback_level: ERROR&lt;br /&gt;
  type: plain&lt;br /&gt;
&lt;br /&gt;
restapi:&lt;br /&gt;
  listen: 0.0.0.0:8008&lt;br /&gt;
  connect_address: $THIS_IP:8008&lt;br /&gt;
&lt;br /&gt;
etcd3:&lt;br /&gt;
  hosts: &lt;br /&gt;
  - 192.168.59.101:2379&lt;br /&gt;
  - 192.168.59.102:2379&lt;br /&gt;
  - 192.168.59.103:2379&lt;br /&gt;
&lt;br /&gt;
bootstrap:&lt;br /&gt;
  dcs:&lt;br /&gt;
    ttl: 30&lt;br /&gt;
    loop_wait: 10&lt;br /&gt;
    retry_timeout: 10&lt;br /&gt;
    maximum_lag_on_failover: 1048576&lt;br /&gt;
    postgresql:&lt;br /&gt;
      use_pg_rewind: true&lt;br /&gt;
      use_slots: true&lt;br /&gt;
      parameters:&lt;br /&gt;
        wal_level: replica&lt;br /&gt;
        hot_standby: &amp;quot;on&amp;quot;&lt;br /&gt;
        wal_keep_segments: 10&lt;br /&gt;
        max_wal_senders: 5&lt;br /&gt;
        max_replication_slots: 10&lt;br /&gt;
        wal_log_hints: &amp;quot;on&amp;quot;&lt;br /&gt;
        logging_collector: &#039;on&#039;&lt;br /&gt;
        max_wal_size: &#039;10GB&#039;&lt;br /&gt;
        archive_mode: &amp;quot;on&amp;quot;&lt;br /&gt;
        archive_timeout: 600s&lt;br /&gt;
        archive_command: &amp;quot;/bin/true&amp;quot;&lt;br /&gt;
&lt;br /&gt;
  initdb: # Note: It needs to be a list (some options need values, others are switches)&lt;br /&gt;
  - encoding: UTF8&lt;br /&gt;
  - data-checksums&lt;br /&gt;
&lt;br /&gt;
  pg_hba: # Add following lines to pg_hba.conf after running &#039;initdb&#039;&lt;br /&gt;
  - host replication replicator 127.0.0.1/32 trust&lt;br /&gt;
  - host replication replicator 0.0.0.0/0 md5&lt;br /&gt;
  - host all all 0.0.0.0/0 md5&lt;br /&gt;
  - host all all ::0/0 md5&lt;br /&gt;
&lt;br /&gt;
  # Some additional users which needs to be created after initializing new cluster&lt;br /&gt;
  users:&lt;br /&gt;
    admin:&lt;br /&gt;
      password: qaz123&lt;br /&gt;
      options:&lt;br /&gt;
        - createrole&lt;br /&gt;
        - createdb&lt;br /&gt;
&lt;br /&gt;
postgresql:&lt;br /&gt;
  listen: 0.0.0.0:5432&lt;br /&gt;
  connect_address: $THIS_IP:5432&lt;br /&gt;
  data_dir: /var/lib/postgresql/17&lt;br /&gt;
  bin_dir: /usr/lib/postgresql/17/bin&lt;br /&gt;
  pgpass: /tmp/pgpass0&lt;br /&gt;
  authentication:&lt;br /&gt;
    replication:&lt;br /&gt;
      username: replicator&lt;br /&gt;
      password: replPasswd&lt;br /&gt;
    superuser:&lt;br /&gt;
      username: postgres&lt;br /&gt;
      password: qaz123&lt;br /&gt;
  parameters:&lt;br /&gt;
    unix_socket_directories: &amp;quot;/var/run/postgresql/&amp;quot;&lt;br /&gt;
&lt;br /&gt;
tags:&lt;br /&gt;
  nofailover: false&lt;br /&gt;
  noloadbalance: false&lt;br /&gt;
  clonefrom: false&lt;br /&gt;
  nosync: false&lt;br /&gt;
&lt;br /&gt;
Validate the configuration file&lt;br /&gt;
patroni --validate-config /etc/patroni/patroni.yml&lt;br /&gt;
Service&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/patroni.service&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Runners to orchestrate a high-availability PostgreSQL&lt;br /&gt;
After=network.target&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
&lt;br /&gt;
User=postgres&lt;br /&gt;
Group=postgres&lt;br /&gt;
&lt;br /&gt;
# Start the patroni process&lt;br /&gt;
ExecStart=/usr/bin/patroni /etc/patroni/patroni.yml&lt;br /&gt;
&lt;br /&gt;
# Send HUP to reload from patroni.yml&lt;br /&gt;
ExecReload=/bin/kill -s HUP $MAINPID&lt;br /&gt;
&lt;br /&gt;
# Only kill the patroni process, not it&#039;s children, so it will gracefully stop postgres&lt;br /&gt;
KillMode=process&lt;br /&gt;
&lt;br /&gt;
# Give a reasonable amount of time for the server to start up/shut down&lt;br /&gt;
TimeoutSec=30&lt;br /&gt;
&lt;br /&gt;
# Restart the service if it crashed&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
&lt;br /&gt;
Set permissions of the data directory to 700&lt;br /&gt;
sudo -iu postgres&lt;br /&gt;
chmod 700 /var/lib/postgresql/17&lt;br /&gt;
&lt;br /&gt;
Start the patroni service&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start patroni&lt;br /&gt;
&lt;br /&gt;
Check status&lt;br /&gt;
export PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml list&lt;br /&gt;
&lt;br /&gt;
+ Cluster: cluster_1 (7439347493790530098) ------+----+-----------+&lt;br /&gt;
| Member  | Host           | Role    | State     | TL | Lag in MB |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
| debian1 | 192.168.59.101 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian2 | 192.168.59.102 | Replica | streaming |  1 |         0 |&lt;br /&gt;
| debian3 | 192.168.59.103 | Leader  | running   |  1 |           |&lt;br /&gt;
+---------+----------------+---------+-----------+----+-----------+&lt;br /&gt;
&lt;br /&gt;
Change configuration&lt;br /&gt;
PATRONICTL_CONFIG_FILE=/etc/patroni/patroni.yml&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest --stanza=ian archive-push %p&amp;quot;&lt;br /&gt;
Issues&lt;br /&gt;
&lt;br /&gt;
Replication showed the following error&lt;br /&gt;
ERROR:  Requested starting point 0/4000000 is ahead of the WAL flush &lt;br /&gt;
        position of this server 0/3000060&lt;br /&gt;
&lt;br /&gt;
The replica instance needed to be re-initialised&lt;br /&gt;
patronictl -c /etc/patroni/patroni.yml reinit &amp;lt;scope&amp;gt; &amp;lt;name&amp;gt;&lt;br /&gt;
VIP Manager&lt;br /&gt;
&lt;br /&gt;
Copy the latest release to your server : &lt;br /&gt;
&lt;br /&gt;
 &lt;br /&gt;
sudo dpkg -i vip-manager_2.8.0_Linux_x86_64.deb&lt;br /&gt;
sudo systemctl stop vip-manager&lt;br /&gt;
sudo vi /etc/patroni/vip-manager.yml&lt;br /&gt;
&lt;br /&gt;
The trigger key below contains the namespace and scope of the Patroni service.&lt;br /&gt;
ip: 192.168.59.100&lt;br /&gt;
netmask: 24&lt;br /&gt;
interface: enp0s3&lt;br /&gt;
trigger-key: &amp;quot;/db/cluster_1/leader&amp;quot;&lt;br /&gt;
dcs-type: etcd&lt;br /&gt;
dcs-endpoints: http://192.168.59.101:2379&lt;br /&gt;
sudo vi /etc/systemd/system/multi-user.target.wants/vip-manager.service&lt;br /&gt;
[Unit]&lt;br /&gt;
Description=Manages Virtual IP for Patroni&lt;br /&gt;
After=network-online.target&lt;br /&gt;
Before=patroni.service&lt;br /&gt;
&lt;br /&gt;
[Service]&lt;br /&gt;
Type=simple&lt;br /&gt;
ExecStart=/usr/bin/vip-manager --config=/etc/patroni/vip-manager.yml&lt;br /&gt;
Restart=on-failure&lt;br /&gt;
&lt;br /&gt;
[Install]&lt;br /&gt;
WantedBy=multi-user.target&lt;br /&gt;
sudo systemctl daemon-reload&lt;br /&gt;
sudo systemctl start vip-manager&lt;br /&gt;
sudo journalctl -u vip-manager.service -n 100 -f&lt;br /&gt;
PGBackRest&lt;br /&gt;
&lt;br /&gt;
Setup the repository on Debian3&lt;br /&gt;
&lt;br /&gt;
Install pgbackrest on all the servers …&lt;br /&gt;
sudo apt install pgbackrest&lt;br /&gt;
sudo mkdir -p -m 770 /var/log/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/log/pgbackrest&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest&lt;br /&gt;
sudo touch /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chmod 640 /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
sudo chown postgres:postgres /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
&lt;br /&gt;
On debian3, create the directory that will hold the backups …&lt;br /&gt;
sudo mkdir -p /etc/pgbackrest/conf.d&lt;br /&gt;
sudo mkdir -p /var/lib/pgbackrest&lt;br /&gt;
sudo chmod 750 /var/lib/pgbackrest&lt;br /&gt;
sudo chown postgres:postgres /var/lib/pgbackrest&lt;br /&gt;
Setup SSH&lt;br /&gt;
&lt;br /&gt;
We will need a passwordless SSH connection between the repository and the database servers.&lt;br /&gt;
For this setup I was using postgres as the user that controls pgbackrest.&lt;br /&gt;
# debian1&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp id_ras.pub postgres@debian3&lt;br /&gt;
ssh postgres@debian3&lt;br /&gt;
# debian3&lt;br /&gt;
ssh-keygen&lt;br /&gt;
scp ./.ssh/id_ras.pub postgres@debian1&lt;br /&gt;
ssh postgres@debian1&lt;br /&gt;
Configuration&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the repository&lt;br /&gt;
vi /etc/pgbackrest/pgbackrest.conf&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-host=debian1&lt;br /&gt;
pg1-path=/var/lib/postgresql/17 &lt;br /&gt;
&lt;br /&gt;
[global]&lt;br /&gt;
repo1-cipher-pass=zWaf6XtpjIVZC5444yXB+cgFDFl7MxGlgkZSaoPv&lt;br /&gt;
repo1-cipher-type=aes-256-cbc&lt;br /&gt;
repo1-path=/var/lib/pgbackrest&lt;br /&gt;
repo1-retention-full=2&lt;br /&gt;
start-fast=y&lt;br /&gt;
&lt;br /&gt;
As the postgres user create the configuration file on the database server&lt;br /&gt;
[cybertec]&lt;br /&gt;
pg1-path=/var/lib/postgresql/17&lt;br /&gt;
 &lt;br /&gt;
[global]&lt;br /&gt;
log-level-file=detail&lt;br /&gt;
repo1-host=debian3 &lt;br /&gt;
repo1-host-user=postgres&lt;br /&gt;
&lt;br /&gt;
Alter the archive_command parameter so that it uses the pgbackrest executable&lt;br /&gt;
patronictl edit-config --pg archive_command=&amp;quot;pgbackrest \&lt;br /&gt;
                       --stanza=cybertec archive-push %p&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Create a Stanza on the repository server and confirm it is working&lt;br /&gt;
pgbackrest --stanza=cybertec stanza-create&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;br /&gt;
&lt;br /&gt;
Also check if the Stanza is correct on the database server.&lt;br /&gt;
pgbackrest --stanza=cybertec check&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Main_Page&amp;diff=108</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Main_Page&amp;diff=108"/>
		<updated>2025-08-03T15:05:23Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Useful Links ==&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User&#039;s Guide]&lt;br /&gt;
* [https://www.postgresql.org/download PostgreSQL Downloads]&lt;br /&gt;
* [https://learn.microsoft.com/en-us/azure/postgresql PostgreSQL Flexible Server]&lt;br /&gt;
* [https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_PostgreSQL.html PostgreSQL RDS]&lt;br /&gt;
* [https://docs.timescale.com/self-hosted/latest TimescaleDB Self-Hosted]&lt;br /&gt;
* [https://patroni.readthedocs.io/en/latest Patroni Documentation]&lt;br /&gt;
* [https://hub.docker.com/search Docker Hub]&lt;br /&gt;
&lt;br /&gt;
== PostgreSQL ==&lt;br /&gt;
* [[Patroni Cluster]]&lt;br /&gt;
* [[TimeScale Notes]]&lt;br /&gt;
* [[TimeScale Training]]&lt;br /&gt;
* [[Oracle PostgreSQL Migration]]&lt;br /&gt;
&lt;br /&gt;
== Getting started ==&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]&lt;br /&gt;
* [https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/ MediaWiki release mailing list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Oracle_PostgreSQL_Migration&amp;diff=107</id>
		<title>Oracle PostgreSQL Migration</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Oracle_PostgreSQL_Migration&amp;diff=107"/>
		<updated>2025-08-03T14:58:33Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: /* Migration Tools */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;===Migration Tools===&lt;br /&gt;
&lt;br /&gt;
[[Ora2pg]] &amp;lt;/br&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Oracle_PostgreSQL_Migration&amp;diff=105</id>
		<title>Oracle PostgreSQL Migration</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Oracle_PostgreSQL_Migration&amp;diff=105"/>
		<updated>2025-08-03T14:51:06Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;===Migration Tools===&lt;br /&gt;
&lt;br /&gt;
[[Ora2pg]] &amp;lt;/br&amp;gt;&lt;br /&gt;
[[Conversion Notes]]&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Oracle_PostgreSQL_Migration&amp;diff=104</id>
		<title>Oracle PostgreSQL Migration</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Oracle_PostgreSQL_Migration&amp;diff=104"/>
		<updated>2025-08-03T14:50:28Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;===Migration Tools===&lt;br /&gt;
&lt;br /&gt;
[[Ora2pg]]&lt;br /&gt;
[[Conversion Notes]]&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Ora2pg&amp;diff=103</id>
		<title>Ora2pg</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Ora2pg&amp;diff=103"/>
		<updated>2025-06-06T10:07:52Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Documentation ===&lt;br /&gt;
https://ora2pg.darold.net/documentation.html&lt;br /&gt;
&lt;br /&gt;
=== Pre-Requisites ===&lt;br /&gt;
Install a full Oracle client or  and instant client &amp;lt;/br&amp;gt;&lt;br /&gt;
An instant client must include the below 4 zip files&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-basic-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sqlplus-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sdk-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-jdbc-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Check that perl is installed and at version 5.10+&lt;br /&gt;
perl -v&lt;br /&gt;
export ORACLE_HOME=/home/ian/oracle/instantclient_23_7&lt;br /&gt;
export LD_LIBRARY_PATH=$ORACLE_HOME:$LD_LIBRARY_PATH&lt;br /&gt;
export PATH=$ORACLE_HOME:$PATH&lt;br /&gt;
perl -MCPAN -e &#039;install DBD::Oracle&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Installation ===&lt;br /&gt;
The latest version can be downloaded here ... &amp;lt;/br&amp;gt;&lt;br /&gt;
https://github.com/darold/ora2pg/releases&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
tar xvf ora2pg-25.0.tar.gz&lt;br /&gt;
cd ora2pg-25.0/&lt;br /&gt;
perl Makefile.PL&lt;br /&gt;
sudo make &amp;amp;&amp;amp; make install&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuration ===&lt;br /&gt;
Example conf file (/etc/ora2pg/ora2pg.conf.dist) &amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Important Parameters ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Set the Oracle home directory&lt;br /&gt;
ORACLE_HOME     /home/ian/oracle/instantclient_23_7&lt;br /&gt;
&lt;br /&gt;
# Set Oracle database connection (datasource, user, password)&lt;br /&gt;
ORACLE_DSN      dbi:Oracle:host=localhost;service_name=XEPDB1;port=1523&lt;br /&gt;
ORACLE_USER     system&lt;br /&gt;
ORACLE_PWD      ipswich&lt;br /&gt;
&lt;br /&gt;
# Export Oracle schema to PostgreSQL schema&lt;br /&gt;
EXPORT_SCHEMA   1&lt;br /&gt;
&lt;br /&gt;
# Oracle schema/owner to use&lt;br /&gt;
SCHEMA          HR&lt;br /&gt;
&lt;br /&gt;
# Enable/disable the CREATE SCHEMA SQL order at starting of the output file.&lt;br /&gt;
# It is enable by default and concern on TABLE export type.&lt;br /&gt;
CREATE_SCHEMA   1&lt;br /&gt;
COMPILE_SCHEMA  1&lt;br /&gt;
&lt;br /&gt;
# Type of export. Values can be the following keyword:&lt;br /&gt;
#       TABLE           Export tables, constraints, indexes, ...&lt;br /&gt;
#       PACKAGE         Export packages&lt;br /&gt;
#       INSERT          Export data from table as INSERT statement&lt;br /&gt;
#       COPY            Export data from table as COPY statement&lt;br /&gt;
#       VIEW            Export views&lt;br /&gt;
#       GRANT           Export grants&lt;br /&gt;
#       SEQUENCE        Export sequences&lt;br /&gt;
#       TRIGGER         Export triggers&lt;br /&gt;
#       FUNCTION        Export functions&lt;br /&gt;
#       PROCEDURE       Export procedures&lt;br /&gt;
#       TABLESPACE      Export tablespace (PostgreSQL &amp;gt;= 8 only)&lt;br /&gt;
#       TYPE            Export user defined Oracle types&lt;br /&gt;
#       PARTITION       Export range or list partition (PostgreSQL &amp;gt;= v8.4)&lt;br /&gt;
#       FDW             Export table as foreign data wrapper tables&lt;br /&gt;
#       MVIEW           Export materialized view as snapshot refresh view&lt;br /&gt;
#       QUERY           Convert Oracle SQL queries from a file.&lt;br /&gt;
#       KETTLE          Generate XML ktr template files to be used by Kettle.&lt;br /&gt;
#       DBLINK          Generate oracle foreign data wrapper server to use as dblink.&lt;br /&gt;
#       SYNONYM         Export Oracle&#039;s synonyms as views on other schema&#039;s objects.&lt;br /&gt;
#       DIRECTORY       Export Oracle&#039;s directories as external_file extension objects.&lt;br /&gt;
#       LOAD            Dispatch a list of queries over multiple PostgreSQl connections.&lt;br /&gt;
#       TEST            perform a diff between Oracle and PostgreSQL database.&lt;br /&gt;
#       TEST_COUNT      perform only a row count between Oracle and PostgreSQL tables.&lt;br /&gt;
#       TEST_VIEW       perform a count on both side of number of rows returned by views&lt;br /&gt;
#       TEST_DATA       perform data validation check on rows at both sides.&lt;br /&gt;
#       SEQUENCE_VALUES export DDL to set the last values of sequences&lt;br /&gt;
&lt;br /&gt;
TYPE            TABLE&lt;br /&gt;
&lt;br /&gt;
# Define the following directive to send export directly to a PostgreSQL&lt;br /&gt;
# database, this will disable file output. Note that these directives are only&lt;br /&gt;
# used for data export, other export need to be imported manually through the&lt;br /&gt;
# use of psql or any other PostgreSQL client.&lt;br /&gt;
PG_DSN          dbi:Pg:dbname=postgres;host=host.docker.internal;port=5433&lt;br /&gt;
PG_USER         postgres&lt;br /&gt;
PG_PWD          ipswich&lt;br /&gt;
&lt;br /&gt;
# By default all output is dump to STDOUT if not send directly to postgresql&lt;br /&gt;
# database (see above). Give a filename to save export to it. If you want&lt;br /&gt;
# a Gzip&#039;d compressed file just add the extension .gz to the filename (you&lt;br /&gt;
# need perl module Compress::Zlib from CPAN). Add extension .bz2 to use Bzip2&lt;br /&gt;
# compression.&lt;br /&gt;
OUTPUT          output.sql&lt;br /&gt;
&lt;br /&gt;
# Base directory where all dumped files must be written&lt;br /&gt;
OUTPUT_DIR      /home/ian/tmp&lt;br /&gt;
&lt;br /&gt;
# Enable PLSQL to PLPSQL conversion. This is a work in progress, feel&lt;br /&gt;
# free modify/add you own code and send me patches. The code is under&lt;br /&gt;
# function plsql_toplpgsql in Ora2PG/PLSQL.pm. Default enabled.&lt;br /&gt;
PLSQL_PGSQL     1&lt;br /&gt;
&lt;br /&gt;
# If you want to use functions defined in the Orafce library and prevent&lt;br /&gt;
# Ora2Pg to translate call to these function, enable this directive.&lt;br /&gt;
# The Orafce library can be found here: https://github.com/orafce/orafce&lt;br /&gt;
# By default Ora2pg rewrite add_month(), add_year(), date_trunc() and&lt;br /&gt;
# to_char() functions, but you may prefer to use the orafce version of&lt;br /&gt;
# these function that do not need any code transformation.&lt;br /&gt;
USE_ORAFCE      0&lt;br /&gt;
&lt;br /&gt;
# By default when using SHOW_REPORT the migration report is generated as&lt;br /&gt;
# simple text, enabling this directive will force ora2pg to create a report&lt;br /&gt;
# in HTML format.&lt;br /&gt;
DUMP_AS_HTML    0&lt;br /&gt;
&lt;br /&gt;
# Set the PostgreSQL major version number of the target database. Ex: 9.6 or 10&lt;br /&gt;
# Default is current major version at time of a new release. This replace the&lt;br /&gt;
# old PG_SUPPORTS_* configuration directives.&lt;br /&gt;
PG_VERSION      17&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Test Connectivity ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ora2pg -t SHOW_VERSION -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Other Example Commands ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ora2pg -t SHOW_SCHEMA -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -t SHOW_ENCODING -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_TABLE -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_COLUMN -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_REPORT -c /etc/ora2pg/ora2pg.conf (--dump_as_html &amp;gt; migration.html)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Migration Template ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mkdir -p /home/ian/oracle/ora2pg/migration&lt;br /&gt;
ora2pg --project_base /home/ian/oracle/ora2pg/migration --init_project test_project&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Copy ora2pg.conf file to the config directory &amp;lt;/br&amp;gt;&lt;br /&gt;
Following types will be exported: &amp;lt;/br&amp;gt;&lt;br /&gt;
SEQUENCE; SEQUENCE_VALUES; TABLE; PACKAGE; VIEW; GRANT; TRIGGER; FUNCTION; &amp;lt;/br&amp;gt;&lt;br /&gt;
PROCEDURE; TABLESPACE; PARTITION; TYPE; MVIEW; DBLINK; SYNONYM; DIRECTORY&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
./export_schema.sh&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Run highlighted command at end to export the copy the data. &amp;lt;/br&amp;gt;&lt;br /&gt;
Create password file and samples database.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
./import_all.sh -h localhost -p 5433 -d samples -U postgres -o hr&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Ora2pg&amp;diff=102</id>
		<title>Ora2pg</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Ora2pg&amp;diff=102"/>
		<updated>2025-06-06T10:07:06Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Documentation ===&lt;br /&gt;
https://ora2pg.darold.net/documentation.html&lt;br /&gt;
&lt;br /&gt;
=== Pre-Requisites ===&lt;br /&gt;
Install a full Oracle client or  and instant client &amp;lt;/br&amp;gt;&lt;br /&gt;
An instant client must include the below 4 zip files&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-basic-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sqlplus-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sdk-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-jdbc-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Install Perl 5.10+ &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export ORACLE_HOME=/home/ian/oracle/instantclient_23_7&lt;br /&gt;
export LD_LIBRARY_PATH=$ORACLE_HOME:$LD_LIBRARY_PATH&lt;br /&gt;
export PATH=$ORACLE_HOME:$PATH&lt;br /&gt;
perl -MCPAN -e &#039;install DBD::Oracle&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Installation ===&lt;br /&gt;
The latest version can be downloaded here ... &amp;lt;/br&amp;gt;&lt;br /&gt;
https://github.com/darold/ora2pg/releases&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
tar xvf ora2pg-25.0.tar.gz&lt;br /&gt;
cd ora2pg-25.0/&lt;br /&gt;
perl Makefile.PL&lt;br /&gt;
sudo make &amp;amp;&amp;amp; make install&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuration ===&lt;br /&gt;
Example conf file (/etc/ora2pg/ora2pg.conf.dist) &amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Important Parameters ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Set the Oracle home directory&lt;br /&gt;
ORACLE_HOME     /home/ian/oracle/instantclient_23_7&lt;br /&gt;
&lt;br /&gt;
# Set Oracle database connection (datasource, user, password)&lt;br /&gt;
ORACLE_DSN      dbi:Oracle:host=localhost;service_name=XEPDB1;port=1523&lt;br /&gt;
ORACLE_USER     system&lt;br /&gt;
ORACLE_PWD      ipswich&lt;br /&gt;
&lt;br /&gt;
# Export Oracle schema to PostgreSQL schema&lt;br /&gt;
EXPORT_SCHEMA   1&lt;br /&gt;
&lt;br /&gt;
# Oracle schema/owner to use&lt;br /&gt;
SCHEMA          HR&lt;br /&gt;
&lt;br /&gt;
# Enable/disable the CREATE SCHEMA SQL order at starting of the output file.&lt;br /&gt;
# It is enable by default and concern on TABLE export type.&lt;br /&gt;
CREATE_SCHEMA   1&lt;br /&gt;
COMPILE_SCHEMA  1&lt;br /&gt;
&lt;br /&gt;
# Type of export. Values can be the following keyword:&lt;br /&gt;
#       TABLE           Export tables, constraints, indexes, ...&lt;br /&gt;
#       PACKAGE         Export packages&lt;br /&gt;
#       INSERT          Export data from table as INSERT statement&lt;br /&gt;
#       COPY            Export data from table as COPY statement&lt;br /&gt;
#       VIEW            Export views&lt;br /&gt;
#       GRANT           Export grants&lt;br /&gt;
#       SEQUENCE        Export sequences&lt;br /&gt;
#       TRIGGER         Export triggers&lt;br /&gt;
#       FUNCTION        Export functions&lt;br /&gt;
#       PROCEDURE       Export procedures&lt;br /&gt;
#       TABLESPACE      Export tablespace (PostgreSQL &amp;gt;= 8 only)&lt;br /&gt;
#       TYPE            Export user defined Oracle types&lt;br /&gt;
#       PARTITION       Export range or list partition (PostgreSQL &amp;gt;= v8.4)&lt;br /&gt;
#       FDW             Export table as foreign data wrapper tables&lt;br /&gt;
#       MVIEW           Export materialized view as snapshot refresh view&lt;br /&gt;
#       QUERY           Convert Oracle SQL queries from a file.&lt;br /&gt;
#       KETTLE          Generate XML ktr template files to be used by Kettle.&lt;br /&gt;
#       DBLINK          Generate oracle foreign data wrapper server to use as dblink.&lt;br /&gt;
#       SYNONYM         Export Oracle&#039;s synonyms as views on other schema&#039;s objects.&lt;br /&gt;
#       DIRECTORY       Export Oracle&#039;s directories as external_file extension objects.&lt;br /&gt;
#       LOAD            Dispatch a list of queries over multiple PostgreSQl connections.&lt;br /&gt;
#       TEST            perform a diff between Oracle and PostgreSQL database.&lt;br /&gt;
#       TEST_COUNT      perform only a row count between Oracle and PostgreSQL tables.&lt;br /&gt;
#       TEST_VIEW       perform a count on both side of number of rows returned by views&lt;br /&gt;
#       TEST_DATA       perform data validation check on rows at both sides.&lt;br /&gt;
#       SEQUENCE_VALUES export DDL to set the last values of sequences&lt;br /&gt;
&lt;br /&gt;
TYPE            TABLE&lt;br /&gt;
&lt;br /&gt;
# Define the following directive to send export directly to a PostgreSQL&lt;br /&gt;
# database, this will disable file output. Note that these directives are only&lt;br /&gt;
# used for data export, other export need to be imported manually through the&lt;br /&gt;
# use of psql or any other PostgreSQL client.&lt;br /&gt;
PG_DSN          dbi:Pg:dbname=postgres;host=host.docker.internal;port=5433&lt;br /&gt;
PG_USER         postgres&lt;br /&gt;
PG_PWD          ipswich&lt;br /&gt;
&lt;br /&gt;
# By default all output is dump to STDOUT if not send directly to postgresql&lt;br /&gt;
# database (see above). Give a filename to save export to it. If you want&lt;br /&gt;
# a Gzip&#039;d compressed file just add the extension .gz to the filename (you&lt;br /&gt;
# need perl module Compress::Zlib from CPAN). Add extension .bz2 to use Bzip2&lt;br /&gt;
# compression.&lt;br /&gt;
OUTPUT          output.sql&lt;br /&gt;
&lt;br /&gt;
# Base directory where all dumped files must be written&lt;br /&gt;
OUTPUT_DIR      /home/ian/tmp&lt;br /&gt;
&lt;br /&gt;
# Enable PLSQL to PLPSQL conversion. This is a work in progress, feel&lt;br /&gt;
# free modify/add you own code and send me patches. The code is under&lt;br /&gt;
# function plsql_toplpgsql in Ora2PG/PLSQL.pm. Default enabled.&lt;br /&gt;
PLSQL_PGSQL     1&lt;br /&gt;
&lt;br /&gt;
# If you want to use functions defined in the Orafce library and prevent&lt;br /&gt;
# Ora2Pg to translate call to these function, enable this directive.&lt;br /&gt;
# The Orafce library can be found here: https://github.com/orafce/orafce&lt;br /&gt;
# By default Ora2pg rewrite add_month(), add_year(), date_trunc() and&lt;br /&gt;
# to_char() functions, but you may prefer to use the orafce version of&lt;br /&gt;
# these function that do not need any code transformation.&lt;br /&gt;
USE_ORAFCE      0&lt;br /&gt;
&lt;br /&gt;
# By default when using SHOW_REPORT the migration report is generated as&lt;br /&gt;
# simple text, enabling this directive will force ora2pg to create a report&lt;br /&gt;
# in HTML format.&lt;br /&gt;
DUMP_AS_HTML    0&lt;br /&gt;
&lt;br /&gt;
# Set the PostgreSQL major version number of the target database. Ex: 9.6 or 10&lt;br /&gt;
# Default is current major version at time of a new release. This replace the&lt;br /&gt;
# old PG_SUPPORTS_* configuration directives.&lt;br /&gt;
PG_VERSION      17&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Test Connectivity ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ora2pg -t SHOW_VERSION -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Other Example Commands ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ora2pg -t SHOW_SCHEMA -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -t SHOW_ENCODING -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_TABLE -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_COLUMN -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_REPORT -c /etc/ora2pg/ora2pg.conf (--dump_as_html &amp;gt; migration.html)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Migration Template ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mkdir -p /home/ian/oracle/ora2pg/migration&lt;br /&gt;
ora2pg --project_base /home/ian/oracle/ora2pg/migration --init_project test_project&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Copy ora2pg.conf file to the config directory &amp;lt;/br&amp;gt;&lt;br /&gt;
Following types will be exported: &amp;lt;/br&amp;gt;&lt;br /&gt;
SEQUENCE; SEQUENCE_VALUES; TABLE; PACKAGE; VIEW; GRANT; TRIGGER; FUNCTION; &amp;lt;/br&amp;gt;&lt;br /&gt;
PROCEDURE; TABLESPACE; PARTITION; TYPE; MVIEW; DBLINK; SYNONYM; DIRECTORY&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
./export_schema.sh&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Run highlighted command at end to export the copy the data. &amp;lt;/br&amp;gt;&lt;br /&gt;
Create password file and samples database.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
./import_all.sh -h localhost -p 5433 -d samples -U postgres -o hr&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Oracle_PostgreSQL_Migration&amp;diff=101</id>
		<title>Oracle PostgreSQL Migration</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Oracle_PostgreSQL_Migration&amp;diff=101"/>
		<updated>2025-06-06T10:05:37Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;===Migration Tools===&lt;br /&gt;
&lt;br /&gt;
[[Ora2pg]]&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Ora2pg&amp;diff=100</id>
		<title>Ora2pg</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Ora2pg&amp;diff=100"/>
		<updated>2025-06-06T10:05:14Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: Ian.readman moved page Pg2ora to Ora2pg without leaving a redirect: Misspelled title&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Documentation ===&lt;br /&gt;
https://ora2pg.darold.net/documentation.html&lt;br /&gt;
&lt;br /&gt;
=== Pre-Requisites ===&lt;br /&gt;
Install Full Oracle Client or Instant Client &amp;lt;/br&amp;gt;&lt;br /&gt;
Instant Client must include the below 4 zip files&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-basic-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sqlplus-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sdk-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-jdbc-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Install Perl 5.10+ &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export ORACLE_HOME=/home/ian/oracle/instantclient_23_7&lt;br /&gt;
export LD_LIBRARY_PATH=$ORACLE_HOME:$LD_LIBRARY_PATH&lt;br /&gt;
export PATH=$ORACLE_HOME:$PATH&lt;br /&gt;
perl -MCPAN -e &#039;install DBD::Oracle&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Installation ===&lt;br /&gt;
https://github.com/darold/ora2pg/releases&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
tar xvf ora2pg-25.0.tar.gz&lt;br /&gt;
cd ora2pg-25.0/&lt;br /&gt;
perl Makefile.PL&lt;br /&gt;
sudo make &amp;amp;&amp;amp; make install&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuration ===&lt;br /&gt;
Example conf file (/etc/ora2pg/ora2pg.conf.dist) &amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Important Parameters ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Set the Oracle home directory&lt;br /&gt;
ORACLE_HOME     /home/ian/oracle/instantclient_23_7&lt;br /&gt;
&lt;br /&gt;
# Set Oracle database connection (datasource, user, password)&lt;br /&gt;
ORACLE_DSN      dbi:Oracle:host=localhost;service_name=XEPDB1;port=1523&lt;br /&gt;
ORACLE_USER     system&lt;br /&gt;
ORACLE_PWD      ipswich&lt;br /&gt;
&lt;br /&gt;
# Export Oracle schema to PostgreSQL schema&lt;br /&gt;
EXPORT_SCHEMA   1&lt;br /&gt;
&lt;br /&gt;
# Oracle schema/owner to use&lt;br /&gt;
SCHEMA          HR&lt;br /&gt;
&lt;br /&gt;
# Enable/disable the CREATE SCHEMA SQL order at starting of the output file.&lt;br /&gt;
# It is enable by default and concern on TABLE export type.&lt;br /&gt;
CREATE_SCHEMA   1&lt;br /&gt;
COMPILE_SCHEMA  1&lt;br /&gt;
&lt;br /&gt;
# Type of export. Values can be the following keyword:&lt;br /&gt;
#       TABLE           Export tables, constraints, indexes, ...&lt;br /&gt;
#       PACKAGE         Export packages&lt;br /&gt;
#       INSERT          Export data from table as INSERT statement&lt;br /&gt;
#       COPY            Export data from table as COPY statement&lt;br /&gt;
#       VIEW            Export views&lt;br /&gt;
#       GRANT           Export grants&lt;br /&gt;
#       SEQUENCE        Export sequences&lt;br /&gt;
#       TRIGGER         Export triggers&lt;br /&gt;
#       FUNCTION        Export functions&lt;br /&gt;
#       PROCEDURE       Export procedures&lt;br /&gt;
#       TABLESPACE      Export tablespace (PostgreSQL &amp;gt;= 8 only)&lt;br /&gt;
#       TYPE            Export user defined Oracle types&lt;br /&gt;
#       PARTITION       Export range or list partition (PostgreSQL &amp;gt;= v8.4)&lt;br /&gt;
#       FDW             Export table as foreign data wrapper tables&lt;br /&gt;
#       MVIEW           Export materialized view as snapshot refresh view&lt;br /&gt;
#       QUERY           Convert Oracle SQL queries from a file.&lt;br /&gt;
#       KETTLE          Generate XML ktr template files to be used by Kettle.&lt;br /&gt;
#       DBLINK          Generate oracle foreign data wrapper server to use as dblink.&lt;br /&gt;
#       SYNONYM         Export Oracle&#039;s synonyms as views on other schema&#039;s objects.&lt;br /&gt;
#       DIRECTORY       Export Oracle&#039;s directories as external_file extension objects.&lt;br /&gt;
#       LOAD            Dispatch a list of queries over multiple PostgreSQl connections.&lt;br /&gt;
#       TEST            perform a diff between Oracle and PostgreSQL database.&lt;br /&gt;
#       TEST_COUNT      perform only a row count between Oracle and PostgreSQL tables.&lt;br /&gt;
#       TEST_VIEW       perform a count on both side of number of rows returned by views&lt;br /&gt;
#       TEST_DATA       perform data validation check on rows at both sides.&lt;br /&gt;
#       SEQUENCE_VALUES export DDL to set the last values of sequences&lt;br /&gt;
&lt;br /&gt;
TYPE            TABLE&lt;br /&gt;
&lt;br /&gt;
# Define the following directive to send export directly to a PostgreSQL&lt;br /&gt;
# database, this will disable file output. Note that these directives are only&lt;br /&gt;
# used for data export, other export need to be imported manually through the&lt;br /&gt;
# use of psql or any other PostgreSQL client.&lt;br /&gt;
PG_DSN          dbi:Pg:dbname=postgres;host=host.docker.internal;port=5433&lt;br /&gt;
PG_USER         postgres&lt;br /&gt;
PG_PWD          ipswich&lt;br /&gt;
&lt;br /&gt;
# By default all output is dump to STDOUT if not send directly to postgresql&lt;br /&gt;
# database (see above). Give a filename to save export to it. If you want&lt;br /&gt;
# a Gzip&#039;d compressed file just add the extension .gz to the filename (you&lt;br /&gt;
# need perl module Compress::Zlib from CPAN). Add extension .bz2 to use Bzip2&lt;br /&gt;
# compression.&lt;br /&gt;
OUTPUT          output.sql&lt;br /&gt;
&lt;br /&gt;
# Base directory where all dumped files must be written&lt;br /&gt;
OUTPUT_DIR      /home/ian/tmp&lt;br /&gt;
&lt;br /&gt;
# Enable PLSQL to PLPSQL conversion. This is a work in progress, feel&lt;br /&gt;
# free modify/add you own code and send me patches. The code is under&lt;br /&gt;
# function plsql_toplpgsql in Ora2PG/PLSQL.pm. Default enabled.&lt;br /&gt;
PLSQL_PGSQL     1&lt;br /&gt;
&lt;br /&gt;
# If you want to use functions defined in the Orafce library and prevent&lt;br /&gt;
# Ora2Pg to translate call to these function, enable this directive.&lt;br /&gt;
# The Orafce library can be found here: https://github.com/orafce/orafce&lt;br /&gt;
# By default Ora2pg rewrite add_month(), add_year(), date_trunc() and&lt;br /&gt;
# to_char() functions, but you may prefer to use the orafce version of&lt;br /&gt;
# these function that do not need any code transformation.&lt;br /&gt;
USE_ORAFCE      0&lt;br /&gt;
&lt;br /&gt;
# By default when using SHOW_REPORT the migration report is generated as&lt;br /&gt;
# simple text, enabling this directive will force ora2pg to create a report&lt;br /&gt;
# in HTML format.&lt;br /&gt;
DUMP_AS_HTML    0&lt;br /&gt;
&lt;br /&gt;
# Set the PostgreSQL major version number of the target database. Ex: 9.6 or 10&lt;br /&gt;
# Default is current major version at time of a new release. This replace the&lt;br /&gt;
# old PG_SUPPORTS_* configuration directives.&lt;br /&gt;
PG_VERSION      17&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Test Connectivity ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ora2pg -t SHOW_VERSION -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Other Example Commands ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ora2pg -t SHOW_SCHEMA -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -t SHOW_ENCODING -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_TABLE -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_COLUMN -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_REPORT -c /etc/ora2pg/ora2pg.conf (--dump_as_html &amp;gt; migration.html)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Migration Template ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mkdir -p /home/ian/oracle/ora2pg/migration&lt;br /&gt;
ora2pg --project_base /home/ian/oracle/ora2pg/migration --init_project test_project&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Copy ora2pg.conf file to the config directory &amp;lt;/br&amp;gt;&lt;br /&gt;
Following types will be exported: &amp;lt;/br&amp;gt;&lt;br /&gt;
SEQUENCE; SEQUENCE_VALUES; TABLE; PACKAGE; VIEW; GRANT; TRIGGER; FUNCTION; &amp;lt;/br&amp;gt;&lt;br /&gt;
PROCEDURE; TABLESPACE; PARTITION; TYPE; MVIEW; DBLINK; SYNONYM; DIRECTORY&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
./export_schema.sh&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Run highlighted command at end to export the copy the data. &amp;lt;/br&amp;gt;&lt;br /&gt;
Create password file and samples database.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
./import_all.sh -h localhost -p 5433 -d samples -U postgres -o hr&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Ora2pg&amp;diff=99</id>
		<title>Ora2pg</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Ora2pg&amp;diff=99"/>
		<updated>2025-06-06T10:03:40Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Documentation ===&lt;br /&gt;
https://ora2pg.darold.net/documentation.html&lt;br /&gt;
&lt;br /&gt;
=== Pre-Requisites ===&lt;br /&gt;
Install Full Oracle Client or Instant Client &amp;lt;/br&amp;gt;&lt;br /&gt;
Instant Client must include the below 4 zip files&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-basic-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sqlplus-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sdk-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-jdbc-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Install Perl 5.10+ &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export ORACLE_HOME=/home/ian/oracle/instantclient_23_7&lt;br /&gt;
export LD_LIBRARY_PATH=$ORACLE_HOME:$LD_LIBRARY_PATH&lt;br /&gt;
export PATH=$ORACLE_HOME:$PATH&lt;br /&gt;
perl -MCPAN -e &#039;install DBD::Oracle&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Installation ===&lt;br /&gt;
https://github.com/darold/ora2pg/releases&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
tar xvf ora2pg-25.0.tar.gz&lt;br /&gt;
cd ora2pg-25.0/&lt;br /&gt;
perl Makefile.PL&lt;br /&gt;
sudo make &amp;amp;&amp;amp; make install&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuration ===&lt;br /&gt;
Example conf file (/etc/ora2pg/ora2pg.conf.dist) &amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Important Parameters ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Set the Oracle home directory&lt;br /&gt;
ORACLE_HOME     /home/ian/oracle/instantclient_23_7&lt;br /&gt;
&lt;br /&gt;
# Set Oracle database connection (datasource, user, password)&lt;br /&gt;
ORACLE_DSN      dbi:Oracle:host=localhost;service_name=XEPDB1;port=1523&lt;br /&gt;
ORACLE_USER     system&lt;br /&gt;
ORACLE_PWD      ipswich&lt;br /&gt;
&lt;br /&gt;
# Export Oracle schema to PostgreSQL schema&lt;br /&gt;
EXPORT_SCHEMA   1&lt;br /&gt;
&lt;br /&gt;
# Oracle schema/owner to use&lt;br /&gt;
SCHEMA          HR&lt;br /&gt;
&lt;br /&gt;
# Enable/disable the CREATE SCHEMA SQL order at starting of the output file.&lt;br /&gt;
# It is enable by default and concern on TABLE export type.&lt;br /&gt;
CREATE_SCHEMA   1&lt;br /&gt;
COMPILE_SCHEMA  1&lt;br /&gt;
&lt;br /&gt;
# Type of export. Values can be the following keyword:&lt;br /&gt;
#       TABLE           Export tables, constraints, indexes, ...&lt;br /&gt;
#       PACKAGE         Export packages&lt;br /&gt;
#       INSERT          Export data from table as INSERT statement&lt;br /&gt;
#       COPY            Export data from table as COPY statement&lt;br /&gt;
#       VIEW            Export views&lt;br /&gt;
#       GRANT           Export grants&lt;br /&gt;
#       SEQUENCE        Export sequences&lt;br /&gt;
#       TRIGGER         Export triggers&lt;br /&gt;
#       FUNCTION        Export functions&lt;br /&gt;
#       PROCEDURE       Export procedures&lt;br /&gt;
#       TABLESPACE      Export tablespace (PostgreSQL &amp;gt;= 8 only)&lt;br /&gt;
#       TYPE            Export user defined Oracle types&lt;br /&gt;
#       PARTITION       Export range or list partition (PostgreSQL &amp;gt;= v8.4)&lt;br /&gt;
#       FDW             Export table as foreign data wrapper tables&lt;br /&gt;
#       MVIEW           Export materialized view as snapshot refresh view&lt;br /&gt;
#       QUERY           Convert Oracle SQL queries from a file.&lt;br /&gt;
#       KETTLE          Generate XML ktr template files to be used by Kettle.&lt;br /&gt;
#       DBLINK          Generate oracle foreign data wrapper server to use as dblink.&lt;br /&gt;
#       SYNONYM         Export Oracle&#039;s synonyms as views on other schema&#039;s objects.&lt;br /&gt;
#       DIRECTORY       Export Oracle&#039;s directories as external_file extension objects.&lt;br /&gt;
#       LOAD            Dispatch a list of queries over multiple PostgreSQl connections.&lt;br /&gt;
#       TEST            perform a diff between Oracle and PostgreSQL database.&lt;br /&gt;
#       TEST_COUNT      perform only a row count between Oracle and PostgreSQL tables.&lt;br /&gt;
#       TEST_VIEW       perform a count on both side of number of rows returned by views&lt;br /&gt;
#       TEST_DATA       perform data validation check on rows at both sides.&lt;br /&gt;
#       SEQUENCE_VALUES export DDL to set the last values of sequences&lt;br /&gt;
&lt;br /&gt;
TYPE            TABLE&lt;br /&gt;
&lt;br /&gt;
# Define the following directive to send export directly to a PostgreSQL&lt;br /&gt;
# database, this will disable file output. Note that these directives are only&lt;br /&gt;
# used for data export, other export need to be imported manually through the&lt;br /&gt;
# use of psql or any other PostgreSQL client.&lt;br /&gt;
PG_DSN          dbi:Pg:dbname=postgres;host=host.docker.internal;port=5433&lt;br /&gt;
PG_USER         postgres&lt;br /&gt;
PG_PWD          ipswich&lt;br /&gt;
&lt;br /&gt;
# By default all output is dump to STDOUT if not send directly to postgresql&lt;br /&gt;
# database (see above). Give a filename to save export to it. If you want&lt;br /&gt;
# a Gzip&#039;d compressed file just add the extension .gz to the filename (you&lt;br /&gt;
# need perl module Compress::Zlib from CPAN). Add extension .bz2 to use Bzip2&lt;br /&gt;
# compression.&lt;br /&gt;
OUTPUT          output.sql&lt;br /&gt;
&lt;br /&gt;
# Base directory where all dumped files must be written&lt;br /&gt;
OUTPUT_DIR      /home/ian/tmp&lt;br /&gt;
&lt;br /&gt;
# Enable PLSQL to PLPSQL conversion. This is a work in progress, feel&lt;br /&gt;
# free modify/add you own code and send me patches. The code is under&lt;br /&gt;
# function plsql_toplpgsql in Ora2PG/PLSQL.pm. Default enabled.&lt;br /&gt;
PLSQL_PGSQL     1&lt;br /&gt;
&lt;br /&gt;
# If you want to use functions defined in the Orafce library and prevent&lt;br /&gt;
# Ora2Pg to translate call to these function, enable this directive.&lt;br /&gt;
# The Orafce library can be found here: https://github.com/orafce/orafce&lt;br /&gt;
# By default Ora2pg rewrite add_month(), add_year(), date_trunc() and&lt;br /&gt;
# to_char() functions, but you may prefer to use the orafce version of&lt;br /&gt;
# these function that do not need any code transformation.&lt;br /&gt;
USE_ORAFCE      0&lt;br /&gt;
&lt;br /&gt;
# By default when using SHOW_REPORT the migration report is generated as&lt;br /&gt;
# simple text, enabling this directive will force ora2pg to create a report&lt;br /&gt;
# in HTML format.&lt;br /&gt;
DUMP_AS_HTML    0&lt;br /&gt;
&lt;br /&gt;
# Set the PostgreSQL major version number of the target database. Ex: 9.6 or 10&lt;br /&gt;
# Default is current major version at time of a new release. This replace the&lt;br /&gt;
# old PG_SUPPORTS_* configuration directives.&lt;br /&gt;
PG_VERSION      17&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Test Connectivity ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ora2pg -t SHOW_VERSION -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Other Example Commands ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ora2pg -t SHOW_SCHEMA -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -t SHOW_ENCODING -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_TABLE -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_COLUMN -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_REPORT -c /etc/ora2pg/ora2pg.conf (--dump_as_html &amp;gt; migration.html)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Migration Template ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mkdir -p /home/ian/oracle/ora2pg/migration&lt;br /&gt;
ora2pg --project_base /home/ian/oracle/ora2pg/migration --init_project test_project&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Copy ora2pg.conf file to the config directory &amp;lt;/br&amp;gt;&lt;br /&gt;
Following types will be exported: &amp;lt;/br&amp;gt;&lt;br /&gt;
SEQUENCE; SEQUENCE_VALUES; TABLE; PACKAGE; VIEW; GRANT; TRIGGER; FUNCTION; &amp;lt;/br&amp;gt;&lt;br /&gt;
PROCEDURE; TABLESPACE; PARTITION; TYPE; MVIEW; DBLINK; SYNONYM; DIRECTORY&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
./export_schema.sh&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Run highlighted command at end to export the copy the data. &amp;lt;/br&amp;gt;&lt;br /&gt;
Create password file and samples database.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
./import_all.sh -h localhost -p 5433 -d samples -U postgres -o hr&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Ora2pg&amp;diff=98</id>
		<title>Ora2pg</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Ora2pg&amp;diff=98"/>
		<updated>2025-06-06T10:02:54Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Documentation ===&lt;br /&gt;
https://ora2pg.darold.net/documentation.html&lt;br /&gt;
&lt;br /&gt;
=== Pre-Requisites ===&lt;br /&gt;
Install Full Oracle Client or Instant Client &amp;lt;/br&amp;gt;&lt;br /&gt;
Instant Client must include the below 4 zip files&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-basic-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sqlplus-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sdk-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-jdbc-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Install Perl 5.10+ &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export ORACLE_HOME=/home/ian/oracle/instantclient_23_7&lt;br /&gt;
export LD_LIBRARY_PATH=$ORACLE_HOME:$LD_LIBRARY_PATH&lt;br /&gt;
export PATH=$ORACLE_HOME:$PATH&lt;br /&gt;
perl -MCPAN -e &#039;install DBD::Oracle&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Installation ===&lt;br /&gt;
https://github.com/darold/ora2pg/releases&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
tar xvf ora2pg-25.0.tar.gz&lt;br /&gt;
cd ora2pg-25.0/&lt;br /&gt;
perl Makefile.PL&lt;br /&gt;
sudo make &amp;amp;&amp;amp; make install&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuration ===&lt;br /&gt;
Example conf file (/etc/ora2pg/ora2pg.conf.dist) &amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Important Parameters ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Set the Oracle home directory&lt;br /&gt;
ORACLE_HOME     /home/ian/oracle/instantclient_23_7&lt;br /&gt;
&lt;br /&gt;
# Set Oracle database connection (datasource, user, password)&lt;br /&gt;
ORACLE_DSN      dbi:Oracle:host=localhost;service_name=XEPDB1;port=1523&lt;br /&gt;
ORACLE_USER     system&lt;br /&gt;
ORACLE_PWD      ipswich&lt;br /&gt;
&lt;br /&gt;
# Export Oracle schema to PostgreSQL schema&lt;br /&gt;
EXPORT_SCHEMA   1&lt;br /&gt;
&lt;br /&gt;
# Oracle schema/owner to use&lt;br /&gt;
SCHEMA          HR&lt;br /&gt;
&lt;br /&gt;
# Enable/disable the CREATE SCHEMA SQL order at starting of the output file.&lt;br /&gt;
# It is enable by default and concern on TABLE export type.&lt;br /&gt;
CREATE_SCHEMA   1&lt;br /&gt;
COMPILE_SCHEMA  1&lt;br /&gt;
&lt;br /&gt;
# Type of export. Values can be the following keyword:&lt;br /&gt;
#       TABLE           Export tables, constraints, indexes, ...&lt;br /&gt;
#       PACKAGE         Export packages&lt;br /&gt;
#       INSERT          Export data from table as INSERT statement&lt;br /&gt;
#       COPY            Export data from table as COPY statement&lt;br /&gt;
#       VIEW            Export views&lt;br /&gt;
#       GRANT           Export grants&lt;br /&gt;
#       SEQUENCE        Export sequences&lt;br /&gt;
#       TRIGGER         Export triggers&lt;br /&gt;
#       FUNCTION        Export functions&lt;br /&gt;
#       PROCEDURE       Export procedures&lt;br /&gt;
#       TABLESPACE      Export tablespace (PostgreSQL &amp;gt;= 8 only)&lt;br /&gt;
#       TYPE            Export user defined Oracle types&lt;br /&gt;
#       PARTITION       Export range or list partition (PostgreSQL &amp;gt;= v8.4)&lt;br /&gt;
#       FDW             Export table as foreign data wrapper tables&lt;br /&gt;
#       MVIEW           Export materialized view as snapshot refresh view&lt;br /&gt;
#       QUERY           Convert Oracle SQL queries from a file.&lt;br /&gt;
#       KETTLE          Generate XML ktr template files to be used by Kettle.&lt;br /&gt;
#       DBLINK          Generate oracle foreign data wrapper server to use as dblink.&lt;br /&gt;
#       SYNONYM         Export Oracle&#039;s synonyms as views on other schema&#039;s objects.&lt;br /&gt;
#       DIRECTORY       Export Oracle&#039;s directories as external_file extension objects.&lt;br /&gt;
#       LOAD            Dispatch a list of queries over multiple PostgreSQl connections.&lt;br /&gt;
#       TEST            perform a diff between Oracle and PostgreSQL database.&lt;br /&gt;
#       TEST_COUNT      perform only a row count between Oracle and PostgreSQL tables.&lt;br /&gt;
#       TEST_VIEW       perform a count on both side of number of rows returned by views&lt;br /&gt;
#       TEST_DATA       perform data validation check on rows at both sides.&lt;br /&gt;
#       SEQUENCE_VALUES export DDL to set the last values of sequences&lt;br /&gt;
&lt;br /&gt;
TYPE            TABLE&lt;br /&gt;
&lt;br /&gt;
# Define the following directive to send export directly to a PostgreSQL&lt;br /&gt;
# database, this will disable file output. Note that these directives are only&lt;br /&gt;
# used for data export, other export need to be imported manually through the&lt;br /&gt;
# use of psql or any other PostgreSQL client.&lt;br /&gt;
PG_DSN          dbi:Pg:dbname=postgres;host=host.docker.internal;port=5433&lt;br /&gt;
PG_USER         postgres&lt;br /&gt;
PG_PWD          ipswich&lt;br /&gt;
&lt;br /&gt;
# By default all output is dump to STDOUT if not send directly to postgresql&lt;br /&gt;
# database (see above). Give a filename to save export to it. If you want&lt;br /&gt;
# a Gzip&#039;d compressed file just add the extension .gz to the filename (you&lt;br /&gt;
# need perl module Compress::Zlib from CPAN). Add extension .bz2 to use Bzip2&lt;br /&gt;
# compression.&lt;br /&gt;
OUTPUT          output.sql&lt;br /&gt;
&lt;br /&gt;
# Base directory where all dumped files must be written&lt;br /&gt;
OUTPUT_DIR      /home/ian/tmp&lt;br /&gt;
&lt;br /&gt;
# Enable PLSQL to PLPSQL conversion. This is a work in progress, feel&lt;br /&gt;
# free modify/add you own code and send me patches. The code is under&lt;br /&gt;
# function plsql_toplpgsql in Ora2PG/PLSQL.pm. Default enabled.&lt;br /&gt;
PLSQL_PGSQL     1&lt;br /&gt;
&lt;br /&gt;
# If you want to use functions defined in the Orafce library and prevent&lt;br /&gt;
# Ora2Pg to translate call to these function, enable this directive.&lt;br /&gt;
# The Orafce library can be found here: https://github.com/orafce/orafce&lt;br /&gt;
# By default Ora2pg rewrite add_month(), add_year(), date_trunc() and&lt;br /&gt;
# to_char() functions, but you may prefer to use the orafce version of&lt;br /&gt;
# these function that do not need any code transformation.&lt;br /&gt;
USE_ORAFCE      0&lt;br /&gt;
&lt;br /&gt;
# By default when using SHOW_REPORT the migration report is generated as&lt;br /&gt;
# simple text, enabling this directive will force ora2pg to create a report&lt;br /&gt;
# in HTML format.&lt;br /&gt;
DUMP_AS_HTML    0&lt;br /&gt;
&lt;br /&gt;
# Set the PostgreSQL major version number of the target database. Ex: 9.6 or 10&lt;br /&gt;
# Default is current major version at time of a new release. This replace the&lt;br /&gt;
# old PG_SUPPORTS_* configuration directives.&lt;br /&gt;
PG_VERSION      17&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Test Connectivity ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ora2pg -t SHOW_VERSION -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Other Example Commands ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ora2pg -t SHOW_SCHEMA -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -t SHOW_ENCODING -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_TABLE -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_COLUMN -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_REPORT -c /etc/ora2pg/ora2pg.conf (--dump_as_html &amp;gt; migration.html)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Migration Template ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mkdir -p /home/ian/oracle/ora2pg/migration&lt;br /&gt;
ora2pg --project_base /home/ian/oracle/ora2pg/migration --init_project test_project&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Copy ora2pg.conf file to the config directory &amp;lt;/br&amp;gt;&lt;br /&gt;
Following types will be exported: &amp;lt;/br&amp;gt;&lt;br /&gt;
SEQUENCE SEQUENCE_VALUES TABLE PACKAGE VIEW GRANT TRIGGER FUNCTION &amp;lt;/br&amp;gt;&lt;br /&gt;
PROCEDURE TABLESPACE PARTITION TYPE MVIEW DBLINK SYNONYM DIRECTORY&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
./export_schema.sh&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Run highlighted command at end to export the copy the data. &amp;lt;/br&amp;gt;&lt;br /&gt;
Create password file and samples database.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
./import_all.sh -h localhost -p 5433 -d samples -U postgres -o hr&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Ora2pg&amp;diff=97</id>
		<title>Ora2pg</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Ora2pg&amp;diff=97"/>
		<updated>2025-06-06T10:01:54Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Documentation ===&lt;br /&gt;
https://ora2pg.darold.net/documentation.html&lt;br /&gt;
&lt;br /&gt;
=== Pre-Requisites ===&lt;br /&gt;
Install Full Oracle Client or Instant Client &amp;lt;/br&amp;gt;&lt;br /&gt;
Instant Client must include the below 4 zip files&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-basic-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sqlplus-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sdk-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-jdbc-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Install Perl 5.10+ &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export ORACLE_HOME=/home/ian/oracle/instantclient_23_7&lt;br /&gt;
export LD_LIBRARY_PATH=$ORACLE_HOME:$LD_LIBRARY_PATH&lt;br /&gt;
export PATH=$ORACLE_HOME:$PATH&lt;br /&gt;
perl -MCPAN -e &#039;install DBD::Oracle&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Installation ===&lt;br /&gt;
https://github.com/darold/ora2pg/releases&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
tar xvf ora2pg-25.0.tar.gz&lt;br /&gt;
cd ora2pg-25.0/&lt;br /&gt;
perl Makefile.PL&lt;br /&gt;
sudo make &amp;amp;&amp;amp; make install&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Configuration ===&lt;br /&gt;
Example conf file (/etc/ora2pg/ora2pg.conf.dist) &amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Important Parameters ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
# Set the Oracle home directory&lt;br /&gt;
ORACLE_HOME     /home/ian/oracle/instantclient_23_7&lt;br /&gt;
&lt;br /&gt;
# Set Oracle database connection (datasource, user, password)&lt;br /&gt;
ORACLE_DSN      dbi:Oracle:host=localhost;service_name=XEPDB1;port=1523&lt;br /&gt;
ORACLE_USER     system&lt;br /&gt;
ORACLE_PWD      ipswich&lt;br /&gt;
&lt;br /&gt;
# Export Oracle schema to PostgreSQL schema&lt;br /&gt;
EXPORT_SCHEMA   1&lt;br /&gt;
&lt;br /&gt;
# Oracle schema/owner to use&lt;br /&gt;
SCHEMA          HR&lt;br /&gt;
&lt;br /&gt;
# Enable/disable the CREATE SCHEMA SQL order at starting of the output file.&lt;br /&gt;
# It is enable by default and concern on TABLE export type.&lt;br /&gt;
CREATE_SCHEMA   1&lt;br /&gt;
COMPILE_SCHEMA  1&lt;br /&gt;
&lt;br /&gt;
# Type of export. Values can be the following keyword:&lt;br /&gt;
#       TABLE           Export tables, constraints, indexes, ...&lt;br /&gt;
#       PACKAGE         Export packages&lt;br /&gt;
#       INSERT          Export data from table as INSERT statement&lt;br /&gt;
#       COPY            Export data from table as COPY statement&lt;br /&gt;
#       VIEW            Export views&lt;br /&gt;
#       GRANT           Export grants&lt;br /&gt;
#       SEQUENCE        Export sequences&lt;br /&gt;
#       TRIGGER         Export triggers&lt;br /&gt;
#       FUNCTION        Export functions&lt;br /&gt;
#       PROCEDURE       Export procedures&lt;br /&gt;
#       TABLESPACE      Export tablespace (PostgreSQL &amp;gt;= 8 only)&lt;br /&gt;
#       TYPE            Export user defined Oracle types&lt;br /&gt;
#       PARTITION       Export range or list partition (PostgreSQL &amp;gt;= v8.4)&lt;br /&gt;
#       FDW             Export table as foreign data wrapper tables&lt;br /&gt;
#       MVIEW           Export materialized view as snapshot refresh view&lt;br /&gt;
#       QUERY           Convert Oracle SQL queries from a file.&lt;br /&gt;
#       KETTLE          Generate XML ktr template files to be used by Kettle.&lt;br /&gt;
#       DBLINK          Generate oracle foreign data wrapper server to use as dblink.&lt;br /&gt;
#       SYNONYM         Export Oracle&#039;s synonyms as views on other schema&#039;s objects.&lt;br /&gt;
#       DIRECTORY       Export Oracle&#039;s directories as external_file extension objects.&lt;br /&gt;
#       LOAD            Dispatch a list of queries over multiple PostgreSQl connections.&lt;br /&gt;
#       TEST            perform a diff between Oracle and PostgreSQL database.&lt;br /&gt;
#       TEST_COUNT      perform only a row count between Oracle and PostgreSQL tables.&lt;br /&gt;
#       TEST_VIEW       perform a count on both side of number of rows returned by views&lt;br /&gt;
#       TEST_DATA       perform data validation check on rows at both sides.&lt;br /&gt;
#       SEQUENCE_VALUES export DDL to set the last values of sequences&lt;br /&gt;
&lt;br /&gt;
TYPE            TABLE&lt;br /&gt;
&lt;br /&gt;
# Define the following directive to send export directly to a PostgreSQL&lt;br /&gt;
# database, this will disable file output. Note that these directives are only&lt;br /&gt;
# used for data export, other export need to be imported manually through the&lt;br /&gt;
# use of psql or any other PostgreSQL client.&lt;br /&gt;
PG_DSN          dbi:Pg:dbname=postgres;host=host.docker.internal;port=5433&lt;br /&gt;
PG_USER         postgres&lt;br /&gt;
PG_PWD          ipswich&lt;br /&gt;
&lt;br /&gt;
# By default all output is dump to STDOUT if not send directly to postgresql&lt;br /&gt;
# database (see above). Give a filename to save export to it. If you want&lt;br /&gt;
# a Gzip&#039;d compressed file just add the extension .gz to the filename (you&lt;br /&gt;
# need perl module Compress::Zlib from CPAN). Add extension .bz2 to use Bzip2&lt;br /&gt;
# compression.&lt;br /&gt;
OUTPUT          output.sql&lt;br /&gt;
&lt;br /&gt;
# Base directory where all dumped files must be written&lt;br /&gt;
OUTPUT_DIR      /home/ian/tmp&lt;br /&gt;
&lt;br /&gt;
# Enable PLSQL to PLPSQL conversion. This is a work in progress, feel&lt;br /&gt;
# free modify/add you own code and send me patches. The code is under&lt;br /&gt;
# function plsql_toplpgsql in Ora2PG/PLSQL.pm. Default enabled.&lt;br /&gt;
PLSQL_PGSQL     1&lt;br /&gt;
&lt;br /&gt;
# If you want to use functions defined in the Orafce library and prevent&lt;br /&gt;
# Ora2Pg to translate call to these function, enable this directive.&lt;br /&gt;
# The Orafce library can be found here: https://github.com/orafce/orafce&lt;br /&gt;
# By default Ora2pg rewrite add_month(), add_year(), date_trunc() and&lt;br /&gt;
# to_char() functions, but you may prefer to use the orafce version of&lt;br /&gt;
# these function that do not need any code transformation.&lt;br /&gt;
USE_ORAFCE      0&lt;br /&gt;
&lt;br /&gt;
# By default when using SHOW_REPORT the migration report is generated as&lt;br /&gt;
# simple text, enabling this directive will force ora2pg to create a report&lt;br /&gt;
# in HTML format.&lt;br /&gt;
DUMP_AS_HTML    0&lt;br /&gt;
&lt;br /&gt;
# Set the PostgreSQL major version number of the target database. Ex: 9.6 or 10&lt;br /&gt;
# Default is current major version at time of a new release. This replace the&lt;br /&gt;
# old PG_SUPPORTS_* configuration directives.&lt;br /&gt;
PG_VERSION      17&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Test Connectivity ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ora2pg -t SHOW_VERSION -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Other Example Commands ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ora2pg -t SHOW_SCHEMA -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -t SHOW_ENCODING -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_TABLE -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_COLUMN -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_REPORT -c /etc/ora2pg/ora2pg.conf (--dump_as_html &amp;gt; migration.html)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Migration Template ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
mkdir -p /home/ian/oracle/ora2pg/migration&lt;br /&gt;
ora2pg --project_base /home/ian/oracle/ora2pg/migration --init_project test_project&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Copy ora2pg.conf file to the config directory &amp;lt;/br&amp;gt;&lt;br /&gt;
Following types will be exported: SEQUENCE SEQUENCE_VALUES TABLE PACKAGE VIEW GRANT TRIGGER FUNCTION &amp;lt;/br&amp;gt;&lt;br /&gt;
                                    PROCEDURE TABLESPACE PARTITION TYPE MVIEW DBLINK SYNONYM DIRECTORY&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
./export_schema.sh&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
Run highlighted command at end to export the copy the data. &amp;lt;/br&amp;gt;&lt;br /&gt;
Create password file and samples database.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
./import_all.sh -h localhost -p 5433 -d samples -U postgres -o hr&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Ora2pg&amp;diff=96</id>
		<title>Ora2pg</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Ora2pg&amp;diff=96"/>
		<updated>2025-06-06T09:58:56Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Documentation ===&lt;br /&gt;
https://ora2pg.darold.net/documentation.html&lt;br /&gt;
&lt;br /&gt;
=== Pre-Requisites ===&lt;br /&gt;
Install Full Oracle Client or Instant Client&lt;br /&gt;
Instant Client must include the below 4 zip files&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-basic-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sqlplus-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sdk-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-jdbc-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Install Perl 5.10+ &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
export ORACLE_HOME=/home/ian/oracle/instantclient_23_7&lt;br /&gt;
export LD_LIBRARY_PATH=$ORACLE_HOME:$LD_LIBRARY_PATH&lt;br /&gt;
export PATH=$ORACLE_HOME:$PATH&lt;br /&gt;
perl -MCPAN -e &#039;install DBD::Oracle&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Installation ===&lt;br /&gt;
https://github.com/darold/ora2pg/releases&lt;br /&gt;
&lt;br /&gt;
tar xvf ora2pg-25.0.tar.gz&lt;br /&gt;
cd ora2pg-25.0/&lt;br /&gt;
perl Makefile.PL&lt;br /&gt;
sudo make &amp;amp;&amp;amp; make install&lt;br /&gt;
&lt;br /&gt;
=== Configuration ===&lt;br /&gt;
Example conf file (/etc/ora2pg/ora2pg.conf.dist)&lt;br /&gt;
&lt;br /&gt;
Important Parameters:&lt;br /&gt;
# Set the Oracle home directory&lt;br /&gt;
ORACLE_HOME     /home/ian/oracle/instantclient_23_7&lt;br /&gt;
&lt;br /&gt;
# Set Oracle database connection (datasource, user, password)&lt;br /&gt;
ORACLE_DSN      dbi:Oracle:host=localhost;service_name=XEPDB1;port=1523&lt;br /&gt;
ORACLE_USER     system&lt;br /&gt;
ORACLE_PWD      ipswich&lt;br /&gt;
&lt;br /&gt;
# Export Oracle schema to PostgreSQL schema&lt;br /&gt;
EXPORT_SCHEMA   1&lt;br /&gt;
&lt;br /&gt;
# Oracle schema/owner to use&lt;br /&gt;
SCHEMA          HR&lt;br /&gt;
&lt;br /&gt;
# Enable/disable the CREATE SCHEMA SQL order at starting of the output file.&lt;br /&gt;
# It is enable by default and concern on TABLE export type.&lt;br /&gt;
CREATE_SCHEMA   1&lt;br /&gt;
COMPILE_SCHEMA  1&lt;br /&gt;
&lt;br /&gt;
# Type of export. Values can be the following keyword:&lt;br /&gt;
#       TABLE           Export tables, constraints, indexes, ...&lt;br /&gt;
#       PACKAGE         Export packages&lt;br /&gt;
#       INSERT          Export data from table as INSERT statement&lt;br /&gt;
#       COPY            Export data from table as COPY statement&lt;br /&gt;
#       VIEW            Export views&lt;br /&gt;
#       GRANT           Export grants&lt;br /&gt;
#       SEQUENCE        Export sequences&lt;br /&gt;
#       TRIGGER         Export triggers&lt;br /&gt;
#       FUNCTION        Export functions&lt;br /&gt;
#       PROCEDURE       Export procedures&lt;br /&gt;
#       TABLESPACE      Export tablespace (PostgreSQL &amp;gt;= 8 only)&lt;br /&gt;
#       TYPE            Export user defined Oracle types&lt;br /&gt;
#       PARTITION       Export range or list partition (PostgreSQL &amp;gt;= v8.4)&lt;br /&gt;
#       FDW             Export table as foreign data wrapper tables&lt;br /&gt;
#       MVIEW           Export materialized view as snapshot refresh view&lt;br /&gt;
#       QUERY           Convert Oracle SQL queries from a file.&lt;br /&gt;
#       KETTLE          Generate XML ktr template files to be used by Kettle.&lt;br /&gt;
#       DBLINK          Generate oracle foreign data wrapper server to use as dblink.&lt;br /&gt;
#       SYNONYM         Export Oracle&#039;s synonyms as views on other schema&#039;s objects.&lt;br /&gt;
#       DIRECTORY       Export Oracle&#039;s directories as external_file extension objects.&lt;br /&gt;
#       LOAD            Dispatch a list of queries over multiple PostgreSQl connections.&lt;br /&gt;
#       TEST            perform a diff between Oracle and PostgreSQL database.&lt;br /&gt;
#       TEST_COUNT      perform only a row count between Oracle and PostgreSQL tables.&lt;br /&gt;
#       TEST_VIEW       perform a count on both side of number of rows returned by views&lt;br /&gt;
#       TEST_DATA       perform data validation check on rows at both sides.&lt;br /&gt;
#       SEQUENCE_VALUES export DDL to set the last values of sequences&lt;br /&gt;
&lt;br /&gt;
TYPE            TABLE&lt;br /&gt;
&lt;br /&gt;
# Define the following directive to send export directly to a PostgreSQL&lt;br /&gt;
# database, this will disable file output. Note that these directives are only&lt;br /&gt;
# used for data export, other export need to be imported manually through the&lt;br /&gt;
# use of psql or any other PostgreSQL client.&lt;br /&gt;
PG_DSN          dbi:Pg:dbname=postgres;host=host.docker.internal;port=5433&lt;br /&gt;
PG_USER         postgres&lt;br /&gt;
PG_PWD          ipswich&lt;br /&gt;
&lt;br /&gt;
# By default all output is dump to STDOUT if not send directly to postgresql&lt;br /&gt;
# database (see above). Give a filename to save export to it. If you want&lt;br /&gt;
# a Gzip&#039;d compressed file just add the extension .gz to the filename (you&lt;br /&gt;
# need perl module Compress::Zlib from CPAN). Add extension .bz2 to use Bzip2&lt;br /&gt;
# compression.&lt;br /&gt;
OUTPUT          output.sql&lt;br /&gt;
&lt;br /&gt;
# Base directory where all dumped files must be written&lt;br /&gt;
OUTPUT_DIR      /home/ian/tmp&lt;br /&gt;
&lt;br /&gt;
# Enable PLSQL to PLPSQL conversion. This is a work in progress, feel&lt;br /&gt;
# free modify/add you own code and send me patches. The code is under&lt;br /&gt;
# function plsql_toplpgsql in Ora2PG/PLSQL.pm. Default enabled.&lt;br /&gt;
PLSQL_PGSQL     1&lt;br /&gt;
&lt;br /&gt;
# If you want to use functions defined in the Orafce library and prevent&lt;br /&gt;
# Ora2Pg to translate call to these function, enable this directive.&lt;br /&gt;
# The Orafce library can be found here: https://github.com/orafce/orafce&lt;br /&gt;
# By default Ora2pg rewrite add_month(), add_year(), date_trunc() and&lt;br /&gt;
# to_char() functions, but you may prefer to use the orafce version of&lt;br /&gt;
# these function that do not need any code transformation.&lt;br /&gt;
USE_ORAFCE      0&lt;br /&gt;
&lt;br /&gt;
# By default when using SHOW_REPORT the migration report is generated as&lt;br /&gt;
# simple text, enabling this directive will force ora2pg to create a report&lt;br /&gt;
# in HTML format.&lt;br /&gt;
DUMP_AS_HTML    0&lt;br /&gt;
&lt;br /&gt;
# Set the PostgreSQL major version number of the target database. Ex: 9.6 or 10&lt;br /&gt;
# Default is current major version at time of a new release. This replace the&lt;br /&gt;
# old PG_SUPPORTS_* configuration directives.&lt;br /&gt;
PG_VERSION      17&lt;br /&gt;
&lt;br /&gt;
==== Test Connectivity ====&lt;br /&gt;
ora2pg -t SHOW_VERSION -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
&lt;br /&gt;
==== Other Example Commands ====&lt;br /&gt;
ora2pg -t SHOW_SCHEMA -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -t SHOW_ENCODING -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_TABLE -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_COLUMN -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_REPORT -c /etc/ora2pg/ora2pg.conf (--dump_as_html &amp;gt; migration.html)&lt;br /&gt;
&lt;br /&gt;
=== Migration Template ===&lt;br /&gt;
mkdir -p /home/ian/oracle/ora2pg/migration&lt;br /&gt;
ora2pg --project_base /home/ian/oracle/ora2pg/migration --init_project test_project&lt;br /&gt;
# Copy ora2pg.conf file to the config directory&lt;br /&gt;
# Following types will be exported: SEQUENCE SEQUENCE_VALUES TABLE PACKAGE VIEW GRANT TRIGGER FUNCTION &lt;br /&gt;
                                    PROCEDURE TABLESPACE PARTITION TYPE MVIEW DBLINK SYNONYM DIRECTORY&lt;br /&gt;
./export_schema.sh&lt;br /&gt;
# Run highlighted command at end to export the copy the data&lt;br /&gt;
# Create password file and samples database.&lt;br /&gt;
./import_all.sh -h localhost -p 5433 -d samples -U postgres -o hr&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Ora2pg&amp;diff=95</id>
		<title>Ora2pg</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Ora2pg&amp;diff=95"/>
		<updated>2025-06-06T09:57:46Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;=== Documentation ===&lt;br /&gt;
https://ora2pg.darold.net/documentation.html&lt;br /&gt;
&lt;br /&gt;
=== Pre-Requisites ===&lt;br /&gt;
Install Full Oracle Client or Instant Client&lt;br /&gt;
Instant Client must include the below 4 zip files&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-basic-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sqlplus-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sdk-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-jdbc-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
&lt;br /&gt;
Perl 5.10+&lt;br /&gt;
&lt;br /&gt;
export ORACLE_HOME=/home/ian/oracle/instantclient_23_7&lt;br /&gt;
export LD_LIBRARY_PATH=$ORACLE_HOME:$LD_LIBRARY_PATH&lt;br /&gt;
export PATH=$ORACLE_HOME:$PATH&lt;br /&gt;
perl -MCPAN -e &#039;install DBD::Oracle&#039;&lt;br /&gt;
&lt;br /&gt;
=== Installation ===&lt;br /&gt;
https://github.com/darold/ora2pg/releases&lt;br /&gt;
&lt;br /&gt;
tar xvf ora2pg-25.0.tar.gz&lt;br /&gt;
cd ora2pg-25.0/&lt;br /&gt;
perl Makefile.PL&lt;br /&gt;
sudo make &amp;amp;&amp;amp; make install&lt;br /&gt;
&lt;br /&gt;
=== Configuration ===&lt;br /&gt;
Example conf file (/etc/ora2pg/ora2pg.conf.dist)&lt;br /&gt;
&lt;br /&gt;
Important Parameters:&lt;br /&gt;
# Set the Oracle home directory&lt;br /&gt;
ORACLE_HOME     /home/ian/oracle/instantclient_23_7&lt;br /&gt;
&lt;br /&gt;
# Set Oracle database connection (datasource, user, password)&lt;br /&gt;
ORACLE_DSN      dbi:Oracle:host=localhost;service_name=XEPDB1;port=1523&lt;br /&gt;
ORACLE_USER     system&lt;br /&gt;
ORACLE_PWD      ipswich&lt;br /&gt;
&lt;br /&gt;
# Export Oracle schema to PostgreSQL schema&lt;br /&gt;
EXPORT_SCHEMA   1&lt;br /&gt;
&lt;br /&gt;
# Oracle schema/owner to use&lt;br /&gt;
SCHEMA          HR&lt;br /&gt;
&lt;br /&gt;
# Enable/disable the CREATE SCHEMA SQL order at starting of the output file.&lt;br /&gt;
# It is enable by default and concern on TABLE export type.&lt;br /&gt;
CREATE_SCHEMA   1&lt;br /&gt;
COMPILE_SCHEMA  1&lt;br /&gt;
&lt;br /&gt;
# Type of export. Values can be the following keyword:&lt;br /&gt;
#       TABLE           Export tables, constraints, indexes, ...&lt;br /&gt;
#       PACKAGE         Export packages&lt;br /&gt;
#       INSERT          Export data from table as INSERT statement&lt;br /&gt;
#       COPY            Export data from table as COPY statement&lt;br /&gt;
#       VIEW            Export views&lt;br /&gt;
#       GRANT           Export grants&lt;br /&gt;
#       SEQUENCE        Export sequences&lt;br /&gt;
#       TRIGGER         Export triggers&lt;br /&gt;
#       FUNCTION        Export functions&lt;br /&gt;
#       PROCEDURE       Export procedures&lt;br /&gt;
#       TABLESPACE      Export tablespace (PostgreSQL &amp;gt;= 8 only)&lt;br /&gt;
#       TYPE            Export user defined Oracle types&lt;br /&gt;
#       PARTITION       Export range or list partition (PostgreSQL &amp;gt;= v8.4)&lt;br /&gt;
#       FDW             Export table as foreign data wrapper tables&lt;br /&gt;
#       MVIEW           Export materialized view as snapshot refresh view&lt;br /&gt;
#       QUERY           Convert Oracle SQL queries from a file.&lt;br /&gt;
#       KETTLE          Generate XML ktr template files to be used by Kettle.&lt;br /&gt;
#       DBLINK          Generate oracle foreign data wrapper server to use as dblink.&lt;br /&gt;
#       SYNONYM         Export Oracle&#039;s synonyms as views on other schema&#039;s objects.&lt;br /&gt;
#       DIRECTORY       Export Oracle&#039;s directories as external_file extension objects.&lt;br /&gt;
#       LOAD            Dispatch a list of queries over multiple PostgreSQl connections.&lt;br /&gt;
#       TEST            perform a diff between Oracle and PostgreSQL database.&lt;br /&gt;
#       TEST_COUNT      perform only a row count between Oracle and PostgreSQL tables.&lt;br /&gt;
#       TEST_VIEW       perform a count on both side of number of rows returned by views&lt;br /&gt;
#       TEST_DATA       perform data validation check on rows at both sides.&lt;br /&gt;
#       SEQUENCE_VALUES export DDL to set the last values of sequences&lt;br /&gt;
&lt;br /&gt;
TYPE            TABLE&lt;br /&gt;
&lt;br /&gt;
# Define the following directive to send export directly to a PostgreSQL&lt;br /&gt;
# database, this will disable file output. Note that these directives are only&lt;br /&gt;
# used for data export, other export need to be imported manually through the&lt;br /&gt;
# use of psql or any other PostgreSQL client.&lt;br /&gt;
PG_DSN          dbi:Pg:dbname=postgres;host=host.docker.internal;port=5433&lt;br /&gt;
PG_USER         postgres&lt;br /&gt;
PG_PWD          ipswich&lt;br /&gt;
&lt;br /&gt;
# By default all output is dump to STDOUT if not send directly to postgresql&lt;br /&gt;
# database (see above). Give a filename to save export to it. If you want&lt;br /&gt;
# a Gzip&#039;d compressed file just add the extension .gz to the filename (you&lt;br /&gt;
# need perl module Compress::Zlib from CPAN). Add extension .bz2 to use Bzip2&lt;br /&gt;
# compression.&lt;br /&gt;
OUTPUT          output.sql&lt;br /&gt;
&lt;br /&gt;
# Base directory where all dumped files must be written&lt;br /&gt;
OUTPUT_DIR      /home/ian/tmp&lt;br /&gt;
&lt;br /&gt;
# Enable PLSQL to PLPSQL conversion. This is a work in progress, feel&lt;br /&gt;
# free modify/add you own code and send me patches. The code is under&lt;br /&gt;
# function plsql_toplpgsql in Ora2PG/PLSQL.pm. Default enabled.&lt;br /&gt;
PLSQL_PGSQL     1&lt;br /&gt;
&lt;br /&gt;
# If you want to use functions defined in the Orafce library and prevent&lt;br /&gt;
# Ora2Pg to translate call to these function, enable this directive.&lt;br /&gt;
# The Orafce library can be found here: https://github.com/orafce/orafce&lt;br /&gt;
# By default Ora2pg rewrite add_month(), add_year(), date_trunc() and&lt;br /&gt;
# to_char() functions, but you may prefer to use the orafce version of&lt;br /&gt;
# these function that do not need any code transformation.&lt;br /&gt;
USE_ORAFCE      0&lt;br /&gt;
&lt;br /&gt;
# By default when using SHOW_REPORT the migration report is generated as&lt;br /&gt;
# simple text, enabling this directive will force ora2pg to create a report&lt;br /&gt;
# in HTML format.&lt;br /&gt;
DUMP_AS_HTML    0&lt;br /&gt;
&lt;br /&gt;
# Set the PostgreSQL major version number of the target database. Ex: 9.6 or 10&lt;br /&gt;
# Default is current major version at time of a new release. This replace the&lt;br /&gt;
# old PG_SUPPORTS_* configuration directives.&lt;br /&gt;
PG_VERSION      17&lt;br /&gt;
&lt;br /&gt;
==== Test Connectivity ====&lt;br /&gt;
ora2pg -t SHOW_VERSION -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
&lt;br /&gt;
==== Other Example Commands ====&lt;br /&gt;
ora2pg -t SHOW_SCHEMA -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -t SHOW_ENCODING -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_TABLE -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_COLUMN -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_REPORT -c /etc/ora2pg/ora2pg.conf (--dump_as_html &amp;gt; migration.html)&lt;br /&gt;
&lt;br /&gt;
=== Migration Template ===&lt;br /&gt;
mkdir -p /home/ian/oracle/ora2pg/migration&lt;br /&gt;
ora2pg --project_base /home/ian/oracle/ora2pg/migration --init_project test_project&lt;br /&gt;
# Copy ora2pg.conf file to the config directory&lt;br /&gt;
# Following types will be exported: SEQUENCE SEQUENCE_VALUES TABLE PACKAGE VIEW GRANT TRIGGER FUNCTION &lt;br /&gt;
                                    PROCEDURE TABLESPACE PARTITION TYPE MVIEW DBLINK SYNONYM DIRECTORY&lt;br /&gt;
./export_schema.sh&lt;br /&gt;
# Run highlighted command at end to export the copy the data&lt;br /&gt;
# Create password file and samples database.&lt;br /&gt;
./import_all.sh -h localhost -p 5433 -d samples -U postgres -o hr&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Ora2pg&amp;diff=94</id>
		<title>Ora2pg</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Ora2pg&amp;diff=94"/>
		<updated>2025-06-06T09:56:32Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==== Documentation ====&lt;br /&gt;
https://ora2pg.darold.net/documentation.html&lt;br /&gt;
&lt;br /&gt;
=== Pre-Requisites ===&lt;br /&gt;
Install Full Oracle Client or Instant Client&lt;br /&gt;
Instant Client must include the below 4 zip files&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-basic-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sqlplus-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sdk-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-jdbc-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
&lt;br /&gt;
Perl 5.10+&lt;br /&gt;
&lt;br /&gt;
export ORACLE_HOME=/home/ian/oracle/instantclient_23_7&lt;br /&gt;
export LD_LIBRARY_PATH=$ORACLE_HOME:$LD_LIBRARY_PATH&lt;br /&gt;
export PATH=$ORACLE_HOME:$PATH&lt;br /&gt;
perl -MCPAN -e &#039;install DBD::Oracle&#039;&lt;br /&gt;
&lt;br /&gt;
Installation&lt;br /&gt;
------------&lt;br /&gt;
https://github.com/darold/ora2pg/releases&lt;br /&gt;
&lt;br /&gt;
tar xvf ora2pg-25.0.tar.gz&lt;br /&gt;
cd ora2pg-25.0/&lt;br /&gt;
perl Makefile.PL&lt;br /&gt;
sudo make &amp;amp;&amp;amp; make install&lt;br /&gt;
&lt;br /&gt;
Configuration&lt;br /&gt;
-------------&lt;br /&gt;
Example conf file (/etc/ora2pg/ora2pg.conf.dist)&lt;br /&gt;
&lt;br /&gt;
Important Parameters:&lt;br /&gt;
# Set the Oracle home directory&lt;br /&gt;
ORACLE_HOME     /home/ian/oracle/instantclient_23_7&lt;br /&gt;
&lt;br /&gt;
# Set Oracle database connection (datasource, user, password)&lt;br /&gt;
ORACLE_DSN      dbi:Oracle:host=localhost;service_name=XEPDB1;port=1523&lt;br /&gt;
ORACLE_USER     system&lt;br /&gt;
ORACLE_PWD      ipswich&lt;br /&gt;
&lt;br /&gt;
# Export Oracle schema to PostgreSQL schema&lt;br /&gt;
EXPORT_SCHEMA   1&lt;br /&gt;
&lt;br /&gt;
# Oracle schema/owner to use&lt;br /&gt;
SCHEMA          HR&lt;br /&gt;
&lt;br /&gt;
# Enable/disable the CREATE SCHEMA SQL order at starting of the output file.&lt;br /&gt;
# It is enable by default and concern on TABLE export type.&lt;br /&gt;
CREATE_SCHEMA   1&lt;br /&gt;
COMPILE_SCHEMA  1&lt;br /&gt;
&lt;br /&gt;
# Type of export. Values can be the following keyword:&lt;br /&gt;
#       TABLE           Export tables, constraints, indexes, ...&lt;br /&gt;
#       PACKAGE         Export packages&lt;br /&gt;
#       INSERT          Export data from table as INSERT statement&lt;br /&gt;
#       COPY            Export data from table as COPY statement&lt;br /&gt;
#       VIEW            Export views&lt;br /&gt;
#       GRANT           Export grants&lt;br /&gt;
#       SEQUENCE        Export sequences&lt;br /&gt;
#       TRIGGER         Export triggers&lt;br /&gt;
#       FUNCTION        Export functions&lt;br /&gt;
#       PROCEDURE       Export procedures&lt;br /&gt;
#       TABLESPACE      Export tablespace (PostgreSQL &amp;gt;= 8 only)&lt;br /&gt;
#       TYPE            Export user defined Oracle types&lt;br /&gt;
#       PARTITION       Export range or list partition (PostgreSQL &amp;gt;= v8.4)&lt;br /&gt;
#       FDW             Export table as foreign data wrapper tables&lt;br /&gt;
#       MVIEW           Export materialized view as snapshot refresh view&lt;br /&gt;
#       QUERY           Convert Oracle SQL queries from a file.&lt;br /&gt;
#       KETTLE          Generate XML ktr template files to be used by Kettle.&lt;br /&gt;
#       DBLINK          Generate oracle foreign data wrapper server to use as dblink.&lt;br /&gt;
#       SYNONYM         Export Oracle&#039;s synonyms as views on other schema&#039;s objects.&lt;br /&gt;
#       DIRECTORY       Export Oracle&#039;s directories as external_file extension objects.&lt;br /&gt;
#       LOAD            Dispatch a list of queries over multiple PostgreSQl connections.&lt;br /&gt;
#       TEST            perform a diff between Oracle and PostgreSQL database.&lt;br /&gt;
#       TEST_COUNT      perform only a row count between Oracle and PostgreSQL tables.&lt;br /&gt;
#       TEST_VIEW       perform a count on both side of number of rows returned by views&lt;br /&gt;
#       TEST_DATA       perform data validation check on rows at both sides.&lt;br /&gt;
#       SEQUENCE_VALUES export DDL to set the last values of sequences&lt;br /&gt;
&lt;br /&gt;
TYPE            TABLE&lt;br /&gt;
&lt;br /&gt;
# Define the following directive to send export directly to a PostgreSQL&lt;br /&gt;
# database, this will disable file output. Note that these directives are only&lt;br /&gt;
# used for data export, other export need to be imported manually through the&lt;br /&gt;
# use of psql or any other PostgreSQL client.&lt;br /&gt;
PG_DSN          dbi:Pg:dbname=postgres;host=host.docker.internal;port=5433&lt;br /&gt;
PG_USER         postgres&lt;br /&gt;
PG_PWD          ipswich&lt;br /&gt;
&lt;br /&gt;
# By default all output is dump to STDOUT if not send directly to postgresql&lt;br /&gt;
# database (see above). Give a filename to save export to it. If you want&lt;br /&gt;
# a Gzip&#039;d compressed file just add the extension .gz to the filename (you&lt;br /&gt;
# need perl module Compress::Zlib from CPAN). Add extension .bz2 to use Bzip2&lt;br /&gt;
# compression.&lt;br /&gt;
OUTPUT          output.sql&lt;br /&gt;
&lt;br /&gt;
# Base directory where all dumped files must be written&lt;br /&gt;
OUTPUT_DIR      /home/ian/tmp&lt;br /&gt;
&lt;br /&gt;
# Enable PLSQL to PLPSQL conversion. This is a work in progress, feel&lt;br /&gt;
# free modify/add you own code and send me patches. The code is under&lt;br /&gt;
# function plsql_toplpgsql in Ora2PG/PLSQL.pm. Default enabled.&lt;br /&gt;
PLSQL_PGSQL     1&lt;br /&gt;
&lt;br /&gt;
# If you want to use functions defined in the Orafce library and prevent&lt;br /&gt;
# Ora2Pg to translate call to these function, enable this directive.&lt;br /&gt;
# The Orafce library can be found here: https://github.com/orafce/orafce&lt;br /&gt;
# By default Ora2pg rewrite add_month(), add_year(), date_trunc() and&lt;br /&gt;
# to_char() functions, but you may prefer to use the orafce version of&lt;br /&gt;
# these function that do not need any code transformation.&lt;br /&gt;
USE_ORAFCE      0&lt;br /&gt;
&lt;br /&gt;
# By default when using SHOW_REPORT the migration report is generated as&lt;br /&gt;
# simple text, enabling this directive will force ora2pg to create a report&lt;br /&gt;
# in HTML format.&lt;br /&gt;
DUMP_AS_HTML    0&lt;br /&gt;
&lt;br /&gt;
# Set the PostgreSQL major version number of the target database. Ex: 9.6 or 10&lt;br /&gt;
# Default is current major version at time of a new release. This replace the&lt;br /&gt;
# old PG_SUPPORTS_* configuration directives.&lt;br /&gt;
PG_VERSION      17&lt;br /&gt;
&lt;br /&gt;
Test Connectivity&lt;br /&gt;
-----------------&lt;br /&gt;
ora2pg -t SHOW_VERSION -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
&lt;br /&gt;
Other Example Commands&lt;br /&gt;
----------------------&lt;br /&gt;
ora2pg -t SHOW_SCHEMA -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -t SHOW_ENCODING -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_TABLE -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_COLUMN -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_REPORT -c /etc/ora2pg/ora2pg.conf (--dump_as_html &amp;gt; migration.html)&lt;br /&gt;
&lt;br /&gt;
Generate Migration Template&lt;br /&gt;
---------------------------&lt;br /&gt;
mkdir -p /home/ian/oracle/ora2pg/migration&lt;br /&gt;
ora2pg --project_base /home/ian/oracle/ora2pg/migration --init_project test_project&lt;br /&gt;
# Copy ora2pg.conf file to the config directory&lt;br /&gt;
# Following types will be exported: SEQUENCE SEQUENCE_VALUES TABLE PACKAGE VIEW GRANT TRIGGER FUNCTION &lt;br /&gt;
                                    PROCEDURE TABLESPACE PARTITION TYPE MVIEW DBLINK SYNONYM DIRECTORY&lt;br /&gt;
./export_schema.sh&lt;br /&gt;
# Run highlighted command at end to export the copy the data&lt;br /&gt;
# Create password file and samples database.&lt;br /&gt;
./import_all.sh -h localhost -p 5433 -d samples -U postgres -o hr&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Ora2pg&amp;diff=93</id>
		<title>Ora2pg</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Ora2pg&amp;diff=93"/>
		<updated>2025-06-06T09:56:14Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: Created page with &amp;quot;==== Documentation ==== https://ora2pg.darold.net/documentation.html  Pre-Requisites -------------- Install Full Oracle Client or Instant Client Instant Client must include the below 4 zip files wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-basic-linux.x64-23.7.0.25.01.zip wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sqlplus-linux.x64-23.7.0.25.01.zip wget https://download.oracle.com/otn...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==== Documentation ====&lt;br /&gt;
https://ora2pg.darold.net/documentation.html&lt;br /&gt;
&lt;br /&gt;
Pre-Requisites&lt;br /&gt;
--------------&lt;br /&gt;
Install Full Oracle Client or Instant Client&lt;br /&gt;
Instant Client must include the below 4 zip files&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-basic-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sqlplus-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-sdk-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
wget https://download.oracle.com/otn_software/linux/instantclient/2370000/instantclient-jdbc-linux.x64-23.7.0.25.01.zip&lt;br /&gt;
&lt;br /&gt;
Perl 5.10+&lt;br /&gt;
&lt;br /&gt;
export ORACLE_HOME=/home/ian/oracle/instantclient_23_7&lt;br /&gt;
export LD_LIBRARY_PATH=$ORACLE_HOME:$LD_LIBRARY_PATH&lt;br /&gt;
export PATH=$ORACLE_HOME:$PATH&lt;br /&gt;
perl -MCPAN -e &#039;install DBD::Oracle&#039;&lt;br /&gt;
&lt;br /&gt;
Installation&lt;br /&gt;
------------&lt;br /&gt;
https://github.com/darold/ora2pg/releases&lt;br /&gt;
&lt;br /&gt;
tar xvf ora2pg-25.0.tar.gz&lt;br /&gt;
cd ora2pg-25.0/&lt;br /&gt;
perl Makefile.PL&lt;br /&gt;
sudo make &amp;amp;&amp;amp; make install&lt;br /&gt;
&lt;br /&gt;
Configuration&lt;br /&gt;
-------------&lt;br /&gt;
Example conf file (/etc/ora2pg/ora2pg.conf.dist)&lt;br /&gt;
&lt;br /&gt;
Important Parameters:&lt;br /&gt;
# Set the Oracle home directory&lt;br /&gt;
ORACLE_HOME     /home/ian/oracle/instantclient_23_7&lt;br /&gt;
&lt;br /&gt;
# Set Oracle database connection (datasource, user, password)&lt;br /&gt;
ORACLE_DSN      dbi:Oracle:host=localhost;service_name=XEPDB1;port=1523&lt;br /&gt;
ORACLE_USER     system&lt;br /&gt;
ORACLE_PWD      ipswich&lt;br /&gt;
&lt;br /&gt;
# Export Oracle schema to PostgreSQL schema&lt;br /&gt;
EXPORT_SCHEMA   1&lt;br /&gt;
&lt;br /&gt;
# Oracle schema/owner to use&lt;br /&gt;
SCHEMA          HR&lt;br /&gt;
&lt;br /&gt;
# Enable/disable the CREATE SCHEMA SQL order at starting of the output file.&lt;br /&gt;
# It is enable by default and concern on TABLE export type.&lt;br /&gt;
CREATE_SCHEMA   1&lt;br /&gt;
COMPILE_SCHEMA  1&lt;br /&gt;
&lt;br /&gt;
# Type of export. Values can be the following keyword:&lt;br /&gt;
#       TABLE           Export tables, constraints, indexes, ...&lt;br /&gt;
#       PACKAGE         Export packages&lt;br /&gt;
#       INSERT          Export data from table as INSERT statement&lt;br /&gt;
#       COPY            Export data from table as COPY statement&lt;br /&gt;
#       VIEW            Export views&lt;br /&gt;
#       GRANT           Export grants&lt;br /&gt;
#       SEQUENCE        Export sequences&lt;br /&gt;
#       TRIGGER         Export triggers&lt;br /&gt;
#       FUNCTION        Export functions&lt;br /&gt;
#       PROCEDURE       Export procedures&lt;br /&gt;
#       TABLESPACE      Export tablespace (PostgreSQL &amp;gt;= 8 only)&lt;br /&gt;
#       TYPE            Export user defined Oracle types&lt;br /&gt;
#       PARTITION       Export range or list partition (PostgreSQL &amp;gt;= v8.4)&lt;br /&gt;
#       FDW             Export table as foreign data wrapper tables&lt;br /&gt;
#       MVIEW           Export materialized view as snapshot refresh view&lt;br /&gt;
#       QUERY           Convert Oracle SQL queries from a file.&lt;br /&gt;
#       KETTLE          Generate XML ktr template files to be used by Kettle.&lt;br /&gt;
#       DBLINK          Generate oracle foreign data wrapper server to use as dblink.&lt;br /&gt;
#       SYNONYM         Export Oracle&#039;s synonyms as views on other schema&#039;s objects.&lt;br /&gt;
#       DIRECTORY       Export Oracle&#039;s directories as external_file extension objects.&lt;br /&gt;
#       LOAD            Dispatch a list of queries over multiple PostgreSQl connections.&lt;br /&gt;
#       TEST            perform a diff between Oracle and PostgreSQL database.&lt;br /&gt;
#       TEST_COUNT      perform only a row count between Oracle and PostgreSQL tables.&lt;br /&gt;
#       TEST_VIEW       perform a count on both side of number of rows returned by views&lt;br /&gt;
#       TEST_DATA       perform data validation check on rows at both sides.&lt;br /&gt;
#       SEQUENCE_VALUES export DDL to set the last values of sequences&lt;br /&gt;
&lt;br /&gt;
TYPE            TABLE&lt;br /&gt;
&lt;br /&gt;
# Define the following directive to send export directly to a PostgreSQL&lt;br /&gt;
# database, this will disable file output. Note that these directives are only&lt;br /&gt;
# used for data export, other export need to be imported manually through the&lt;br /&gt;
# use of psql or any other PostgreSQL client.&lt;br /&gt;
PG_DSN          dbi:Pg:dbname=postgres;host=host.docker.internal;port=5433&lt;br /&gt;
PG_USER         postgres&lt;br /&gt;
PG_PWD          ipswich&lt;br /&gt;
&lt;br /&gt;
# By default all output is dump to STDOUT if not send directly to postgresql&lt;br /&gt;
# database (see above). Give a filename to save export to it. If you want&lt;br /&gt;
# a Gzip&#039;d compressed file just add the extension .gz to the filename (you&lt;br /&gt;
# need perl module Compress::Zlib from CPAN). Add extension .bz2 to use Bzip2&lt;br /&gt;
# compression.&lt;br /&gt;
OUTPUT          output.sql&lt;br /&gt;
&lt;br /&gt;
# Base directory where all dumped files must be written&lt;br /&gt;
OUTPUT_DIR      /home/ian/tmp&lt;br /&gt;
&lt;br /&gt;
# Enable PLSQL to PLPSQL conversion. This is a work in progress, feel&lt;br /&gt;
# free modify/add you own code and send me patches. The code is under&lt;br /&gt;
# function plsql_toplpgsql in Ora2PG/PLSQL.pm. Default enabled.&lt;br /&gt;
PLSQL_PGSQL     1&lt;br /&gt;
&lt;br /&gt;
# If you want to use functions defined in the Orafce library and prevent&lt;br /&gt;
# Ora2Pg to translate call to these function, enable this directive.&lt;br /&gt;
# The Orafce library can be found here: https://github.com/orafce/orafce&lt;br /&gt;
# By default Ora2pg rewrite add_month(), add_year(), date_trunc() and&lt;br /&gt;
# to_char() functions, but you may prefer to use the orafce version of&lt;br /&gt;
# these function that do not need any code transformation.&lt;br /&gt;
USE_ORAFCE      0&lt;br /&gt;
&lt;br /&gt;
# By default when using SHOW_REPORT the migration report is generated as&lt;br /&gt;
# simple text, enabling this directive will force ora2pg to create a report&lt;br /&gt;
# in HTML format.&lt;br /&gt;
DUMP_AS_HTML    0&lt;br /&gt;
&lt;br /&gt;
# Set the PostgreSQL major version number of the target database. Ex: 9.6 or 10&lt;br /&gt;
# Default is current major version at time of a new release. This replace the&lt;br /&gt;
# old PG_SUPPORTS_* configuration directives.&lt;br /&gt;
PG_VERSION      17&lt;br /&gt;
&lt;br /&gt;
Test Connectivity&lt;br /&gt;
-----------------&lt;br /&gt;
ora2pg -t SHOW_VERSION -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
&lt;br /&gt;
Other Example Commands&lt;br /&gt;
----------------------&lt;br /&gt;
ora2pg -t SHOW_SCHEMA -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -t SHOW_ENCODING -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_TABLE -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_COLUMN -c /etc/ora2pg/ora2pg.conf&lt;br /&gt;
ora2pg -n HR -t SHOW_REPORT -c /etc/ora2pg/ora2pg.conf (--dump_as_html &amp;gt; migration.html)&lt;br /&gt;
&lt;br /&gt;
Generate Migration Template&lt;br /&gt;
---------------------------&lt;br /&gt;
mkdir -p /home/ian/oracle/ora2pg/migration&lt;br /&gt;
ora2pg --project_base /home/ian/oracle/ora2pg/migration --init_project test_project&lt;br /&gt;
# Copy ora2pg.conf file to the config directory&lt;br /&gt;
# Following types will be exported: SEQUENCE SEQUENCE_VALUES TABLE PACKAGE VIEW GRANT TRIGGER FUNCTION &lt;br /&gt;
                                    PROCEDURE TABLESPACE PARTITION TYPE MVIEW DBLINK SYNONYM DIRECTORY&lt;br /&gt;
./export_schema.sh&lt;br /&gt;
# Run highlighted command at end to export the copy the data&lt;br /&gt;
# Create password file and samples database.&lt;br /&gt;
./import_all.sh -h localhost -p 5433 -d samples -U postgres -o hr&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Oracle_PostgreSQL_Migration&amp;diff=92</id>
		<title>Oracle PostgreSQL Migration</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Oracle_PostgreSQL_Migration&amp;diff=92"/>
		<updated>2025-06-06T09:55:19Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;===Migration Tools===&lt;br /&gt;
&lt;br /&gt;
[[pg2ora]]&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Oracle_PostgreSQL_Migration&amp;diff=91</id>
		<title>Oracle PostgreSQL Migration</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Oracle_PostgreSQL_Migration&amp;diff=91"/>
		<updated>2025-06-06T09:55:00Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: Created page with &amp;quot;Migration Tools  pg2ora&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Migration Tools&lt;br /&gt;
&lt;br /&gt;
[[pg2ora]]&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=Main_Page&amp;diff=90</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=Main_Page&amp;diff=90"/>
		<updated>2025-06-06T09:54:32Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: /* PostgreSQL */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Useful Links ==&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents User&#039;s Guide]&lt;br /&gt;
* [https://www.postgresql.org/download PostgreSQL Downloads]&lt;br /&gt;
* [https://learn.microsoft.com/en-us/azure/postgresql PostgreSQL Flexible Server]&lt;br /&gt;
* [https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_PostgreSQL.html PostgreSQL RDS]&lt;br /&gt;
* [https://docs.timescale.com/self-hosted/latest TimescaleDB Self-Hosted]&lt;br /&gt;
* [https://patroni.readthedocs.io/en/latest Patroni Documentation]&lt;br /&gt;
* [https://hub.docker.com/search Docker Hub]&lt;br /&gt;
&lt;br /&gt;
== PostgreSQL ==&lt;br /&gt;
* [[TimeScale Notes]]&lt;br /&gt;
* [[TimeScale Training]]&lt;br /&gt;
* [[Oracle PostgreSQL Migration]]&lt;br /&gt;
&lt;br /&gt;
== Getting started ==&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings Configuration settings list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki FAQ]&lt;br /&gt;
* [https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/ MediaWiki release mailing list]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation#Translation_resources Localise MediaWiki for your language]&lt;br /&gt;
* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Combating_spam Learn how to combat spam on your wiki]&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=89</id>
		<title>TimeScale Training</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=89"/>
		<updated>2025-04-20T17:02:33Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Installation ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/&lt;br /&gt;
=== Debian ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &amp;quot;deb https://packagecloud.io/timescale/timescaledb/debian/ $(lsb_release -c -s) main&amp;quot; | /&lt;br /&gt;
  sudo tee /etc/apt/sources.list.d/timescaledb.list&lt;br /&gt;
wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | /&lt;br /&gt;
  sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/timescaledb.gpg&lt;br /&gt;
sudo apt update&lt;br /&gt;
&lt;br /&gt;
# Get latest version&lt;br /&gt;
sudo apt install timescaledb-2-postgresql-17&lt;br /&gt;
# Get specific version&lt;br /&gt;
sudo apt-get install timescaledb-2-postgresql-15=&#039;2.14.2*&#039; timescaledb-2-loader-postgresql-15=&#039;2.14.2*&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When running timescaledb-tune, accept the option to update shared_preload_libraries. Other settings are optional &amp;lt;/br&amp;gt;&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/configuration/timescaledb-tune&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo timescaledb-tune&lt;br /&gt;
sudo systemctl restart postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE EXTENSION IF NOT EXISTS timescaledb;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/installation-windows/#install-and-configure-timescaledb-on-postgresql&lt;br /&gt;
&lt;br /&gt;
== Setup ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SET search_path = &amp;quot;$user&amp;quot;, public, &amp;quot;timescaledb_information&amp;quot;;&lt;br /&gt;
SET timezone TO &#039;Europe/London&#039;;&lt;br /&gt;
&lt;br /&gt;
\dv timescaledb_information.*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DROP TABLE IF EXISTS metrics;&lt;br /&gt;
CREATE TABLE metrics (id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, &lt;br /&gt;
                      device_time timestamptz NOT NULL, &lt;br /&gt;
                      device_id integer, &lt;br /&gt;
                      cpu_usage numeric);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create Hypertable ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ERROR:  cannot create a unique index without the column &amp;quot;device_time&amp;quot; (used in partitioning) &amp;lt;/br&amp;gt;&lt;br /&gt;
HINT:  If you&#039;re creating a hypertable on a table with a primary key, ensure the partitioning column is part of the primary or composite key.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics DROP CONSTRAINT metrics_pkey;&lt;br /&gt;
ALTER TABLE metrics ADD PRIMARY KEY (id, device_time);&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the existing table already has data in it then you must use the &amp;quot;migrate_data&amp;quot; option. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;), migrate_data =&amp;gt; true);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Chunk Interval ===&lt;br /&gt;
The default chunk interval is 7 days.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;time&#039;, INTERVAL &#039;1 month&#039;));&lt;br /&gt;
SELECT set_chunk_time_interval(&#039;metrics&#039;, INTERVAL &#039;14 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT hypertable_name, column_name, time_interval FROM timescaledb_information.dimensions;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Generate Time Series Data ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
TRUNCATE TABLE metrics RESTART IDENTITY;&lt;br /&gt;
INSERT INTO metrics (device_time, device_id, cpu_usage) (&lt;br /&gt;
SELECT time, device_id, random()*100 as cpu_usage &lt;br /&gt;
  FROM generate_series(date_trunc(&#039;hour&#039;,now()) - INTERVAL &#039;6 months&#039;, now(), INTERVAL &#039;5 minutes&#039;) as time, &lt;br /&gt;
       generate_series(1,1000) device_id&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Table Size ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT PG_SIZE_PRETTY (pg_total_relation_size(&#039;metrics&#039;));&lt;br /&gt;
SELECT PG_SIZE_PRETTY (hypertable_size(&#039;metrics&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Check Statistics ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ANALYZE metrics;&lt;br /&gt;
SELECT tablename, attname, n_distinct FROM pg_stats WHERE tablename = &#039;metrics&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Examine Chunks ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
\d+ metrics&lt;br /&gt;
SELECT MAX(device_time), MIN(device_time) FROM _timescaledb_internal.&amp;lt;chunk&amp;gt;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Compression ==&lt;br /&gt;
=== Old ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics &lt;br /&gt;
SET (timescaledb.compress, &lt;br /&gt;
     timescaledb.compress_segmentby=&#039;device_id&#039;, &lt;br /&gt;
     timescaledb.compress_orderby=&#039;device_time DESC&#039;);&lt;br /&gt;
	 &lt;br /&gt;
SELECT compress_chunk(c) from show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT add_compression_policy(&#039;metrics&#039;, INTERVAL &#039;1 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT remove_compression_policy(&#039;metrics&#039;);&lt;br /&gt;
SELECT decompress_chunk(c, true) FROM show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== New (HyperCore) ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics SET (&lt;br /&gt;
   timescaledb.enable_columnstore = true, &lt;br /&gt;
   timescaledb.segmentby = &#039;device_id&#039;);&lt;br /&gt;
   &lt;br /&gt;
CALL add_columnstore_policy(&#039;metrics&#039;, after =&amp;gt; INTERVAL &#039;1d&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT pg_size_pretty(before_compression_table_bytes) as before_table,&lt;br /&gt;
       pg_size_pretty(before_compression_index_bytes) as before_index,&lt;br /&gt;
       pg_size_pretty(before_compression_toast_bytes) as before_toast,&lt;br /&gt;
       pg_size_pretty(before_compression_total_bytes) as before_total,&lt;br /&gt;
       pg_size_pretty(after_compression_table_bytes) as after_table,&lt;br /&gt;
       pg_size_pretty(after_compression_index_bytes) as after_index,&lt;br /&gt;
       pg_size_pretty(after_compression_toast_bytes) as after_toast,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after_total&lt;br /&gt;
  FROM hypertable_columnstore_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT * FROM timescaledb_information.jobs WHERE proc_name=&#039;policy_compression&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tablespaces ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/manage-storage &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE TABLESPACE historic_data OWNER postgres LOCATION &#039;/home/postgres/timescale&#039;;&lt;br /&gt;
&lt;br /&gt;
SELECT show_chunks(&#039;metrics&#039;, older_than =&amp;gt; INTERVAL &#039;5 months&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT move_chunk(&lt;br /&gt;
  chunk =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk&#039;,&lt;br /&gt;
  destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  index_destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  reorder_index =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk_netdata_time_idx&#039;,&lt;br /&gt;
  verbose =&amp;gt; TRUE&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Limitations ==&lt;br /&gt;
https://docs.timescale.com/timescaledb/latest/overview/limitations &amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Time dimensions (columns) used for partitioning cannot have NULL values.&lt;br /&gt;
* Unique indexes must include all columns that are partitioning dimensions. &lt;br /&gt;
* UPDATE statements that move values between partitions (chunks) are not supported.&lt;br /&gt;
* Foreign key constraints from a hypertable referencing another hypertable are not supported.&lt;br /&gt;
&lt;br /&gt;
== Parameters ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT name, setting, short_desc FROM pg_settings WHERE name like &#039;timescaledb.%&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Continuous Aggregates ==&lt;br /&gt;
=== Create ===&lt;br /&gt;
The materialised view will be created immediately unless you use the option &amp;lt;b&amp;gt;WITH NO DATA&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE MATERIALIZED VIEW daily_cpu_stats&lt;br /&gt;
WITH (timescaledb.continuous) AS&lt;br /&gt;
SELECT time_bucket(&#039;1 day&#039;, device_time) AS day,&lt;br /&gt;
       device_id,&lt;br /&gt;
       avg(cpu_usage) AS avg_cpu,&lt;br /&gt;
       max(cpu_usage) AS max_cpu&lt;br /&gt;
FROM metrics&lt;br /&gt;
GROUP BY day, device_id;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Refresh ===&lt;br /&gt;
Refresh all data between the dates specified.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CALL refresh_continuous_aggregate(&#039;daily_cpu_stats&#039;, &#039;2023-01-01&#039;, &#039;2025-04-01&#039;);&lt;br /&gt;
CALL refresh_continuous_aggregate(&#039;daily_cpu_stats&#039;, date_trunc(&#039;month&#039;, now())&#039;, date_trunc(&#039;day&#039;, now()));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Add Policy ===&lt;br /&gt;
Create a policy that refreshes data between 1 month old and 1 day. This occurs every hour. &amp;lt;/br&amp;gt;&lt;br /&gt;
This means data older than 1 month will never be removed from the CA, even if it is deleted from the underlying table.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT add_continuous_aggregate_policy(&#039;daily_cpu_stats&#039;,&lt;br /&gt;
  start_offset =&amp;gt; INTERVAL &#039;1 month&#039;,&lt;br /&gt;
  end_offset =&amp;gt; INTERVAL &#039;1 day&#039;,&lt;br /&gt;
  schedule_interval =&amp;gt; INTERVAL &#039;1 h&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=88</id>
		<title>TimeScale Training</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=88"/>
		<updated>2025-04-20T17:02:03Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Installation ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/&lt;br /&gt;
=== Debian ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &amp;quot;deb https://packagecloud.io/timescale/timescaledb/debian/ $(lsb_release -c -s) main&amp;quot; | /&lt;br /&gt;
  sudo tee /etc/apt/sources.list.d/timescaledb.list&lt;br /&gt;
wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | /&lt;br /&gt;
  sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/timescaledb.gpg&lt;br /&gt;
sudo apt update&lt;br /&gt;
&lt;br /&gt;
# Get latest version&lt;br /&gt;
sudo apt install timescaledb-2-postgresql-17&lt;br /&gt;
# Get specific version&lt;br /&gt;
sudo apt-get install timescaledb-2-postgresql-15=&#039;2.14.2*&#039; timescaledb-2-loader-postgresql-15=&#039;2.14.2*&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When running timescaledb-tune, accept the option to update shared_preload_libraries. Other settings are optional &amp;lt;/br&amp;gt;&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/configuration/timescaledb-tune&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo timescaledb-tune&lt;br /&gt;
sudo systemctl restart postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE EXTENSION IF NOT EXISTS timescaledb;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/installation-windows/#install-and-configure-timescaledb-on-postgresql&lt;br /&gt;
&lt;br /&gt;
== Setup ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SET search_path = &amp;quot;$user&amp;quot;, public, &amp;quot;timescaledb_information&amp;quot;;&lt;br /&gt;
SET timezone TO &#039;Europe/London&#039;;&lt;br /&gt;
&lt;br /&gt;
\dv timescaledb_information.*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DROP TABLE IF EXISTS metrics;&lt;br /&gt;
CREATE TABLE metrics (id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, &lt;br /&gt;
                      device_time timestamptz NOT NULL, &lt;br /&gt;
                      device_id integer, &lt;br /&gt;
                      cpu_usage numeric);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create Hypertable ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ERROR:  cannot create a unique index without the column &amp;quot;device_time&amp;quot; (used in partitioning) &amp;lt;/br&amp;gt;&lt;br /&gt;
HINT:  If you&#039;re creating a hypertable on a table with a primary key, ensure the partitioning column is part of the primary or composite key.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics DROP CONSTRAINT metrics_pkey;&lt;br /&gt;
ALTER TABLE metrics ADD PRIMARY KEY (id, device_time);&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the existing table already has data in it then you must use the &amp;quot;migrate_data&amp;quot; option. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;), migrate_data =&amp;gt; true);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Chunk Interval ===&lt;br /&gt;
The default chunk interval is 7 days.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;time&#039;, INTERVAL &#039;1 month&#039;));&lt;br /&gt;
SELECT set_chunk_time_interval(&#039;metrics&#039;, INTERVAL &#039;14 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT hypertable_name, column_name, time_interval FROM timescaledb_information.dimensions;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Generate Time Series Data ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
TRUNCATE TABLE metrics RESTART IDENTITY;&lt;br /&gt;
INSERT INTO metrics (device_time, device_id, cpu_usage) (&lt;br /&gt;
SELECT time, device_id, random()*100 as cpu_usage &lt;br /&gt;
  FROM generate_series(date_trunc(&#039;hour&#039;,now()) - INTERVAL &#039;6 months&#039;, now(), INTERVAL &#039;5 minutes&#039;) as time, &lt;br /&gt;
       generate_series(1,1000) device_id&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Table Size ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT PG_SIZE_PRETTY (pg_total_relation_size(&#039;metrics&#039;));&lt;br /&gt;
SELECT PG_SIZE_PRETTY (hypertable_size(&#039;metrics&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Check Statistics ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ANALYZE metrics;&lt;br /&gt;
SELECT tablename, attname, n_distinct FROM pg_stats WHERE tablename = &#039;metrics&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Examine Chunks ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
\d+ metrics&lt;br /&gt;
SELECT MAX(device_time), MIN(device_time) FROM _timescaledb_internal.&amp;lt;chunk&amp;gt;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Compression ==&lt;br /&gt;
=== Old ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics &lt;br /&gt;
SET (timescaledb.compress, &lt;br /&gt;
     timescaledb.compress_segmentby=&#039;device_id&#039;, &lt;br /&gt;
     timescaledb.compress_orderby=&#039;device_time DESC&#039;);&lt;br /&gt;
	 &lt;br /&gt;
SELECT compress_chunk(c) from show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT add_compression_policy(&#039;metrics&#039;, INTERVAL &#039;1 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT remove_compression_policy(&#039;metrics&#039;);&lt;br /&gt;
SELECT decompress_chunk(c, true) FROM show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== New (HyperCore) ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics SET (&lt;br /&gt;
   timescaledb.enable_columnstore = true, &lt;br /&gt;
   timescaledb.segmentby = &#039;device_id&#039;);&lt;br /&gt;
   &lt;br /&gt;
CALL add_columnstore_policy(&#039;metrics&#039;, after =&amp;gt; INTERVAL &#039;1d&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT pg_size_pretty(before_compression_table_bytes) as before_table,&lt;br /&gt;
       pg_size_pretty(before_compression_index_bytes) as before_index,&lt;br /&gt;
       pg_size_pretty(before_compression_toast_bytes) as before_toast,&lt;br /&gt;
       pg_size_pretty(before_compression_total_bytes) as before_total,&lt;br /&gt;
       pg_size_pretty(after_compression_table_bytes) as after_table,&lt;br /&gt;
       pg_size_pretty(after_compression_index_bytes) as after_index,&lt;br /&gt;
       pg_size_pretty(after_compression_toast_bytes) as after_toast,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after_total&lt;br /&gt;
  FROM hypertable_columnstore_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT * FROM timescaledb_information.jobs WHERE proc_name=&#039;policy_compression&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tablespaces ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/manage-storage &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE TABLESPACE historic_data OWNER postgres LOCATION &#039;/home/postgres/timescale&#039;;&lt;br /&gt;
&lt;br /&gt;
SELECT show_chunks(&#039;metrics&#039;, older_than =&amp;gt; INTERVAL &#039;5 months&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT move_chunk(&lt;br /&gt;
  chunk =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk&#039;,&lt;br /&gt;
  destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  index_destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  reorder_index =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk_netdata_time_idx&#039;,&lt;br /&gt;
  verbose =&amp;gt; TRUE&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Limitations ==&lt;br /&gt;
https://docs.timescale.com/timescaledb/latest/overview/limitations &amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Time dimensions (columns) used for partitioning cannot have NULL values.&lt;br /&gt;
* Unique indexes must include all columns that are partitioning dimensions. &lt;br /&gt;
* UPDATE statements that move values between partitions (chunks) are not supported.&lt;br /&gt;
* Foreign key constraints from a hypertable referencing another hypertable are not supported.&lt;br /&gt;
&lt;br /&gt;
== Parameters ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT name, setting, short_desc FROM pg_settings WHERE name like &#039;timescaledb.%&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Continuous Aggregates ==&lt;br /&gt;
=== Create ===&lt;br /&gt;
The materialised view will be created immediately unless you use the option &amp;lt;b&amp;gt;WITH NO DATA&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE MATERIALIZED VIEW daily_cpu_stats&lt;br /&gt;
WITH (timescaledb.continuous) AS&lt;br /&gt;
SELECT time_bucket(&#039;1 day&#039;, device_time) AS day,&lt;br /&gt;
       device_id,&lt;br /&gt;
       avg(cpu_usage) AS avg_cpu,&lt;br /&gt;
       max(cpu_usage) AS max_cpu&lt;br /&gt;
FROM metrics&lt;br /&gt;
GROUP BY day, device_id;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Refresh ===&lt;br /&gt;
Refresh all data between the dates specified.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CALL refresh_continuous_aggregate(&#039;daily_cpu_stats&#039;, &#039;2023-01-01&#039;, &#039;2025-04-01&#039;);&lt;br /&gt;
CALL refresh_continuous_aggregate(&#039;daily_cpu_stats&#039;, &#039;month&#039;, now())&#039;, date_trunc(&#039;day&#039;, now()));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Add Policy ===&lt;br /&gt;
Create a policy that refreshes data between 1 month old and 1 day. This occurs every hour. &amp;lt;/br&amp;gt;&lt;br /&gt;
This means data older than 1 month will never be removed from the CA, even if it is deleted from the underlying table.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT add_continuous_aggregate_policy(&#039;daily_cpu_stats&#039;,&lt;br /&gt;
  start_offset =&amp;gt; INTERVAL &#039;1 month&#039;,&lt;br /&gt;
  end_offset =&amp;gt; INTERVAL &#039;1 day&#039;,&lt;br /&gt;
  schedule_interval =&amp;gt; INTERVAL &#039;1 h&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=87</id>
		<title>TimeScale Training</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=87"/>
		<updated>2025-04-20T16:57:00Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Installation ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/&lt;br /&gt;
=== Debian ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &amp;quot;deb https://packagecloud.io/timescale/timescaledb/debian/ $(lsb_release -c -s) main&amp;quot; | /&lt;br /&gt;
  sudo tee /etc/apt/sources.list.d/timescaledb.list&lt;br /&gt;
wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | /&lt;br /&gt;
  sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/timescaledb.gpg&lt;br /&gt;
sudo apt update&lt;br /&gt;
&lt;br /&gt;
# Get latest version&lt;br /&gt;
sudo apt install timescaledb-2-postgresql-17&lt;br /&gt;
# Get specific version&lt;br /&gt;
sudo apt-get install timescaledb-2-postgresql-15=&#039;2.14.2*&#039; timescaledb-2-loader-postgresql-15=&#039;2.14.2*&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When running timescaledb-tune, accept the option to update shared_preload_libraries. Other settings are optional &amp;lt;/br&amp;gt;&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/configuration/timescaledb-tune&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo timescaledb-tune&lt;br /&gt;
sudo systemctl restart postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE EXTENSION IF NOT EXISTS timescaledb;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/installation-windows/#install-and-configure-timescaledb-on-postgresql&lt;br /&gt;
&lt;br /&gt;
== Setup ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SET search_path = &amp;quot;$user&amp;quot;, public, &amp;quot;timescaledb_information&amp;quot;;&lt;br /&gt;
SET timezone TO &#039;Europe/London&#039;;&lt;br /&gt;
&lt;br /&gt;
\dv timescaledb_information.*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DROP TABLE IF EXISTS metrics;&lt;br /&gt;
CREATE TABLE metrics (id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, &lt;br /&gt;
                      device_time timestamptz NOT NULL, &lt;br /&gt;
                      device_id integer, &lt;br /&gt;
                      cpu_usage numeric);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create Hypertable ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ERROR:  cannot create a unique index without the column &amp;quot;device_time&amp;quot; (used in partitioning) &amp;lt;/br&amp;gt;&lt;br /&gt;
HINT:  If you&#039;re creating a hypertable on a table with a primary key, ensure the partitioning column is part of the primary or composite key.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics DROP CONSTRAINT metrics_pkey;&lt;br /&gt;
ALTER TABLE metrics ADD PRIMARY KEY (id, device_time);&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the existing table already has data in it then you must use the &amp;quot;migrate_data&amp;quot; option. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;), migrate_data =&amp;gt; true);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Chunk Interval ===&lt;br /&gt;
The default chunk interval is 7 days.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;time&#039;, INTERVAL &#039;1 month&#039;));&lt;br /&gt;
SELECT set_chunk_time_interval(&#039;metrics&#039;, INTERVAL &#039;14 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT hypertable_name, column_name, time_interval FROM timescaledb_information.dimensions;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Generate Time Series Data ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
TRUNCATE TABLE metrics RESTART IDENTITY;&lt;br /&gt;
INSERT INTO metrics (device_time, device_id, cpu_usage) (&lt;br /&gt;
SELECT time, device_id, random()*100 as cpu_usage &lt;br /&gt;
  FROM generate_series(date_trunc(&#039;hour&#039;,now()) - INTERVAL &#039;6 months&#039;, now(), INTERVAL &#039;5 minutes&#039;) as time, &lt;br /&gt;
       generate_series(1,1000) device_id&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Table Size ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT PG_SIZE_PRETTY (pg_total_relation_size(&#039;metrics&#039;));&lt;br /&gt;
SELECT PG_SIZE_PRETTY (hypertable_size(&#039;metrics&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Check Statistics ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ANALYZE metrics;&lt;br /&gt;
SELECT tablename, attname, n_distinct FROM pg_stats WHERE tablename = &#039;metrics&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Examine Chunks ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
\d+ metrics&lt;br /&gt;
SELECT MAX(device_time), MIN(device_time) FROM _timescaledb_internal.&amp;lt;chunk&amp;gt;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Compression ==&lt;br /&gt;
=== Old ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics &lt;br /&gt;
SET (timescaledb.compress, &lt;br /&gt;
     timescaledb.compress_segmentby=&#039;device_id&#039;, &lt;br /&gt;
     timescaledb.compress_orderby=&#039;device_time DESC&#039;);&lt;br /&gt;
	 &lt;br /&gt;
SELECT compress_chunk(c) from show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT add_compression_policy(&#039;metrics&#039;, INTERVAL &#039;1 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT remove_compression_policy(&#039;metrics&#039;);&lt;br /&gt;
SELECT decompress_chunk(c, true) FROM show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== New (HyperCore) ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics SET (&lt;br /&gt;
   timescaledb.enable_columnstore = true, &lt;br /&gt;
   timescaledb.segmentby = &#039;device_id&#039;);&lt;br /&gt;
   &lt;br /&gt;
CALL add_columnstore_policy(&#039;metrics&#039;, after =&amp;gt; INTERVAL &#039;1d&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT pg_size_pretty(before_compression_table_bytes) as before_table,&lt;br /&gt;
       pg_size_pretty(before_compression_index_bytes) as before_index,&lt;br /&gt;
       pg_size_pretty(before_compression_toast_bytes) as before_toast,&lt;br /&gt;
       pg_size_pretty(before_compression_total_bytes) as before_total,&lt;br /&gt;
       pg_size_pretty(after_compression_table_bytes) as after_table,&lt;br /&gt;
       pg_size_pretty(after_compression_index_bytes) as after_index,&lt;br /&gt;
       pg_size_pretty(after_compression_toast_bytes) as after_toast,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after_total&lt;br /&gt;
  FROM hypertable_columnstore_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT * FROM timescaledb_information.jobs WHERE proc_name=&#039;policy_compression&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tablespaces ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/manage-storage &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE TABLESPACE historic_data OWNER postgres LOCATION &#039;/home/postgres/timescale&#039;;&lt;br /&gt;
&lt;br /&gt;
SELECT show_chunks(&#039;metrics&#039;, older_than =&amp;gt; INTERVAL &#039;5 months&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT move_chunk(&lt;br /&gt;
  chunk =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk&#039;,&lt;br /&gt;
  destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  index_destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  reorder_index =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk_netdata_time_idx&#039;,&lt;br /&gt;
  verbose =&amp;gt; TRUE&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Limitations ==&lt;br /&gt;
https://docs.timescale.com/timescaledb/latest/overview/limitations &amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Time dimensions (columns) used for partitioning cannot have NULL values.&lt;br /&gt;
* Unique indexes must include all columns that are partitioning dimensions. &lt;br /&gt;
* UPDATE statements that move values between partitions (chunks) are not supported.&lt;br /&gt;
* Foreign key constraints from a hypertable referencing another hypertable are not supported.&lt;br /&gt;
&lt;br /&gt;
== Parameters ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT name, setting, short_desc FROM pg_settings WHERE name like &#039;timescaledb.%&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Continuous Aggregates ==&lt;br /&gt;
=== Create ===&lt;br /&gt;
The materialised view will be created immediately unless you use the option &amp;lt;b&amp;gt;WITH NO DATA&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE MATERIALIZED VIEW daily_cpu_stats&lt;br /&gt;
WITH (timescaledb.continuous) AS&lt;br /&gt;
SELECT time_bucket(&#039;1 day&#039;, device_time) AS day,&lt;br /&gt;
       device_id,&lt;br /&gt;
       avg(cpu_usage) AS avg_cpu,&lt;br /&gt;
       max(cpu_usage) AS max_cpu&lt;br /&gt;
FROM metrics&lt;br /&gt;
GROUP BY day, device_id;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Refresh ===&lt;br /&gt;
Refresh all data between the dates specified.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CALL refresh_continuous_aggregate(&#039;daily_cpu_stats&#039;, &#039;2023-01-01&#039;, &#039;2025-04-01&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Add Policy ===&lt;br /&gt;
Create a policy that refreshes data between 1 month old and 1 day. This occurs every hour. &amp;lt;/br&amp;gt;&lt;br /&gt;
This means data older than 1 month will never be removed from the CA, even if it is deleted from the underlying table.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT add_continuous_aggregate_policy(&#039;daily_cpu_stats&#039;,&lt;br /&gt;
  start_offset =&amp;gt; INTERVAL &#039;1 month&#039;,&lt;br /&gt;
  end_offset =&amp;gt; INTERVAL &#039;1 day&#039;,&lt;br /&gt;
  schedule_interval =&amp;gt; INTERVAL &#039;1 h&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=86</id>
		<title>TimeScale Training</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=86"/>
		<updated>2025-04-20T16:55:35Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Installation ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/&lt;br /&gt;
=== Debian ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &amp;quot;deb https://packagecloud.io/timescale/timescaledb/debian/ $(lsb_release -c -s) main&amp;quot; | /&lt;br /&gt;
  sudo tee /etc/apt/sources.list.d/timescaledb.list&lt;br /&gt;
wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | /&lt;br /&gt;
  sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/timescaledb.gpg&lt;br /&gt;
sudo apt update&lt;br /&gt;
&lt;br /&gt;
# Get latest version&lt;br /&gt;
sudo apt install timescaledb-2-postgresql-17&lt;br /&gt;
# Get specific version&lt;br /&gt;
sudo apt-get install timescaledb-2-postgresql-15=&#039;2.14.2*&#039; timescaledb-2-loader-postgresql-15=&#039;2.14.2*&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When running timescaledb-tune, accept the option to update shared_preload_libraries. Other settings are optional &amp;lt;/br&amp;gt;&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/configuration/timescaledb-tune&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo timescaledb-tune&lt;br /&gt;
sudo systemctl restart postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE EXTENSION IF NOT EXISTS timescaledb;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/installation-windows/#install-and-configure-timescaledb-on-postgresql&lt;br /&gt;
&lt;br /&gt;
== Setup ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SET search_path = &amp;quot;$user&amp;quot;, public, &amp;quot;timescaledb_information&amp;quot;;&lt;br /&gt;
SET timezone TO &#039;Europe/London&#039;;&lt;br /&gt;
&lt;br /&gt;
\dv timescaledb_information.*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DROP TABLE IF EXISTS metrics;&lt;br /&gt;
CREATE TABLE metrics (id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, &lt;br /&gt;
                      device_time timestamptz NOT NULL, &lt;br /&gt;
                      device_id integer, &lt;br /&gt;
                      cpu_usage numeric);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create Hypertable ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ERROR:  cannot create a unique index without the column &amp;quot;device_time&amp;quot; (used in partitioning) &amp;lt;/br&amp;gt;&lt;br /&gt;
HINT:  If you&#039;re creating a hypertable on a table with a primary key, ensure the partitioning column is part of the primary or composite key.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics DROP CONSTRAINT metrics_pkey;&lt;br /&gt;
ALTER TABLE metrics ADD PRIMARY KEY (id, device_time);&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the existing table already has data in it then you must use the &amp;quot;migrate_data&amp;quot; option. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;), migrate_data =&amp;gt; true);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Chunk Interval ===&lt;br /&gt;
The default chunk interval is 7 days.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;time&#039;, INTERVAL &#039;1 month&#039;));&lt;br /&gt;
SELECT set_chunk_time_interval(&#039;metrics&#039;, INTERVAL &#039;14 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT hypertable_name, column_name, time_interval FROM timescaledb_information.dimensions;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Generate Time Series Data ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
TRUNCATE TABLE metrics RESTART IDENTITY;&lt;br /&gt;
INSERT INTO metrics (device_time, device_id, cpu_usage) (&lt;br /&gt;
SELECT time, device_id, random()*100 as cpu_usage &lt;br /&gt;
  FROM generate_series(date_trunc(&#039;hour&#039;,now()) - INTERVAL &#039;6 months&#039;, now(), INTERVAL &#039;5 minutes&#039;) as time, &lt;br /&gt;
       generate_series(1,1000) device_id&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Table Size ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT PG_SIZE_PRETTY (pg_total_relation_size(&#039;metrics&#039;));&lt;br /&gt;
SELECT PG_SIZE_PRETTY (hypertable_size(&#039;metrics&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Check Statistics ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ANALYZE metrics;&lt;br /&gt;
SELECT tablename, attname, n_distinct FROM pg_stats WHERE tablename = &#039;metrics&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Examine Chunks ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
\d+ metrics&lt;br /&gt;
SELECT MAX(device_time), MIN(device_time) FROM _timescaledb_internal.&amp;lt;chunk&amp;gt;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Compression ==&lt;br /&gt;
=== Old ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics &lt;br /&gt;
SET (timescaledb.compress, &lt;br /&gt;
     timescaledb.compress_segmentby=&#039;device_id&#039;, &lt;br /&gt;
     timescaledb.compress_orderby=&#039;device_time DESC&#039;);&lt;br /&gt;
	 &lt;br /&gt;
SELECT compress_chunk(c) from show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT add_compression_policy(&#039;metrics&#039;, INTERVAL &#039;1 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT remove_compression_policy(&#039;metrics&#039;);&lt;br /&gt;
SELECT decompress_chunk(c, true) FROM show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== New (HyperCore) ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics SET (&lt;br /&gt;
   timescaledb.enable_columnstore = true, &lt;br /&gt;
   timescaledb.segmentby = &#039;device_id&#039;);&lt;br /&gt;
   &lt;br /&gt;
CALL add_columnstore_policy(&#039;metrics&#039;, after =&amp;gt; INTERVAL &#039;1d&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT pg_size_pretty(before_compression_table_bytes) as before_table,&lt;br /&gt;
       pg_size_pretty(before_compression_index_bytes) as before_index,&lt;br /&gt;
       pg_size_pretty(before_compression_toast_bytes) as before_toast,&lt;br /&gt;
       pg_size_pretty(before_compression_total_bytes) as before_total,&lt;br /&gt;
       pg_size_pretty(after_compression_table_bytes) as after_table,&lt;br /&gt;
       pg_size_pretty(after_compression_index_bytes) as after_index,&lt;br /&gt;
       pg_size_pretty(after_compression_toast_bytes) as after_toast,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after_total&lt;br /&gt;
  FROM hypertable_columnstore_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT * FROM timescaledb_information.jobs WHERE proc_name=&#039;policy_compression&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tablespaces ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/manage-storage &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE TABLESPACE historic_data OWNER postgres LOCATION &#039;/home/postgres/timescale&#039;;&lt;br /&gt;
&lt;br /&gt;
SELECT show_chunks(&#039;metrics&#039;, older_than =&amp;gt; INTERVAL &#039;5 months&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT move_chunk(&lt;br /&gt;
  chunk =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk&#039;,&lt;br /&gt;
  destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  index_destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  reorder_index =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk_netdata_time_idx&#039;,&lt;br /&gt;
  verbose =&amp;gt; TRUE&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Limitations ==&lt;br /&gt;
https://docs.timescale.com/timescaledb/latest/overview/limitations &amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Time dimensions (columns) used for partitioning cannot have NULL values.&lt;br /&gt;
* Unique indexes must include all columns that are partitioning dimensions. &lt;br /&gt;
* UPDATE statements that move values between partitions (chunks) are not supported.&lt;br /&gt;
* Foreign key constraints from a hypertable referencing another hypertable are not supported.&lt;br /&gt;
&lt;br /&gt;
== Parameters ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT name, setting, short_desc FROM pg_settings WHERE name like &#039;timescaledb.%&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Continuous Aggregates ==&lt;br /&gt;
=== Create ===&lt;br /&gt;
The materialised view will be created immediately unless you use the option &amp;lt;b&amp;gt;WITH NO DATA&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE MATERIALIZED VIEW daily_cpu_stats&lt;br /&gt;
WITH (timescaledb.continuous) AS&lt;br /&gt;
SELECT time_bucket(&#039;1 day&#039;, device_time) AS day,&lt;br /&gt;
       device_id,&lt;br /&gt;
       avg(cpu_usage) AS avg_cpu,&lt;br /&gt;
       max(cpu_usage) AS max_cpu&lt;br /&gt;
FROM metrics&lt;br /&gt;
GROUP BY day, device_id;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Refresh ===&lt;br /&gt;
Refresh all data between the dates specified.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CALL refresh_continuous_aggregate(&#039;daily_cpu_stats&#039;, &#039;2023-01-01&#039;, &#039;2025-04-01&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Add Policy ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT add_continuous_aggregate_policy(&#039;daily_cpu_stats&#039;,&lt;br /&gt;
  start_offset =&amp;gt; INTERVAL &#039;1 month&#039;,&lt;br /&gt;
  end_offset =&amp;gt; INTERVAL &#039;1 day&#039;,&lt;br /&gt;
  schedule_interval =&amp;gt; INTERVAL &#039;1 h&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=85</id>
		<title>TimeScale Training</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=85"/>
		<updated>2025-04-20T16:45:54Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Installation ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/&lt;br /&gt;
=== Debian ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &amp;quot;deb https://packagecloud.io/timescale/timescaledb/debian/ $(lsb_release -c -s) main&amp;quot; | /&lt;br /&gt;
  sudo tee /etc/apt/sources.list.d/timescaledb.list&lt;br /&gt;
wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | /&lt;br /&gt;
  sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/timescaledb.gpg&lt;br /&gt;
sudo apt update&lt;br /&gt;
&lt;br /&gt;
# Get latest version&lt;br /&gt;
sudo apt install timescaledb-2-postgresql-17&lt;br /&gt;
# Get specific version&lt;br /&gt;
sudo apt-get install timescaledb-2-postgresql-15=&#039;2.14.2*&#039; timescaledb-2-loader-postgresql-15=&#039;2.14.2*&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When running timescaledb-tune, accept the option to update shared_preload_libraries. Other settings are optional &amp;lt;/br&amp;gt;&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/configuration/timescaledb-tune&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo timescaledb-tune&lt;br /&gt;
sudo systemctl restart postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE EXTENSION IF NOT EXISTS timescaledb;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/installation-windows/#install-and-configure-timescaledb-on-postgresql&lt;br /&gt;
&lt;br /&gt;
== Setup ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SET search_path = &amp;quot;$user&amp;quot;, public, &amp;quot;timescaledb_information&amp;quot;;&lt;br /&gt;
SET timezone TO &#039;Europe/London&#039;;&lt;br /&gt;
&lt;br /&gt;
\dv timescaledb_information.*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DROP TABLE IF EXISTS metrics;&lt;br /&gt;
CREATE TABLE metrics (id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, &lt;br /&gt;
                      device_time timestamptz NOT NULL, &lt;br /&gt;
                      device_id integer, &lt;br /&gt;
                      cpu_usage numeric);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create Hypertable ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ERROR:  cannot create a unique index without the column &amp;quot;device_time&amp;quot; (used in partitioning) &amp;lt;/br&amp;gt;&lt;br /&gt;
HINT:  If you&#039;re creating a hypertable on a table with a primary key, ensure the partitioning column is part of the primary or composite key.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics DROP CONSTRAINT metrics_pkey;&lt;br /&gt;
ALTER TABLE metrics ADD PRIMARY KEY (id, device_time);&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the existing table already has data in it then you must use the &amp;quot;migrate_data&amp;quot; option. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;), migrate_data =&amp;gt; true);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Chunk Interval ===&lt;br /&gt;
The default chunk interval is 7 days.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;time&#039;, INTERVAL &#039;1 month&#039;));&lt;br /&gt;
SELECT set_chunk_time_interval(&#039;metrics&#039;, INTERVAL &#039;14 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT hypertable_name, column_name, time_interval FROM timescaledb_information.dimensions;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Generate Time Series Data ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
TRUNCATE TABLE metrics RESTART IDENTITY;&lt;br /&gt;
INSERT INTO metrics (device_time, device_id, cpu_usage) (&lt;br /&gt;
SELECT time, device_id, random()*100 as cpu_usage &lt;br /&gt;
  FROM generate_series(date_trunc(&#039;hour&#039;,now()) - INTERVAL &#039;6 months&#039;, now(), INTERVAL &#039;5 minutes&#039;) as time, &lt;br /&gt;
       generate_series(1,1000) device_id&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Table Size ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT PG_SIZE_PRETTY (pg_total_relation_size(&#039;metrics&#039;));&lt;br /&gt;
SELECT PG_SIZE_PRETTY (hypertable_size(&#039;metrics&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Check Statistics ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ANALYZE metrics;&lt;br /&gt;
SELECT tablename, attname, n_distinct FROM pg_stats WHERE tablename = &#039;metrics&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Examine Chunks ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
\d+ metrics&lt;br /&gt;
SELECT MAX(device_time), MIN(device_time) FROM _timescaledb_internal.&amp;lt;chunk&amp;gt;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Compression ==&lt;br /&gt;
=== Old ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics &lt;br /&gt;
SET (timescaledb.compress, &lt;br /&gt;
     timescaledb.compress_segmentby=&#039;device_id&#039;, &lt;br /&gt;
     timescaledb.compress_orderby=&#039;device_time DESC&#039;);&lt;br /&gt;
	 &lt;br /&gt;
SELECT compress_chunk(c) from show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT add_compression_policy(&#039;metrics&#039;, INTERVAL &#039;1 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT remove_compression_policy(&#039;metrics&#039;);&lt;br /&gt;
SELECT decompress_chunk(c, true) FROM show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== New (HyperCore) ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics SET (&lt;br /&gt;
   timescaledb.enable_columnstore = true, &lt;br /&gt;
   timescaledb.segmentby = &#039;device_id&#039;);&lt;br /&gt;
   &lt;br /&gt;
CALL add_columnstore_policy(&#039;metrics&#039;, after =&amp;gt; INTERVAL &#039;1d&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT pg_size_pretty(before_compression_table_bytes) as before_table,&lt;br /&gt;
       pg_size_pretty(before_compression_index_bytes) as before_index,&lt;br /&gt;
       pg_size_pretty(before_compression_toast_bytes) as before_toast,&lt;br /&gt;
       pg_size_pretty(before_compression_total_bytes) as before_total,&lt;br /&gt;
       pg_size_pretty(after_compression_table_bytes) as after_table,&lt;br /&gt;
       pg_size_pretty(after_compression_index_bytes) as after_index,&lt;br /&gt;
       pg_size_pretty(after_compression_toast_bytes) as after_toast,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after_total&lt;br /&gt;
  FROM hypertable_columnstore_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT * FROM timescaledb_information.jobs WHERE proc_name=&#039;policy_compression&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tablespaces ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/manage-storage &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE TABLESPACE historic_data OWNER postgres LOCATION &#039;/home/postgres/timescale&#039;;&lt;br /&gt;
&lt;br /&gt;
SELECT show_chunks(&#039;metrics&#039;, older_than =&amp;gt; INTERVAL &#039;5 months&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT move_chunk(&lt;br /&gt;
  chunk =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk&#039;,&lt;br /&gt;
  destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  index_destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  reorder_index =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk_netdata_time_idx&#039;,&lt;br /&gt;
  verbose =&amp;gt; TRUE&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Limitations ==&lt;br /&gt;
https://docs.timescale.com/timescaledb/latest/overview/limitations &amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Time dimensions (columns) used for partitioning cannot have NULL values.&lt;br /&gt;
* Unique indexes must include all columns that are partitioning dimensions. &lt;br /&gt;
* UPDATE statements that move values between partitions (chunks) are not supported.&lt;br /&gt;
* Foreign key constraints from a hypertable referencing another hypertable are not supported.&lt;br /&gt;
&lt;br /&gt;
== Parameters ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT name, setting, short_desc FROM pg_settings WHERE name like &#039;timescaledb.%&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Continuous Aggregates ==&lt;br /&gt;
The materialised view will be created immediately unless you use the option &amp;lt;b&amp;gt;WITH NO DATA&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE MATERIALIZED VIEW daily_cpu_stats&lt;br /&gt;
WITH (timescaledb.continuous) AS&lt;br /&gt;
SELECT time_bucket(&#039;1 day&#039;, device_time) AS day,&lt;br /&gt;
       device_id,&lt;br /&gt;
       avg(cpu_usage) AS avg_cpu,&lt;br /&gt;
       max(cpu_usage) AS max_cpu&lt;br /&gt;
FROM metrics&lt;br /&gt;
GROUP BY day, device_id;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=84</id>
		<title>TimeScale Training</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=84"/>
		<updated>2025-04-20T16:43:59Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Installation ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/&lt;br /&gt;
=== Debian ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &amp;quot;deb https://packagecloud.io/timescale/timescaledb/debian/ $(lsb_release -c -s) main&amp;quot; | /&lt;br /&gt;
  sudo tee /etc/apt/sources.list.d/timescaledb.list&lt;br /&gt;
wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | /&lt;br /&gt;
  sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/timescaledb.gpg&lt;br /&gt;
sudo apt update&lt;br /&gt;
&lt;br /&gt;
# Get latest version&lt;br /&gt;
sudo apt install timescaledb-2-postgresql-17&lt;br /&gt;
# Get specific version&lt;br /&gt;
sudo apt-get install timescaledb-2-postgresql-15=&#039;2.14.2*&#039; timescaledb-2-loader-postgresql-15=&#039;2.14.2*&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When running timescaledb-tune, accept the option to update shared_preload_libraries. Other settings are optional &amp;lt;/br&amp;gt;&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/configuration/timescaledb-tune&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo timescaledb-tune&lt;br /&gt;
sudo systemctl restart postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE EXTENSION IF NOT EXISTS timescaledb;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/installation-windows/#install-and-configure-timescaledb-on-postgresql&lt;br /&gt;
&lt;br /&gt;
== Setup ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SET search_path = &amp;quot;$user&amp;quot;, public, &amp;quot;timescaledb_information&amp;quot;;&lt;br /&gt;
SET timezone TO &#039;Europe/London&#039;;&lt;br /&gt;
&lt;br /&gt;
\dv timescaledb_information.*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DROP TABLE IF EXISTS metrics;&lt;br /&gt;
CREATE TABLE metrics (id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, &lt;br /&gt;
                      device_time timestamptz NOT NULL, &lt;br /&gt;
                      device_id integer, &lt;br /&gt;
                      cpu_usage numeric);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create Hypertable ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ERROR:  cannot create a unique index without the column &amp;quot;device_time&amp;quot; (used in partitioning) &amp;lt;/br&amp;gt;&lt;br /&gt;
HINT:  If you&#039;re creating a hypertable on a table with a primary key, ensure the partitioning column is part of the primary or composite key.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics DROP CONSTRAINT metrics_pkey;&lt;br /&gt;
ALTER TABLE metrics ADD PRIMARY KEY (id, device_time);&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the existing table already has data in it then you must use the &amp;quot;migrate_data&amp;quot; option. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;), migrate_data =&amp;gt; true);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Chunk Interval ===&lt;br /&gt;
The default chunk interval is 7 days.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;time&#039;, INTERVAL &#039;1 month&#039;));&lt;br /&gt;
SELECT set_chunk_time_interval(&#039;metrics&#039;, INTERVAL &#039;14 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT hypertable_name, column_name, time_interval FROM timescaledb_information.dimensions;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Generate Time Series Data ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
TRUNCATE TABLE metrics RESTART IDENTITY;&lt;br /&gt;
INSERT INTO metrics (device_time, device_id, cpu_usage) (&lt;br /&gt;
SELECT time, device_id, random()*100 as cpu_usage &lt;br /&gt;
  FROM generate_series(date_trunc(&#039;hour&#039;,now()) - INTERVAL &#039;6 months&#039;, now(), INTERVAL &#039;5 minutes&#039;) as time, &lt;br /&gt;
       generate_series(1,1000) device_id&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Table Size ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT PG_SIZE_PRETTY (pg_total_relation_size(&#039;metrics&#039;));&lt;br /&gt;
SELECT PG_SIZE_PRETTY (hypertable_size(&#039;metrics&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Check Statistics ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ANALYZE metrics;&lt;br /&gt;
SELECT tablename, attname, n_distinct FROM pg_stats WHERE tablename = &#039;metrics&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Examine Chunks ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
\d+ metrics&lt;br /&gt;
SELECT MAX(device_time), MIN(device_time) FROM _timescaledb_internal.&amp;lt;chunk&amp;gt;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Compression ==&lt;br /&gt;
=== Old ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics &lt;br /&gt;
SET (timescaledb.compress, &lt;br /&gt;
     timescaledb.compress_segmentby=&#039;device_id&#039;, &lt;br /&gt;
     timescaledb.compress_orderby=&#039;device_time DESC&#039;);&lt;br /&gt;
	 &lt;br /&gt;
SELECT compress_chunk(c) from show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT add_compression_policy(&#039;metrics&#039;, INTERVAL &#039;1 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT remove_compression_policy(&#039;metrics&#039;);&lt;br /&gt;
SELECT decompress_chunk(c, true) FROM show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== New (HyperCore) ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics SET (&lt;br /&gt;
   timescaledb.enable_columnstore = true, &lt;br /&gt;
   timescaledb.segmentby = &#039;device_id&#039;);&lt;br /&gt;
   &lt;br /&gt;
CALL add_columnstore_policy(&#039;metrics&#039;, after =&amp;gt; INTERVAL &#039;1d&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT pg_size_pretty(before_compression_table_bytes) as before_table,&lt;br /&gt;
       pg_size_pretty(before_compression_index_bytes) as before_index,&lt;br /&gt;
       pg_size_pretty(before_compression_toast_bytes) as before_toast,&lt;br /&gt;
       pg_size_pretty(before_compression_total_bytes) as before_total,&lt;br /&gt;
       pg_size_pretty(after_compression_table_bytes) as after_table,&lt;br /&gt;
       pg_size_pretty(after_compression_index_bytes) as after_index,&lt;br /&gt;
       pg_size_pretty(after_compression_toast_bytes) as after_toast,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after_total&lt;br /&gt;
  FROM hypertable_columnstore_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT * FROM timescaledb_information.jobs WHERE proc_name=&#039;policy_compression&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tablespaces ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/manage-storage &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE TABLESPACE historic_data OWNER postgres LOCATION &#039;/home/postgres/timescale&#039;;&lt;br /&gt;
&lt;br /&gt;
SELECT show_chunks(&#039;metrics&#039;, older_than =&amp;gt; INTERVAL &#039;5 months&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT move_chunk(&lt;br /&gt;
  chunk =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk&#039;,&lt;br /&gt;
  destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  index_destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  reorder_index =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk_netdata_time_idx&#039;,&lt;br /&gt;
  verbose =&amp;gt; TRUE&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Limitations ==&lt;br /&gt;
https://docs.timescale.com/timescaledb/latest/overview/limitations &amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Time dimensions (columns) used for partitioning cannot have NULL values.&lt;br /&gt;
* Unique indexes must include all columns that are partitioning dimensions. &lt;br /&gt;
* UPDATE statements that move values between partitions (chunks) are not supported.&lt;br /&gt;
* Foreign key constraints from a hypertable referencing another hypertable are not supported.&lt;br /&gt;
&lt;br /&gt;
== Parameters ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT name, setting, short_desc FROM pg_settings WHERE name like &#039;timescaledb.%&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Continuous Aggregates ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE MATERIALIZED VIEW daily_cpu_stats&lt;br /&gt;
WITH (timescaledb.continuous) AS&lt;br /&gt;
SELECT time_bucket(&#039;1 day&#039;, device_time) AS day,&lt;br /&gt;
       device_id,&lt;br /&gt;
       avg(cpu_usage) AS avg_cpu,&lt;br /&gt;
       max(cpu_usage) AS max_cpu&lt;br /&gt;
FROM metrics&lt;br /&gt;
GROUP BY day, device_id;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=83</id>
		<title>TimeScale Training</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=83"/>
		<updated>2025-04-17T17:08:59Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Installation ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/&lt;br /&gt;
=== Debian ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &amp;quot;deb https://packagecloud.io/timescale/timescaledb/debian/ $(lsb_release -c -s) main&amp;quot; | /&lt;br /&gt;
  sudo tee /etc/apt/sources.list.d/timescaledb.list&lt;br /&gt;
wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | /&lt;br /&gt;
  sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/timescaledb.gpg&lt;br /&gt;
sudo apt update&lt;br /&gt;
&lt;br /&gt;
# Get latest version&lt;br /&gt;
sudo apt install timescaledb-2-postgresql-17&lt;br /&gt;
# Get specific version&lt;br /&gt;
sudo apt-get install timescaledb-2-postgresql-15=&#039;2.14.2*&#039; timescaledb-2-loader-postgresql-15=&#039;2.14.2*&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When running timescaledb-tune, accept the option to update shared_preload_libraries. Other settings are optional &amp;lt;/br&amp;gt;&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/configuration/timescaledb-tune&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo timescaledb-tune&lt;br /&gt;
sudo systemctl restart postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE EXTENSION IF NOT EXISTS timescaledb;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/installation-windows/#install-and-configure-timescaledb-on-postgresql&lt;br /&gt;
&lt;br /&gt;
== Setup ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SET search_path = &amp;quot;$user&amp;quot;, public, &amp;quot;timescaledb_information&amp;quot;;&lt;br /&gt;
SET timezone TO &#039;Europe/London&#039;;&lt;br /&gt;
&lt;br /&gt;
\dv timescaledb_information.*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DROP TABLE IF EXISTS metrics;&lt;br /&gt;
CREATE TABLE metrics (id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, &lt;br /&gt;
                      device_time timestamptz NOT NULL, &lt;br /&gt;
                      device_id integer, &lt;br /&gt;
                      cpu_usage numeric);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create Hypertable ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ERROR:  cannot create a unique index without the column &amp;quot;device_time&amp;quot; (used in partitioning) &amp;lt;/br&amp;gt;&lt;br /&gt;
HINT:  If you&#039;re creating a hypertable on a table with a primary key, ensure the partitioning column is part of the primary or composite key.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics DROP CONSTRAINT metrics_pkey;&lt;br /&gt;
ALTER TABLE metrics ADD PRIMARY KEY (id, device_time);&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the existing table already has data in it then you must use the &amp;quot;migrate_data&amp;quot; option. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;), migrate_data =&amp;gt; true);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Chunk Interval ===&lt;br /&gt;
The default chunk interval is 7 days.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;time&#039;, INTERVAL &#039;1 month&#039;));&lt;br /&gt;
SELECT set_chunk_time_interval(&#039;metrics&#039;, INTERVAL &#039;14 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT hypertable_name, column_name, time_interval FROM timescaledb_information.dimensions;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Generate Time Series Data ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
TRUNCATE TABLE metrics RESTART IDENTITY;&lt;br /&gt;
INSERT INTO metrics (device_time, device_id, cpu_usage) (&lt;br /&gt;
SELECT time, device_id, random()*100 as cpu_usage &lt;br /&gt;
  FROM generate_series(date_trunc(&#039;hour&#039;,now()) - INTERVAL &#039;6 months&#039;, now(), INTERVAL &#039;5 minutes&#039;) as time, &lt;br /&gt;
       generate_series(1,1000) device_id&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Table Size ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT PG_SIZE_PRETTY (pg_total_relation_size(&#039;metrics&#039;));&lt;br /&gt;
SELECT PG_SIZE_PRETTY (hypertable_size(&#039;metrics&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Check Statistics ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ANALYZE metrics;&lt;br /&gt;
SELECT tablename, attname, n_distinct FROM pg_stats WHERE tablename = &#039;metrics&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Examine Chunks ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
\d+ metrics&lt;br /&gt;
SELECT MAX(device_time), MIN(device_time) FROM _timescaledb_internal.&amp;lt;chunk&amp;gt;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Compression ==&lt;br /&gt;
=== Old ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics &lt;br /&gt;
SET (timescaledb.compress, &lt;br /&gt;
     timescaledb.compress_segmentby=&#039;device_id&#039;, &lt;br /&gt;
     timescaledb.compress_orderby=&#039;device_time DESC&#039;);&lt;br /&gt;
	 &lt;br /&gt;
SELECT compress_chunk(c) from show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT add_compression_policy(&#039;metrics&#039;, INTERVAL &#039;1 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT remove_compression_policy(&#039;metrics&#039;);&lt;br /&gt;
SELECT decompress_chunk(c, true) FROM show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== New (HyperCore) ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics SET (&lt;br /&gt;
   timescaledb.enable_columnstore = true, &lt;br /&gt;
   timescaledb.segmentby = &#039;device_id&#039;);&lt;br /&gt;
   &lt;br /&gt;
CALL add_columnstore_policy(&#039;metrics&#039;, after =&amp;gt; INTERVAL &#039;1d&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT pg_size_pretty(before_compression_table_bytes) as before_table,&lt;br /&gt;
       pg_size_pretty(before_compression_index_bytes) as before_index,&lt;br /&gt;
       pg_size_pretty(before_compression_toast_bytes) as before_toast,&lt;br /&gt;
       pg_size_pretty(before_compression_total_bytes) as before_total,&lt;br /&gt;
       pg_size_pretty(after_compression_table_bytes) as after_table,&lt;br /&gt;
       pg_size_pretty(after_compression_index_bytes) as after_index,&lt;br /&gt;
       pg_size_pretty(after_compression_toast_bytes) as after_toast,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after_total&lt;br /&gt;
  FROM hypertable_columnstore_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT * FROM timescaledb_information.jobs WHERE proc_name=&#039;policy_compression&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tablespaces ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/manage-storage &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE TABLESPACE historic_data OWNER postgres LOCATION &#039;/home/postgres/timescale&#039;;&lt;br /&gt;
&lt;br /&gt;
SELECT show_chunks(&#039;metrics&#039;, older_than =&amp;gt; INTERVAL &#039;5 months&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT move_chunk(&lt;br /&gt;
  chunk =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk&#039;,&lt;br /&gt;
  destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  index_destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  reorder_index =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk_netdata_time_idx&#039;,&lt;br /&gt;
  verbose =&amp;gt; TRUE&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Limitations ==&lt;br /&gt;
https://docs.timescale.com/timescaledb/latest/overview/limitations &amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Time dimensions (columns) used for partitioning cannot have NULL values.&lt;br /&gt;
* Unique indexes must include all columns that are partitioning dimensions. &lt;br /&gt;
* UPDATE statements that move values between partitions (chunks) are not supported.&lt;br /&gt;
* Foreign key constraints from a hypertable referencing another hypertable are not supported.&lt;br /&gt;
&lt;br /&gt;
== Parameters ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT name, setting, short_desc FROM pg_settings WHERE name like &#039;timescaledb.%&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=82</id>
		<title>TimeScale Training</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=82"/>
		<updated>2025-04-17T17:07:54Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Installation ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/&lt;br /&gt;
=== Debian ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &amp;quot;deb https://packagecloud.io/timescale/timescaledb/debian/ $(lsb_release -c -s) main&amp;quot; | /&lt;br /&gt;
  sudo tee /etc/apt/sources.list.d/timescaledb.list&lt;br /&gt;
wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | /&lt;br /&gt;
  sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/timescaledb.gpg&lt;br /&gt;
sudo apt update&lt;br /&gt;
sudo apt install timescaledb-2-postgresql-17&lt;br /&gt;
sudo apt-get install timescaledb-2-postgresql-15=&#039;2.14.2*&#039; timescaledb-2-loader-postgresql-15=&#039;2.14.2*&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When running timescaledb-tune, accept the option to update shared_preload_libraries. Other settings are optional &amp;lt;/br&amp;gt;&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/configuration/timescaledb-tune&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo timescaledb-tune&lt;br /&gt;
sudo systemctl restart postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE EXTENSION IF NOT EXISTS timescaledb;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/installation-windows/#install-and-configure-timescaledb-on-postgresql&lt;br /&gt;
&lt;br /&gt;
== Setup ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SET search_path = &amp;quot;$user&amp;quot;, public, &amp;quot;timescaledb_information&amp;quot;;&lt;br /&gt;
SET timezone TO &#039;Europe/London&#039;;&lt;br /&gt;
&lt;br /&gt;
\dv timescaledb_information.*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DROP TABLE IF EXISTS metrics;&lt;br /&gt;
CREATE TABLE metrics (id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, &lt;br /&gt;
                      device_time timestamptz NOT NULL, &lt;br /&gt;
                      device_id integer, &lt;br /&gt;
                      cpu_usage numeric);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create Hypertable ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ERROR:  cannot create a unique index without the column &amp;quot;device_time&amp;quot; (used in partitioning) &amp;lt;/br&amp;gt;&lt;br /&gt;
HINT:  If you&#039;re creating a hypertable on a table with a primary key, ensure the partitioning column is part of the primary or composite key.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics DROP CONSTRAINT metrics_pkey;&lt;br /&gt;
ALTER TABLE metrics ADD PRIMARY KEY (id, device_time);&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the existing table already has data in it then you must use the &amp;quot;migrate_data&amp;quot; option. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;), migrate_data =&amp;gt; true);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Chunk Interval ===&lt;br /&gt;
The default chunk interval is 7 days.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;time&#039;, INTERVAL &#039;1 month&#039;));&lt;br /&gt;
SELECT set_chunk_time_interval(&#039;metrics&#039;, INTERVAL &#039;14 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT hypertable_name, column_name, time_interval FROM timescaledb_information.dimensions;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Generate Time Series Data ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
TRUNCATE TABLE metrics RESTART IDENTITY;&lt;br /&gt;
INSERT INTO metrics (device_time, device_id, cpu_usage) (&lt;br /&gt;
SELECT time, device_id, random()*100 as cpu_usage &lt;br /&gt;
  FROM generate_series(date_trunc(&#039;hour&#039;,now()) - INTERVAL &#039;6 months&#039;, now(), INTERVAL &#039;5 minutes&#039;) as time, &lt;br /&gt;
       generate_series(1,1000) device_id&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Table Size ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT PG_SIZE_PRETTY (pg_total_relation_size(&#039;metrics&#039;));&lt;br /&gt;
SELECT PG_SIZE_PRETTY (hypertable_size(&#039;metrics&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Check Statistics ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ANALYZE metrics;&lt;br /&gt;
SELECT tablename, attname, n_distinct FROM pg_stats WHERE tablename = &#039;metrics&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Examine Chunks ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
\d+ metrics&lt;br /&gt;
SELECT MAX(device_time), MIN(device_time) FROM _timescaledb_internal.&amp;lt;chunk&amp;gt;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Compression ==&lt;br /&gt;
=== Old ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics &lt;br /&gt;
SET (timescaledb.compress, &lt;br /&gt;
     timescaledb.compress_segmentby=&#039;device_id&#039;, &lt;br /&gt;
     timescaledb.compress_orderby=&#039;device_time DESC&#039;);&lt;br /&gt;
	 &lt;br /&gt;
SELECT compress_chunk(c) from show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT add_compression_policy(&#039;metrics&#039;, INTERVAL &#039;1 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT remove_compression_policy(&#039;metrics&#039;);&lt;br /&gt;
SELECT decompress_chunk(c, true) FROM show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== New (HyperCore) ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics SET (&lt;br /&gt;
   timescaledb.enable_columnstore = true, &lt;br /&gt;
   timescaledb.segmentby = &#039;device_id&#039;);&lt;br /&gt;
   &lt;br /&gt;
CALL add_columnstore_policy(&#039;metrics&#039;, after =&amp;gt; INTERVAL &#039;1d&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT pg_size_pretty(before_compression_table_bytes) as before_table,&lt;br /&gt;
       pg_size_pretty(before_compression_index_bytes) as before_index,&lt;br /&gt;
       pg_size_pretty(before_compression_toast_bytes) as before_toast,&lt;br /&gt;
       pg_size_pretty(before_compression_total_bytes) as before_total,&lt;br /&gt;
       pg_size_pretty(after_compression_table_bytes) as after_table,&lt;br /&gt;
       pg_size_pretty(after_compression_index_bytes) as after_index,&lt;br /&gt;
       pg_size_pretty(after_compression_toast_bytes) as after_toast,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after_total&lt;br /&gt;
  FROM hypertable_columnstore_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT * FROM timescaledb_information.jobs WHERE proc_name=&#039;policy_compression&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tablespaces ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/manage-storage &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE TABLESPACE historic_data OWNER postgres LOCATION &#039;/home/postgres/timescale&#039;;&lt;br /&gt;
&lt;br /&gt;
SELECT show_chunks(&#039;metrics&#039;, older_than =&amp;gt; INTERVAL &#039;5 months&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT move_chunk(&lt;br /&gt;
  chunk =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk&#039;,&lt;br /&gt;
  destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  index_destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  reorder_index =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk_netdata_time_idx&#039;,&lt;br /&gt;
  verbose =&amp;gt; TRUE&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Limitations ==&lt;br /&gt;
https://docs.timescale.com/timescaledb/latest/overview/limitations &amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Time dimensions (columns) used for partitioning cannot have NULL values.&lt;br /&gt;
* Unique indexes must include all columns that are partitioning dimensions. &lt;br /&gt;
* UPDATE statements that move values between partitions (chunks) are not supported.&lt;br /&gt;
* Foreign key constraints from a hypertable referencing another hypertable are not supported.&lt;br /&gt;
&lt;br /&gt;
== Parameters ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT name, setting, short_desc FROM pg_settings WHERE name like &#039;timescaledb.%&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=81</id>
		<title>TimeScale Training</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=81"/>
		<updated>2025-04-17T13:33:19Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Installation ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/&lt;br /&gt;
=== Debian ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &amp;quot;deb https://packagecloud.io/timescale/timescaledb/debian/ $(lsb_release -c -s) main&amp;quot; | sudo tee /etc/apt/sources.list.d/timescaledb.list&lt;br /&gt;
wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/timescaledb.gpg&lt;br /&gt;
sudo apt update&lt;br /&gt;
sudo apt install timescaledb-2-postgresql-17&lt;br /&gt;
sudo apt-get install timescaledb-2-postgresql-15=&#039;2.14.2*&#039; timescaledb-2-loader-postgresql-15=&#039;2.14.2*&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When running timescaledb-tune, accept the option to update shared_preload_libraries. Other settings are optional &amp;lt;/br&amp;gt;&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/configuration/timescaledb-tune&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo timescaledb-tune&lt;br /&gt;
sudo systemctl restart postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE EXTENSION IF NOT EXISTS timescaledb;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/installation-windows/#install-and-configure-timescaledb-on-postgresql&lt;br /&gt;
&lt;br /&gt;
== Setup ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SET search_path = &amp;quot;$user&amp;quot;, public, &amp;quot;timescaledb_information&amp;quot;;&lt;br /&gt;
SET timezone TO &#039;Europe/London&#039;;&lt;br /&gt;
&lt;br /&gt;
\dv timescaledb_information.*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DROP TABLE IF EXISTS metrics;&lt;br /&gt;
CREATE TABLE metrics (id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, &lt;br /&gt;
                      device_time timestamptz NOT NULL, &lt;br /&gt;
                      device_id integer, &lt;br /&gt;
                      cpu_usage numeric);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create Hypertable ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ERROR:  cannot create a unique index without the column &amp;quot;device_time&amp;quot; (used in partitioning) &amp;lt;/br&amp;gt;&lt;br /&gt;
HINT:  If you&#039;re creating a hypertable on a table with a primary key, ensure the partitioning column is part of the primary or composite key.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics DROP CONSTRAINT metrics_pkey;&lt;br /&gt;
ALTER TABLE metrics ADD PRIMARY KEY (id, device_time);&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the existing table already has data in it then you must use the &amp;quot;migrate_data&amp;quot; option. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;), migrate_data =&amp;gt; true);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Chunk Interval ===&lt;br /&gt;
The default chunk interval is 7 days.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;time&#039;, INTERVAL &#039;1 month&#039;));&lt;br /&gt;
SELECT set_chunk_time_interval(&#039;metrics&#039;, INTERVAL &#039;14 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT hypertable_name, column_name, time_interval FROM timescaledb_information.dimensions;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Generate Time Series Data ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
TRUNCATE TABLE metrics RESTART IDENTITY;&lt;br /&gt;
INSERT INTO metrics (device_time, device_id, cpu_usage) (&lt;br /&gt;
SELECT time, device_id, random()*100 as cpu_usage &lt;br /&gt;
  FROM generate_series(date_trunc(&#039;hour&#039;,now()) - INTERVAL &#039;6 months&#039;, now(), INTERVAL &#039;5 minutes&#039;) as time, &lt;br /&gt;
       generate_series(1,1000) device_id&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Table Size ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT PG_SIZE_PRETTY (pg_total_relation_size(&#039;metrics&#039;));&lt;br /&gt;
SELECT PG_SIZE_PRETTY (hypertable_size(&#039;metrics&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Check Statistics ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ANALYZE metrics;&lt;br /&gt;
SELECT tablename, attname, n_distinct FROM pg_stats WHERE tablename = &#039;metrics&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Examine Chunks ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
\d+ metrics&lt;br /&gt;
SELECT MAX(device_time), MIN(device_time) FROM _timescaledb_internal.&amp;lt;chunk&amp;gt;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Compression ==&lt;br /&gt;
=== Old ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics &lt;br /&gt;
SET (timescaledb.compress, &lt;br /&gt;
     timescaledb.compress_segmentby=&#039;device_id&#039;, &lt;br /&gt;
     timescaledb.compress_orderby=&#039;device_time DESC&#039;);&lt;br /&gt;
	 &lt;br /&gt;
SELECT compress_chunk(c) from show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT add_compression_policy(&#039;metrics&#039;, INTERVAL &#039;1 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT remove_compression_policy(&#039;metrics&#039;);&lt;br /&gt;
SELECT decompress_chunk(c, true) FROM show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== New (HyperCore) ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics SET (&lt;br /&gt;
   timescaledb.enable_columnstore = true, &lt;br /&gt;
   timescaledb.segmentby = &#039;device_id&#039;);&lt;br /&gt;
   &lt;br /&gt;
CALL add_columnstore_policy(&#039;metrics&#039;, after =&amp;gt; INTERVAL &#039;1d&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT pg_size_pretty(before_compression_table_bytes) as before_table,&lt;br /&gt;
       pg_size_pretty(before_compression_index_bytes) as before_index,&lt;br /&gt;
       pg_size_pretty(before_compression_toast_bytes) as before_toast,&lt;br /&gt;
       pg_size_pretty(before_compression_total_bytes) as before_total,&lt;br /&gt;
       pg_size_pretty(after_compression_table_bytes) as after_table,&lt;br /&gt;
       pg_size_pretty(after_compression_index_bytes) as after_index,&lt;br /&gt;
       pg_size_pretty(after_compression_toast_bytes) as after_toast,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after_total&lt;br /&gt;
  FROM hypertable_columnstore_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT * FROM timescaledb_information.jobs WHERE proc_name=&#039;policy_compression&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tablespaces ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/manage-storage &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE TABLESPACE historic_data OWNER postgres LOCATION &#039;/home/postgres/timescale&#039;;&lt;br /&gt;
&lt;br /&gt;
SELECT show_chunks(&#039;metrics&#039;, older_than =&amp;gt; INTERVAL &#039;5 months&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT move_chunk(&lt;br /&gt;
  chunk =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk&#039;,&lt;br /&gt;
  destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  index_destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  reorder_index =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk_netdata_time_idx&#039;,&lt;br /&gt;
  verbose =&amp;gt; TRUE&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Limitations ==&lt;br /&gt;
https://docs.timescale.com/timescaledb/latest/overview/limitations &amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Time dimensions (columns) used for partitioning cannot have NULL values.&lt;br /&gt;
* Unique indexes must include all columns that are partitioning dimensions. &lt;br /&gt;
* UPDATE statements that move values between partitions (chunks) are not supported.&lt;br /&gt;
* Foreign key constraints from a hypertable referencing another hypertable are not supported.&lt;br /&gt;
&lt;br /&gt;
== Parameters ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT name, setting, short_desc FROM pg_settings WHERE name like &#039;timescaledb.%&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=80</id>
		<title>TimeScale Training</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=80"/>
		<updated>2025-04-17T12:20:54Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Installation ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/&lt;br /&gt;
=== Debian ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &amp;quot;deb https://packagecloud.io/timescale/timescaledb/debian/ $(lsb_release -c -s) main&amp;quot; | sudo tee /etc/apt/sources.list.d/timescaledb.list&lt;br /&gt;
wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/timescaledb.gpg&lt;br /&gt;
sudo apt update&lt;br /&gt;
sudo apt install timescaledb-2-postgresql-17&lt;br /&gt;
sudo apt-get install timescaledb-2-postgresql-15=&#039;2.14.2*&#039; timescaledb-2-loader-postgresql-15=&#039;2.14.2*&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When running timescaledb-tune, accept the option to update shared_preload_libraries. Other settings are optional &amp;lt;/br&amp;gt;&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/configuration/timescaledb-tune&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo timescaledb-tune&lt;br /&gt;
sudo systemctl restart postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE EXTENSION IF NOT EXISTS timescaledb;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/installation-windows/#install-and-configure-timescaledb-on-postgresql&lt;br /&gt;
&lt;br /&gt;
== Setup ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SET search_path = &amp;quot;$user&amp;quot;, public, &amp;quot;timescaledb_information&amp;quot;;&lt;br /&gt;
SET timezone TO &#039;Europe/London&#039;;&lt;br /&gt;
&lt;br /&gt;
\dv timescaledb_information.*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DROP TABLE IF EXISTS metrics;&lt;br /&gt;
CREATE TABLE metrics (id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, &lt;br /&gt;
                      device_time timestamptz NOT NULL, &lt;br /&gt;
                      device_id integer, &lt;br /&gt;
                      cpu_usage numeric);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create Hypertable ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ERROR:  cannot create a unique index without the column &amp;quot;device_time&amp;quot; (used in partitioning) &amp;lt;/br&amp;gt;&lt;br /&gt;
HINT:  If you&#039;re creating a hypertable on a table with a primary key, ensure the partitioning column is part of the primary or composite key.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics DROP CONSTRAINT metrics_pkey;&lt;br /&gt;
ALTER TABLE metrics ADD PRIMARY KEY (id, device_time);&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the existing table already has data in it then you must use the &amp;quot;migrate_data&amp;quot; option. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;), migrate_data =&amp;gt; true);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Chunk Interval ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;time&#039;, INTERVAL &#039;1 month&#039;));&lt;br /&gt;
SELECT set_chunk_time_interval(&#039;metrics&#039;, INTERVAL &#039;14 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT hypertable_name, column_name, time_interval FROM timescaledb_information.dimensions;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Generate Time Series Data ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
TRUNCATE TABLE metrics RESTART IDENTITY;&lt;br /&gt;
INSERT INTO metrics (device_time, device_id, cpu_usage) (&lt;br /&gt;
SELECT time, device_id, random()*100 as cpu_usage &lt;br /&gt;
  FROM generate_series(date_trunc(&#039;hour&#039;,now()) - INTERVAL &#039;6 months&#039;, now(), INTERVAL &#039;5 minutes&#039;) as time, &lt;br /&gt;
       generate_series(1,1000) device_id&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Table Size ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT PG_SIZE_PRETTY (pg_total_relation_size(&#039;metrics&#039;));&lt;br /&gt;
SELECT PG_SIZE_PRETTY (hypertable_size(&#039;metrics&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Check Statistics ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ANALYZE metrics;&lt;br /&gt;
SELECT tablename, attname, n_distinct FROM pg_stats WHERE tablename = &#039;metrics&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Examine Chunks ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
\d+ metrics&lt;br /&gt;
SELECT MAX(device_time), MIN(device_time) FROM _timescaledb_internal.&amp;lt;chunk&amp;gt;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Compression ==&lt;br /&gt;
=== Old ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics &lt;br /&gt;
SET (timescaledb.compress, &lt;br /&gt;
     timescaledb.compress_segmentby=&#039;device_id&#039;, &lt;br /&gt;
     timescaledb.compress_orderby=&#039;device_time DESC&#039;);&lt;br /&gt;
	 &lt;br /&gt;
SELECT compress_chunk(c) from show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT add_compression_policy(&#039;metrics&#039;, INTERVAL &#039;1 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT remove_compression_policy(&#039;metrics&#039;);&lt;br /&gt;
SELECT decompress_chunk(c, true) FROM show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== New (HyperCore) ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics SET (&lt;br /&gt;
   timescaledb.enable_columnstore = true, &lt;br /&gt;
   timescaledb.segmentby = &#039;device_id&#039;);&lt;br /&gt;
   &lt;br /&gt;
CALL add_columnstore_policy(&#039;metrics&#039;, after =&amp;gt; INTERVAL &#039;1d&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT pg_size_pretty(before_compression_table_bytes) as before_table,&lt;br /&gt;
       pg_size_pretty(before_compression_index_bytes) as before_index,&lt;br /&gt;
       pg_size_pretty(before_compression_toast_bytes) as before_toast,&lt;br /&gt;
       pg_size_pretty(before_compression_total_bytes) as before_total,&lt;br /&gt;
       pg_size_pretty(after_compression_table_bytes) as after_table,&lt;br /&gt;
       pg_size_pretty(after_compression_index_bytes) as after_index,&lt;br /&gt;
       pg_size_pretty(after_compression_toast_bytes) as after_toast,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after_total&lt;br /&gt;
  FROM hypertable_columnstore_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT * FROM timescaledb_information.jobs WHERE proc_name=&#039;policy_compression&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tablespaces ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/manage-storage &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE TABLESPACE historic_data OWNER postgres LOCATION &#039;/home/postgres/timescale&#039;;&lt;br /&gt;
&lt;br /&gt;
SELECT show_chunks(&#039;metrics&#039;, older_than =&amp;gt; INTERVAL &#039;5 months&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT move_chunk(&lt;br /&gt;
  chunk =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk&#039;,&lt;br /&gt;
  destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  index_destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  reorder_index =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk_netdata_time_idx&#039;,&lt;br /&gt;
  verbose =&amp;gt; TRUE&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Limitations ==&lt;br /&gt;
https://docs.timescale.com/timescaledb/latest/overview/limitations &amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Time dimensions (columns) used for partitioning cannot have NULL values.&lt;br /&gt;
* Unique indexes must include all columns that are partitioning dimensions. &lt;br /&gt;
* UPDATE statements that move values between partitions (chunks) are not supported.&lt;br /&gt;
* Foreign key constraints from a hypertable referencing another hypertable are not supported.&lt;br /&gt;
&lt;br /&gt;
== Parameters ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT name, setting, short_desc FROM pg_settings WHERE name like &#039;timescaledb.%&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=79</id>
		<title>TimeScale Training</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=79"/>
		<updated>2025-04-17T12:08:13Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Installation ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/&lt;br /&gt;
=== Debian ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &amp;quot;deb https://packagecloud.io/timescale/timescaledb/debian/ $(lsb_release -c -s) main&amp;quot; | sudo tee /etc/apt/sources.list.d/timescaledb.list&lt;br /&gt;
wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/timescaledb.gpg&lt;br /&gt;
sudo apt update&lt;br /&gt;
sudo apt install timescaledb-2-postgresql-17&lt;br /&gt;
sudo apt-get install timescaledb-2-postgresql-15=&#039;2.14.2*&#039; timescaledb-2-loader-postgresql-15=&#039;2.14.2*&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When running timescaledb-tune, accept the option to update shared_preload_libraries. Other settings are optional &amp;lt;/br&amp;gt;&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/configuration/timescaledb-tune&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo timescaledb-tune&lt;br /&gt;
sudo systemctl restart postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE EXTENSION IF NOT EXISTS timescaledb;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/installation-windows/#install-and-configure-timescaledb-on-postgresql&lt;br /&gt;
&lt;br /&gt;
== Setup ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SET search_path = &amp;quot;$user&amp;quot;, public, &amp;quot;timescaledb_information&amp;quot;;&lt;br /&gt;
SET timezone TO &#039;Europe/Berlin&#039;;&lt;br /&gt;
&lt;br /&gt;
\dv timescaledb_information.*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DROP TABLE IF EXISTS metrics;&lt;br /&gt;
CREATE TABLE metrics (id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, &lt;br /&gt;
                      device_time timestamptz NOT NULL, &lt;br /&gt;
                      device_id integer, &lt;br /&gt;
                      cpu_usage numeric);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create Hypertable ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ERROR:  cannot create a unique index without the column &amp;quot;device_time&amp;quot; (used in partitioning) &amp;lt;/br&amp;gt;&lt;br /&gt;
HINT:  If you&#039;re creating a hypertable on a table with a primary key, ensure the partitioning column is part of the primary or composite key.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics DROP CONSTRAINT metrics_pkey;&lt;br /&gt;
ALTER TABLE metrics ADD PRIMARY KEY (id, device_time);&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the existing table already has data in it then you must use the &amp;quot;migrate_data&amp;quot; option. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;), migrate_data =&amp;gt; true);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Chunk Interval ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;time&#039;, INTERVAL &#039;1 month&#039;));&lt;br /&gt;
SELECT set_chunk_time_interval(&#039;metrics&#039;, INTERVAL &#039;14 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT hypertable_name, column_name, time_interval FROM timescaledb_information.dimensions;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Generate Time Series Data ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
TRUNCATE TABLE metrics RESTART IDENTITY;&lt;br /&gt;
INSERT INTO metrics (device_time, device_id, cpu_usage) (&lt;br /&gt;
SELECT time, device_id, random()*100 as cpu_usage &lt;br /&gt;
  FROM generate_series(date_trunc(&#039;hour&#039;,now()) - INTERVAL &#039;6 months&#039;, now(), INTERVAL &#039;5 minutes&#039;) as time, &lt;br /&gt;
       generate_series(1,1000) device_id&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Table Size ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT PG_SIZE_PRETTY (pg_total_relation_size(&#039;metrics&#039;));&lt;br /&gt;
SELECT PG_SIZE_PRETTY (hypertable_size(&#039;metrics&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Check Statistics ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ANALYZE metrics;&lt;br /&gt;
SELECT tablename, attname, n_distinct FROM pg_stats WHERE tablename = &#039;metrics&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Examine Chunks ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
\d+ metrics&lt;br /&gt;
SELECT MAX(device_time), MIN(device_time) FROM _timescaledb_internal.&amp;lt;chunk&amp;gt;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Compression ==&lt;br /&gt;
=== Old ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics &lt;br /&gt;
SET (timescaledb.compress, &lt;br /&gt;
     timescaledb.compress_segmentby=&#039;device_id&#039;, &lt;br /&gt;
     timescaledb.compress_orderby=&#039;device_time DESC&#039;);&lt;br /&gt;
	 &lt;br /&gt;
SELECT compress_chunk(c) from show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT add_compression_policy(&#039;metrics&#039;, INTERVAL &#039;1 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT remove_compression_policy(&#039;metrics&#039;);&lt;br /&gt;
SELECT decompress_chunk(c, true) FROM show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== New (HyperCore) ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics SET (&lt;br /&gt;
   timescaledb.enable_columnstore = true, &lt;br /&gt;
   timescaledb.segmentby = &#039;device_id&#039;);&lt;br /&gt;
   &lt;br /&gt;
CALL add_columnstore_policy(&#039;metrics&#039;, after =&amp;gt; INTERVAL &#039;1d&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT pg_size_pretty(before_compression_table_bytes) as before_table,&lt;br /&gt;
       pg_size_pretty(before_compression_index_bytes) as before_index,&lt;br /&gt;
       pg_size_pretty(before_compression_toast_bytes) as before_toast,&lt;br /&gt;
       pg_size_pretty(before_compression_total_bytes) as before_total,&lt;br /&gt;
       pg_size_pretty(after_compression_table_bytes) as after_table,&lt;br /&gt;
       pg_size_pretty(after_compression_index_bytes) as after_index,&lt;br /&gt;
       pg_size_pretty(after_compression_toast_bytes) as after_toast,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after_total&lt;br /&gt;
  FROM hypertable_columnstore_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT * FROM timescaledb_information.jobs WHERE proc_name=&#039;policy_compression&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tablespaces ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/manage-storage &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE TABLESPACE historic_data OWNER postgres LOCATION &#039;/home/postgres/timescale&#039;;&lt;br /&gt;
&lt;br /&gt;
SELECT show_chunks(&#039;metrics&#039;, older_than =&amp;gt; INTERVAL &#039;5 months&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT move_chunk(&lt;br /&gt;
  chunk =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk&#039;,&lt;br /&gt;
  destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  index_destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  reorder_index =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk_netdata_time_idx&#039;,&lt;br /&gt;
  verbose =&amp;gt; TRUE&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Limitations ==&lt;br /&gt;
https://docs.timescale.com/timescaledb/latest/overview/limitations &amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Time dimensions (columns) used for partitioning cannot have NULL values.&lt;br /&gt;
* Unique indexes must include all columns that are partitioning dimensions. &lt;br /&gt;
* UPDATE statements that move values between partitions (chunks) are not supported.&lt;br /&gt;
* Foreign key constraints from a hypertable referencing another hypertable are not supported.&lt;br /&gt;
&lt;br /&gt;
== Parameters ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT name, setting, short_desc FROM pg_settings WHERE name like &#039;timescaledb.%&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=78</id>
		<title>TimeScale Training</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=78"/>
		<updated>2025-04-17T12:07:55Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Installation ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/&lt;br /&gt;
=== Debian ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &amp;quot;deb https://packagecloud.io/timescale/timescaledb/debian/ $(lsb_release -c -s) main&amp;quot; | sudo tee /etc/apt/sources.list.d/timescaledb.list&lt;br /&gt;
wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/timescaledb.gpg&lt;br /&gt;
sudo apt update&lt;br /&gt;
sudo apt install timescaledb-2-postgresql-17&lt;br /&gt;
sudo apt-get install timescaledb-2-postgresql-15=&#039;2.14.2*&#039; timescaledb-2-loader-postgresql-15=&#039;2.14.2*&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When running timescaledb-tune, accept the option to update shared_preload_libraries. Other settings are optional &amp;lt;/br&amp;gt;&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/configuration/timescaledb-tune&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo timescaledb-tune&lt;br /&gt;
sudo systemctl restart postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE EXTENSION IF NOT EXISTS timescaledb;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/installation-windows/#install-and-configure-timescaledb-on-postgresql&lt;br /&gt;
&lt;br /&gt;
== Setup ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SET search_path = &amp;quot;$user&amp;quot;, public, &amp;quot;timescaledb_information&amp;quot;;&lt;br /&gt;
SET timezone TO &#039;Europe/Berlin&#039;;&lt;br /&gt;
&lt;br /&gt;
\dv timescaledb_information.*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DROP TABLE IF EXISTS metrics;&lt;br /&gt;
CREATE TABLE metrics (id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, &lt;br /&gt;
                      device_time timestamptz NOT NULL, &lt;br /&gt;
                      device_id integer, &lt;br /&gt;
                      cpu_usage numeric);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create Hypertable ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ERROR:  cannot create a unique index without the column &amp;quot;device_time&amp;quot; (used in partitioning) &amp;lt;/br&amp;gt;&lt;br /&gt;
HINT:  If you&#039;re creating a hypertable on a table with a primary key, ensure the partitioning column is part of the primary or composite key.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics DROP CONSTRAINT metrics_pkey;&lt;br /&gt;
ALTER TABLE metrics ADD PRIMARY KEY (id, device_time);&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the existing table already has data in it then you must use the &amp;quot;migrate_data&amp;quot; option. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;), migrate_data =&amp;gt; true);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Chunk Interval ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;time&#039;, INTERVAL &#039;1 month&#039;));&lt;br /&gt;
SELECT set_chunk_time_interval(&#039;metrics&#039;, INTERVAL &#039;14 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT hypertable_name, column_name, time_interval FROM timescaledb_information.dimensions;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Generate Time Series Data ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
TRUNCATE TABLE metrics RESTART IDENTITY;&lt;br /&gt;
INSERT INTO metrics (device_time, device_id, cpu_usage) (&lt;br /&gt;
SELECT time, device_id, random()*100 as cpu_usage &lt;br /&gt;
  FROM generate_series(date_trunc(&#039;hour&#039;,now()) - INTERVAL &#039;6 months&#039;, now(), INTERVAL &#039;5 minutes&#039;) as time, &lt;br /&gt;
       generate_series(1,1000) device_id&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Table Size ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT PG_SIZE_PRETTY (pg_total_relation_size(&#039;metrics&#039;));&lt;br /&gt;
SELECT PG_SIZE_PRETTY (hypertable_size(&#039;metrics&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Check Statistics ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ANALYZE metrics;&lt;br /&gt;
SELECT tablename, attname, n_distinct FROM pg_stats WHERE tablename = &#039;metrics&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Examine Chunks ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
\d+ metrics&lt;br /&gt;
SELECT MAX(device_time), MIN(device_time) FROM _timescaledb_internal.&amp;lt;chunk&amp;gt;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Compression ==&lt;br /&gt;
=== Old ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics &lt;br /&gt;
SET (timescaledb.compress, &lt;br /&gt;
     timescaledb.compress_segmentby=&#039;device_id&#039;, &lt;br /&gt;
     timescaledb.compress_orderby=&#039;device_time DESC&#039;);&lt;br /&gt;
	 &lt;br /&gt;
SELECT compress_chunk(c) from show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT add_compression_policy(&#039;metrics&#039;, INTERVAL &#039;1 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT remove_compression_policy(&#039;metrics&#039;);&lt;br /&gt;
SELECT decompress_chunk(c, true) FROM show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Compression ==&lt;br /&gt;
=== New (HyperCore) ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics SET (&lt;br /&gt;
   timescaledb.enable_columnstore = true, &lt;br /&gt;
   timescaledb.segmentby = &#039;device_id&#039;);&lt;br /&gt;
   &lt;br /&gt;
CALL add_columnstore_policy(&#039;metrics&#039;, after =&amp;gt; INTERVAL &#039;1d&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT pg_size_pretty(before_compression_table_bytes) as before_table,&lt;br /&gt;
       pg_size_pretty(before_compression_index_bytes) as before_index,&lt;br /&gt;
       pg_size_pretty(before_compression_toast_bytes) as before_toast,&lt;br /&gt;
       pg_size_pretty(before_compression_total_bytes) as before_total,&lt;br /&gt;
       pg_size_pretty(after_compression_table_bytes) as after_table,&lt;br /&gt;
       pg_size_pretty(after_compression_index_bytes) as after_index,&lt;br /&gt;
       pg_size_pretty(after_compression_toast_bytes) as after_toast,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after_total&lt;br /&gt;
  FROM hypertable_columnstore_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT * FROM timescaledb_information.jobs WHERE proc_name=&#039;policy_compression&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tablespaces ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/manage-storage &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE TABLESPACE historic_data OWNER postgres LOCATION &#039;/home/postgres/timescale&#039;;&lt;br /&gt;
&lt;br /&gt;
SELECT show_chunks(&#039;metrics&#039;, older_than =&amp;gt; INTERVAL &#039;5 months&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT move_chunk(&lt;br /&gt;
  chunk =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk&#039;,&lt;br /&gt;
  destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  index_destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  reorder_index =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk_netdata_time_idx&#039;,&lt;br /&gt;
  verbose =&amp;gt; TRUE&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Limitations ==&lt;br /&gt;
https://docs.timescale.com/timescaledb/latest/overview/limitations &amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Time dimensions (columns) used for partitioning cannot have NULL values.&lt;br /&gt;
* Unique indexes must include all columns that are partitioning dimensions. &lt;br /&gt;
* UPDATE statements that move values between partitions (chunks) are not supported.&lt;br /&gt;
* Foreign key constraints from a hypertable referencing another hypertable are not supported.&lt;br /&gt;
&lt;br /&gt;
== Parameters ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT name, setting, short_desc FROM pg_settings WHERE name like &#039;timescaledb.%&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=77</id>
		<title>TimeScale Training</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=77"/>
		<updated>2025-04-17T12:04:03Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Installation ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/&lt;br /&gt;
=== Debian ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &amp;quot;deb https://packagecloud.io/timescale/timescaledb/debian/ $(lsb_release -c -s) main&amp;quot; | sudo tee /etc/apt/sources.list.d/timescaledb.list&lt;br /&gt;
wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/timescaledb.gpg&lt;br /&gt;
sudo apt update&lt;br /&gt;
sudo apt install timescaledb-2-postgresql-17&lt;br /&gt;
sudo apt-get install timescaledb-2-postgresql-15=&#039;2.14.2*&#039; timescaledb-2-loader-postgresql-15=&#039;2.14.2*&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When running timescaledb-tune, accept the option to update shared_preload_libraries. Other settings are optional &amp;lt;/br&amp;gt;&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/configuration/timescaledb-tune&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo timescaledb-tune&lt;br /&gt;
sudo systemctl restart postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE EXTENSION IF NOT EXISTS timescaledb;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/installation-windows/#install-and-configure-timescaledb-on-postgresql&lt;br /&gt;
&lt;br /&gt;
== Setup ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SET search_path = &amp;quot;$user&amp;quot;, public, &amp;quot;timescaledb_information&amp;quot;;&lt;br /&gt;
SET timezone TO &#039;Europe/Berlin&#039;;&lt;br /&gt;
&lt;br /&gt;
\dv timescaledb_information.*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DROP TABLE IF EXISTS metrics;&lt;br /&gt;
CREATE TABLE metrics (id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, &lt;br /&gt;
                      device_time timestamptz NOT NULL, &lt;br /&gt;
                      device_id integer, &lt;br /&gt;
                      cpu_usage numeric);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create Hypertable ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ERROR:  cannot create a unique index without the column &amp;quot;device_time&amp;quot; (used in partitioning) &amp;lt;/br&amp;gt;&lt;br /&gt;
HINT:  If you&#039;re creating a hypertable on a table with a primary key, ensure the partitioning column is part of the primary or composite key.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics DROP CONSTRAINT metrics_pkey;&lt;br /&gt;
ALTER TABLE metrics ADD PRIMARY KEY (id, device_time);&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the existing table already has data in it then you must use the &amp;quot;migrate_data&amp;quot; option. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;), migrate_data =&amp;gt; true);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Chunk Interval ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;time&#039;, INTERVAL &#039;1 month&#039;));&lt;br /&gt;
SELECT set_chunk_time_interval(&#039;metrics&#039;, INTERVAL &#039;14 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT hypertable_name, column_name, time_interval FROM timescaledb_information.dimensions;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Generate Time Series Data ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
TRUNCATE TABLE metrics RESTART IDENTITY;&lt;br /&gt;
INSERT INTO metrics (device_time, device_id, cpu_usage) (&lt;br /&gt;
SELECT time, device_id, random()*100 as cpu_usage &lt;br /&gt;
  FROM generate_series(date_trunc(&#039;hour&#039;,now()) - INTERVAL &#039;6 months&#039;, now(), INTERVAL &#039;5 minutes&#039;) as time, &lt;br /&gt;
       generate_series(1,1000) device_id&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Table Size ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT PG_SIZE_PRETTY (pg_total_relation_size(&#039;metrics&#039;));&lt;br /&gt;
SELECT PG_SIZE_PRETTY (hypertable_size(&#039;metrics&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Check Statistics ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ANALYZE metrics;&lt;br /&gt;
SELECT tablename, attname, n_distinct FROM pg_stats WHERE tablename = &#039;metrics&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Examine Chunks ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
\d+ metrics&lt;br /&gt;
SELECT MAX(device_time), MIN(device_time) FROM _timescaledb_internal.&amp;lt;chunk&amp;gt;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Compression (Old) ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics &lt;br /&gt;
SET (timescaledb.compress, &lt;br /&gt;
     timescaledb.compress_segmentby=&#039;device_id&#039;, &lt;br /&gt;
     timescaledb.compress_orderby=&#039;device_time DESC&#039;);&lt;br /&gt;
	 &lt;br /&gt;
SELECT compress_chunk(c) from show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT add_compression_policy(&#039;metrics&#039;, INTERVAL &#039;1 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT remove_compression_policy(&#039;metrics&#039;);&lt;br /&gt;
SELECT decompress_chunk(c, true) FROM show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Compression (New) ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics SET (&lt;br /&gt;
   timescaledb.enable_columnstore = true, &lt;br /&gt;
   timescaledb.segmentby = &#039;device_id&#039;);&lt;br /&gt;
   &lt;br /&gt;
CALL add_columnstore_policy(&#039;metrics&#039;, after =&amp;gt; INTERVAL &#039;1d&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT pg_size_pretty(before_compression_table_bytes) as before_table,&lt;br /&gt;
       pg_size_pretty(before_compression_index_bytes) as before_index,&lt;br /&gt;
       pg_size_pretty(before_compression_toast_bytes) as before_toast,&lt;br /&gt;
       pg_size_pretty(before_compression_total_bytes) as before_total,&lt;br /&gt;
       pg_size_pretty(after_compression_table_bytes) as after_table,&lt;br /&gt;
       pg_size_pretty(after_compression_index_bytes) as after_index,&lt;br /&gt;
       pg_size_pretty(after_compression_toast_bytes) as after_toast,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after_total&lt;br /&gt;
  FROM hypertable_columnstore_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT * FROM timescaledb_information.jobs WHERE proc_name=&#039;policy_compression&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tablespaces ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/manage-storage &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE TABLESPACE historic_data OWNER postgres LOCATION &#039;/home/postgres/timescale&#039;;&lt;br /&gt;
&lt;br /&gt;
SELECT show_chunks(&#039;metrics&#039;, older_than =&amp;gt; INTERVAL &#039;5 months&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT move_chunk(&lt;br /&gt;
  chunk =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk&#039;,&lt;br /&gt;
  destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  index_destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  reorder_index =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk_netdata_time_idx&#039;,&lt;br /&gt;
  verbose =&amp;gt; TRUE&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Limitations ==&lt;br /&gt;
https://docs.timescale.com/timescaledb/latest/overview/limitations &amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Time dimensions (columns) used for partitioning cannot have NULL values.&lt;br /&gt;
* Unique indexes must include all columns that are partitioning dimensions. &lt;br /&gt;
* UPDATE statements that move values between partitions (chunks) are not supported.&lt;br /&gt;
* Foreign key constraints from a hypertable referencing another hypertable are not supported.&lt;br /&gt;
&lt;br /&gt;
== Parameters ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT name, setting, short_desc FROM pg_settings WHERE name like &#039;timescaledb.%&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=76</id>
		<title>TimeScale Training</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=76"/>
		<updated>2025-04-17T11:59:36Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Installation ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/&lt;br /&gt;
=== Debian ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &amp;quot;deb https://packagecloud.io/timescale/timescaledb/debian/ $(lsb_release -c -s) main&amp;quot; | sudo tee /etc/apt/sources.list.d/timescaledb.list&lt;br /&gt;
wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/timescaledb.gpg&lt;br /&gt;
sudo apt update&lt;br /&gt;
sudo apt install timescaledb-2-postgresql-17&lt;br /&gt;
sudo apt-get install timescaledb-2-postgresql-15=&#039;2.14.2*&#039; timescaledb-2-loader-postgresql-15=&#039;2.14.2*&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When running timescaledb-tune, accept the option to update shared_preload_libraries. Other settings are optional &amp;lt;/br&amp;gt;&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/configuration/timescaledb-tune&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo timescaledb-tune&lt;br /&gt;
sudo systemctl restart postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE EXTENSION IF NOT EXISTS timescaledb;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/installation-windows/#install-and-configure-timescaledb-on-postgresql&lt;br /&gt;
&lt;br /&gt;
== Setup ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SET search_path = &amp;quot;$user&amp;quot;, public, &amp;quot;timescaledb_information&amp;quot;;&lt;br /&gt;
SET timezone TO &#039;Europe/Berlin&#039;;&lt;br /&gt;
&lt;br /&gt;
\dv timescaledb_information.*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DROP TABLE IF EXISTS metrics;&lt;br /&gt;
CREATE TABLE metrics (id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, &lt;br /&gt;
                      device_time timestamptz NOT NULL, &lt;br /&gt;
                      device_id integer, &lt;br /&gt;
                      cpu_usage numeric);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Create Hypertable ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ERROR:  cannot create a unique index without the column &amp;quot;device_time&amp;quot; (used in partitioning) &amp;lt;/br&amp;gt;&lt;br /&gt;
HINT:  If you&#039;re creating a hypertable on a table with a primary key, ensure the partitioning column is part of the primary or composite key.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics DROP CONSTRAINT metrics_pkey;&lt;br /&gt;
ALTER TABLE metrics ADD PRIMARY KEY (id, device_time);&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the existing table already has data in it then you must use the &amp;quot;migrate_data&amp;quot; option. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;), migrate_data =&amp;gt; true);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Chunk Interval ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;time&#039;, INTERVAL &#039;1 month&#039;));&lt;br /&gt;
SELECT set_chunk_time_interval(&#039;metrics&#039;, INTERVAL &#039;14 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT hypertable_name, column_name, time_interval FROM timescaledb_information.dimensions;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Generate Time Series Data ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
TRUNCATE TABLE metrics RESTART IDENTITY;&lt;br /&gt;
INSERT INTO metrics (device_time, device_id, cpu_usage) (&lt;br /&gt;
SELECT time, device_id, random()*100 as cpu_usage &lt;br /&gt;
  FROM generate_series(date_trunc(&#039;hour&#039;,now()) - INTERVAL &#039;6 months&#039;, now(), INTERVAL &#039;5 minutes&#039;) as time, &lt;br /&gt;
       generate_series(1,1000) device_id&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Table Size ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT PG_SIZE_PRETTY (pg_total_relation_size(&#039;metrics&#039;));&lt;br /&gt;
SELECT PG_SIZE_PRETTY (hypertable_size(&#039;metrics&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Check Statistics ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ANALYZE metrics;&lt;br /&gt;
SELECT tablename, attname, n_distinct FROM pg_stats WHERE tablename = &#039;metrics&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Examine Chunks ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
\d+ metrics&lt;br /&gt;
SELECT MAX(device_time), MIN(device_time) FROM _timescaledb_internal.&amp;lt;chunk&amp;gt;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Compression (Old) ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics &lt;br /&gt;
SET (timescaledb.compress, &lt;br /&gt;
     timescaledb.compress_segmentby=&#039;device_id&#039;, &lt;br /&gt;
     timescaledb.compress_orderby=&#039;device_time DESC&#039;);&lt;br /&gt;
	 &lt;br /&gt;
SELECT compress_chunk(c) from show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT add_compression_policy(&#039;metrics&#039;, INTERVAL &#039;1 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT remove_compression_policy(&#039;metrics&#039;);&lt;br /&gt;
SELECT decompress_chunk(c, true) FROM show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Compression (New) ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics SET (&lt;br /&gt;
   timescaledb.enable_columnstore = true, &lt;br /&gt;
   timescaledb.segmentby = &#039;device_id&#039;);&lt;br /&gt;
   &lt;br /&gt;
CALL add_columnstore_policy(&#039;metrics&#039;, after =&amp;gt; INTERVAL &#039;1d&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT pg_size_pretty(before_compression_table_bytes) as before_table,&lt;br /&gt;
       pg_size_pretty(before_compression_index_bytes) as before_index,&lt;br /&gt;
       pg_size_pretty(before_compression_toast_bytes) as before_toast,&lt;br /&gt;
       pg_size_pretty(before_compression_total_bytes) as before_total,&lt;br /&gt;
       pg_size_pretty(after_compression_table_bytes) as after_table,&lt;br /&gt;
       pg_size_pretty(after_compression_index_bytes) as after_index,&lt;br /&gt;
       pg_size_pretty(after_compression_toast_bytes) as after_toast,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after_total&lt;br /&gt;
  FROM hypertable_columnstore_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT * FROM timescaledb_information.jobs WHERE proc_name=&#039;policy_compression&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tablespaces ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/manage-storage &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE TABLESPACE historic_data OWNER postgres LOCATION &#039;/home/postgres/timescale&#039;;&lt;br /&gt;
&lt;br /&gt;
SELECT show_chunks(&#039;metrics&#039;, older_than =&amp;gt; INTERVAL &#039;5 months&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT move_chunk(&lt;br /&gt;
  chunk =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk&#039;,&lt;br /&gt;
  destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  index_destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  reorder_index =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk_netdata_time_idx&#039;,&lt;br /&gt;
  verbose =&amp;gt; TRUE&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Limitations ==&lt;br /&gt;
https://docs.timescale.com/timescaledb/latest/overview/limitations &amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Time dimensions (columns) used for partitioning cannot have NULL values.&lt;br /&gt;
* Unique indexes must include all columns that are partitioning dimensions. &lt;br /&gt;
* UPDATE statements that move values between partitions (chunks) are not supported.&lt;br /&gt;
* Foreign key constraints from a hypertable referencing another hypertable are not supported.&lt;br /&gt;
&lt;br /&gt;
== Parameters ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT name, setting, short_desc FROM pg_settings WHERE name like &#039;timescaledb.%&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
	<entry>
		<id>https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=75</id>
		<title>TimeScale Training</title>
		<link rel="alternate" type="text/html" href="https://iandoob.net/index.php?title=TimeScale_Training&amp;diff=75"/>
		<updated>2025-04-17T11:58:15Z</updated>

		<summary type="html">&lt;p&gt;Ian.readman: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Installation ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/&lt;br /&gt;
=== Debian ===&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
echo &amp;quot;deb https://packagecloud.io/timescale/timescaledb/debian/ $(lsb_release -c -s) main&amp;quot; | sudo tee /etc/apt/sources.list.d/timescaledb.list&lt;br /&gt;
wget --quiet -O - https://packagecloud.io/timescale/timescaledb/gpgkey | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/timescaledb.gpg&lt;br /&gt;
sudo apt update&lt;br /&gt;
sudo apt install timescaledb-2-postgresql-17 postgresql-client-17&lt;br /&gt;
sudo apt-get install timescaledb-2-postgresql-14=&#039;2.6.0*&#039; timescaledb-2-loader-postgresql-14=&#039;2.6.0*&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When running timescaledb-tune, accept the option to update shared_preload_libraries. Other settings are optional &amp;lt;/br&amp;gt;&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/configuration/timescaledb-tune&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
sudo timescaledb-tune&lt;br /&gt;
sudo systemctl restart postgresql&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE EXTENSION IF NOT EXISTS timescaledb;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Windows ===&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/install/installation-windows/#install-and-configure-timescaledb-on-postgresql&lt;br /&gt;
&lt;br /&gt;
== Setup ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SET search_path = &amp;quot;$user&amp;quot;, public, &amp;quot;timescaledb_information&amp;quot;;&lt;br /&gt;
SET timezone TO &#039;Europe/Berlin&#039;;&lt;br /&gt;
&lt;br /&gt;
\dv timescaledb_information.*&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
DROP TABLE IF EXISTS metrics;&lt;br /&gt;
CREATE TABLE metrics (id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY, &lt;br /&gt;
                      device_time timestamptz NOT NULL, &lt;br /&gt;
                      device_id integer, &lt;br /&gt;
                      cpu_usage numeric);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Create Hypertable ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
ERROR:  cannot create a unique index without the column &amp;quot;device_time&amp;quot; (used in partitioning) &amp;lt;/br&amp;gt;&lt;br /&gt;
HINT:  If you&#039;re creating a hypertable on a table with a primary key, ensure the partitioning column is part of the primary or composite key.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics DROP CONSTRAINT metrics_pkey;&lt;br /&gt;
ALTER TABLE metrics ADD PRIMARY KEY (id, device_time);&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If the existing table already has data in it then you must use the &amp;quot;migrate_data&amp;quot; option. &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;device_time&#039;), migrate_data =&amp;gt; true);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Chunk Interval ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT create_hypertable(&#039;metrics&#039;, by_range(&#039;time&#039;, INTERVAL &#039;1 month&#039;));&lt;br /&gt;
SELECT set_chunk_time_interval(&#039;metrics&#039;, INTERVAL &#039;14 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT hypertable_name, column_name, time_interval FROM timescaledb_information.dimensions;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Generate Time Series Data ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
TRUNCATE TABLE metrics RESTART IDENTITY;&lt;br /&gt;
INSERT INTO metrics (device_time, device_id, cpu_usage) (&lt;br /&gt;
SELECT time, device_id, random()*100 as cpu_usage &lt;br /&gt;
  FROM generate_series(date_trunc(&#039;hour&#039;,now()) - INTERVAL &#039;6 months&#039;, now(), INTERVAL &#039;5 minutes&#039;) as time, &lt;br /&gt;
       generate_series(1,1000) device_id&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Table Size ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT PG_SIZE_PRETTY (pg_total_relation_size(&#039;metrics&#039;));&lt;br /&gt;
SELECT PG_SIZE_PRETTY (hypertable_size(&#039;metrics&#039;));&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Check Statistics ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ANALYZE metrics;&lt;br /&gt;
SELECT tablename, attname, n_distinct FROM pg_stats WHERE tablename = &#039;metrics&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Examine Chunks ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
\d+ metrics&lt;br /&gt;
SELECT MAX(device_time), MIN(device_time) FROM _timescaledb_internal.&amp;lt;chunk&amp;gt;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Compression (Old) ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics &lt;br /&gt;
SET (timescaledb.compress, &lt;br /&gt;
     timescaledb.compress_segmentby=&#039;device_id&#039;, &lt;br /&gt;
     timescaledb.compress_orderby=&#039;device_time DESC&#039;);&lt;br /&gt;
	 &lt;br /&gt;
SELECT compress_chunk(c) from show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT add_compression_policy(&#039;metrics&#039;, INTERVAL &#039;1 days&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT remove_compression_policy(&#039;metrics&#039;);&lt;br /&gt;
SELECT decompress_chunk(c, true) FROM show_chunks(&#039;metrics&#039;) c;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Compression (New) ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ALTER TABLE metrics SET (&lt;br /&gt;
   timescaledb.enable_columnstore = true, &lt;br /&gt;
   timescaledb.segmentby = &#039;device_id&#039;);&lt;br /&gt;
   &lt;br /&gt;
CALL add_columnstore_policy(&#039;metrics&#039;, after =&amp;gt; INTERVAL &#039;1d&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT pg_size_pretty(before_compression_total_bytes) as before,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after&lt;br /&gt;
  FROM hypertable_compression_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT pg_size_pretty(before_compression_table_bytes) as before_table,&lt;br /&gt;
       pg_size_pretty(before_compression_index_bytes) as before_index,&lt;br /&gt;
       pg_size_pretty(before_compression_toast_bytes) as before_toast,&lt;br /&gt;
       pg_size_pretty(before_compression_total_bytes) as before_total,&lt;br /&gt;
       pg_size_pretty(after_compression_table_bytes) as after_table,&lt;br /&gt;
       pg_size_pretty(after_compression_index_bytes) as after_index,&lt;br /&gt;
       pg_size_pretty(after_compression_toast_bytes) as after_toast,&lt;br /&gt;
       pg_size_pretty(after_compression_total_bytes) as after_total&lt;br /&gt;
  FROM hypertable_columnstore_stats(&#039;metrics&#039;);&lt;br /&gt;
  &lt;br /&gt;
SELECT * FROM timescaledb_information.jobs WHERE proc_name=&#039;policy_compression&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Tablespaces ==&lt;br /&gt;
https://docs.timescale.com/self-hosted/latest/manage-storage &amp;lt;/br&amp;gt;&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
CREATE TABLESPACE historic_data OWNER postgres LOCATION &#039;/home/postgres/timescale&#039;;&lt;br /&gt;
&lt;br /&gt;
SELECT show_chunks(&#039;metrics&#039;, older_than =&amp;gt; INTERVAL &#039;5 months&#039;);&lt;br /&gt;
&lt;br /&gt;
SELECT move_chunk(&lt;br /&gt;
  chunk =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk&#039;,&lt;br /&gt;
  destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  index_destination_tablespace =&amp;gt; &#039;historic_data&#039;,&lt;br /&gt;
  reorder_index =&amp;gt; &#039;_timescaledb_internal._hyper_1_4_chunk_netdata_time_idx&#039;,&lt;br /&gt;
  verbose =&amp;gt; TRUE&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Limitations ==&lt;br /&gt;
https://docs.timescale.com/timescaledb/latest/overview/limitations &amp;lt;/br&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Time dimensions (columns) used for partitioning cannot have NULL values.&lt;br /&gt;
* Unique indexes must include all columns that are partitioning dimensions. &lt;br /&gt;
* UPDATE statements that move values between partitions (chunks) are not supported.&lt;br /&gt;
* Foreign key constraints from a hypertable referencing another hypertable are not supported.&lt;br /&gt;
&lt;br /&gt;
== Parameters ==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
SELECT name, setting, short_desc FROM pg_settings WHERE name like &#039;timescaledb.%&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Ian.readman</name></author>
	</entry>
</feed>