{"record":{"id":"7d7919f92d1c1f73","repo":"dgraph-io/badger","slug":"invalid-table-size-in-footer-data-corrupted","errorCode":null,"errorMessage":"invalid table size in footer. Data corrupted","messagePattern":"invalid table size in footer\\. Data corrupted","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"table/table.go","lineNumber":432,"sourceCode":"\nfunc (t *Table) read(off, sz int) ([]byte, error) {\n\treturn t.Bytes(off, sz)\n}\n\nfunc (t *Table) readNoFail(off, sz int) []byte {\n\tres, err := t.read(off, sz)\n\ty.Check(err)\n\treturn res\n}\n\n// initIndex reads the index and populate the necessary table fields and returns\n// first block offset\nfunc (t *Table) initIndex() (*fb.BlockOffset, error) {\n\treadPos := t.tableSize\n\n\t// Read checksum len from the last 4 bytes.\n\tif readPos < 4 {\n\t\treturn nil, errors.New(\"invalid table size in footer. Data corrupted\")\n\t}\n\treadPos -= 4\n\tbuf := t.readNoFail(readPos, 4)\n\tchecksumLen := int(y.BytesToU32(buf))\n\t// checksumLen == 0 is legal (a zero checksum marshals to nothing), so only\n\t// reject negative lengths and lengths that don't fit in the bytes remaining\n\t// before readPos. The < 0 guard catches a uint32 value >= 2^31 wrapping to a\n\t// negative int on 32-bit platforms.\n\tif checksumLen < 0 || checksumLen > readPos {\n\t\treturn nil, errors.New(\"invalid checksum length in footer. Data corrupted\")\n\t}\n\n\t// Read checksum.\n\texpectedChk := &pb.Checksum{}\n\treadPos -= checksumLen\n\tbuf = t.readNoFail(readPos, checksumLen)\n\tif err := proto.Unmarshal(buf, expectedChk); err != nil {\n\t\treturn nil, err","sourceCodeStart":414,"sourceCodeEnd":450,"githubUrl":"https://github.com/dgraph-io/badger/blob/2a001d466f6b71a917319a1db41f99860e16e269/table/table.go#L414-L450","documentation":"initIndex reads the table footer starting from the end of the file: the last 4 bytes hold the checksum length. If the table is smaller than 4 bytes, there is no valid footer, so Badger declares the data corrupted.","triggerScenarios":"Opening a table whose t.tableSize < 4 — a truncated, empty, or zero-length SST file passed to initIndex via initBiggestAndSmallest during DB open or table ingestion.","commonSituations":"Incomplete file copy; disk crash mid-write leaving a stub file; a stray non-SST file in the directory being picked up as a table; backup restore that skipped file contents.","solutions":["Check the file size with ls/stat; remove or quarantine zero/truncated SST files","Restore the table file from a known-good backup","Verify the data directory matches the MANIFEST (drop orphan files)","Rebuild the database from an exported backup (db.Load from db.Backup output)"],"exampleFix":"// before: blindly opening every file in dir\n// (badger hits the truncated 0-byte 000001.sst)\n\n// after: verify before opening\ninfo, err := os.Stat(path)\nif err != nil || info.Size() < 4096 {\n    os.Rename(path, path+\".corrupt\") // quarantine\n}","handlingStrategy":"validation","validationCode":"fi, err := os.Stat(path)\nif err != nil || fi.Size() < 4096 { // minimal valid SST is far larger than 4 bytes\n    os.Rename(path, path+\".corrupt\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Verify copied file sizes/checksums after any directory transfer","Keep the MANIFEST in sync with the table files; drop orphans","Use filesystem-level snapshots (LVM/ZFS) rather than partial copies"],"tags":["data-corruption","footer","truncated-file","storage"],"backgroundTag":"sst-file-corruption","analyzedSha":"2a001d466f6b71a917319a1db41f99860e16e269","analyzedAt":"2026-09-05T13:00:02.264Z","contentChangedAt":"2026-09-05T13:00:02.264Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}