#!/usr/bin/env bash # Show blocks_done / blocks_total of pg_stat_progress_create_index while # CREATE INDEX CONCURRENTLY and REINDEX CONCURRENTLY wait after their # validation scan. # # usage: bash repro-f3.sh.txt [port] # holds initdb, pg_ctl and psql of the build under test. # # A throwaway cluster is created in a temporary directory and removed at # the end. The waits are made by plain sessions: # CIC: a REPEATABLE READ transaction opened before the command # ("waiting for old snapshots"). # REINDEX: the same, then two transactions that read the table, each # opened while the previous wait is in progress ("waiting for # readers before marking dead", "... before dropping"). # Each wait is sampled 5 times, 1 second apart; it lasts as long as the # session that causes it stays open. set -u BIN=${1:?usage: repro-f3.sh.txt [port]} PORT=${2:-54329} D=$(mktemp -d "${TMPDIR:-/tmp}/repro-f3.XXXXXX") trap 'touch "$D"/release-h1 "$D"/release-h2 "$D"/release-h3; sleep 0.5; "$BIN"/pg_ctl -D "$D/data" -m immediate stop >/dev/null 2>&1; rm -rf "$D"' EXIT "$BIN"/initdb -D "$D/data" -A trust --no-sync >"$D/initdb.log" 2>&1 || { cat "$D/initdb.log"; exit 1; } "$BIN"/pg_ctl -D "$D/data" -l "$D/server.log" -w \ -o "-p $PORT -c listen_addresses='' -c unix_socket_directories='$D'" \ start >/dev/null || { cat "$D/server.log"; exit 1; } q() { "$BIN"/psql -X -At -h "$D" -p "$PORT" -d postgres -c "$1"; } # hold : run in a new transaction and keep it open until # $D/release- exists. hold() { rm -f "$D/release-$1" { printf 'BEGIN ISOLATION LEVEL REPEATABLE READ;\n%s\n' "$2" while [ ! -e "$D/release-$1" ]; do sleep 0.1; done echo 'COMMIT;' } | PGAPPNAME=$1 "$BIN"/psql -X -q -h "$D" -p "$PORT" -d postgres \ >/dev/null & for _ in $(seq 100); do [ "$(q "SELECT count(*) FROM pg_stat_activity WHERE application_name = '$1' AND state = 'idle in transaction'")" = 1 ] && return 0 sleep 0.1 done echo "session $1 did not start"; exit 1 } PROGRESS="SELECT phase, blocks_done, blocks_total FROM pg_stat_progress_create_index WHERE relid = 't'::regclass" wait_phase() { for _ in $(seq 600); do [ "$(q "$PROGRESS" | cut -d'|' -f1)" = "$1" ] && return 0 sleep 0.1 done echo "phase '$1' not reached"; q "$PROGRESS"; exit 1 } SHORT=0; FULL=0 sample() { for i in 1 2 3 4 5; do IFS='|' read -r ph done total <<<"$(q "$PROGRESS")" echo "$1 sample $i: phase=\"$ph\" blocks_done=$done blocks_total=$total" if [ "$done" = "$total" ]; then FULL=$((FULL + 1)); else SHORT=$((SHORT + 1)); fi [ $i -lt 5 ] && sleep 1 done } q "CREATE TABLE t (a int) WITH (autovacuum_enabled = off); INSERT INTO t SELECT g FROM generate_series(1, 100000) g;" >/dev/null echo "table t: $(q "SELECT pg_relation_size('t') / current_setting('block_size')::int") blocks" # CREATE INDEX CONCURRENTLY hold h1 'SELECT 1;' q 'CREATE INDEX CONCURRENTLY t_a ON t (a);' >/dev/null & CMD=$! wait_phase 'waiting for old snapshots' sample cic touch "$D/release-h1"; wait $CMD # REINDEX CONCURRENTLY hold h1 'SELECT 1;' q 'REINDEX INDEX CONCURRENTLY t_a;' >/dev/null & CMD=$! wait_phase 'waiting for old snapshots' sample reindex hold h2 'SELECT count(*) FROM t;' touch "$D/release-h1" wait_phase 'waiting for readers before marking dead' sample reindex hold h3 'SELECT count(*) FROM t;' touch "$D/release-h2" wait_phase 'waiting for readers before dropping' sample reindex touch "$D/release-h3"; wait $CMD echo "RESULT: $SHORT samples with blocks_done < blocks_total," \ "$FULL with blocks_done = blocks_total"