Skip to content

[BUG] VersionSet::get() does not check LogRecord::is_deleted — deleted keys return Some([]) instead of None #189

@ElioNeto

Description

@ElioNeto

Description

Randomized testing (test_random_ops_linearizability) found a linearizability violation: after deleting a key, get() returns Some([]) (empty value) instead of None. The tombstone record is returned as a valid value.

Evidence

assertion failed: LINEARIZABILITY VIOLATION: read of non-existent key should be None
  left: Some([])
 right: None

Root cause

VersionSet::get() (line 83 of version_set.rs) does:

if let Some(val) = table.data.get(key) {
    return Some(val.clone());  // ← val is Vec<u8> = [] for tombstones
}

It never checks whether the record represents a tombstone. The LogRecord::is_deleted flag is lost during flush because Table::build() stores only (Vec<u8>, Vec<u8>).

Impact

  • Applications cannot distinguish between "key exists with empty value" and "key was deleted"
  • Backup/restore may resurrect deleted keys
  • Compaction skips empty values correctly (line 114 of compaction.rs), but the point-read path does not

Proposed fix

Two options:

  1. Quick fix: In VersionSet::get(), if the value is empty, return None (treat empty values as tombstones). Fragile but matches compaction behavior.
  2. Proper fix: Store LogRecord in Table::data instead of Vec<u8> (see [BUG] Compaction detects tombstones by empty value instead of is_deleted flag — data loss risk #188 for related tombstone detection improvements).

Severity

High — data integrity issue: deleted keys appear to exist.

Labels

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions