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
  • Lss is associated with one LSS file and its lifetime.
  • Partition is the transaction, MVCC snapshot and mutation-concurrency boundary. At most one mutative transaction is active on a partition, while different partitions may mutate concurrently.
  • Space is a 32-bit Seid address space containing serial elements.
  • SerialElement is a variable-length binary value identified by a Seid (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.