Browse Source

Fix uncleanable size=0 orphans with volume.fsck -forcePurging (#7783)

This is a follow-up fix to PR #7332 which partially addressed the issue.

The problem is that size=0 needles are in a gray area:
- IsValid() returns false for size=0 (because size must be > 0)
- IsDeleted() returns false for size=0 (because size must be < 0 or == TombstoneFileSize)

PR #7332 only fixed 2 places, but several other places still had the same bug:

1. needle_map_memory.go:doLoading - line 43 still used oldSize.IsValid()
2. needle_map_memory.go:DoOffsetLoading - used during vacuum and incremental loading
3. needle_map_leveldb.go:generateLevelDbFile - used when generating LevelDB needle maps
4. needle_map_leveldb.go:DoOffsetLoading - used during incremental loading for LevelDB
5. needle_map/compact_map.go:delete - couldn't delete size=0 entries because:
   - The condition 'size > 0' failed for size=0
   - Even if it passed, negating 0 gives 0 (not marking as deleted)

Changes:
- Changed size.IsValid() to !size.IsDeleted() in doLoading and DoOffsetLoading functions
- Fixed compact_map delete to use TombstoneFileSize for size=0 entries

Fixes #7293
pull/7784/head
Chris Lu 4 days ago
committed by GitHub
parent
commit
7920ffa98c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 7
      weed/storage/needle_map/compact_map.go
  2. 6
      weed/storage/needle_map_leveldb.go
  3. 6
      weed/storage/needle_map_memory.go
  4. 9
      weed/storage/types/needle_types.go

7
weed/storage/needle_map/compact_map.go

@ -175,9 +175,14 @@ func (cs *CompactMapSegment) get(key types.NeedleId) (*CompactNeedleValue, bool)
// delete deletes a map entry by key. Returns the entries' previous Size, if available. // delete deletes a map entry by key. Returns the entries' previous Size, if available.
func (cs *CompactMapSegment) delete(key types.NeedleId) types.Size { func (cs *CompactMapSegment) delete(key types.NeedleId) types.Size {
if i, found := cs.bsearchKey(key); found { if i, found := cs.bsearchKey(key); found {
if cs.list[i].size > 0 && cs.list[i].size.IsValid() {
if !cs.list[i].size.IsDeleted() {
ret := cs.list[i].size ret := cs.list[i].size
if cs.list[i].size == 0 {
// size=0 needles can't be marked deleted by negating, use tombstone
cs.list[i].size = types.TombstoneFileSize
} else {
cs.list[i].size = -cs.list[i].size cs.list[i].size = -cs.list[i].size
}
return ret return ret
} }
} }

6
weed/storage/needle_map_leveldb.go

@ -122,7 +122,7 @@ func generateLevelDbFile(dbFileName string, indexFile *os.File) error {
glog.V(0).Infof("generateLevelDbFile %s, watermark %d, num of entries:%d", dbFileName, watermark, (uint64(stat.Size())-watermark*NeedleMapEntrySize)/NeedleMapEntrySize) glog.V(0).Infof("generateLevelDbFile %s, watermark %d, num of entries:%d", dbFileName, watermark, (uint64(stat.Size())-watermark*NeedleMapEntrySize)/NeedleMapEntrySize)
} }
return idx.WalkIndexFile(indexFile, watermark, func(key NeedleId, offset Offset, size Size) error { return idx.WalkIndexFile(indexFile, watermark, func(key NeedleId, offset Offset, size Size) error {
if !offset.IsZero() && size.IsValid() {
if !offset.IsZero() && !size.IsDeleted() {
levelDbWrite(db, key, offset, size, false, 0) levelDbWrite(db, key, offset, size, false, 0)
} else { } else {
levelDbDelete(db, key) levelDbDelete(db, key)
@ -380,10 +380,10 @@ func (m *LevelDbNeedleMap) DoOffsetLoading(v *Volume, indexFile *os.File, startF
// needle is found // needle is found
oldSize := BytesToSize(data[OffsetSize : OffsetSize+SizeSize]) oldSize := BytesToSize(data[OffsetSize : OffsetSize+SizeSize])
oldOffset := BytesToOffset(data[0:OffsetSize]) oldOffset := BytesToOffset(data[0:OffsetSize])
if !offset.IsZero() && size.IsValid() {
if !offset.IsZero() && !size.IsDeleted() {
// updated needle // updated needle
m.mapMetric.FileByteCounter += uint64(size) m.mapMetric.FileByteCounter += uint64(size)
if !oldOffset.IsZero() && oldSize.IsValid() {
if !oldOffset.IsZero() && !oldSize.IsDeleted() {
m.mapMetric.DeletionCounter++ m.mapMetric.DeletionCounter++
m.mapMetric.DeletionByteCounter += uint64(oldSize) m.mapMetric.DeletionByteCounter += uint64(oldSize)
} }

6
weed/storage/needle_map_memory.go

@ -40,7 +40,7 @@ func doLoading(file *os.File, nm *NeedleMap) (*NeedleMap, error) {
nm.FileCounter++ nm.FileCounter++
nm.FileByteCounter = nm.FileByteCounter + uint64(size) nm.FileByteCounter = nm.FileByteCounter + uint64(size)
oldOffset, oldSize := nm.m.Set(NeedleId(key), offset, size) oldOffset, oldSize := nm.m.Set(NeedleId(key), offset, size)
if !oldOffset.IsZero() && oldSize.IsValid() {
if !oldOffset.IsZero() && !oldSize.IsDeleted() {
nm.DeletionCounter++ nm.DeletionCounter++
nm.DeletionByteCounter = nm.DeletionByteCounter + uint64(oldSize) nm.DeletionByteCounter = nm.DeletionByteCounter + uint64(oldSize)
} }
@ -112,10 +112,10 @@ func (nm *NeedleMap) DoOffsetLoading(v *Volume, indexFile *os.File, startFrom ui
e := idx.WalkIndexFile(indexFile, startFrom, func(key NeedleId, offset Offset, size Size) error { e := idx.WalkIndexFile(indexFile, startFrom, func(key NeedleId, offset Offset, size Size) error {
nm.MaybeSetMaxFileKey(key) nm.MaybeSetMaxFileKey(key)
nm.FileCounter++ nm.FileCounter++
if !offset.IsZero() && size.IsValid() {
if !offset.IsZero() && !size.IsDeleted() {
nm.FileByteCounter = nm.FileByteCounter + uint64(size) nm.FileByteCounter = nm.FileByteCounter + uint64(size)
oldOffset, oldSize := nm.m.Set(NeedleId(key), offset, size) oldOffset, oldSize := nm.m.Set(NeedleId(key), offset, size)
if !oldOffset.IsZero() && oldSize.IsValid() {
if !oldOffset.IsZero() && !oldSize.IsDeleted() {
nm.DeletionCounter++ nm.DeletionCounter++
nm.DeletionByteCounter = nm.DeletionByteCounter + uint64(oldSize) nm.DeletionByteCounter = nm.DeletionByteCounter + uint64(oldSize)
} }

9
weed/storage/types/needle_types.go

@ -14,9 +14,18 @@ type Offset struct {
type Size int32 type Size int32
// IsDeleted checks if the needle entry has been marked as deleted (tombstoned).
// Use this when checking if an entry should exist in the needle map.
// Returns true for negative sizes or TombstoneFileSize.
// Note: size=0 is NOT considered deleted - it's an anomalous but active entry.
func (s Size) IsDeleted() bool { func (s Size) IsDeleted() bool {
return s < 0 || s == TombstoneFileSize return s < 0 || s == TombstoneFileSize
} }
// IsValid checks if the needle has actual readable data.
// Use this when checking if needle data can be read or when counting data bytes.
// Returns true only for positive sizes (size > 0).
// Note: size=0 returns false - such needles exist in the map but have no readable data.
func (s Size) IsValid() bool { func (s Size) IsValid() bool {
return s > 0 && s != TombstoneFileSize return s > 0 && s != TombstoneFileSize
} }

Loading…
Cancel
Save