Summary
MergeOperator::FullMergeV3 can produce a new plain value, a new wide-column value, or return an existing operand — but it cannot express "this key should be deleted." This makes the abstraction incomplete: a read-modify-write primitive should be able to conclude that a key no longer needs to exist.
Motivation
The limitation is already being worked around in the codebase. The Cassandra integration uses a two-phase pattern: CassandraValueMergeOperator resolves columns during merge, then CassandraCompactionFilter::FilterV2 checks if the result is empty and returns Decision::kRemove (cassandra_compaction_filter.cc:51-52 (https://github.com/facebook/rocksdb/blob/main/utilities/cassandra/cassandra_compaction_filter.cc#L51-L52)). This works but has drawbacks:
- The key remains visible with a sentinel/empty value between compactions — reads see stale data until the CompactionFilter runs
- The deletion logic is split across two unrelated abstractions (MergeOperator + CompactionFilter)
- It only takes effect during compaction, not at read time
There is also a related FIXME in merge_helper.cc:511 (https://github.com/facebook/rocksdb/blob/main/db/merge_helper.cc#L511) noting that kRemove is not properly checked during merge operand processing, suggesting the merge/deletion interaction is known to be incomplete.
Use cases
- Conditional mutations: delete or update a key only if the current value meets a condition, resolved at read/compaction time with zero write-path overhead (instead of using transactions/optimistic transaction)
- Counter auto-delete at zero: a merge-based counter that deletes the key when it reaches zero
- Simplifying the Cassandra pattern: collapse the MergeOperator + CompactionFilter two-phase workaround into just MergeOperator
Summary
MergeOperator::FullMergeV3 can produce a new plain value, a new wide-column value, or return an existing operand — but it cannot express "this key should be deleted." This makes the abstraction incomplete: a read-modify-write primitive should be able to conclude that a key no longer needs to exist.
Motivation
The limitation is already being worked around in the codebase. The Cassandra integration uses a two-phase pattern: CassandraValueMergeOperator resolves columns during merge, then CassandraCompactionFilter::FilterV2 checks if the result is empty and returns Decision::kRemove (cassandra_compaction_filter.cc:51-52 (https://github.com/facebook/rocksdb/blob/main/utilities/cassandra/cassandra_compaction_filter.cc#L51-L52)). This works but has drawbacks:
There is also a related FIXME in merge_helper.cc:511 (https://github.com/facebook/rocksdb/blob/main/db/merge_helper.cc#L511) noting that kRemove is not properly checked during merge operand processing, suggesting the merge/deletion interaction is known to be incomplete.
Use cases