kopia snapshot verify: qué comprueba de verdad y por qué la política de retención no encoge el repositorio
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:
snapshot verifysin opciones no lee el contenido de los ficheros: lista los bloques del repositorio y procesa los objetos de los snapshots, pero su salida dice "Read 0 files". Con un bloque de datos corrupto dio exit 0.- Para leer los datos hay que pasar
--verify-files-percent. En esta prueba, con el valor 100, el fallo solo apareció después dekopia cache clear. Con la caché de contenido llena, la verificación leyó los datos de la caché y no del repositorio. No se probó con qué frecuencia ocurre esto en un uso normal, ni con porcentajes menores. - El error es una autenticación de cifrado fallida, no una "corrupción" genérica. No se probó si kopia puede reparar el bloque (no había otra copia).
- Si programas verificaciones, usa
--verify-files-percentcon un porcentaje que puedas permitirte y vacía la caché antes, o la prueba puede dar un falso "todo bien".
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
snapshot expiresin ruta no hizo nada y no avisó (exit 0). Pásale la ruta del origen o comprueba el número de snapshots después.- Probar una copia es restaurarla.
restorefalló con exit 1 ante el bloque corrupto ydiff -rdemostró que el restaurado sano era idéntico. - Un snapshot sin cambios casi no ocupa, pero tampoco aporta nada: la política de retención lo cuenta como uno más.
- El primer
snapshot createlanzó por sí mismo un mantenimiento (se vio "Finished full maintenance" en su salida). No se probó cuándo vuelve a ocurrir. - Para comparar con otra herramienta con verificación, mira restic: restaurar y comprobar y rclone check.
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]}"