Skip to main content

Buffer Layer

note

The files corresponding to this layer can be found in the Buffer directory. The code is to be written in the files StaticBuffer.cpp and BlockBuffer.cpp. The declaration for the functions can be found in the respective header files StaticBuffer.h and BlockBuffer.h.

The stub code for these files can be found here.

Layout

Whenever NITCbase needs to work on a disk block, the block has to be first fetched from the secondary memory storage (disk) to the primary memory. A large pool of memory (called buffer in the documentation) is pre-allocated and managed to hold copies of disk blocks in the primary memory. When a request involving access/update of a disk block comes from any of the higher layers, the corresponding disk block is loaded into the buffer. After performing updates, the block is committed back to the disk from the buffer.

NITCbase uses a dedicated Buffer Layer for the above functionality. All the requests involving disk blocks go through the Buffer Layer. The interface provided by the Buffer Layer gives a memory address space abstraction to the higher layers, hiding the complexities involved in the reads and writes to the actual physical disk blocks.

NITCbase has pre-allocated memory for holding 32 disk blocks in its buffer memory at a given time. Buffer Layer is responsible for maintaining the buffer memory and making replacements and writebacks as required. The disk class functions are used by the Buffer Layer to load blocks from the disk to the buffer and also to write back blocks as and when necessary. A single object of the disk class needs to be declared at the start of the session. Its purpose is to run the constructor and the destructor of the class.

Note

The Disk class constructor will create a new Run Copy of the actual disk and all disk accesses during runtime of NITCbase is done via this Run Copy. At the close of the system, the Disk class destructor will write back the Run Copy of disk to the actual disk.

NITCbase follows an Object-Oriented design for Buffer Layer. The class diagram is as shown below.



Various structures used in the buffer layer are outlined in the below diagrams.


Certain other structure definitions and functions that help access record data and metadata from the disk block are also included in the Buffer Layer. These are discussed at the end of this page (see miscellaneous section).

Block Structures

The Buffer Layer defines the following block data structures.

Each structure is designed to store a subset of the data stored in a disk block. A disk block contains 2048 bytes of data. Higher layer functions, however, instead of processing the whole block data together, typically request access to a particular set of related data in a disk block at a time. Whenever such a selective access request is made, the method in the Buffer Layer implementing the access functionality will pack the requested data into the corresponding block structure designed to store that particular type of data. Variables of these structures will be declared and used in the Cache Layer, the Block Access Layer, and the B+ Tree Layer.

HeadInfo

NITCbase maintains a 32 byte fixed-size header for every disk block. This header stores meta-information, like the type of the block, and a few block specific information, like #Attrs and #Slots. Though the header has many fields, usage of the fields depends on the type of the block. The structure HeadInfo is used to collect all the entries of the header, as shown below. The setHeader() and the getHeader() methods take a pointer to struct HeadInfo as argument.

Implementation Note

getHeader() and setHeader() methods expect the higher layers to allocate memory for the struct HeadInfo before calling them.

struct HeadInfo {
int32_t blockType;
int32_t pblock;
int32_t lblock;
int32_t rblock;
int32_t numEntries;
int32_t numAttrs;
int32_t numSlots;
unsigned char reserved[4];
};

Attribute

According to the Disk Model, a record block has slots for storing records, and each record contains a set of attributes. The Attribute block data structure is used to hold an attribute in memory. Since an attribute can have either NUMBER or STRING type, Attribute is a union containing the two types. The size of an Attribute variable is fixed at 16 bytes. A **record** will be an array of Attribute whose size is equal to the number of attributes in the relation.

tip

Attribute is the fundamental unit of data in a record. Hence, the Attribute data structure is used in several functions of NITCbase.

The definition for union Attribute is given below:

union Attribute {
double nVal;
char sVal[ATTR_SIZE];
};

InternalEntry

Each Internal Index block of a B+ Tree consists of many attribute values and the child pointers. This data is arranged in the block in such a way that an attribute value is stored between its left child and right child pointers.

Note

The right child pointer of one attribute value will be the same as the left child pointer of the next attribute value. Hence to avoid redundancy, only one copy is stored, making the data overlapped.

The combination of left child, attribute value, and right child makes up the InternalEntry structure, as shown below. An Internal Index block is a combination of 100 such overlapped entries. The getEntry() and setEntry() methods of the class IndInternal take a pointer to struct InternalEntry as an argument.

Implementation Note

The getEntry() and setEntry() methods are declared in the class IndBuffer but are overridden in the class IndInternal. getEntry() and setEntry() methods expect the higher layers to allocate memory for struct InternalEntry before calling them.

struct InternalEntry {
int32_t lChild;
union Attribute attrVal;
int32_t rChild;
};
/* #include <cstdint> must be done */

Index

An index of a relation should store a reference to its record along with the corresponding attribute value. NITCbase uses **RecId**, which is a (block#, slot#) pair, for referencing any record. In NITCbase, an Index structure is a combination of attribute value, block#, and slot#, followed by some unused space left for future use, as shown below.

Each Leaf Index block is a combination of 63 such Index entries. The getEntry() and the setEntry() methods of the class IndLeaf take a pointer to struct Index as an argument.

Implementation Note

The getEntry() and setEntry() methods are declared in the class IndBuffer but are overridden in the class LeafBuffer. getEntry() and setEntry() methods expect the higher layers to allocate memory for struct Index before calling them.

struct Index {
union Attribute attrVal;
int32_t block;
int32_t slot;
unsigned char unused[8];
};
/* #include <cstdint> must be done */

Buffer Structure

The Buffer Layer also defines a buffer structure. StaticBuffer class maintains meta-information for each block loaded to a buffer.

The BufferMetaInfo structure is used for storing this meta-information. This structure contains four fields: a free flag which indicates whether the buffer is occupied, a dirty flag which indicates whether the block has been modified, a blockNum field which is the block number of the block that is stored in the given buffer and a timeStamp field which indicates the last time the buffer had been accessed.

Block Replacement is done using a simple Least Recently Used (LRU) algorithm, which has been implemented in the getFreeBuffer() method. The timeStamp field has to be updated each time the buffer is accessed, as is done in the getBufferPtr() method.

struct BufferMetaInfo {
bool free;
bool dirty;
int blockNum;
int timeStamp;
};

Miscellaneous

Given below are the definitions of RecId and IndexId structures. Variables of these structures will be of use in several layers of NITCbase, such as Cache layer, Block access layer and B+ tree layer, to name a few.

note

The code for RecId struct and IndexId struct can be found in the id.h file defined inside define/ directory.

RecId

Relations in NITCbase are made up of records. Every record of any relation can be referenced using an id called RecId. RecId is a combination of the block number of the corresponding record block and the slot number of the slot occupied by the record in the block. It is used to locate where the record is stored in the disk.

struct RecId {
int block;
int slot;
};

IndexId

The Leaf Index blocks of a B+ Tree are made of Index entries. Every Index entry of any Leaf Index block can be referenced using an id called IndexId. It is a combination of block number of the corresponding leaf index block and index number, which is the offset of the index in that block. It is used to locate where the index is stored in the disk.

struct IndexId {
int block;
int index;
};

compareAttrs()

Description

This function compares two union Attribute values on the basis of the input attribute type.

Arguments

NameTypeDescription
attr1union AttributeFirst attribute value to be compared.
attr2union AttributeSecond attribute value to be compared.
attrTypeintType of the attribute NUMBER/STRING.

Return Values

ValueDescription
Negative integerValue in attr1 is less than the value in attr2.
ZeroValue in attr1 is equal to the value in attr2.
Positive integerValue in attr1 is greater than the value in attr2.
int compareAttrs(union Attribute attr1, union Attribute attr2, int attrType) {

double diff;
// if attrType == STRING
// diff = strcmp(attr1.sval, attr2.sval)

// else
// diff = attr1.nval - attr2.nval

/*
if diff > 0 then return 1
if diff < 0 then return -1
if diff = 0 then return 0
*/
}
note
  • Both the attributes given as input must be of the same type as the input type.
  • For string type, the comparision is performed with respect to lexicographic order.