fix(apt): cache snapshot collision checks

Cache the complete snapshot-name listing once per hook invocation instead\nof querying ZFS separately for every dataset and counter candidate. This\nremoves the repeated full-pool scan that becomes expensive when recursive\nsnapshot histories are large.\n\nCache the operation suffix as well and return an explicit success status\nfrom snapshot-name generation so collision retries are unambiguous. Ignore\nAPT configuration-only records because they do not represent package data\nchanges requiring a new pre-install snapshot.
This commit is contained in:
2026-09-16 09:46:01 +02:00
parent e227e27ec8
commit 457db3934c

14
apt-zfs-snapshot.sh Normal file → Executable file
View File

@@ -7,6 +7,8 @@ declare -r zfs_property='space.quico:auto-snapshot'
declare -r zfs_property_value='true' declare -r zfs_property_value='true'
declare -r zfs_max_name_length=255 declare -r zfs_max_name_length=255
set -xv
if [[ -r "${config_file}" ]]; then if [[ -r "${config_file}" ]]; then
# shellcheck disable=SC1090 # shellcheck disable=SC1090
source "${config_file}" source "${config_file}"
@@ -33,6 +35,7 @@ mixed_suffix="${mixed_suffix:-mixd}"
package_separator="${package_separator:-:}" package_separator="${package_separator:-:}"
declare -a packages operations declare -a packages operations
declare existing_snapshots
print_msg() { print_msg() {
local level="$1" message="$2" local level="$1" message="$2"
@@ -77,7 +80,9 @@ parse_apt_input() {
read -r package old_version _old_arch direction new_version _new_arch _multiarch action <<<"${line}" read -r package old_version _old_arch direction new_version _new_arch _multiarch action <<<"${line}"
[[ -n "${package}" ]] || continue [[ -n "${package}" ]] || continue
if [[ "${action}" == '**REMOVE**' ]]; then if [[ "${action}" == '**CONFIGURE**' ]]; then
continue
elif [[ "${action}" == '**REMOVE**' ]]; then
add_package "${package}" 'remove' add_package "${package}" 'remove'
elif [[ "${direction}" == '>' ]]; then elif [[ "${direction}" == '>' ]]; then
add_package "${package}" 'downgrade' add_package "${package}" 'downgrade'
@@ -157,17 +162,18 @@ shorten_packages() {
package_list="${value}" package_list="${value}"
} }
existing_snapshot() { zfs list -H -t snapshot -o name | grep -Fxq -- "$1"; } existing_snapshot() { grep -Fxq -- "$1" <<<"${existing_snapshots}"; }
make_snapshot_name() { make_snapshot_name() {
local counter="$1" dataset base local counter="$1" dataset base
names=() names=()
for dataset in "${snappable_datasets[@]}"; do for dataset in "${snappable_datasets[@]}"; do
base="${dataset}@${snapshot_prefix}${field_separator}${date_string}${field_separator}${counter}${field_separator}op:$(operation_suffix)${field_separator}sev:${severity}${field_separator}pkgs:${package_list}" base="${dataset}@${snapshot_prefix}${field_separator}${date_string}${field_separator}${counter}${field_separator}op:${operation_suffix_result}${field_separator}sev:${severity}${field_separator}pkgs:${package_list}"
(( ${#base} <= zfs_max_name_length )) || { print_msg ERR "Snapshot name exceeds ${zfs_max_name_length} characters: ${base}"; return 2; } (( ${#base} <= zfs_max_name_length )) || { print_msg ERR "Snapshot name exceeds ${zfs_max_name_length} characters: ${base}"; return 2; }
names+=("${base}") names+=("${base}")
existing_snapshot "${base}" && return 1 existing_snapshot "${base}" && return 1
done done
return 0
} }
prune_snapshots() { prune_snapshots() {
@@ -196,9 +202,11 @@ main() {
(( ${#packages[@]} > 0 )) || { print_msg INFO 'No package actions received; skipping snapshot'; return 0; } (( ${#packages[@]} > 0 )) || { print_msg INFO 'No package actions received; skipping snapshot'; return 0; }
get_datasets get_datasets
(( ${#snappable_datasets[@]} > 0 )) || { print_msg INFO "No eligible ZFS datasets; skipping snapshot"; return 0; } (( ${#snappable_datasets[@]} > 0 )) || { print_msg INFO "No eligible ZFS datasets; skipping snapshot"; return 0; }
existing_snapshots="$(zfs list -H -t snapshot -o name)"
date_string="$(TZ="${timezone}" date +"${date_format}")" date_string="$(TZ="${timezone}" date +"${date_format}")"
severity_and_packages severity_and_packages
shorten_packages shorten_packages
operation_suffix_result="$(operation_suffix)"
while :; do while :; do
make_snapshot_name "$((++counter))" make_snapshot_name "$((++counter))"
case "$?" in case "$?" in