Here is one bash script which I place here to preserve it – we do not use it anymore (we switched to golang solution). But this bash script worked well for a very long time so maybe it can be useful also for someone else.

Use case – synchronizes both partitioned and unpartitioned tables between source and archive database once per day. List of tables is in env variables stored in .env file. Tasks run in parallel.

#!/usr/bin/env bash

debug=${DBADEBUG:-0}

vmaxjobs=8

echo_log () {
  echo "$(date +"%Y-%m-%d %H:%M:%S.%N"): $1"
}

function psqlsource() {
  psql -U $SOURCE_USER -h $SOURCE_HOST -p $SOURCE_PORT -d $SOURCE_DB -t -c "$1"
  [ $? -ne 0 ] && echo_log "ERROR in psqltosource - query: $1" && exit 1
}

function checkjobs {
  [ "$debug" == "1" ] && echo_log "checkjobs jobsarray dim: ${!jobsarray[@]}"
  [ "$debug" == "1" ] && echo_log "checkjobs jobsarray elements: ${jobsarray[@]}"
  for j in "${!jobsarray[@]}"; do
    [ "$debug" == "1" ] && echo_log "checking job: ${jobsarray[$j]}"
    wait ${jobsarray[$j]}
    rc=$?
    [ "$debug" == "1" ] && echo_log "job ${jobsarray[$j]} exit code: $rc"
    if [[ $rc -ne 0 ]] && [[ $rc -ne 127 ]]; then
      echo_log "$(basename $0): ERROR in job ${jobsarray[$j]} exit code $rc" 
      exit 1
    fi
    [ "$debug" == "1" ] && echo_log "job ${jobsarray[$j]} OK"
    unset jobsarray[$j]
  done
}

function maxjobs {
  [ "$debug" == "1" ] && echo_log "maxjobs: ${#jobsarray[@]}"
  [ "$debug" == "1" ] && echo_log "maxjobs jobsarray dim: ${!jobsarray[@]}"
  [ "$debug" == "1" ] && echo_log "maxjobs jobsarray elements: ${jobsarray[@]}"
  if [[ ${#jobsarray[@]} -ge $vmaxjobs ]]; then
    checkjobs
  fi
}

echo_log "=== COPYING DAILY PARTITIONS ==="

scriptdir=$( dirname "${BASH_SOURCE[0]}")
cd $scriptdir
. .env

queryunpartitioned=""
queryparents=""
[ "$debug" == "1" ] && echo_log "UNPARTITIONED_TABLES_TO_COPY: $UNPARTITIONED_TABLES_TO_COPY"
if [ "$UNPARTITIONED_TABLES_TO_COPY" != "" ]; then
  queryunpartitioned="union all select relnamespace::regnamespace::text||'.'||relname from pg_class where relname in ($(echo "'$UNPARTITIONED_TABLES_TO_COPY'"|sed "s/,/','/g"))"
fi  
[ "$debug" == "1" ] && echo_log "queryunpartitioned: $queryunpartitioned"

[ "$debug" == "1" ] && echo_log "PARTITIONED_TABLES_TO_COPY: $PARTITIONED_TABLES_TO_COPY"
if [ "$PARTITIONED_TABLES_TO_COPY" != "" ]; then
  queryparents="and p.relname in ($(echo "'$PARTITIONED_TABLES_TO_COPY'"|sed "s/,/','/g"))"
fi
[ "$debug" == "1" ] && echo_log "queryparents: $queryparents"

sourcequery="select * from (with dates as (select to_char(clock_timestamp(),'YYYYMMDD') as _today, to_char(clock_timestamp()+interval'1day','YYYYMMDD') as _tomorrow), children as (select c.relnamespace::regnamespace::text||'.'||c.relname as childtablename from pg_inherits i join pg_class c on i.inhrelid=c.oid join pg_class p on i.inhparent=p.oid where c.relname not like '%unknowns%' $queryparents ) select childtablename from children, dates where childtablename not like '%'||_today::text||'%' and childtablename not like '%'||_tomorrow::text||'%' $queryunpartitioned ) a order by 1"
[ "$debug" == "1" ] && echo_log "sourcequery: $sourcequery"

for table in $(psqlsource "$sourcequery"); do
  maxjobs
  $scriptdir/table_pg2pg.sh $table &
  jobsarray=("${jobsarray[@]}" $!)
done

echo_log "=== END ==="