Repository: cmu-db/noisepage

Issue #510: Reduce time spent on visibility check in indexes
Created: 2019-08-14T19:23:56Z
Background
@mbutrovich has identified that a large amount of time during the TPC-C benchmark is spent on visibility checks on tupleslots during index operations.  The current method for the visibility check involves reading both two different sections of the block (allocation bitmap and the version pointer) and then unconditionally traversing the version chain as you would for materialization.
Proposed Solution
Steal the low order bits of the version pointer to store visibility hints.  Specifically, our undo records are 64-bit aligned which gives us three low order bits that do not currently carry information.  We could use these bits to track allocation, "insert in version chain", and deleted.  This would allow a visibility check to read atomically read the version pointer and current visibility state together, and in the common case (allocated, insert not in the version chain, and not deleted) return true.
Details:
Flag definitions:

Unallocated: Indicates whether the spot is unallocated (1) or allocated (0).  This semantic makes the visibility check cleaner.
Insert in chain: Indicates whether the existing version chain ends in an insert (1) or the insert is no longer visible (0) [definition means this flag is cleared when it is safe to unlink but before that has happened].
Deleted:  Indicates whether this tuple is logically deleted (1) or still exists (0).  A deletion will set this when it first stages the delete.

SqlTable behavior

On initial allocation of a block, this solution would require scanning down the version pointer column and initializing it to set the unallocated (incurs some cost on inserts, existing allocated bitmap would be removed).
On an insert, the atomic swap of the undo record would include clearing the "unallocated" bit and setting the "insert in chain" bit.
On unlink of an insert record, the GC would first clear the "insert in chain" bit through repeated CAS until successful and then proceed with the normal unlink process.
On a delete, the atomic swap adding the delete record would include setting the "deleted" bit.
On unlink of a deletion, the GC would ensure the deleted bit remains set and issue a deferred action to set the "unallocated" bit.
Other operations would remain generally unmodified other than requiring the lowest three bits to be masked out before dereferencing the pointer.

Index Visibility Check

Read the version information: if low order three bits equals 0, return true.  (Should generally be the common case)
Apply mask for the version pointer:  if the pointer is nullptr, return false. (either unallocated or deleted)
Chase the version chain.  This can potentially be optimized for early exiting, if needed.

Duplicate issue #540: Optimize visibility check by changing allocated and logical delete bitmaps
Created: 2019-09-12T15:22:03Z
I believe we currently have a dedicated bitmap for allocation, and then the version pointer's null bitmap represents logical delete. We want to change it to the version pointer's null bitmap representing allocation, and stash logical delete in a bit in the version pointer.
As I type this though, I realize that this mean's we can't use AccessForceNotNull or whatever to flip bits in the null bitmap. Null bitmaps are generally not thread safe because we assume we have the write lock, while the allocation bitmap is thread safe. We may need safeties put in place to prevent unsafe access to the version pointer's null bitmap if we're gonna use it as an allocation bitmap.

Closing comment (2019-09-12T22:28:32Z): Is this a duplicate of #510?