39.2 LogRecord

Log records are written to flush units within segments. Obviously it is not permissible for a log record to overflow a FlushUnit or Segment.

The physical position of a log record is specified with a LogRecordPosition. This identifies the segment using a 32 bit SegId and a 32 bit integer offset within the segment.


struct LogRecordPosition
{
    SegId segid_;
    int32 offset_;
};

Given the physical position, it is possible to efficiently seek directly to that location and read the log record.

Kinds of log records

There is an enum corresponding to the 5 kinds of log records:


enum ELogRecordType
{
    LR_PACKET,
    LR_DELETE_PACKET,
    LR_SNAPSHOT,
    LR_TIMESTAMPED_SNAPSHOT,
    LR_NEXT_SEID_HIGH
};
Record Description
LR_PACKET A packet contains a contiguous blob of binary data and is uniquely identified by a Seid.

There are two kinds of packets:

  • An RPM packet records the serialised state of a single RPM node at level 0 through to level 6. Dirty RPM nodes are serialised as RPM packet log records in bottom up order during a check point.
  • Data packets are used for recording serial elements. Serial elements can be very large (many giga bytes) therefore they may be split into a forward linked list of data packets. These are sometimes called packet chains. We call the first packet in the chain the head packet. Each packet in the chain is identified by a Seid. A data packet records the seid of the next data packet in the chain if any. The RPM records the locations of all the data packets in a chain.
LR_DELETE_PACKET Records the deletion of a single serial element with a given Seid. The Seid must identify the head packet of a packet chain.
LR_SNAPSHOT Commits previously written packet records. Used in version 1 stores, otherwise no longer used.
LR_TIMESTAMPED_SNAPSHOT Like LR_SNAPSHOT but adds both a transaction sequence number and a timestamp for when the transaction was committed. Used after version 1 stores.
LR_NEXT_SEID_HIGH Records a SeidHigh to log a change to the seid high allocator in the RPM (i.e. the member nextSeidHigh_ of RPM7).

A log record begins with a single octet which gives its type:


using LogRecordType = octet_t;

Since there are only 5 kinds of log records, we can use the low 4 bits of LogRecordType for recording the ELogRecordType and the high 4 bits provide 4 additional flags, which are used by LR_PACKET log records.


const int NEXT_SEID_PRESENT_BITPOS = 7;
const int HEAD_PACKET_BITPOS       = 6;
const int RPM_PACKET_BITPOS        = 5;
const int CLEANING_PACKET_BITPOS   = 4;

const LogRecordType NEXT_SEID_PRESENT_MASK = (LogRecordType) (1 << NEXT_SEID_PRESENT_BITPOS);
const LogRecordType HEAD_PACKET_MASK       = (LogRecordType) (1 << HEAD_PACKET_BITPOS);
const LogRecordType RPM_PACKET_MASK        = (LogRecordType) (1 << RPM_PACKET_BITPOS);
const LogRecordType CLEANING_PACKET_MASK   = (LogRecordType) (1 << CLEANING_PACKET_BITPOS);
Bit Description
NEXT_SEID_PRESENT_BITPOS Set for a data packet record which is not at the end of the packet chain. In that case the serialised packet finishes with the Seid of the next packet in the chain.
HEAD_PACKET_BITPOS Set for a data packet which is at the head of a packet chain recording a serial element (i.e. the first packet in the packet chain). For robustness, it is preferable to distinguish between packets at the head of their chain from the remaining overflow packets. This allows the LSS to detect when an attempt is made to read/write or delete an invalid Seid - i.e. that doesn't correspond to the head packet of a serial element.
RPM_PACKET_BITPOS Set for an RPM packet. Otherwise it is a data packet.
CLEANING_PACKET_BITPOS

Serialised format of log records

The serialised format of a log record is described by the following (binary) grammar


    <octet> = 8 bit unsigned integer

    <uint32> = <octet> <octet> <octet> <octet>
               (little endian binary representation of a 32 bit unsigned integer)

    <uint64> = <octet> <octet> <octet> <octet> <octet> <octet> <octet> <octet>
               (little endian binary representation of a 64 bit unsigned integer)

    <LogRecordType> = <octet>

    <SeidHigh> = <uint32>

    <seid> = <uint64>

    <next-seid> = <seid>

    <data-size> = <uint32>

    <data> = <octet>*

    <TxnSeqNumber> = <uint64>

    <HPTime> = <uint64>

    <PacketLogRecord> =
        <LogRecordType>
        <seid>
        <data-size>
        <data>
        [ <next-seid> ]

    <DeletePacketLogRecord> =
        <LogRecordType>
        <seid>

    <SnapShotLogRecord> =
        <LogRecordType>

    <TimeStampedSnapShotLogRecord> =
        <LogRecordType>
        <TxnSeqNumber>
        <HPTime>

    <NextSeidHighLogRecord> =
        <LogRecordType>
        <SeidHigh>

    <LogRecord> =
        <PacketLogRecord> |
        <DeletePacketLogRecord> |
        <SnapShotLogRecord> |
        <TimeStampedSnapShotLogRecord> |
        <NextSeidHighLogRecord>

Data alignment

Log records can appear at almost any offset position within a segment because log records are written consecutively within a flush unit and log records can be almost any size when serialised.

Therefore is not possible to use data structures for reading and writing the parameters of log records on architectures such as ARMv7 which don't support unaligned memory access for 32 bit and 64 bit values.

Code


enum ELogRecordType
{
    LR_PACKET,
    LR_DELETE_PACKET,
    LR_SNAPSHOT,
    LR_TIMESTAMPED_SNAPSHOT,
    LR_NEXT_SEID_HIGH
};

const int NEXT_SEID_PRESENT_BITPOS = 7;
const int HEAD_PACKET_BITPOS       = 6;
const int RPM_PACKET_BITPOS        = 5;
const int CLEANING_PACKET_BITPOS   = 4;

using LogRecordType = octet_t;

const LogRecordType NEXT_SEID_PRESENT_MASK = (LogRecordType) (1 << NEXT_SEID_PRESENT_BITPOS);
const LogRecordType HEAD_PACKET_MASK       = (LogRecordType) (1 << HEAD_PACKET_BITPOS);
const LogRecordType RPM_PACKET_MASK        = (LogRecordType) (1 << RPM_PACKET_BITPOS);
const LogRecordType CLEANING_PACKET_MASK   = (LogRecordType) (1 << CLEANING_PACKET_BITPOS);

// The size of a packet header is 13 octets:
//
//      LogRecordType   type        1 octet
//      Seid            seid        8 octets
//      int32           bufferSize  4 octets
const size_t PACKET_HEADER_SIZE = sizeof(LogRecordType) + sizeof(Seid) + sizeof(int32);

// Sequence number assigned to snapshot records in the log
using TxnSeqNumber = int64;
const size_t TIMESTAMPED_SNAPSHOT_LOG_RECORD_SIZE = sizeof(LogRecordType) + sizeof(TxnSeqNumber) + sizeof(HPTime);

const size_t NEXT_SEID_HIGH_LOG_RECORD_SIZE = sizeof(LogRecordType) + sizeof(SeidHigh);

const size_t DELETE_PACKET_LOG_RECORD_SIZE = sizeof(LogRecordType) + sizeof(Seid);

struct PacketInfo
{
    LogRecordType type_ = 0;           // Low two bits equals LR_PACKET
    Seid seid_;
    int32 bufferSize_ = 0;
    LogRecordPosition position_;       // Current position of the packet
    octet_t* buffer_ = nullptr;        // Points at the data of the packet (i.e. offset by PACKET_HEADER_SIZE)
    Seid nextSeid_;
    SegmentUnreserver segmentUnreserver_;      // Unreserves segid when destructs
    SegmentAccessor segmentAccessor_;          // Releases the segment when destructs
};

struct ILogRecordVisitor
{
    virtual void VisitPacketRecord(const PacketInfo& pi) = 0;
    virtual void VisitSnapshotRecord(LogRecordPosition nextPos, TxnSeqNumber txsn, HPTime timeStamp) = 0;
    virtual void VisitDeletePacketRecord(Seid seid) = 0;
    virtual void VisitNextSeidHighLogRecord(SeidHigh seidHigh) = 0;
};

void VisitLogRecordsInBuffer(octet_t* buffer, int segid, int s1, int s2, bool onlyVisitPackets, ILogRecordVisitor& visitor);

// Visit all log records within the given segment in the range [s1,s2) where s1 and s2 are integer
// offset positions into the buffer
// 'onlyVisitPackets' indicates whether the client is only interested in packet log records
void VisitLogRecordsInBuffer(Segment& segment, int s1, int s2, bool onlyVisitPackets, ILogRecordVisitor& visitor);

void VisitAllLogRecordsInSegment(Segment& segment, bool onlyVisitPackets, ILogRecordVisitor& visitor);

///////////////////////////////////////////////////////////////////////////////////////////////////

/*
RetrievePacketBufferAtGivenPosition() provides the basis for reading any given packet in the LSS.
It is suitabe for both data and RPM packets.

Typically the given LogRecordPosition will have been recorded by the RPM.

This function is fully threadsafe (any number of threads can access existing packet buffers).

The implementation calls GetSegment() on the segment cache which may block on I/O or on eviction
of a segment from the cache.  The corresponding ReleaseSegment() in performed when the PacketInfo
provided by the caller destructs (see PacketInfo::segmentAccessor_).

The segment is not reserved (in the SUT) by this function.
*/
void RetrievePacketBufferAtGivenPosition(LSS& lss, LogRecordPosition p, PacketInfo* pi);

bool RemoveChain(LSS& lss, Seid seid);