volume.scrub does not report a damaged header (cookie/id) on a live needle, while reads of it return 404
Describe the bug
If the first 12 bytes of a live needle (cookie + id) are damaged on disk, none of the volume.scrub modes (index, local, full, reads) reports the volume. Yet every read of that needle on that replica returns 404.
In a replicated cluster, reads fall back to another replica, so nothing fails visibly. The volume silently has one healthy replica fewer, and every later scrub still reports it clean. On a replicated cluster, volume.check.disk did not report it either.
With only the 8 id bytes damaged, reads still return 200, since the cookie still matches, and scrub is still silent.
Likely cause
scrubVolumeData (volume_checking.go#L69-L107) reads each live needle with n.ReadData. That validates the data CRC, which does not cover the header, but the header id is never compared with the index key. On master, #11396 added that comparison for deleted needles only (volume_checking.go#L95). Comparing n.Id with the index key for live needles too would catch any header damage that touches the id. The cookie cannot be checked from the index alone.
System Setup
chrislusf/seaweedfs:4.47—weed version:version 30GB 4.47 c50733600 linux arm64weed server -dir=/data -volume.max=5in a single container (Docker 29.2, linux/arm64)
Steps to reproduce
#!/usr/bin/env bash
# SeaweedFS 4.47: volume.scrub does not report a live needle whose header (cookie + id) is damaged.
IMG=${IMG:-chrislusf/seaweedfs:4.47}
docker run -d --name sw "$IMG" server -dir=/data -volume.max=5 >/dev/null
IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' sw)
x() { docker exec sw sh -c "$1"; }
until x "wget -qO- http://$IP:8080/status" >/dev/null 2>&1; do sleep 1; done; sleep 3
# byte offset of a needle, read from the volume's .idx (key 8B | offset 4B, in 8-byte units | size 4B)
offset_of() {
local vid=${1%%,*} rest=${1#*,}; local key=$((16#${rest:0:${#rest}-8}))
x "od -An -tx1 -v -w16 /data/$vid.idx" | tr -d ' ' | while read -r l; do
[ ${#l} -eq 32 ] && [ $((16#${l:0:16})) -eq $key ] && echo $((16#${l:16:8} * 8))
done | tail -1
}
upload() { x "weed upload -master=localhost:9333 $1" | sed -n 's/.*"fid":"\([^"]*\)".*/\1/p'; }
x 'head -c 300000 /dev/urandom > /tmp/f'
fid=$(upload /tmp/f); vid=${fid%%,*}; off=$(offset_of "$fid")
echo "== fid $fid, needle at byte $off of volume $vid"
x "wget -S -O /dev/null http://$IP:8080/$fid 2>&1 | grep HTTP/"
# the first 12 bytes of the needle: cookie (4) + id (8)
x "dd if=/dev/urandom of=/data/$vid.dat bs=1 count=12 seek=$off conv=notrunc 2>/dev/null"
for mode in index local full reads; do
echo "== volume.scrub -mode $mode"
x "printf 'lock\nvolume.scrub -volumeId $vid -node $IP:8080 -mode $mode\nunlock\n' | weed shell -master=localhost:9333 2>&1 | grep -E 'Scrubbed|failures|Affected'"
done
x "wget -S -O /dev/null http://$IP:8080/$fid 2>&1 | grep HTTP/"
docker rm -f -v sw >/dev/nullOutput on 4.47:
== fid 3,01f76a1927, needle at byte 8 of volume 3
HTTP/1.1 200 OK
== volume.scrub -mode index
Scrubbed 1 files and 1 volumes on 1 nodes
== volume.scrub -mode local
Scrubbed 1 files and 1 volumes on 1 nodes
== volume.scrub -mode full
Scrubbed 1 files and 1 volumes on 1 nodes
== volume.scrub -mode reads
Scrubbed 1 files and 1 volumes on 1 nodes
HTTP/1.1 404 Not Found
wget: server returned error: HTTP/1.1 404 Not FoundExpected behavior
At least volume.scrub -mode full reports the volume: the needle at the indexed offset does not carry the indexed id.
Source: seaweedfs/seaweedfs