68 LSS API
This chapter defines the Version 2 LSS API. Value types are structs or aliases, operations which do not belong to an object are free functions, and runtime polymorphism is expressed using pure abstract interfaces. The API does not expose implementation classes.
An LSS is a persistent heap of variable-length serial elements organised in a hierarchical structure.
The logical data hierarchy is:
Lss
Partition
Space
SerialElement
Lssis associated with one LSS file and its lifetime.-
Partitionis the transaction, MVCC snapshot and mutation-concurrency boundary. At most one mutative transaction is active on a partition, while different partitions may mutate concurrently. -
Spaceis a 32-bit Seid address space containing serial elements. -
SerialElementis a variable-length binary value identified by aSeid(pronounced “see-id”), a contraction of serial element identifier.
An LSS can have up to approximately four billion partitions, each partition can have up to approximately four billion spaces, and each space can have up to approximately four billion serial elements.
Identifiers
using PartitionId = uint32;
using SpaceId = uint32;
using Seid = uint32;
inline constexpr PartitionId PRIMARY_PARTITION_ID = 1;
inline constexpr Seid NULL_SEID = 0;
struct PartitionSeid
{
SpaceId spaceId;
Seid seid;
};
An Partition is logically a set of Spaces indexed by
32-bit SpaceId. A Space is logically a set of serial
elements indexed by 32-bit Seid. Seid zero is null and does not
identify a serial element.
Serial-element input
struct ReadOnlyBuffer
{
const octet_t* data;
std::size_t size;
};
struct IContiguousSerialElement
{
virtual ReadOnlyBuffer GetBuffer() const = 0;
virtual void Close() = 0;
};
An IContiguousSerialElement pins the storage containing its buffer
until Close() is called. It is not deleted by the caller.
Views
struct ISpaceView
{
virtual SpaceId GetSpaceId() const = 0;
virtual bool SerialElementExists(Seid seid) const = 0;
virtual ICloseableInputStream* ReadSerialElement(Seid seid) const = 0;
virtual IContiguousSerialElement* ReadContiguousSerialElement(Seid seid) const = 0;
};
struct IPartitionView
{
virtual PartitionId GetPartitionId() const = 0;
virtual const ISpaceView* FindSpace(SpaceId spaceId) const = 0;
virtual void Close() = 0;
};
FindSpace() returns null when the selected partition snapshot
does not contain the given Space. A returned Space view is owned by the partition view and remains
valid until that partition view is closed.
An IPartitionView pins one immutable MVCC snapshot
of the partition's SpaceDirectory. That snapshot entails the RPM
snapshot of every Space it contains. Every read through the transaction therefore observes the
same completed partition transaction boundary.
Every input stream and contiguous serial element obtained from a partition view must be closed before the view is closed. A partition view may be used concurrently by multiple reader threads, but an individual returned stream is not shared between threads.
Mutative transactions
struct ISpaceTransaction : ISpaceView
{
virtual Seid AllocateSeids(uint32 count) = 0;
virtual ICloseableOutputStream* WriteSerialElement(Seid seid) = 0;
virtual bool DeleteSerialElement(Seid seid) = 0;
};
struct IPartitionTransaction
{
virtual PartitionId GetPartitionId() const = 0;
virtual ISpaceTransaction* FindSpace(SpaceId spaceId) = 0;
virtual ISpaceTransaction* CreateSpace() = 0;
virtual void FlushWhenClose() = 0;
virtual void Close() = 0;
virtual IPartitionView* CloseAndPublishSnapshot() = 0;
};
AllocateSeids(count) returns the first Seid in a newly allocated
contiguous range [first, first + count). The count may be zero. In
that case no Seids are allocated and the function returns the next Seid, allowing the caller to
determine how many Seids remain available in the Space.
An IPartitionTransaction holds the mutex of its containing
Partition. At most one such transaction is open on a partition.
Transactions on different partitions are independent and may be open concurrently. A transaction
never spans partitions.
The Space transaction views are owned by the partition transaction. They become invalid when the partition transaction is closed. Seid allocation and all serial-element mutation occur through these views. A returned output stream must be closed before another mutative operation is performed through the transaction.
There is no transaction abort. Close() completes the transaction
without publishing a reader snapshot. CloseAndPublishSnapshot()
completes the transaction, publishes its immutable
SpaceDirectory snapshot and returns a partition view pinned to
that exact snapshot. Both functions consume the mutative transaction.
Calling FlushWhenClose() requests that closing the transaction wait
until that transaction and preceding transactions on the same partition have been flushed.
Partitions and stores
struct IPartition
{
virtual PartitionId GetPartitionId() const = 0;
virtual IPartitionView* OpenView() const = 0;
virtual IPartitionTransaction* OpenTransaction() = 0;
};
struct ILss
{
virtual ~ILss() = default;
virtual IPartition* GetPartition(PartitionId partitionId) = 0;
virtual void Close() = 0;
};
struct LssSettings;
struct IRAS;
std::unique_ptr<ILss> CreateOrOpenLss(
std::unique_ptr<IRAS> ras,
const LssSettings& settings);
Partition ID zero is never used. PRIMARY_PARTITION_ID is one.
GetPartition() returns null if the LSS does not contain the given
partition.
The initial implementation contains only the primary partition and does not require a
PartitionDirectory. The API for creating and deleting additional
partitions will be added when the PartitionDirectory design is
selected.
The store owns the RAS passed to CreateOrOpenLss(). The returned
unique_ptr owns the LSS interface. All transactions, Space
views, streams and contiguous serial elements must be closed before the LSS is closed.