~hacktivista/pglr

591c36ef32ff7af76f0860027c48757c775754dc — Felix Freeman 11 days ago b3825c3 main
Replicate local changes only.

pglr_output PostgreSQL module filters by local origin, so you never have
bi-directional dead loops.
4 files changed, 86 insertions(+), 16 deletions(-)

A Makefile
M README.md
A pglr_output.c
M setup.sh
A Makefile => Makefile +12 -0
@@ 0,0 1,12 @@
# Copyright 2021 Felix Freeman <libsys@hacktivista.org>
#
# This file is part of "pglr" and licensed under the terms of the GNU General
# Public License version 3. You should have received a copy of this license
# along with the software. If not, see <https://www.gnu.org/licenses/>.

MODULES = pglr_output
PGFILEDESC = "pglr_output - same as pgoutput but replicates local changes only."

PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)

M README.md => README.md +22 -4
@@ 12,16 12,26 @@ albeit there's no warranty implied.
## Requirements

- POSIX-compilant operating system
- `sed` that allows the `-i` flag
- PostgreSQL
- `sed` that allows the `-i` flag (like GNU and BusyBox sed)
- sudo
- postgresql-dev or equivalent
- libc-dev or equivalent
- gcc
- make
- An already setup interconnection between Postgres hosts (ex: VPN for safe
  remote connection)
- sudo

## Run
## Deploy

As root user.

On first-run
```
make
make install
```

Whenever you want to replicate a database
```
./setup.sh [options] <database>
```


@@ 51,10 61,18 @@ stored procedure might be a nice idea for data consistency.
[Here is an example](https://git.hacktivista.org/scripts/blob/main/dynfail)
of what I'm using.

### Will it work for multi-master?

Depending on your application and setup, it might.

### This is great, can I buy you a coffee?

[Sure pal](https://hacktivista.org/payments?ref=pglr)

## TODO

- Probably use a .control PGXS file.

## License

GNU GPL v3 only.

A pglr_output.c => pglr_output.c +41 -0
@@ 0,0 1,41 @@
/**
 * PGLR output module.
 * Does the same as default pgoutput, but replicates local changes only.
 *
 * Copyright 2021 Felix Freeman <libsys@hacktivista.org>
 *
 * This file is part of "pglr" and licensed under the terms of the GNU General
 * Public License version 3. You should have received a copy of this license
 * along with the software. If not, see <https://www.gnu.org/licenses/>.
 */

#include "postgres.h"
#include "replication/logical.h"
#include "replication/origin.h"
#include "replication/output_plugin.h"

PG_MODULE_MAGIC;

static bool
pgoutput_origin_filter(LogicalDecodingContext *ctx, RepOriginId origin_id)
{
	return (origin_id != 0);
}

extern void _PG_output_plugin_init(OutputPluginCallbacks *cb);

void
_PG_output_plugin_init(OutputPluginCallbacks *cb)
{
	LogicalOutputPluginInit plugin_init;

	plugin_init = (LogicalOutputPluginInit)
		load_external_function("$libdir/pgoutput", "_PG_output_plugin_init", false, NULL);

	if (plugin_init == NULL)
		elog(ERROR, "output plugins have to declare the _PG_output_plugin_init symbol");

	plugin_init(cb);

	cb->filter_by_origin_cb = pgoutput_origin_filter;
}

M setup.sh => setup.sh +11 -12
@@ 268,8 268,10 @@ fi
echo 'How do you want to name this replica publication? (ex: mypub)'
pub_name=$(read_str word)
echo "Creating $pub_name publication"
[ -n "$flag_test" ] ||
if [ -z "$flag_test" ]; then
    printf "CREATE PUBLICATION %s FOR ALL TABLES;\n" "$pub_name" | supsql
    echo "SELECT pg_create_logical_replication_slot('${pub_name}_sub', 'pglr_output');" | supsql
fi

# Initialize subscription
echo 'Do you want to initialize a subscription? (y/n)'


@@ 280,26 282,23 @@ if [ "$(read_choice 'yn')" = 'y' ]; then
    echo 'Type connection string (help on https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING)'
    sub_connstring=$(read_str any)
    echo 'Type remote publication name'
    sub_pub_names=$(read_str word)
    while
        echo 'Do you want to add other subscription? (y/n)'
        [ "$(read_choice 'yn')" = 'y' ]
    do
        echo 'Type remote publication name'
        sub_pub_names="$sub_pub_names,$(read_str word)"
    done

    sub_pub_name=$(read_str word)
    echo 'Copy existing remote data? (y/n)'
    sub_copy_data="$(read_choice 'yn')"
    [ "$sub_copy_data" = 'y' ] && sub_copy_data='true' || sub_copy_data='false'
    [ -n "$flag_test" ] || cat << EOF | supsql
CREATE SUBSCRIPTION $sub_name
CONNECTION '$sub_connstring'
PUBLICATION $sub_pub_names;
PUBLICATION $sub_pub_name
WITH (copy_data = $sub_copy_data, create_slot = false, slot_name = '${sub_pub_name}_sub');
EOF
else
    cat << EOF
No worries, you can subscribe later with:
CREATE SUBSCRIPTION <subscription name>
CONNECTION '<subscription connection string>'
PUBLICATION <remote publication name(s)>;
PUBLICATION <remote publication name>
WITH (copy_data = <true|false>, create_slot = false, slot_name = '<remote_publication_name>_sub');
EOF
fi