{"id":244,"date":"2020-12-11T00:05:45","date_gmt":"2020-12-11T00:05:45","guid":{"rendered":"https:\/\/blog.inplico.uk\/?p=244"},"modified":"2024-07-29T18:02:10","modified_gmt":"2024-07-29T17:02:10","slug":"postgresql-replication","status":"publish","type":"post","link":"https:\/\/blog.inplico.uk\/?p=244","title":{"rendered":"Postgresql replication"},"content":{"rendered":"<h4><span style=\"text-decoration: underline;\">PREAMBLE<\/span><\/h4>\n<p>It goes without saying that if you want to set up replication then you need a master and a slave postgresql server.\u00a0 There is a bit of configuration to do in order to get it running.<\/p>\n<p>This how to was originally written for older versions of Postgresql but has been updated to reflect the significant changes in later versions.\u00a0 It has been tested against version 15.<\/p>\n<p><span style=\"color: #ff0000;\">WARNING:<\/span> Before continuing with this tutorial you should ensure that you can connect to both servers using SSL as replicating over the internet without ssl is a little bit insane. See <a href=\"https:\/\/blog.inplico.uk\/?p=504\">here<\/a><\/p>\n<p>We are using sha-256 encryption for passwords (which is now the default) so the first thing we want to do is edit postgresql.conf on the master and check that password_encryption is set correctly.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"shell\" data-enlighter-theme=\"godzilla\">vi \/etc\/postgresql\/15\/main\/postgresql.conf<\/pre>\n<p>NOTE: The directory path will change depending on the version.<\/p>\n<p>Look for the &#8220;password_encryption&#8221; directive and change it to &#8220;scram-sha-256&#8221;.\u00a0 This line may be commented out, if it is then uncomment it.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"godzilla\">password_encryption = scram-sha-256             # md5 or scram-sha-256<\/pre>\n<p>If you have already set a password for your replication user that is not sha-256 encoded then you will have to set it again or you will not be able to authenticate.\u00a0 The reason quoted below is explained in a stack overflow question <a href=\"https:\/\/stackoverflow.com\/questions\/64316324\/i-tried-to-change-postgresql-md5-to-scram-sha-256-and-i-get-fatal-password-authe\">here.<\/a><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\" data-enlighter-theme=\"enlighter\" data-enlighter-linenumbers=\"false\">Each user password hash is saved in the table pg_authid. It includes the hashing algorithm that is used to transform the password to its hash.\r\n\r\nWhen setting the password_encryption in postgresql.conf, you are setting the default encryption, i.e. the one used when creating a user or when (re)setting your password. The table pg_authid is not updated.\r\n\r\nWhen changing pg_hba.conf, you are saying to accept only passwords hashed using the given method. The table pg_authid is not updated.<\/pre>\n<h4><span style=\"text-decoration: underline;\">MASTER<\/span><\/h4>\n<p>On the master the first thing we need to do is create a user for replication purposes so start a postgresql shell and add a user.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\" data-enlighter-theme=\"godzilla\">CREATE ROLE replicate LOGIN REPLICATION ENCRYPTED PASSWORD 'mySecretPassword';<\/pre>\n<p>While we are in the postgresql shell we also want to create a slot.\u00a0 For some reason we create a slot using a SLEECT statement, not sure why, but it is unimportant in any event.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\" data-enlighter-theme=\"godzilla\">SELECT * FROM pg_create_physical_replication_slot('replicate');<\/pre>\n<p>The slot does not have to have the same name as the user, we just use &#8220;<strong>replicate<\/strong>&#8221; in our example for the sake of simplicity.\u00a0 NOTE:\u00a0 It is not necessary to create a slot as postgresql will create a temporary slot if one isn&#8217;t explicitly created.<\/p>\n<p>Now we can exit our postgresql client shell and move on to editing our <strong>pg_hba.conf<\/strong> file, usually located in <strong>\/etc\/postgresql\/11\/main<\/strong><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"shell\" data-enlighter-highlight=\"3\" data-enlighter-theme=\"godzilla\"># TYPE DATABASE USER ADDRESS METHOD\r\n...\r\nhostssl replication  replicate    xxx.xxx.xxx.xxx\/xx   scram-sha-256\r\n....<\/pre>\n<p><span style=\"color: #ff6600;\"><strong>IMPORTANT: <\/strong><\/span>Replace xxx.xxx.xxx.xxx\/xx with the ip and subnet mask of the slave.<\/p>\n<p>The line highlighted above will allow the user replicate to use the built in role &#8216;replication&#8217; from the slave.\u00a0 The type is set to &#8216;<strong>hostssl<\/strong>&#8216; to force the use of ssl encryption and &#8216;<strong>scram-sha-256<\/strong>&#8216; is the method used to decrypt the password.<\/p>\n<p>Next you need to edit the postgresql.conf file to tell the server that it is acting as a master server.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"godzilla\">#vi \/etc\/postgresql\/11\/main\/postgresql.conf<\/pre>\n<p>edit the file as follows, uncommenting anything that is commented out.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"godzilla\">listen_addresses = 'localhost,xxx.xxx.xxx.xxx'\r\nwal_level = replica\r\nmax_wal_senders = 10\r\nwal_keep_size = 1024MB\r\n<\/pre>\n<p>Note that xxx.xxx.xxx.xxx is the address of the interface you want to listen on, you can add several addresses here if you are listening on the WAN port and the LAN port for example, albeit if this is the case you may just wish to use a wildcard instead.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"shell\" data-enlighter-theme=\"godzilla\">listen_address = '*'<\/pre>\n<p>Once you have done that you can restart the master and move on to configuring the slave.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"godzilla\">#service postgresql restart\r\n<\/pre>\n<p>&nbsp;<\/p>\n<h3><span style=\"text-decoration: underline;\">SLAVE<\/span><\/h3>\n<p>Before you configure the slave you need to create an ssl certificate using the Certificate Authority that you should have already created on the master (this is a prerequisite).<\/p>\n<p>Once you have generated your certificate you need to copy the <strong>.key<\/strong> and <strong>.crt<\/strong> files that you have generated across to the slave along with the <strong>postgresCA.crt<\/strong> file.\u00a0 These should be in the <strong>\/pgsql<\/strong> folder.<\/p>\n<p>Make sure that the postgres user owns them and that they are accessible and that the <strong>.key<\/strong> file has limited permissions.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">chown postgres:postgres \/pgsql\/*.crt\r\nchown postgres:postgres \/pgsql\/*.key\r\nchmod 400 \/pgsql\/*.key<\/pre>\n<p>&nbsp;<\/p>\n<p>Next stop the slave server<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"shell\" data-enlighter-theme=\"godzilla\">#service postgresql stop<\/pre>\n<p>Now you can configure the slave in a similar manner to the master.\u00a0 Firstly edit postgresql.conf.<\/p>\n<p><span style=\"color: #ff0000;\">WARNING<\/span>:\u00a0 If you have a <strong>recovery.conf<\/strong> file in your data directory you need to get rid of it or postgresql will not start.\u00a0 More information provided here <a href=\"https:\/\/www.cybertec-postgresql.com\/en\/recovery-conf-is-gone-in-postgresql-v12\">https:\/\/www.cybertec-postgresql.com\/en\/recovery-conf-is-gone-in-postgresql-v12<\/a>\/ (Acknowledgement).<\/p>\n<p>Find the following directives in the file and set\/uncomment them paying particular attention to the primary_conninfo line where you put the connection string.<\/p>\n<p>NOTE that the text between the &#8216; &#8216; after the primary_conninfo directive is all on the same line.<\/p>\n<p>You will need to change the host, password, sslcert and sslkey directives to reflect your configuration.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">primary_conninfo = 'host=XXX.XXX.XXX.XXX port=5432 user=replicate password=mysecretpassword sslmode=require sslcert=\/pgsql\/slave.crt sslkey=\/pgsql\/slave.key sslrootcert=\/pgsql\/postgresCA.crt' \r\nprimary_slot_name = 'replicate'         # replication slot on sending server\r\n#promote_trigger_file = ''              # file name whose presence ends recovery\r\nhot_standby = on                        # \"off\" disallows queries during recovery\r\n                                        # (change requires restart)\r\n#max_standby_archive_delay = 30s        # max delay before canceling queries\r\n                                        # when reading WAL from archive;\r\n                                        # -1 allows indefinite delay\r\n#max_standby_streaming_delay = 30s      # max delay before canceling queries\r\n                                        # when reading streaming WAL;\r\n                                        # -1 allows indefinite delay\r\nwal_receiver_create_temp_slot = on      # create temp slot if primary_slot_name\r\n                                        # is not set\r\nwal_receiver_status_interval = 10s      # send replies at least this often\r\n                                        # 0 disables\r\nhot_standby_feedback = on               # send info from standby to prevent\r\n                                        # query conflicts\r\n#wal_receiver_timeout = 60s             # time that receiver waits for\r\n                                        # communication from primary\r\n                                        # in milliseconds; 0 disables\r\nwal_retrieve_retry_interval = 5s        # time to wait before retrying to\r\n                                        # retrieve WAL after a failed attempt\r\n#recovery_min_apply_delay = 0           # minimum delay for applying changes during recovery\r\n<\/pre>\n<p>Next we need to remove any existing data and copy the folders from the master<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">rm -R \/pgsql\/15 \r\npg_basebackup -v -h xxx.xxx.xxx.xxx -U replicate -P -D \/pgsql\/data --slot replicate \r\nchown -R postgres:postgres \/pgsql\/15<\/pre>\n<p>where xxx.xxx.xxx.xxx is the ip address of the master. If you have set up postgresql according to this site then the data directory will be <strong>\/pgsql<\/strong>.\u00a0 Note you may also need to set the permissions if you are not logged in as the postgres user.<\/p>\n<p>You will be prompted for the &#8216;<strong>replicate<\/strong>&#8216; user&#8217;s password.\u00a0 If all is well then the script should terminate with something like<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"shell\" data-enlighter-theme=\"godzilla\">177156\/177156 kB (100%), 1\/1 tablespace\r\n<\/pre>\n<p>The slave&#8217;s data folder should now be populated with a copy of the master.<\/p>\n<p><strong>NOTE<\/strong>: If you have issues performing this task then you need to resolve them before proceeding.<\/p>\n<p>Now in order for our slave to know it is acting as a standby server we need to create an empty text file in the data directory.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">touch \/pgsql\/15\/standby.signal\r\nchown postgres:postgres \/pgsql\/15\/standby.signal<\/pre>\n<p>You should now be able to start the server and all should work as it should.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">systemctl start postgrtesql<\/pre>\n<h3>Troubleshooting<\/h3>\n<p>The log files in \/var\/log\/postgresql on the master and the slave may help resolve problems.<\/p>\n<p>On the master you can issue the following in the psql client<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">SELECT client_addr, state, sent_lsn, write_lsn, flush_lsn, replay_lsn FROM pg_stat_replication;<\/pre>\n<p>which should return a record.\u00a0 The state should be &#8220;streaming&#8221;<\/p>\n<p>Most of the problems are caused by permissions so review permissions on the data directory and for the ssl certificates.<\/p>\n<p>Double check the connection string paying attention to the names of the certificates and make sure that they match the files stored in \/pgsql\/<\/p>\n<p>On the slave from the psql client run<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">\\x\r\nSELECT * FROM pg_stat_wal_receiver;<\/pre>\n<p>Again there should be a record for the master.<\/p>\n<p>Check the password for the replication user is encoded with sha-256<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\">SELECT rolpassword from pg_authid where rolname = 'replicate';\r\n\r\n<\/pre>\n<p><strong>rollpassword<\/strong> should begin with\u00a0<strong> SCRAM-SHA-256<\/strong><\/p>\n<p>If there is no result then check that the user exists.<\/p>\n<p>NOTE: One of my databases has lots of roles that cause \\du to bring up reams of gibberish.\u00a0 For this reason I use this lookup query instead:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">SELECT usename AS role_name,\r\n  CASE \r\n     WHEN usesuper AND usecreatedb THEN \r\n       CAST('superuser, create database' AS pg_catalog.text)\r\n     WHEN usesuper THEN \r\n        CAST('superuser' AS pg_catalog.text)\r\n     WHEN usecreatedb THEN \r\n        CAST('create database' AS pg_catalog.text)\r\n     ELSE \r\n        CAST('' AS pg_catalog.text)\r\n  END role_attributes\r\nFROM pg_catalog.pg_user\r\nORDER BY role_name desc;<\/pre>\n<p>&nbsp;<\/p>\n<h4>WAL files not being deleted<span style=\"text-decoration: underline;\"><br \/>\n<\/span><\/h4>\n<p>For some reason the WAL files used for replication do not always get deleted and simply build up until you run out of space on the partition.<\/p>\n<p>To get rid of them manually from psql -U postgres run<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"sql\">postgres=# \r\nSELECT slot_name,\r\n   lpad((pg_control_checkpoint()).timeline_id::text, 8, '0') ||\r\n   lpad(split_part(restart_lsn::text, '\/', 1), 8, '0') ||\r\n   lpad(substr(split_part(restart_lsn::text, '\/', 2), 1, 2), 8, '0')\r\n   AS wal_file\r\nFROM pg_replication_slots;\r\nslot_name |         wal_file\r\n-----------+--------------------------\r\n postgres2 | 0000000100000000000000B0\r\n(1 row)\r\n\r\npostgres=# \r\n    SELECT pg_drop_replication_slot('postgres2');\r\n pg_drop_replication_slot\r\n--------------------------\r\n\r\n(1 row)<\/pre>\n<p>and the shortly after most of the WAL files will have disappeared.\u00a0 Change &#8220;<strong>postgres2<\/strong>&#8221; in the second statement to whatever &#8220;<strong>slot_name<\/strong>&#8221; is returned in the first.<\/p>\n<p>Acknowledgement: https:\/\/stackoverflow.com\/questions\/49539938\/postgres-wal-file-not-getting-deleted<\/p>\n<p><code class=\"hljs language-sql\"><\/code><\/p>\n","protected":false},"excerpt":{"rendered":"<p>PREAMBLE It goes without saying that if you want to set up replication then you need a master and a slave postgresql server.\u00a0 There is a bit of configuration to do in order to get it running. This how to was originally written for older versions of Postgresql but has been updated to reflect the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-244","post","type-post","status-publish","format-standard","hentry","category-a-series-of-hints-and-tips-when-using-the-postgresql-library-libpq-fe-h"],"_links":{"self":[{"href":"https:\/\/blog.inplico.uk\/index.php?rest_route=\/wp\/v2\/posts\/244","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.inplico.uk\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.inplico.uk\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.inplico.uk\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.inplico.uk\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=244"}],"version-history":[{"count":29,"href":"https:\/\/blog.inplico.uk\/index.php?rest_route=\/wp\/v2\/posts\/244\/revisions"}],"predecessor-version":[{"id":517,"href":"https:\/\/blog.inplico.uk\/index.php?rest_route=\/wp\/v2\/posts\/244\/revisions\/517"}],"wp:attachment":[{"href":"https:\/\/blog.inplico.uk\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=244"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.inplico.uk\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=244"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.inplico.uk\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=244"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}