fix(vector): publish backups without replacement
This commit is contained in:
@@ -83,3 +83,12 @@ All five final review findings were addressed in a follow-up commit:
|
|||||||
sequence authority, and 768-dimensional compatibility are therefore verified through the
|
sequence authority, and 768-dimensional compatibility are therefore verified through the
|
||||||
production adapter. Reconciliation now restores group-role schema `USAGE`, which table-selected
|
production adapter. Reconciliation now restores group-role schema `USAGE`, which table-selected
|
||||||
archives cannot carry.
|
archives cannot carry.
|
||||||
|
|
||||||
|
### Atomic no-replace backup publication
|
||||||
|
|
||||||
|
The final publication review is also closed. The private same-directory archive is published with
|
||||||
|
an atomic hard-link create rather than rename-overwrite semantics. If any process creates the final
|
||||||
|
file or symlink after preflight but before publication, `ln` fails with `EEXIST`, the backup exits
|
||||||
|
nonzero, the concurrent destination remains byte-for-byte intact, and the trap removes only the
|
||||||
|
randomly named temporary archive owned by this invocation. The fake `pg_dump` safety test creates
|
||||||
|
that destination immediately before returning and pins the failure and cleanup behavior.
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ cat >"$fakebin/pg_dump" <<'SH'
|
|||||||
set -eu
|
set -eu
|
||||||
for arg in "$@"; do case "$arg" in --file=*) output=${arg#--file=} ;; esac; done
|
for arg in "$@"; do case "$arg" in --file=*) output=${arg#--file=} ;; esac; done
|
||||||
printf 'custom dump' >"$output"
|
printf 'custom dump' >"$output"
|
||||||
|
if [ -n "${RACE_OUTPUT:-}" ]; then
|
||||||
|
printf 'concurrent owner' >"$RACE_OUTPUT"
|
||||||
|
fi
|
||||||
SH
|
SH
|
||||||
chmod 0755 "$fakebin/pg_dump"
|
chmod 0755 "$fakebin/pg_dump"
|
||||||
|
|
||||||
@@ -26,6 +29,19 @@ test "$(cat "$victim")" = sentinel
|
|||||||
test "$(cat "$output")" = 'custom dump'
|
test "$(cat "$output")" = 'custom dump'
|
||||||
test -L "$output.partial"
|
test -L "$output.partial"
|
||||||
|
|
||||||
|
race_output="$tmp/raced.dump"
|
||||||
|
if PATH="$fakebin:$PATH" RACE_OUTPUT="$race_output" ./scripts/vector-backup.sh \
|
||||||
|
--host source --database thoth --user admin --password-file "$tmp/password" \
|
||||||
|
--output "$race_output" >"$tmp/race.out" 2>"$tmp/race.err"; then
|
||||||
|
echo "backup replaced a destination created concurrently" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
test "$(cat "$race_output")" = 'concurrent owner'
|
||||||
|
if find "$tmp" -name '.raced.dump.tmp.*' -print | grep -q .; then
|
||||||
|
echo "backup left its owned temporary archive after publication failure" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
cat >"$fakebin/psql" <<'SH'
|
cat >"$fakebin/psql" <<'SH'
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -eu
|
set -eu
|
||||||
|
|||||||
@@ -45,5 +45,9 @@ PGPASSFILE=$passfile pg_dump \
|
|||||||
--format=custom --compress=9 \
|
--format=custom --compress=9 \
|
||||||
--table=vectors.schema_records --table=vectors.evidence --table=vectors.memory \
|
--table=vectors.schema_records --table=vectors.evidence --table=vectors.memory \
|
||||||
--table=public.tht_vector_migrations --file="$temporary_output"
|
--table=public.tht_vector_migrations --file="$temporary_output"
|
||||||
mv "$temporary_output" "$output"
|
if ! ln "$temporary_output" "$output"; then
|
||||||
|
echo "refusing to replace backup destination created concurrently: $output" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
rm -f "$temporary_output"
|
||||||
echo "Vector backup written: $output"
|
echo "Vector backup written: $output"
|
||||||
|
|||||||
Reference in New Issue
Block a user