Copias de seguridad

kopia snapshot verify: qué comprueba de verdad y por qué la política de retención no encoge el repositorio

Publicado el 29 de octubre de 2026Última revisión: 29 de octubre de 20269 min de lectura

Resultado de la prueba

kopia snapshot verify sin opciones dio exit 0 con un bloque de datos corrupto (no lee ficheros). Con --verify-files-percent=100 también dio exit 0 mientras la caché de contenido tenía los datos, y exit 1 tras kopia cache clear. restore del snapshot con el bloque corrupto falló con exit 1. Tras dejar 1 de 6 snapshots, el repositorio siguió en unos 10,5 MB (de 10516 a 10532 KiB) aunque kopia contaba 2,1 MB de contenido vivo.

Contenido

Este artículo no contiene enlaces de afiliado.

Kopia es una herramienta de copias con deduplicación, cifrado y políticas de retención. Antes de fiarte de ella conviene saber qué demuestra kopia snapshot verify y qué pasa con el tamaño del repositorio cuando borras snapshots. Se probó con un repositorio local y datos desechables, y con un fallo provocado a propósito.

Condiciones: kopia 0.23.1 (binario linux-arm64 de la página de versiones de GitHub) en Linux arm64, repositorio de tipo filesystem en una carpeta local, contraseña desechable, caché en una carpeta propia (--cache-directory). Datos: un fichero de texto y ficheros aleatorios de 2 MiB, es decir, sin posibilidad de compresión ni deduplicación entre ellos. No se probó con almacenamiento remoto (S3, SFTP, B2...), con repositorios grandes, con la interfaz web (kopia server), con compresión activada ni con otras versiones. Los tamaños son los de du -sk sobre la carpeta del repositorio. Script completo al final.

Crear el repositorio y hacer snapshots

kopia repository create filesystem terminó con exit 0 y el repositorio vacío ocupó 64 KiB. Después:

Paso Tamaño del repositorio
Snapshot 1 (texto + datos1.bin de 2 MiB) 2156 KiB
Snapshot idéntico, sin cambios en el origen 2180 KiB
Se edita el texto y se añade datos2.bin (2 MiB) 4264 KiB

El snapshot sin cambios añadió 24 KiB (metadatos), no otros 2 MiB. kopia snapshot list agrupa los idénticos: mostró la línea del primer snapshot, + 1 identical snapshots y el tercero, con 4,2 MB. Los tres existen: el recuento por --json dio 3.

Restaurar un fichero y un snapshot entero

kopia restore RAÍZ/notas.txt destino recuperó solo ese fichero del primer snapshot ("Restored 1 files ... (12 B)", exit 0) y el contenido era contrato v1, la versión antigua. La raíz es el identificador que empieza por k que muestra snapshot list. Restaurar el último snapshot entero a otra carpeta dio exit 0 y diff -r con el origen no encontró diferencias.

snapshot verify con un bloque corrupto

Se sobrescribieron 16 bytes en mitad del fichero de datos de 2,1 MB del repositorio (el que contiene datos2.bin). Resultado:

Comando Exit Resultado
snapshot verify --verify-files-percent=100, caché sin tocar 0 "Read 4 files (4.2 MB)", no detectó nada
kopia cache clear, y otra vez --verify-files-percent=100 1 invalid checksum ... message authentication failed en datos2.bin; "Read 3 files"
snapshot verify sin opciones, caché vacía 0 "Read 0 files (0 B)"
restore del último snapshot 1 falla al abrir datos2.bin con el mismo error de autenticación
Se devuelve el bloque original, cache clear y verify al 100 % 0 "Read 4 files (4.2 MB)"

Lo que importa:

Retención y maintenance --full: el tamaño no bajó

Se hicieron tres snapshots más, cada uno con un fichero aleatorio distinto de 2 MiB (seis snapshots en total, 10516 KiB). Se fijó la política global con --keep-latest=1 y el resto a 0.

Paso Snapshots Repositorio
Antes de la política 6 10516 KiB
snapshot expire --delete sin ruta 6 (no borró nada, sin mensaje, exit 0)
snapshot expire /ruta/origen --delete 1 ("Deleted 5 snapshots") 10568 KiB
maintenance run --full 1 10572 KiB
maintenance run --full --safety=none 1 10520 KiB
Dos --full --safety=none más 1 10532 y 10536 KiB

Tras todo eso kopia content stats contaba 2,1 MB de contenido (12 contenidos) y, sin embargo, en disco quedaba un único fichero de datos de 10 490 258 bytes. Es decir, los 8 MB de los snapshots borrados seguían ocupando disco. El registro de kopia maintenance info de la última ejecución decía "0 contents to rewrite" y "0 unreferenced pack blobs to delete". En una ejecución anterior de --safety=none el registro mostró que se habían reescrito 21 contenidos (10,5 MB) en un fichero nuevo y borrado 14 ficheros viejos, pero el resultado fue otro fichero grande.

Qué no se sabe: si con esperas más largas (con la seguridad por defecto kopia retiene contenido reciente), con más datos, o con kopia content rewrite (que pidió --dangerous-commands=enabled y no se probó) el espacio se recupera. Lo observado aquí es que, en un repositorio pequeño y recién creado, la retención y maintenance run --full no devolvieron espacio.

Detalles que importan

Script completo

#!/bin/bash
# kopia: filesystem repo, snapshots, list, restore one file, snapshot verify, retention + maintenance --full effect on size. Usage: test-kopia-verify-restore.sh WORKDIR
W=$1; rm -rf "$W"; mkdir -p "$W/src" "$W/repo" "$W/restore" "$W/cfg"
export KOPIA_PASSWORD=prueba-desechable KOPIA_CHECK_FOR_UPDATES=false
K="kopia --config-file=$W/cfg/repo.config --log-dir=$W/cfg/logs --no-progress"
size() { du -sk "$W/repo" | cut -f1; }
kopia --version | head -1
echo "== crear repositorio"
$K repository create filesystem --path "$W/repo" --cache-directory "$W/cache" 2>&1 | grep -iE "initializing|error|Policy" | head -3; echo "exit=${PIPESTATUS[0]}"
echo "repo KiB: $(size)"
echo "== datos: un fichero de texto y uno aleatorio de 2 MiB"
echo "contrato v1" > "$W/src/notas.txt"; head -c 2097152 /dev/urandom > "$W/src/datos1.bin"
echo "== snapshot 1"
$K snapshot create "$W/src" 2>&1 | tail -2; echo "exit=${PIPESTATUS[0]}"
echo "repo KiB: $(size)"
echo "== snapshot sin cambios"
$K snapshot create "$W/src" 2>&1 | tail -1; echo "exit=${PIPESTATUS[0]}"
echo "repo KiB: $(size)"
echo "== cambios: notas.txt editado, datos2.bin nuevo (2 MiB)"
echo "contrato v2" > "$W/src/notas.txt"; head -c 2097152 /dev/urandom > "$W/src/datos2.bin"
$K snapshot create "$W/src" 2>&1 | tail -1; echo "exit=${PIPESTATUS[0]}"
echo "repo KiB: $(size)"
echo "== snapshot list"
$K snapshot list 2>&1; echo "exit=${PIPESTATUS[0]}"
ids() { $K snapshot list --json 2>/dev/null | grep -oE '"id":"[0-9a-f]+"' | grep -oE '[0-9a-f]{32}'; }
nsnap() { ids | wc -l; }
echo "snapshots en el repo: $(nsnap)"
ROOT1=$($K snapshot list --json 2>/dev/null | grep -oE '"obj":"k[0-9a-f]+"' | head -1 | grep -oE 'k[0-9a-f]+')
echo "== restaurar solo notas.txt del primer snapshot (raíz $ROOT1)"
$K restore "$ROOT1/notas.txt" "$W/restore/notas-v1.txt" 2>&1 | grep -i restored; echo "exit=${PIPESTATUS[0]}"
echo "contenido restaurado: $(cat $W/restore/notas-v1.txt 2>&1)"
echo "== restaurar el último snapshot completo y comparar con el origen"
LAST=$(ids | tail -1)
$K restore "$LAST" "$W/restore/ultimo" 2>&1 | grep -i restored; echo "exit=${PIPESTATUS[0]}"
diff -r "$W/src" "$W/restore/ultimo" && echo "diff: idéntico"
echo "== snapshot verify"
$K snapshot verify 2>&1 | tail -3; echo "exit=${PIPESTATUS[0]}"
echo "== snapshot verify --verify-files-percent=100"
$K snapshot verify --verify-files-percent=100 2>&1 | tail -3; echo "exit=${PIPESTATUS[0]}"
echo "== corromper un fichero de datos del repositorio y verify otra vez"
BLOB=$(find "$W/repo/p" -type f -size +1000k | head -1); echo "blob: $(basename $BLOB) $(stat -c %s $BLOB) bytes"
cp "$BLOB" "$W/blob.bak"
printf 'XXXXXXXXXXXXXXXX' | dd of="$BLOB" bs=1 seek=100000 conv=notrunc 2>/dev/null
echo "== verify con lectura de ficheros, con el blob corrupto y la caché tal como estaba"
$K snapshot verify --verify-files-percent=100 2>&1 | tail -2; echo "exit=${PIPESTATUS[0]}"
echo "== kopia cache clear y verify con lectura de ficheros"
$K cache clear 2>&1 | tail -1
$K snapshot verify --verify-files-percent=100 2>&1 | tail -4; echo "exit=${PIPESTATUS[0]}"
echo "== verify por defecto (sin leer ficheros) con el blob corrupto y caché vacía"
$K snapshot verify 2>&1 | tail -2; echo "exit=${PIPESTATUS[0]}"
echo "== restaurar el último snapshot con el blob corrupto"
$K restore "$LAST" "$W/restore/corrupto" 2>&1 | grep -iE "restored|error|fail" | head -3; echo "exit=${PIPESTATUS[0]}"
cp "$W/blob.bak" "$BLOB"; $K cache clear >/dev/null 2>&1
echo "== blob restaurado: verify de nuevo"
$K snapshot verify --verify-files-percent=100 2>&1 | tail -2; echo "exit=${PIPESTATUS[0]}"
echo "== retención: 3 snapshots más con ficheros aleatorios distintos (total 6)"
for i in 3 4 5; do rm -f "$W/src"/datos*.bin; head -c 2097152 /dev/urandom > "$W/src/datos$i.bin"; $K snapshot create "$W/src" 2>&1 >/dev/null; done
echo "snapshots: $(nsnap)"
echo "repo KiB: $(size)"
echo "== política keep-latest 1 (resto a 0)"
$K policy set --global --keep-latest=1 --keep-hourly=0 --keep-daily=0 --keep-weekly=0 --keep-monthly=0 --keep-annual=0 2>&1 | tail -1
echo "== snapshot expire --delete sin indicar ruta"
$K snapshot expire --delete 2>&1 | tail -3; echo "exit=${PIPESTATUS[0]}"
echo "snapshots: $(nsnap)"
echo "== snapshot expire --delete con la ruta"
$K snapshot expire "$W/src" --delete 2>&1 | tail -3; echo "exit=${PIPESTATUS[0]}"
echo "snapshots: $(nsnap)"
echo "repo KiB tras expire: $(size)"
echo "== maintenance run --full (seguridad por defecto)"
$K maintenance run --full 2>&1 | tail -3; echo "exit=${PIPESTATUS[0]}"
echo "repo KiB tras maintenance --full: $(size)"
echo "== maintenance run --full --safety=none"
$K maintenance run --full --safety=none 2>&1 | tail -3; echo "exit=${PIPESTATUS[0]}"
echo "repo KiB tras maintenance --safety=none: $(size)"
echo "== dos ejecuciones más de maintenance --full --safety=none"
for i in 1 2; do $K maintenance run --full --safety=none >/dev/null 2>&1; echo "repo KiB: $(size)"; done
echo "== contenido vivo según kopia frente a ficheros de datos en disco"
$K content stats 2>&1 | grep -E "^(Count|Total Bytes)"
echo "ficheros de datos (p/): $(find "$W/repo/p" -type f | wc -l), bytes: $(find "$W/repo/p" -type f -printf '%s\n' | awk '{s+=$1} END{print s}')"
echo "== registro de maintenance (primera línea de cada tarea relevante)"
$K maintenance info 2>&1 | grep -A1 -E "^  (snapshot-gc|full-rewrite-contents|full-delete-blobs):" | grep -vE "^--"
echo "== verify tras la limpieza"
$K snapshot verify --verify-files-percent=100 2>&1 | tail -2; echo "exit=${PIPESTATUS[0]}"