[DMDB] Memory management

This commit is contained in:
2026-07-03 15:27:42 +02:00
parent c409d74ad4
commit 970f7c0bba
14 changed files with 232 additions and 2 deletions
@@ -0,0 +1,37 @@
\subsubsection{Physical to logical storage}
\begin{wrapfigure}[14]{r}[0cm]{0.3\textwidth}
\begin{center}
\includegraphics[width=0.25\columnwidth]{assets/db-system-entity-relationship.png}
\end{center}
\caption{Entity relationship diagram, where the crow's feet imply one-to-many relationships
(Figure from \hlhref{https://docs.oracle.com/en/database/oracle/oracle-database/19/cncpt/img/cncpt227.gif}{Oracle Docs})}
\label{fig:entity-relation-diagram}
\end{wrapfigure}
To the left, in figure \ref{fig:entity-relation-diagram}, the relationship between the different storage
abstractions are connected using crow's feet.
Since database tables often include large amounts of data, that would span many OS pages, thus database systems use blocks as opposed to OS pages.
These database blocks are structured as follows:
\begin{itemize}
\item \bi{Header}: Contains the address and type of segment (that being index, table, etc)
\item \bi{Table directory}: Contains the schema of the table stored in the block
\item \bi{Row directory}: Contains pointers to the actual tuples stored in the block. Grows downwards (like the stack)
\item \bi{Free Space}: Well... it's free real estate, isn't it?
\item \bi{Row data}: The tuples stored in the block. Grows upwards (like the heap).
The pointers in the row directory point here
\end{itemize}
Typically, database systems allow inserts of new tuples until there is about 20\% of free space left.
This space is allocated to allow for updates of existing tuples, as they may become larger.
Thus, a larger storage footprint is used to prevent having to move all the data to a different location when the data block inevitably fills up,
avoiding thrashing on the page.
Some systems even go a step further, only again unlocking a data block for writing if its used percentage falls below 40\%.
Of course, as with any storage system, fragmentation becomes an issue.
Compaction is only done when the block has enough space for an \texttt{INSERT} or \texttt{UPDATE}, but the space is not contiguous.
An \texttt{UPDATE} may require that a tuple is moved into a different block. In that case, the tuple is inserted into a new block and a pointer is stored in the freed space.
@@ -0,0 +1,37 @@
\subsubsection{Record Layout}
Each record (commonly called tuple) contains a header (which in turn contains validity flags for deletion,
visibility information for concurrency control, bit maps for null values and more), and the non-null attributes, or pointer to those attributes.
Relational engines do not have to store the schema for the tuple inside of it because that is set globally for the entire table.
Schema-less systems however have to store that information because it can differ for each record.
\paragraph{Optimizations}
\begin{wrapfigure}{l}{0.35\textwidth}
\begin{center}
\includegraphics[width=0.3\columnwidth]{assets/db-system-record-ordering.png}
\end{center}
\caption{Record layouts in memory
(Figure from Lecture 09, Slide 19)}
\end{wrapfigure}
It is crucial to store the data in a way that allows quick accesses and updates.
Each variable length attribute also has to store the length, so in a simple system we would simply store them together with the data.
This however is not ideal for performance reasons (we have linear access time), thus we keep the fixed size part (attributes and lengths) at the start of the record
and move the variable length part to the end, putting pointers after the length attributes that point to the end of the variable length region.
Even more performance can be gained by reordering the attributes altogether, moving the variable length attributes to the very end and then applying the above optimization.
\paragraph{Data Types}
\begin{itemize}
\item \bi{Integers}: Most \acrshort{dbms} use the same format as \texttt{C}.
\item \bi{Real numbers}: Either in IEEE-754 (for variable precision) or using fixed point representations, which commonly are variable length,
by storing all digits, plus where the decimal point is (they are NOT stored as strings)
\item \bi{Strings} and \bi{BLOBs}: Store the length and the data
\item \bi{Time}, \bi{Coordinates}, \bi{points}, \dots: System specific.
\end{itemize}
For large attributes, commonly instead of storing the whole tuple, only the fixed length attributes of the tuples are stored directly in the block,
with pointers to the rest of the attributes, that may also lay outside of the current block.
BLOBs may also be \textit{very} large, taking up more than a single block.
@@ -0,0 +1,48 @@
\subsubsection{Data in the blocks}
The pages allocated to a given object (table, index, etc) are managed through lists, which are stored as part of the segment header
\begin{figure}[h!]
\begin{center}
\includegraphics[width=0.9\columnwidth]{assets/db-system-page-lists.png}
\end{center}
\caption{Free/used lists in databases
(Figure from Lecture 09, Slide 24)}
\end{figure}
These lists can become a bottleneck, especially for transactions that result in modifications.
There are some ways to improve the performance:
\begin{itemize}
\item We can use several free lists, so concurrent transactions can find free space in parallel, rather than having conflicts all the time
\item Make the traversal of the list faster (sorting, etc)
\item Make sure that holes can be found efficiently (store the available space in each page in incremental steps)
\end{itemize}
\paragraph{Slotted Pages}
To find tuples within pages, slotted pages are used often.
In slotted pages, each page has a header containing checksum, version, transaction visibility, compression information, utilization, etc.
Then, each tuple gets an id (typically a block ID and an offset).
The page maintains a list of the ``slots'' (each slot is assigned to one tuple and thus the number of tuples in the pages is further constrained by the number of pages available).
The offsets, or sometimes pointers, are stored at the beginning of each tuple.
Advantages of slotted pages are:
\begin{itemize}
\item storing tuples of different sizes (and variable length tuples)
\item Each tuple gets a permanent tuple / record id, that does not change
\item When data doesn't change, space is used efficiently
\item When data \textit{does} change, we need to be careful
\end{itemize}
Considerations for when data \textit{does} change:
\begin{itemize}
\item If a tuple is modified and becomes larger, the original space is used to store a pointer to the new location of the tuple
\item If a tuple is deleted, we just remove the pointer
\item If a tuple is modified and becomes smaller, leave the unnecessary space empty
\item For insertion, we look for a page with enough free space and compact the page, if needed
\end{itemize}
The percentage free measure is used to avoid fragmentation caused by pages running out of space to accommodate updates for tuples that become larger.
Thus, some space is reserved for growing the tuples, rather than for inserting.
Without this measure, the block can constantly go from \texttt{FREE} to \texttt{USED} and back and forth.
@@ -0,0 +1,29 @@
\subsubsection{Row and Column Stores}
\paragraph{Row Store}
Here, as we have assumed thus far, tuples are stored with all their attributes together.
This means that accesses to tuples is quick and easy.
This scheme is used by actual systems, but they also have many additional details added to improve management and speed.
A typical ideal application for a row store is Online Transaction Processing (OLTP).
This is visible for example with banking applications, shopping carts, etc, where the operations mostly are on a single tuple.
However, for xomplex queries, the row store can become cumbersome.
\paragraph{Column Store}
This is where the column store comes in, in which the data is stored by columns.
This obviously speeds up operations on the columns, as the access is very quick.
Column stores also have the advantage that they present the data exactly in the representation needed for \acrshort{simd} instructions such as \acrshort{avxa},
which is now widely available on modern CPUs.
Modern database systems have started to adopt column stores to address the memory wall,
since Column Stores are a more compact representation and cache lines are likely to bring the data we want to see,
thus the cache utilization tends to be higher than with row stores.
Column store databases are today primarily used today for analytical databases and in-memory databases.
An alternative is a hybrid approach, called \bi{Partition Attributes Across (PAX)} representation.
There, each block contains several tuples, but it is organized as a column store.
@@ -0,0 +1,66 @@
\subsubsection{Execution Models}
\label{sec:execution-models-advanced}
We model execution as a tree of operators (as seen previously). At the root, we have the query results, at the leaves we have the tables.
Typically, each operator has \textit{at most} two inputs from lower layers (join operators have two, the rest typically has one).
We remember that we can use any subtree of a query as input for the higher parts of the tree, since we can ``materialize'' the result as a table.
\paragraph{Basic Execution Models}
Each node (operator) in the tree operates independently, i.e. does input, putput, output.
Each operator can read their input tuples in one go, or one at a time and the same goes for the output.
Some operators may also be \textit{blocking}, i.e. no result can be issued until all operations are completed.
\paragraph{More advanced models}
As a reminder, we already covered some aspects of three execution models in section \ref{sec:execution-models}.
\subparagraph{Iterator Model}
Some typical dataflow operators:
\begin{itemize}
\item \bi{Union}: read both sides, issue all tuples (if they have the same schema)
\item \bi{Union without duplicates}: We first union, then we check for duplicates
\item \bi{Select}: We apply a filter function to the tuples
\item \bi{Projection}: For each tuple, we output only the specified attributes
\item \bi{Join}: Trivial nested loop join
\item \bi{Join with index on inner relation}: For all tuples, fetch matchin tuple in S <= another operator
\end{itemize}
\shade{green}{Advantages}
\begin{multicols}{2}
\begin{itemize}
\item Generic interface for all operators
\item Easy to implement operators
\item Supports buffer management strategies
\item No overhead in terms of main memory
\item Supports pipelining
\item Supports parallelism and distribution
\end{itemize}
\end{multicols}
\shade{red}{Disadvantages}
\begin{multicols}{2}
\begin{itemize}
\item High overhead of method calls
\item Poor instruction cache locality
\end{itemize}
\end{multicols}
\paragraph{Pull \& Push Mode}
\inlinedefinition[Pull Mode] Intuitively like dynamic programming: Data is obtained by one operator through a function call to a lower operator.
It is good for disk based systems and when data doesn't fit into memory and is also really easy to implement.
\inlinedefinition[Push Mode] This is the Pull Mode flipped on its head.
While it \textit{can} be faster, there need to be buffers everywhere and it is significantly harder to implement.
\paragraph{EXCHANGE operator}
The \texttt{EXCHANGE} operator is useful for parallel processing, as it simply moves data from one place to another.
This allows to parallelize a plan across one or more machines.
This can be implemented as a sort of ``driver'' on top of an operator, by calling \texttt{next()} on the operator a few times,
collecting the results and sending them to the other side.
The other side uses the data to respond to further next calls to the original operator.