ILogStructuredStore.h
// ILogStructuredStore.h
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2006
#ifndef Ceda_cxLss_ILogStructuredStore_H
#define Ceda_cxLss_ILogStructuredStore_H
#include "cxLss.h"
#include "Ceda/cxUtils/xchar.h"
#include "Ceda/cxUtils/StreamInterfaces.h"
#include "Ceda/cxUtils/OpenMode.h"
#include "Seid.h"
/*
This header represents the public interface to the LSS
The LSS (Log Structured Store) is a persistent store for arbitrary sized binary objects, referred
to as serial elements.
See http://www.cedanet.com.au/ceda/libs/cxLss/
*/
namespace ceda
{
struct LssSettings;
class xostream;
class Segment;
template<typename T> class xvector;
///////////////////////////////////////////////////////////////////////////////////////////////////
// ELssEvictionPolicy
/*
An eviction policy is recorded in thread local storage for each thread, such that when a thread
accesses a segment there is either a tendency to evict the segment on an LRU basis or else an
MRU basis.
By default a thread has an LRU eviction policy.
*/
enum ELssEvictionPolicy
{
LSS_LRU_EVICTION,
LSS_MRU_EVICTION
};
cxLss_API ELssEvictionPolicy GetThreadLssEvictionPolicy();
cxLss_API void SetThreadLssEvictionPolicy(ELssEvictionPolicy p);
// Convenient class typically used on the frame to set an eviction policy in a lexical scope,
// reinstating the previously policy as the thread falls out of scope.
class LssEvictionPolicySetter
{
public:
LssEvictionPolicySetter(ELssEvictionPolicy p) :
prev_(GetThreadLssEvictionPolicy())
{
SetThreadLssEvictionPolicy(p);
}
~LssEvictionPolicySetter()
{
// Reinstate previous mode
SetThreadLssEvictionPolicy(prev_);
}
private:
ELssEvictionPolicy prev_;
};
///////////////////////////////////////////////////////////////////////////////////////////////////
// LssDumpSettings
struct LssDumpSettings
{
LssDumpSettings() :
showRecoveryLog(true),
showRootBlockInfo(true),
showSeidAllocInfo(true),
showSUT(true),
showFlushUnits(true),
showSegments(true),
showLivePackets(true),
showDeadPackets(true),
showStats(true),
showCRC(true)
{
}
bool showRecoveryLog;
bool showRootBlockInfo;
bool showSeidAllocInfo;
bool showSUT;
bool showFlushUnits;
bool showSegments;
bool showLivePackets;
bool showDeadPackets;
bool showStats;
bool showCRC;
};
///////////////////////////////////////////////////////////////////////////////////////////////////
// LssStats
// Records summary statistics over the entire store
struct LssStats
{
int numCheckPoints; // Total number of check points that have been performed
int numRecoveries; // Number of times that the store has performed a recovery
int64 numTransactions; // Total number of transactions
int segmentSize; // Size of each segment in bytes
int64 fileSize; // The current size of the LSS file in bytes
int64 utilisation; // The total size in bytes taken up by all live packets in the store
int numLiveHeadPackets;
int numLiveOverflowPackets;
int numLiveRPMPackets;
int numObsoletePackets;
};
cxLss_API void WriteLssStats(xostream& os, const LssStats& stats);
///////////////////////////////////////////////////////////////////////////////////////////////////
// A reserved Seid which is allocated to the root serial element in the LSS
#if CEDA_LSS_USE_CONTIGUOUS_SEID_SPACE
const Seid ROOT_SEID(0x00000000, 0x00000001); // i.e. 00000001.00000000
#else
const Seid ROOT_SEID(0x01010101, 0x01010101);
#endif
// todo: ensure this is 32bit on all platforms
typedef int MSSN;
#if CEDA_LSS_USE_CONTIGUOUS_SEID_SPACE
const Seid BEGIN_RECURSE_SEID_MAP(0xffffffffffffffff);
#else
const Seid BEGIN_RECURSE_SEID_MAP(0x0000000000000000);
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////
struct ReadOnlyBuffer
{
const octet_t* buffer;
ssize_t size;
};
// Provides access to a serial element recorded as a contiguous buffer in memory.
struct IContiguousSerialElement
{
// A contiguous serial element must be explicitly closed, even if exceptions have been
// thrown by the LSS.
// It is an error to close the LSS before the closing all the contiguous serial elements
// that have been opened for reading.
virtual void Close() = 0;
virtual ReadOnlyBuffer GetBuffer() const = 0;
};
///////////////////////////////////////////////////////////////////////////////////////////////////
// ILssTransaction
struct ILssTransaction
{
// A transaction *must* be explicitly closed, even if exceptions have been thrown by the LSS
// Must only be called by the thread that originally opened this transaction
// It is an error to close the transaction before the current serial element being written is
// closed.
// If FlushWhenClose() has previously been called on this transaction then Close() will
// synchronously flush this and all previous transactions on the LSS.
virtual void Close() = 0;
// Puts this transaction into a mode where it will synchronously flush all data written to the
// LSS when this transaction is closed.
// Must only be called by the thread that originally opened this transaction
// The subsequent Close() will only return after the transaction (and all previous
// transactions) have been written to disk - at least according to the Win32 calls. Note that
// the LSS file is opened with "no write through cache". Despite this hard-disks that have
// their local cache enabled may defeat the assumption that the data is made durable. This
// could be a problem for a multi-phase commit protocol (for example).
virtual void FlushWhenClose() = 0;
// Returns a stream to be used to write the serial element with the given Seid. If the
// serial element already exists then the previous rendition will be replaced by a new one.
// The returned stream must be closed after it is used. Furthermore, it must be closed before
// the next call to WriteSerialElement(), DeleteSerialElement(), DeleteSeidSpace() or
// Close() on this transaction.
// Never returns nullptr.
// Must only be called by the thread that originally opened this transaction
// It is an error to call this function on a serial element that is currently opened for
// reading
virtual ICloseableOutputStream* WriteSerialElement(Seid seid) = 0;
// Permanently delete the serial element with the given Seid
// Must only be called by the thread that originally opened this transaction
// It is an error to delete a serial element that is currently opened for reading or writing
// Returns false if there is no serial element with the given Seid.
virtual bool DeleteSerialElement(Seid seid) = 0;
// Delete the Seid space associated with the given SeidHigh. The Seid space must be empty -
// i.e. by calling DeleteSerialElement() as required to delete all serial elements in the Seid
// space.
// Must only be called by the thread that originally opened this transaction
virtual void DeleteSeidSpace(SeidHigh seidHigh) = 0;
};
///////////////////////////////////////////////////////////////////////////////////////////////////
// ILogStructuredStore
/*
Any of the methods in the interface can throw either of the following types of exception
CorruptLSSException
Indicates a serious error with the store. In theory this should never happen unless there
has been file corruption.
FileException
Indicates a low level file I/O error.
*/
struct ILogStructuredStore
{
// The LSS *must* be explicitly closed, even if exceptions have been thrown by the LSS
// It is an error to close the LSS while reading a serial element, or a transaction is open
virtual void Close() = 0;
// Set the maximum number of segments in the segment cache. Note that the one segment cache
// serves as both a read and write cache. n must be at least 4.
virtual void SetMaxNumSegmentsInCache(int n) = 0;
// Returns the number of times the store has not been gracefully shut down over its entire life
virtual MSSN GetMissingShutdownSeqNum() const = 0;
///////////////////////////// Serial Element Ids (Seids) //////////////////////////////////////
// Create a new Seid space. This is the upper 32 bits of the Seids shared by a group of
// related serial elements that should be clustered together on disk.
// This function is threadsafe - i.e. multiple threads can safely call this function
// Never returns 0.
virtual SeidHigh CreateSeidSpace() = 0;
// Allocate a new, unused Seid within the Seid space associated with the given SeidHigh.
// This function is threadsafe - i.e. multiple threads can safely call this function
// Never returns a null Seid.
virtual Seid AllocateSeid(SeidHigh seidHigh) = 0;
// Ensures the seids in { Seid(low,high) | high == seid.high_ && low <= seid.low_ } are reserved.
// Returns false if that range of seids was already reserved.
virtual bool ReserveSeid(Seid seid) = 0;
// Returns the next available SeidLow for the given SeidHigh, without actually performing an
// allocation. Not compatible with using AllocateAffiliateSeid().
virtual SeidLow PeekNextSeidLow(SeidHigh seidHigh) = 0;
/*
The Seid passed by reference serves as both an in and out parameter to the function. This
function allocates a fresh Seid (the out-parameter) that is "affiliated" with an existing
Seid (passed as the in-parameter).
Consider that a new Seid needs to be allocated, and the new serial element should be
clustered with some other existing serial element, called the "affiliate". Eg the affiliate
may be a parent node in a tree of nodes. AllocateAffiliateSeid() does a good job of
allocating Seids no matter the order in which nodes are added to the tree.
This function is threadsafe - i.e. multiple threads can safely call this function
*/
virtual bool AllocateAffiliateSeid(Seid& seid) = 0;
// Retrieve all the Seids (actually only the low 32 bit part of each Seid) in the Seid space
// associated with the given SeidHigh. seidHigh must not be zero.
virtual void GetSeidsInSeidSpace(xvector<SeidLow>& seidLows, SeidHigh seidHigh) const = 0;
///////////////////////////// Read serial elements ////////////////////////////////////////////
// Does a serial element with the given Seid exist?
// The given Seid must not be null
// This function is threadsafe - i.e. multiple threads can safely call this function
virtual bool SerialElementExists(Seid seid) const = 0;
/*
Provides a stream for reading the serial element with the given Seid.
The given Seid must not be null
The returned stream must be closed after it is used (including when exceptions are thrown
by the LSS).
Returns nullptr if no serial element exists with the given Seid
It is an error to call this function on a serial element that is currently opened for
writing (within a transaction), or being deleted using a call to DeleteSerialElement().
Shared reading of serial elements is supported. I.e. any number of threads can independently
(and concurrently) read the same serial element, assuming each such thread has made an
independent call to ReadSerialElement() - i.e. they don't try to share a returned
ICloseableInputStream.
*/
virtual ICloseableInputStream* ReadSerialElement(Seid seid) const = 0;
/*
Provides an alternative to ReadSerialElement() for reading a serial element as a contiguous
block of memory. Obviously this function shouldn't be called for very large serial elements
that don't fit in physical memory and therefore would result in page faulting.
The given Seid must not be null
The returned IContiguousSerialElement must be closed after it is used (including when
exceptions are thrown by the LSS).
Returns nullptr if no serial element exists with the given Seid
It is an error to call this function on a serial element that is currently opened for
writing (within a transaction), or being deleted using a call to DeleteSerialElement().
Shared reading of serial elements is supported. I.e. any number of threads can independently
(and concurrently) read the same serial element
*/
virtual IContiguousSerialElement* ReadContiguousSerialElement(Seid seid) const = 0;
///////////////////////////// Transactions ////////////////////////////////////////////////////
/*
All changes (i.e. mutative work) done on an LSS (apart from Seid allocations) must be done by
a thread that has opened a transaction.
A transaction is intended for a single thread. Only the thread that called
OpenTransaction() on the LSS is permitted to call the methods on the returned
ILssTransaction
It is an error to close the LSS while there is an open transaction.
The LSS internally uses a mutex to ensure that only one thread opens a transaction at a
time.
There is no concept of aborting or rolling back a transaction.
*/
virtual ILssTransaction* OpenTransaction() = 0;
///////////////////////////////// Diagnostics support /////////////////////////////////////////
virtual void GetStats(LssStats& stats) const = 0;
// Write information about all the segments, flush units and packets in the entire store to the
// given output stream. This function should only be called on relatively small stores because
// it will write many mega bytes of text for a large store.
virtual void DumpLSS(LssStats& stats, xostream& os, const LssDumpSettings& ds) const = 0;
/*
Can be used to recurse through all the Seids in the store. This is based on the fact that
Seids form an 8 level hierarchical map.
RecurseSeidMap() should first be called with a seid equal to BEGIN_RECURSE_SEID_MAP. This
will return the children at the top level. Up to 256 children may be returned. A child
seid may then be passed in again to a call to RecurseSeidMap(), to recurse down through the
Seid map. After doing this 4 times, a SeidHigh will be obtained. After doing this an extra
4 times the Seids of serial elements will be obtained. ReadSerialElement() may then call
called to access their serialised state.
enableOverflowPackets determines whether seids of overflow packets will be returned in the
iteration of the seids. It is usually appropriate to call with enableOverflowPackets = false
Returns false if the given Seid is invalid
*/
virtual bool RecurseSeidMap(
xvector<Seid>& children,
Seid seid = BEGIN_RECURSE_SEID_MAP,
bool enableOverflowPackets = false) const = 0;
// For the given Seid, writes diagnostic information to the given stream. This includes the
// total size of the packet in bytes.
virtual bool WriteInfoOnNodeForGivenSeid(Seid seid, xostream& os) const = 0;
};
///////////////////////////////////////////////////////////////////////////////////////////////////
/*
Create or open a Log Structured Store, using the given path
Note that the underlying file is opened with excusive read/write access. It is not possible for
another process (say) to open the same LSS file.
The EOpenMode parameter provides a number of options for opening and creating LSS stores.
See Ceda/cxUtils/pub/OpenMode.h for further details. If errors occur due to a file's existence
when it's not expected or absence when it's expected will result in a FileException being thrown.
If lssPath = "memfile", then the LSS will reside in memory.
createdNew returns a flag for whether a new store was created. If the openMode = OM_CREATE_ALWAYS,
OM_DELETE_EXISTING or OM_CREATE_NEW any existing files will be deleted and createdNew will be true.
Note that if an existing store is opened and the existing segment size doesn't match the
segment size specified in the settings, then the requested segment size will be ignored.
If an error occurs then throws a FileException (see FileException.h) or CorruptLSSException.
After using the store it must be closed by calling the Close() method. The interface pointer must
not be deleted.
If deltasDirPath is not nullptr then it specifies the path to a directory in which to create delta
files.
CreateOrOpenLSS() never returns nullptr
*/
cxLss_API ILogStructuredStore* CreateOrOpenLSS(
ConstStringZ lssPath,
ConstStringZ deltasDirPath,
bool& createdNew,
EOpenMode openMode,
const LssSettings& settings);
/*
In order to validate a store, this function may be called in order to read every serial element
in the store. This may detect corruption of the store.
*/
cxLss_API void ScanAllObjectsInLss(const ILogStructuredStore* lss);
cxLss_API void WriteSeidsInLss(xostream& os, const ILogStructuredStore* lss);
/*
Calculate a 32 bit CRC across all objects in the store, processing their byte streams in an order determined
by their Seids.
*/
cxLss_API unsigned int CalcCrc32OnAllObjectsInLss(const ILogStructuredStore* lss);
/*
PROPOSAL [Not yet implemented]
// The following function merges delta-files in the given directory. The generated file represents
// the range [cpsn1, cpsn2). Various return error codes are returned.
cxLss_API void MergeLSSDeltas(ConstStringZ deltasDirPath, int cpsn1, int cpsn2);
*/
struct ApplyDeltasInfo
{
ApplyDeltasInfo() : cpsn1(0), cpsn2(0), txsn1(0), txsn2(0) {}
// The range [cpsn1,cpsn2) of delta files that were successfully applied
int cpsn1;
int cpsn2;
// The range of transaction seqence numbers that were applied
int64 txsn1;
int64 txsn2;
};
// Applies delta files within a given directory to a given level 0 LSS
// Delta files up to but not including cpsn2 are applied. If cpsn2 = -1 then all delta files will
// be applied, until an error occurs or a delta file is not found
// The following types of exceptions can be thrown
// FileException
// CorruptLSSException
// ApplyDeltaException
// If a delta file is not found then no exception is thrown. Instead the caller can determine the
// number of delta files that were applied by looking at info.cpsn2.
cxLss_API void ApplyDeltasToLSS(
const LssSettings& settings,
ConstStringZ level0Path,
ConstStringZ deltasDirPath,
ApplyDeltasInfo& info,
int cpsn2 = -1);
// Test whether two LSS's are equal
//
// To be equal they must store precisely the same set of serial elements - i.e. matching byte streams
// keyed by matching Seids.
//
// For debugging purposes, used to compare equivalence of two given stores
cxLss_API bool IsEqual(const ILogStructuredStore* lss1, const ILogStructuredStore* lss2);
cxLss_API bool IsEqualLss(
const LssSettings& settings,
ConstStringZ lssPath1, ConstStringZ deltasDirPath1,
ConstStringZ lssPath2, ConstStringZ deltasDirPath2);
/*
Reads all serial elements in 'lssSrc' and writes them to 'txn'.
Can be used to make a copy of an LSS by reading all serial elements in one store and copying
them into another store. This can be useful for validation, backup, clustering and compaction
purposes.
If the target LSS already has serial elements with the same Seids then the serialised state is
rewritten.
May throw:
FileException
CorruptLSSException
*/
cxLss_API void LssCopy(ILssTransaction* txn, const ILogStructuredStore* lssSrc);
cxLss_API void LssCopy(const LssSettings& settings, ConstStringZ lssPathSrc, ConstStringZ lssPathTgt);
} // namespace ceda
#endif // include guard