├── .github └── workflows │ └── installcheck.yml ├── LICENSE ├── Makefile ├── README.md ├── bgw_replstatus.c └── debian ├── changelog ├── control ├── control.in ├── copyright ├── pgversions ├── rules ├── source └── format ├── tests ├── control └── installcheck └── watch /.github/workflows/installcheck.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | defaults: 10 | run: 11 | shell: sh 12 | 13 | strategy: 14 | matrix: 15 | pgversion: 16 | - 9.5 17 | - 9.6 18 | - 10 19 | - 11 20 | - 12 21 | - 13 22 | - 14 23 | - 15 24 | - 16 25 | 26 | env: 27 | PGVERSION: ${{ matrix.pgversion }} 28 | 29 | steps: 30 | - name: checkout 31 | uses: actions/checkout@v2 32 | 33 | - name: install pg 34 | run: | 35 | sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -v $PGVERSION -p -i 36 | sudo -u postgres createuser -s "$USER" 37 | 38 | - name: build 39 | run: | 40 | make PROFILE="-Werror" 41 | sudo -E make install 42 | 43 | - name: test 44 | run: | 45 | sudo pg_conftool set shared_preload_libraries bgw_replstatus 46 | sudo pg_ctlcluster $PGVERSION main restart 47 | make installcheck 48 | 49 | - name: show regression diffs 50 | if: ${{ failure() }} 51 | run: | 52 | cat regression.diffs 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This plugins is released under the PostgreSQL License. 2 | 3 | See https://www.postgresql.org/about/licence/ for details. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MODULES = bgw_replstatus 2 | 3 | PG_CONFIG=pg_config 4 | PGXS := $(shell $(PG_CONFIG) --pgxs) 5 | include $(PGXS) 6 | 7 | NC = nc 8 | NC_ARGS = -dq1 9 | LISTEN_ADDRESS = 127.0.0.1 10 | LISTEN_PORT = 5400 11 | 12 | installcheck: 13 | [ "$(shell $(NC) $(NC_ARGS) $(LISTEN_ADDRESS) $(LISTEN_PORT))" = "MASTER" ] 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | bgw_replstatus 2 | ============== 3 | 4 | `bgw_replstatus` is a tiny background worker to cheaply report the 5 | replication status of a node. It's intended to be polled by a load 6 | balancer such as `haproxy`. 7 | 8 | When installed, a background worker will be started that listens on a 9 | defined TCP port (configured `bgw_replstatus.port`). Any connection to 10 | this port will get a TCP response back (no request necessary, response 11 | will be sent immediately on connect) saying either `MASTER` or 12 | `STANDBY` depending on the current state of the node. The connection 13 | is then automatically closed. 14 | 15 | Using a background worker like this will make polling a lot more light 16 | weight than making a full PostgreSQL connection, logging in, and 17 | checking the status. 18 | 19 | Installing 20 | ---------- 21 | 22 | Build and install is done using PGXS. As long as `pg_config` is 23 | available in the path, build and install using: 24 | 25 | ``` 26 | $ make 27 | $ make install 28 | ``` 29 | 30 | Once the binary is installed, it needs to be enabled in 31 | `shared_preload_libraries` in postgresql.conf: 32 | 33 | ``` 34 | shared_preload_libraries = 'bgw_replstatus' 35 | ``` 36 | 37 | If other libraries are already configured for loading, it can be 38 | appended to the end of the list. Order should not matter. 39 | 40 | Configuration 41 | ------------- 42 | 43 | By default, the background worker will listen to port 5400 on a 44 | wild card IP address. There is *no* verification of the source done, so 45 | protect the port with a proper host firewall!!! 46 | 47 | To change the port, set the value of `bgw_replstatus.port` to another 48 | value. Any TCP port above 1024 will work (but don't pick the same one 49 | as PostgreSQL itself...). 50 | 51 | To change the socket to bind to a specific IP address, set 52 | `bgw_replstatus.bind` to an IP address, which will cause the 53 | background worker to bind to this IP on the defined port. 54 | 55 | There is no support for multiple ports or multiple IP addresses. 56 | 57 | Example usage 58 | ------------- 59 | 60 | In it's simplest form, you can just verify the status of your system 61 | with `nc` or `telnet`: 62 | 63 | ``` 64 | $ nc localhost 5400 65 | MASTER 66 | ``` 67 | 68 | Since the text coming back is easily identifiable, it's easy enough to 69 | integrate with a load balancer such as haproxy. This example haproxy 70 | configuration will show how to ensure that haproxy is connected to the 71 | master node of the cluster, and automatically switches over to the 72 | backup node if the master goes down and the backup is 73 | promoted. Multiple backups can be used. 74 | 75 | ``` 76 | frontend test 77 | bind 127.0.0.1:5999 78 | default_backend pgcluster 79 | 80 | backend pgcluster 81 | mode tcp 82 | option tcp-check 83 | tcp-check expect string MASTER 84 | server s1 127.0.0.1:5500 check port 5400 85 | server s2 127.0.0.1:5501 check port 5401 backup 86 | server s3 127.0.0.1:5502 check port 5402 backup 87 | ``` 88 | 89 | In this example all nodes are local, with postgres running on ports 90 | 5500/5501/5502 with `bgw_replstatus` bound to ports 5400/5401/5402 91 | respectively. 92 | -------------------------------------------------------------------------------- /bgw_replstatus.c: -------------------------------------------------------------------------------- 1 | #include "postgres.h" 2 | 3 | #include "miscadmin.h" 4 | #include "postmaster/bgworker.h" 5 | #include "storage/ipc.h" 6 | #include "storage/latch.h" 7 | #include "access/xlog.h" 8 | #include "utils/guc.h" 9 | #include "pgstat.h" 10 | 11 | #include 12 | #include 13 | 14 | PG_MODULE_MAGIC; 15 | 16 | void _PG_init(void); 17 | extern PGDLLEXPORT void bgw_replstatus_main(Datum d); 18 | 19 | /* flags set by signal handlers */ 20 | static volatile sig_atomic_t got_sigterm = false; 21 | 22 | /* config */ 23 | static int portnum = 5400; 24 | static char *bindaddr = NULL; 25 | 26 | /* 27 | * Perform a clean shutdown on SIGTERM. To do that, just 28 | * set a boolean in the sig handler and then set our own 29 | * latch to break the main loop. 30 | */ 31 | static void 32 | bgw_replstatus_sigterm(SIGNAL_ARGS) 33 | { 34 | int save_errno = errno; 35 | 36 | got_sigterm = true; 37 | SetLatch(MyLatch); 38 | errno = save_errno; 39 | } 40 | 41 | void bgw_replstatus_main(Datum d) 42 | { 43 | int enable = 1; 44 | int listensocket; 45 | struct sockaddr_in addr; 46 | 47 | pqsignal(SIGTERM, bgw_replstatus_sigterm); 48 | 49 | BackgroundWorkerUnblockSignals(); 50 | 51 | /* Setup our listening socket */ 52 | listensocket = socket(AF_INET, SOCK_STREAM, 0); 53 | if (listensocket == -1) 54 | ereport(ERROR, 55 | (errmsg("bgw_replstatus: could not create socket: %m"))); 56 | 57 | if (setsockopt(listensocket, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)) != 0) 58 | ereport(ERROR, 59 | (errmsg("bgw_replstatus: could not set socket option: %m"))); 60 | 61 | if (!pg_set_noblock(listensocket)) 62 | ereport(ERROR, 63 | (errmsg("bgw_replstatus: could not set non blocking socket: %m"))); 64 | 65 | memset(&addr, 0, sizeof(addr)); 66 | addr.sin_family = AF_INET; 67 | addr.sin_port = htons(portnum); 68 | 69 | if (bindaddr == NULL || strlen(bindaddr) == 0) 70 | addr.sin_addr.s_addr = INADDR_ANY; 71 | else 72 | { 73 | if (inet_aton(bindaddr, &addr.sin_addr) == 0) 74 | ereport(ERROR, 75 | (errmsg("bgw_replstatus: could not translate IP address '%s'", 76 | bindaddr))); 77 | } 78 | 79 | if (bind(listensocket, &addr, sizeof(addr)) != 0) 80 | ereport(ERROR, 81 | (errmsg("bgw_replstatus: could not bind socket: %m"))); 82 | 83 | if (listen(listensocket, 5) != 0) 84 | ereport(ERROR, 85 | (errmsg("bgw_replstatus: could not listen on socket: %m"))); 86 | 87 | /* 88 | * Loop forever looking for new connections. Terminate on SIGTERM, 89 | * which is sent by the postmaster when it wants us to shut down. 90 | * XXX: we don't currently support changing the port at runtime. 91 | */ 92 | while (!got_sigterm) 93 | { 94 | int rc; 95 | 96 | rc = WaitLatchOrSocket(MyLatch, 97 | WL_LATCH_SET | WL_POSTMASTER_DEATH | WL_SOCKET_READABLE, 98 | listensocket, 99 | -1 100 | #if PG_VERSION_NUM >= 100000 101 | /* 10.0 introduced PG_WAIT_EXTENSION */ 102 | ,PG_WAIT_EXTENSION); 103 | #else 104 | ); 105 | #endif 106 | ResetLatch(MyLatch); 107 | 108 | if (rc & WL_POSTMASTER_DEATH) 109 | proc_exit(1); 110 | else if (rc & WL_SOCKET_READABLE) 111 | { 112 | char *status_str; 113 | socklen_t addrsize = sizeof(addr); 114 | int worksock = accept4(listensocket, &addr, &addrsize, SOCK_NONBLOCK); 115 | if (worksock == -1) 116 | { 117 | ereport(LOG, 118 | (errmsg("bgw_replstatus: could not accept socket: %m"))); 119 | continue; 120 | } 121 | 122 | status_str = RecoveryInProgress() ? "STANDBY" : "MASTER"; 123 | if (write(worksock, status_str, strlen(status_str)) != strlen(status_str)) 124 | { 125 | ereport(LOG, 126 | (errmsg("bgw_replstatus: could not write %s: %m", 127 | status_str))); 128 | close(worksock); 129 | continue; 130 | } 131 | 132 | if (close(worksock) != 0) 133 | { 134 | ereport(LOG, 135 | (errmsg("bgw_replstatus: could not close working socket: %m"))); 136 | continue; 137 | } 138 | } 139 | } 140 | 141 | ereport(LOG, 142 | (errmsg("bgw_replstatus: shutting down"))); 143 | 144 | close(listensocket); 145 | 146 | proc_exit(0); 147 | } 148 | 149 | 150 | /* 151 | * Initialization entrypoint 152 | */ 153 | void _PG_init(void) 154 | { 155 | BackgroundWorker worker; 156 | 157 | /* 158 | * Define our GUCs so we can get the configuration values. 159 | */ 160 | DefineCustomIntVariable("bgw_replstatus.port", 161 | "TCP port to bind to", 162 | NULL, 163 | &portnum, 164 | 5400, 165 | 1025, 166 | 65535, 167 | PGC_POSTMASTER, 168 | 0, 169 | NULL, 170 | NULL, 171 | NULL); 172 | 173 | DefineCustomStringVariable("bgw_replstatus.bind", 174 | "IP address to bind to", 175 | NULL, 176 | &bindaddr, 177 | "", 178 | PGC_POSTMASTER, 179 | 0, 180 | NULL, 181 | NULL, 182 | NULL); 183 | 184 | if (!process_shared_preload_libraries_in_progress) 185 | return; 186 | 187 | /* 188 | * Set up our bgworker 189 | */ 190 | MemSet(&worker, 0, sizeof(worker)); 191 | 192 | worker.bgw_flags = BGWORKER_SHMEM_ACCESS; 193 | worker.bgw_start_time = BgWorkerStart_PostmasterStart; 194 | worker.bgw_restart_time = 30; /* Restart after 30 seconds -- just 195 | so we don't end up in a hard loop 196 | if something fails */ 197 | sprintf(worker.bgw_library_name, "bgw_replstatus"); 198 | sprintf(worker.bgw_function_name, "bgw_replstatus_main"); 199 | worker.bgw_notify_pid = 0; 200 | snprintf(worker.bgw_name, BGW_MAXLEN, "bgw_replstatus"); 201 | 202 | RegisterBackgroundWorker(&worker); 203 | } 204 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | bgw-replstatus (1.0.8-1) unstable; urgency=medium 2 | 3 | * Support PostgreSQL 18. 4 | 5 | -- Christoph Berg Sun, 20 Apr 2025 17:57:24 +0200 6 | 7 | bgw-replstatus (1.0.7-3) unstable; urgency=medium 8 | 9 | * Upload for PostgreSQL 17. 10 | * Restrict to 64-bit architectures. 11 | * Mark postgresql-all as . 12 | 13 | -- Christoph Berg Tue, 10 Sep 2024 13:32:04 +0200 14 | 15 | bgw-replstatus (1.0.7-2) unstable; urgency=medium 16 | 17 | * Upload for PostgreSQL 16. 18 | * Use ${postgresql:Depends}. 19 | 20 | -- Christoph Berg Sat, 16 Sep 2023 14:42:46 +0200 21 | 22 | bgw-replstatus (1.0.7-1) unstable; urgency=medium 23 | 24 | * Mark bgw_replstatus_main extern PGDLLEXPORT so PG16 can find it. 25 | 26 | -- Christoph Berg Sun, 02 Jul 2023 23:00:08 +0200 27 | 28 | bgw-replstatus (1.0.6-3) unstable; urgency=medium 29 | 30 | * Upload for PostgreSQL 15. 31 | 32 | -- Christoph Berg Thu, 20 Oct 2022 15:21:32 +0200 33 | 34 | bgw-replstatus (1.0.6-2) unstable; urgency=medium 35 | 36 | * Fix GitHub watch file. 37 | 38 | -- Christoph Berg Tue, 04 Jan 2022 16:34:43 +0100 39 | 40 | bgw-replstatus (1.0.6-1) unstable; urgency=medium 41 | 42 | * Upload for PostgreSQL 14. 43 | 44 | -- Christoph Berg Mon, 11 Oct 2021 10:33:45 +0200 45 | 46 | bgw-replstatus (1.0.5) unstable; urgency=medium 47 | 48 | [ Debian Janitor ] 49 | * Use secure copyright file specification URI. 50 | * Bump debhelper from deprecated 9 to 12. 51 | * Set debhelper-compat version in Build-Depends. 52 | * Update standards version to 4.2.1, no changes needed. 53 | 54 | [ Christoph Berg ] 55 | * Upload for PostgreSQL 13. 56 | * Use dh --with pgxs. 57 | * Build-depend on netcat-openbsd and run tests at build time as well. 58 | * DH 13. 59 | * R³: no. 60 | * debian/tests: Use 'make' instead of postgresql-server-dev-all. 61 | * Add myself to uploaders. 62 | 63 | -- Christoph Berg Sun, 18 Oct 2020 21:51:33 +0200 64 | 65 | bgw-replstatus (1.0.4) unstable; urgency=medium 66 | 67 | * Team upload for PostgreSQL 12. 68 | 69 | -- Christoph Berg Tue, 29 Oct 2019 09:40:31 +0100 70 | 71 | bgw-replstatus (1.0.3) unstable; urgency=medium 72 | 73 | * Team upload for PostgreSQL 11. 74 | * Priority: optional. 75 | * Update PostgreSQL team address. 76 | * Bump S-V. 77 | 78 | -- Christoph Berg Thu, 11 Oct 2018 22:15:39 +0200 79 | 80 | bgw-replstatus (1.0.2) unstable; urgency=medium 81 | 82 | * Team upload for PostgreSQL 10. 83 | 84 | -- Christoph Berg Thu, 21 Sep 2017 18:42:06 +0200 85 | 86 | bgw-replstatus (1.0.1) unstable; urgency=medium 87 | 88 | * Team upload. 89 | * Use netcat to run basic functionality test on installcheck. 90 | * Add copyright file and long package description. 91 | 92 | -- Christoph Berg Fri, 31 Mar 2017 20:24:44 +0200 93 | 94 | bgw-replstatus (1.0.0) unstable; urgency=medium 95 | 96 | * Initial release. 97 | 98 | -- Magnus Hagander Fri, 31 Mar 2017 14:01:35 +0200 99 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: bgw-replstatus 2 | Section: database 3 | Priority: optional 4 | Maintainer: Magnus Hagander 5 | Uploaders: 6 | Debian PostgreSQL Maintainers , 7 | Christoph Berg , 8 | Build-Depends: 9 | architecture-is-64-bit , 10 | debhelper-compat (= 13), 11 | netcat-openbsd, 12 | postgresql-all , 13 | postgresql-server-dev-all (>= 217~), 14 | Standards-Version: 4.7.0 15 | Rules-Requires-Root: no 16 | Homepage: https://github.com/mhagander/bgw_replstatus 17 | Vcs-Git: https://github.com/mhagander/bgw_replstatus.git 18 | Vcs-Browser: https://github.com/mhagander/bgw_replstatus 19 | 20 | Package: postgresql-17-bgw-replstatus 21 | Architecture: any 22 | Depends: 23 | ${misc:Depends}, 24 | ${postgresql:Depends}, 25 | ${shlibs:Depends}, 26 | Description: report whether PostgreSQL node is master or standby 27 | bgw_replstatus is a tiny PostgreSQL background worker to cheaply report the 28 | replication status of a node. It's intended to be polled by a load balancer 29 | such as haproxy. 30 | . 31 | When installed, a background worker will be started that listens on a TCP 32 | port. A connection to this port will get a TCP response back saying either 33 | MASTER or STANDBY depending on the current state of the node. 34 | -------------------------------------------------------------------------------- /debian/control.in: -------------------------------------------------------------------------------- 1 | Source: bgw-replstatus 2 | Section: database 3 | Priority: optional 4 | Maintainer: Magnus Hagander 5 | Uploaders: 6 | Debian PostgreSQL Maintainers , 7 | Christoph Berg , 8 | Build-Depends: 9 | architecture-is-64-bit , 10 | debhelper-compat (= 13), 11 | netcat-openbsd, 12 | postgresql-all , 13 | postgresql-server-dev-all (>= 217~), 14 | Standards-Version: 4.7.0 15 | Rules-Requires-Root: no 16 | Homepage: https://github.com/mhagander/bgw_replstatus 17 | Vcs-Git: https://github.com/mhagander/bgw_replstatus.git 18 | Vcs-Browser: https://github.com/mhagander/bgw_replstatus 19 | 20 | Package: postgresql-PGVERSION-bgw-replstatus 21 | Architecture: any 22 | Depends: 23 | ${misc:Depends}, 24 | ${postgresql:Depends}, 25 | ${shlibs:Depends}, 26 | Description: report whether PostgreSQL node is master or standby 27 | bgw_replstatus is a tiny PostgreSQL background worker to cheaply report the 28 | replication status of a node. It's intended to be polled by a load balancer 29 | such as haproxy. 30 | . 31 | When installed, a background worker will be started that listens on a TCP 32 | port. A connection to this port will get a TCP response back saying either 33 | MASTER or STANDBY depending on the current state of the node. 34 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: bgw_replstatus 3 | Source: https://github.com/mhagander/bgw_replstatus 4 | 5 | Files: * 6 | Copyright: Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group 7 | Portions Copyright (c) 1994, The Regents of the University of California 8 | License: PostgreSQL 9 | Permission to use, copy, modify, and distribute this software and its 10 | documentation for any purpose, without fee, and without a written agreement 11 | is hereby granted, provided that the above copyright notice and this 12 | paragraph and the following two paragraphs appear in all copies. 13 | . 14 | IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR 15 | DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING 16 | LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS 17 | DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE 18 | POSSIBILITY OF SUCH DAMAGE. 19 | . 20 | THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 21 | INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 22 | AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS 23 | ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO 24 | PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 25 | -------------------------------------------------------------------------------- /debian/pgversions: -------------------------------------------------------------------------------- 1 | 9.5+ 2 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | override_dh_installdocs: 4 | dh_installdocs --all README.* 5 | 6 | override_dh_pgxs_test: 7 | +pg_buildext -o 'shared_preload_libraries=bgw_replstatus' installcheck . . postgresql-%v-bgw-replstatus 8 | 9 | %: 10 | dh $@ --with pgxs 11 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /debian/tests/control: -------------------------------------------------------------------------------- 1 | Depends: @, make, netcat-openbsd 2 | Tests: installcheck 3 | -------------------------------------------------------------------------------- /debian/tests/installcheck: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | pg_buildext -o 'shared_preload_libraries=bgw_replstatus' installcheck 4 | -------------------------------------------------------------------------------- /debian/watch: -------------------------------------------------------------------------------- 1 | version=4 2 | https://github.com/mhagander/bgw_replstatus/tags .*/([0-9.]+).tar.gz 3 | --------------------------------------------------------------------------------