《闪存数据库概念与技术》
厦门大学数据库实验室 林子雨 编著
(本网页是第11章内容,全书内容请点击这里访问本书官网,官网提供本书整本电子书PDF下载)
第11章 闪存数据库实验环境的搭建
在闪存数据库的研究中,需要对各种算法的性能进行验证。算法性能验证方法主要包括两大类:(1)在闪存模拟器这种仿真环境下进行性能验证;(2)修改开源数据库相关模块代码,在真实数据库和闪存设备环境下进行验证。本章内容分别介绍上述两种方法。
11.1 使用闪存模拟器开展实验
闪存模拟器为闪存相关研究提供了一种仿真的实验环境,简单易用。目前已经存在多种闪存模拟器,这里以国内研究人员开发的Flash-DBSim为实例,介绍闪存模拟器的体系架构以及如何利用闪存模拟器开展相关实验(主要是缓冲区替换算法实验)。
11.1.1 Flash-DBSim介绍
Flash-DBSim[SuJXCY09]是由中国科学与技术大学知识与数据工程实验室开发的闪存模拟器,它为各种算法性能验证提供了仿真环境,可以模拟闪存技术研究中出现的各种实验环境。该模拟器可以根据上层应用的需要模拟出不同特性的固态盘,如闪存的读写代价不一致和写前擦除等特性。Flash-DBSim使用的程序设计语言为c++,开发工具为visual studio 2008。很多已有的研究(比如缓冲区替换算法CCF-LRU和AD-LRU)都采用了该模拟器进行算法性能的比较。
Flash-DBSim具有如下特点:
- 模块化:采用了模块化的设计方式,自下而上包含了三个主要模块,即虚拟闪存设备模块 (Virtual Flash Device Module, VFD),存储技术设备模块 (Memory Technology Device Module, MTD)以及闪存转换层模块 (Flash Translation Layer Module, FTL)。每个模块中都封装了一些具有代表性的算法,大大减轻了研究人员开展相关实验时的冗余工作,如果需要替换其中的一个或多个算法,只需要修改相关的模块即可。
- 接口化:所有模块之间使用通用接口进行交互,由于接口的不变性,因此,某个模块的内容修改不会影响到其他模块。
- 可扩展性:研究人员可以根据自己的实际研究需要,对模拟器的各个模块内容进行修改,只要保证遵循Flash-DBSim各模块的接口定义,新的代码就能够被配置到Flash-DBSim中运行。
- 易配置性:不同的闪存技术研究需要配置不同的实验环境,为了方便用户配置模拟器,Flash-DBSim尽可能简化了接口数量,从而使得只需要使用少量的代码就可以对整个模拟器环境进行配置。
11.1.2 Flash-DBSim体系架构
闪存存储系统包括两个关键组件,即闪存技术设备模块MTD和闪存转换层FTL。MTD(Memory Technology Driver)提供了对闪存的读、写和擦除操作的函数,它直接控制物理闪存芯片。FTL(Flash Translation Layer)是架构在MTD层之上的,用来实现地址翻译、空间分配和垃圾回收等操作,从而把一个闪存设备模拟成一个块设备,使得文件系统和用户应用可以像访问磁盘一样访问闪存设备。
图[Flash-DBsim-two-ways]显示了实现一个闪存存储系统的两种不同方式。第一种方式是,MTD和FTL被封装在闪存设备(比如固态盘)中,如图[Flash-DBsim-two-ways] (a)所示。第二种方式是,MTD和FTL在闪存设备上面的软件系统中实现,如图[Flash-DBsim-two-ways] (b)所示。Flash-DBSim采用了第一种实现方式,即封装在一个闪存设备中,只不过在Flash-DBSim中,这个闪存设备并非真实的设备,而是一个虚拟的设备。
图[Flash-DBsim-two-ways] 闪存存储系统的两种实现方式
图[Flash-DBsim-architecture]给出了Flash-DBSim的体系架构,包括以下几个组件:
- 虚拟闪存设备模块:即VFD(Virtual Flash Device)模块,可以提供虚拟闪存的一些特性,比如IO延迟和擦除限制,它为其他模块提供了基本的操作接口,比如读、写和擦除。
- 闪存技术设备模块:即MTD(Memory Technology Device)模块,可以通过接口来控制VFD模块。MTD模块隐藏了NAND和NOR闪存之间的差别,因此,通过使用MTD模块中定义的相同的接口,上层的模块可以访问各种不同类型的虚拟闪存。
- 闪存转换层模块:即FTL(Flash Translation Layer)模块,可以实现地址翻译、空间分配和垃圾回收。该模块通过调用MTD模块的接口来访问下面的虚拟闪存。
- 库函数:包含许多源代码,可以直接用于闪存研究工作,减少了研究人员的冗余工作。所有这些源代码都遵循了相关模块的定义。
公共访问接口:该组件是开放给所有用户的,它对用户隐藏了所有底层细节。
图[Flash-DBsim-architecture] Flash-DBSim的体系架构
11.1.3 Flash-DBSim的下载和简要说明
Flash-DBSim官方网站(http://kdelab.ustc.edu.cn/flash-dbsim/)提供了关于该模拟器的详细说明和源代码下载。
Flash-DBSim官方网站提供了两个不同版本的Flash-DBSim下载内容:
- “运行组件”版本:如果只是使用Flash-DBSim进行闪存研究实验,请下载此版本,然后,根据网站上的说明进行配置并开展相关实验;
- “源代码+示例”版本:如果需要对Flash-DBSim系统进行二次开发,请下载此版本,很显然,该版本也可以用来进行闪存研究实验。
这里将假设读者下载的Flash-DBSim文件版本为”源代码+示例”,版本号为“1.6.0.32”,从网站下载得到的原始压缩文件为“flash-dbsim-source-1.6.zip”。
Flash-DBSim使用的程序设计语言为c++,开发工具为visual studio 2008,因此,读者对压缩文件flash-dbsim-source-1.6.zip进行解压后,可以得到名为“flash-dbsim-source-1.6”的文件夹,该文件夹下面包含一个解决方案文件和两个工程文件夹:
- sln文件:Visual Studio的解决方案文件;
- FlashDBSimDll文件夹:包含Flash-DBSim源代码,可以进行二次开发;
- FlashDBSimDll_Sample文件夹:包含了一个测试LRU算法性能的实例,可以在这个工程下修改相关代码,设计和实现其他闪存相关算法并测试性能。
11.1.4 使用Flash-DBSim模拟器开展相关实验
这部分内容首先简单介绍FlashDBSimDll_Sample工程中各个文件的作用,然后介绍如何设置模拟器的参数,接下来给出了一个实例来演示如何测试LRU算法的性能,最后,简单介绍如何测试自己的缓冲区替换算法性能。
11.1.4.1 使用Visual Studio开发工具打开解决方案
从Flash-DBSim官方网站下载得到原始压缩文件“flash-dbsim-source-1.6.zip”以后,解压缩得到名为“flash-dbsim-source-1.6”的文件夹,该文件夹下面包含一个解决方案文件FlashDBSimDll.sln和两个工程文件夹。
使用visual studio 2008/2010打开FlashDBSimDll.sln,就可以看到一个解决方案(名称为FlashDBSimDll)下面包括两个工程,即FlashDBSimDll和FlashDBSimDll_Sample。如果需要进行二次开发,可以根据Flash-DBSim官方网站的详细说明来修改FlashDBSimDll工程下面的相关代码;如果需要进行实验,可以直接在FlashDBSimDll_Sample工程下面根据需要编写相关代码,运行程序,测试算法性能。这里不需要进行二次开发,只是开展相关实验,因此,只需要修改FlashDBSimDll_Sample工程下面的代码,不需要理会FlashDBSimDll工程。
在FlashDBSimDll_Sample工程下面,包含4个头文件、3个C++源文件和2个测试数据集文件:
- 4个头文件:h,BufferAlgorithm.h,flashdbsim_i.h和LRU.h;
- 3个C++源文件:cpp,LRU.cpp,main.cpp;
- 2个测试数据集文件:trace1000000和trace200000。
打开工程FlashDBSimDll_Sample下的main.cpp文件,可以直接运行程序。下面介绍各个文件的作用:
- h和stdafx.h:不管开展什么实验,都不应该去修改这两个文件;
- h和BufferAlgorithm.cpp:定义了所有缓冲区替换算法的基类,所有的缓存替换算法都应该从该类继承;
- h和LRU.cpp:给出了缓冲区替换算法LRU的实现,用来演示Flash-DBSim的使用方法;
- trace1000000和trace200000:是两个测试数据集文件,里面包含了若干行数据,每行数据代表一次读操作或一次写操作。每行数据包含两个列,其中,第一列是逻辑页号,第二列中用0和1分别表示读操作和写操作。
- cpp:程序入口,包含了一些实验参数的设置,并调用缓冲区替换算法进行算法的性能测试。
11.1.4.2 模拟器的参数设置
Flash-DBSim是一种高效的、可重用和可配置的闪存存储系统仿真平台,可以根据上层应用的需要模拟出不同的特性的固态盘,从而可以方便地为上层应用提供仿真测试环境。Flash-DBSim闪存模拟器通常可以采用下面表[Flash-DBSim-parameters]中的典型参数配置:
表[Flash-DBSim-parameters]. NAND闪存的特性参数
Attribute | Value |
Page Size | 2,048B |
Block Size | 64 pages |
Read Latency | 25us/page |
Write Latency | 200us/page |
Erase Latency | 1.5ms/block |
Endurance | 100,000 |
Flash-DBSim中的闪存基本参数设置是在FlashDBSimDll_Sample工程下面的main.cpp文件中完成的,相应的代码如下:
vfdInfo.blockCount = 1024;//设置块的数量
vfdInfo.pageCountPerBlock = 64;//设置每个块中包含的页的数量 vfdInfo.pageSize.size1 = 2048;//设置页的数据区域的大小 vfdInfo.pageSize.size2 = 0;//设置页的带外数据区域的大小 vfdInfo.eraseLimitation = 100000;//设置闪存的每个块的擦除次数上限 vfdInfo.readTime.randomTime = 25;//设置随机读操作延迟为25微秒 vfdInfo.readTime.serialTime = 0;//设置顺序读操作延迟为0微秒 vfdInfo.programTime = 200;//设置写操作延迟为200微秒 vfdInfo.eraseTime = 1500;//设置擦除操作延迟为1500微秒 |
这些闪存特性参数可以根据具体的实验要求而有所不同,可以在main.cpp文件中进行修改。需要指出的是,如果实验中需要修改缓冲区的大小,可以在BufferAlgorithm.h文件中修改变量DEFBUFSIZE的值,该值默认为1536,即缓冲区可以容纳1536个页,每个页大小为2048字节,因此,缓冲区大小为3MB。。
11.1.4.3 实例:测试LRU算法的性能
FlashDBSimDll_Sample工程提供了一个测试LRU(Least Recently Used)算法性能实验的例子。为了更好地设计实现自己的缓冲区替换算法,读者应该首先了解LRU算法性能测试的基本步骤、所使用的数据结构和算法成员函数。
11.1.4.3.1 LRU算法性能测试的基本步骤
在FlashDBSimDll_Sample工程中,通过main.cpp来调用各种缓冲区替换算法,测试任何缓冲区替换算法(包括LRU算法和自己设计的算法)的性能时,都需要严格遵循以下5个步骤:
- 第1步:初始化配置信息;
- 第2步:写入数据页;
- 第3步:读入测试数据集,开始测试;
- 第4步:测试完毕,将内存中的脏页写回闪存;
- 第5步:获取测试结果数据。
11.1.4.3.2 LRU算法的数据结构
在Flash-DBSim自带的实例工程FlashDBSimDll_Sample中,包含一个头文件LRU.h,这个头文件里定义了两个结构体LRUBCB和LRUElement,以及一个数组ftop,它们都是为LRU算法服务的。如图[Flash-DBSim-LRU]所示,LRU算法使用的数据结构包含了一个LRU链表、一个哈希表(ptop)和一个数组(ftop),具体作用如下:
- LRU链表:如图[Flash-DBSim-LRU](a)所示,在LRU算法中,缓冲区中的所有页被组织成一个LRU链表,链表的MRU(Most Recently Used)端表示最频繁访问的页,链表的LRU(Least Recently Used)端表示最少访问的页。一个页被命中以后,会被转移到链表的MRU端。LRU链表采用的数据结构是LRUElement,包含了三个成员变量frame_id,LessRecent,MoreRecent,其中,MoreRecent和LessRecent是两个指针,分别指向左右两边的相邻元素。
- 数组ftop:如图[Flash-DBSim-LRU](b)所示,在LRU算法中,内存中会有一个固定大小的空间作为缓冲区,Flash-DBSim把每个缓冲区单元称为“帧”(frame),每个帧都有编号frame_id,一个帧可以保存一个逻辑页(page),每个逻辑页都有编号page_id,数组ftop记录了每个帧中存储的页号,即数组ftop的下表是frame_id,数组元素ftop[frame_id]中存储的是页号page_id。
- 哈希表ptop:如图[Flash-DBSim-LRU](c)所示,这个哈希表的键(key)是页号page_id,对page_id使用哈希函数,可以把一个页号映射到相应的哈希桶中。多个页号可能映射到同一个哈希桶中,因此,每个哈希桶都包含了一个链表,它采用了数据结构LRUBCB,LRUBCB中包含了4个成员变量page_id,frame_id,dirty,next,表示页号为page_id的页被存放在缓冲区的帧号为frame_id的帧中,dirty表示是否是一个脏页,next是一个指针,指向链表中的下一个元素。
当需要读取一个逻辑页时,需要判断该页是否在缓冲区中,方法很简单,只需要根据页号page_id得到哈希值,然后在哈希表ptop找到对应的哈希桶,每个哈希桶都存储了一个链表,如果该链表中不存在与page_id对应的元素,则说明该页不在缓冲区中。如果链表中存在一个与page_id对应的元素,则读取出该元素的frame_id属性的值,到相应的缓冲区单元中读取数据页。
当需要把一个页号为page_id的逻辑页写入缓冲区的帧号为frame_id的某个帧时,首先,修改数组内容,把数组元素ftop[frame_id]的内容设置为page_id,然后,需要根据页号page_id得到哈希值,在哈希表ptop找到对应的哈希桶,每个哈希桶都存储了一个链表,为page_id和frame_id创建一个新的元素加入到链表中。
当需要把一个逻辑页驱逐出缓冲区时,只需要根据页号page_id得到哈希值,然后在哈希表ptop找到对应的哈希桶,每个哈希桶都存储了一个链表,在链表中找到与page_id对应的元素,读取frame_id的值,然后,把内存中帧号为frame_id的缓冲区单元的内容清空。
图[Flash-DBSim-LRU] Flash-DBSim中LRU算法使用的数据结构
11.1.4.3.3 LRU算法的成员函数
为了快速理解LRU算法,读者应该重点阅读理解LRU.cpp中的FixPage()函数和SelectVictim()函数的源代码,LRU.cpp中很多其他成员函数是不用修改的,也不需要深入理解。另外还需要注意的是,LRU.cpp中所有与外部存储相关的操作都是“假动作”,只是模拟闪存的读和写操作,并没有实际读、写某个文件,只是统计读写次数。
下面分别介绍LRU算法的成员函数及其作用。
Public成员函数:
Public:LRU() // 构造函数,初始化数据
~LRU() // 析构函数,释放资源
void Init() // 初始化,在LRU算法性能测试的基本步骤的第2步与第3步之间调用
int FixPage(int page_id) // 安插一个数据页
NewPage FixNewPage(LBA lba) // 安插一个新的数据页
int UnFixPage(int page_id) // 基本不会用到
void ReadFrame(int frame_id, char* buffer) // 读一帧数据,用不到
void WriteFrame(int frame_id, const char*buffer) // 写一帧数据,用不到
int WriteDirty(void) // 将内存中的脏页写回闪存
double HitRatio(void) // 返回命中率
void RWInfo() // 返回读写统计信息
int IsLBAValid(LBA lba) // 判断LBA是否有效
int LBAToPID( LBA lba) // 通过LBA获取PID
Private成员函数:
int hash(int page_id) // 哈希函数
LRUBCB* PageToLRUBCB(int page_id) // 通过page_id 获取块数据
void RemoveLRUBCB(LRUBCB* pb) // 移除块数据
void InsertLRUEle(int frame_id) // 在链表中插入元素
void RemoveLRUEle(int frame_id) // 移除链表元素
void AdjustLRUList(int frame_id) // 调整链表元素(缓冲区命中以后调整命中页在链表中的位置)
void SetDirty(int frame_id) // 设置frame_id 的脏标识
int SelectVictim() // 选择替换页,缓存替换算法的核心函数
void RegistEntry(LBA lba, PID page_id) // 注册一页,用不到
Private成员变量:
int ftop[DEFBUFSIZE] // 数组
LRUBC* ptop[DEFBUFSIZE] // 哈希表
bFrame buf[DEFBUFSIZE] // 存放帧数据,用不到
Map<LBA,PID> // maplist逻辑页号与物理页号的映射关系
LRUElement* lru // 链表的表尾指针
LRUElement* mru // 链表的表头指针
int hit // 命中次数
int total // 读写操作的次数
int flashreadcount // 物理读操作次数
int flashwritecount // 物理写操作次数
11.1.4.4 测试自己的缓冲区替换算法的性能
假设读者需要开展实验测试自己的缓冲区替换算法的性能,建议首先阅读FlashDBSimDll_Sample工程下面的main.cpp、LRU.h和LRU.cpp文件,理解程序运行过程,从而更好地了解Flash-DBSim的使用方法以及如何在main.cpp中调用自己设计实现的其他缓冲区替换算法。在实现自己的缓冲区替换算法时,建议直接在LRU算法基础之上进行修改,这样可以避免引入未知错误。建议深刻理解LRU算法实例的运行细节,这样才能更好地设计实现自己的算法,知道哪些成员函数需要修改,哪些成员函数不需要修改。在实现自己的算法中,可以拷贝LRU算法的代码,修改关键的几个成员函数。这里需要注意的是,由于大部分成员函数不需要修改,如果读者需要实现多个不同的缓冲区替换算法,建议将那些不需要修改的成员函数移到基类BMgr中去。
11.2 在PostgreSQL开源数据库的真实环境下开展实验
利用闪存模拟器提供的仿真环境进行实验具有一定的局限性,因此,对于某些闪存研究实验,需要在真实的数据库和闪存设备环境下进行,因此,这里将介绍如何修改PostgreSQL开源数据库的源代码,从而实现真实环境下的性能测试。
11.2.1 在DBMS真实环境下开展实验的必要性和可行性
使用闪存模拟器(比如Flash-DBSim)提供的仿真环境进行闪存相关算法的性能测试,这种方法的优点是,灵活易用,绝大多数算法都可以在仿真环境下进行性能测试。但是,某些算法在模拟器这种仿真环境下的性能测试结果,可能缺乏说服力,甚至准确性和真实性都会存在一定的问题。比如,缓冲区替换算法是DBMS内部的一个核心模块,算法的性能会受到系统内部诸多其他模块运行过程的影响,简单地采用闪存模拟器来测试缓冲区替换算法的性能,存在一定的局限性。
正是因为使用闪存模拟器进行算法的性能测试,存在一定的局限性,因此,很有必要在DBMS真实环境下开展相关实验。实际上,对于一些算法而言(比如缓冲区替换算法),在DBMS真实环境下开展实验是可以实现的,具有较好的可行性。基本思路是,根据实验的不同需求,对开源数据库(比如PostgreSQL)的相关模块的代码进行修改。
12.2.2 在PostgreSQL下开展实验之前需要回答的一些问题
在使用PostgreSQL开展实验之前,读者可能会思考一些问题,因此,这里以问答的形式帮助读者对实验的开展有一个整体的认识。
问1:做缓冲区替换算法的实验,是否只需要修改PostgreSQL 缓冲区替换算法相关的源码,然后编译安装即可?
答1:是的,在postgresql-7.3.40/src/backend/storage/buffer 目录下修改缓冲区替换算法,修改完成以后还和以前一样安装即可。
问2:要实现多个缓冲区替换算法,是否需要多次安装、卸载PostgreSQL?
答2:是的,一个系统中只有一个PostgreSQL 数据库,如果要比较多个缓冲区替换算法的性能,则需要在每次实现一个算法的时候,都分别安装PostgreSQL,获取测试数据,然后卸载PostgreSQL,然后,再去实现下一个算法。
问3:如何将数据存储到固态盘?
答3:首先用mount 命令挂载固态盘,然后,在postgresql-7.4.30目录下执行“./configure –prefix=[安装目录]”命令的时候指定路径。
问4:实验的输入数据是什么?从哪里来?
答4: 开展实验时,不需要我们自己获取输入数据,有一个名为BenchmarkSQL 的测试工具,会自动帮我们创建表,创建索引,添加数据。
问5:如何获取运行时间?
答5:数据库性能测试工具—— BenchmarkSQL,会显示运行时间。
问6:如何设置缓冲区大小?
答6:在PostgreSQL 的源码中,有一个名为NBuffer 的变量,默认值是1000,表示有几个缓冲区(数据页),修改缓冲区大小,只需要修改这个变量的值即可。
问7:如何统计写操作次数和命中率?
答7:BenchmarkSQL虽然帮助我们输入了数据,完成了数据库性能的测试,获取了数据库的执行时间,但是,没有给出命中率、物理写操作次数等信息。不过PostgreSQL源码中有统计这些信息,我们需要修改PostgreSQL,把这些信息打印出来。
11.2.3 使用PostgreSQL 开展缓冲区替换算法的步骤
使用PostgreSQL 开展缓冲区替换算法,包括以下4个大的步骤:
第1步:下载PostgreSQL,修改PostgreSQL自带的缓冲区替换算法(LRU算法)的源代码,实现自己的缓冲区替换算法;
第2步:用源码安装的方式把PostgreSQL数据库安装到固态盘;
第3步:安装BenchmarkSQL;
第4步:使用BenchmarkSQL进行性能测试,获取测试结果
以上每一步都可以分为很多小步,其中,第1步最复杂,需要读者首先阅读PostgreSQL源码,在此基础上实现自己的缓冲区替换算法。
接下来的内容,将介绍以下几个方面的细节:
- 如何下载PostgreSQL以及如何在Linux系统下把PostgreSQL安装到固态盘;
- 如何下载和安装BenchmarkSQL,以及如何使用BenchmarkSQL对PostgreSQL 数据库的性能进行测试;
- 如何修改PostgreSQL数据库的缓冲区替换算法,实现自己的缓冲区替换算法并测试性能。
11.2.4 PostgreSQL的下载和安装
11.2.4.1 PostgreSQL的下载
请到PostgreSQL官方网站(http://www.postgresql.org/ftp/source/)下载PostgreSQLv7.4.30。下载PostgreSQL v7.4.30是因为这是PostgreSQL最后一个使用LRU算法的版本,更新版本的PostgreSQL使用了更加复杂的缓冲区替换算法,阅读起来更有难度,而且,在实现自己的缓冲区替换算法时,几乎都要和LRU算法进行性能比较,因此,下载这个版本的PostgreSQL,就不用再实现一遍LRU算法,节省了实验工作量。将下载的PostgreSQL解压后,会得到一个名为 postgresql-7.4.30 的文件夹。
11.2.4.2 PostgreSQL的安装和配置
PostgreSQL需要在Linux环境下运行,可以采用虚拟机软件VMware,在其中安装ubuntu系统。
在Linux下安装PostgreSQL,需要遵循Linux下安装源码文件的三个步骤:
(1) ./configure
(2)Make
(3)Make install
通过上述三个步骤,就可以把PostgreSQL安装到prefix指定的目录下。
实际上,读者也可以根据PostgreSQL的安装说明文件来指导自己的安装过程。在刚才解压缩得到的postgresql-7.4.30目录下,有一个安装说明文件INSTALL,这里对该说明文件中的几行关键代码做简要解释,具体如下:
./configure #配置
gmake #make
su #切换到root用户
gmake install #安装,以上几步是linux 下安装软件的典型方式
adduser postgres #添加一个用户,添加完用户以后需要通过passwd postgres来修改该用户的密码
mkdir /usr/local/pgsql/data #新建一个目录,以后数据库的所有数据和操作都在该目录下
chown postgres /usr/local/pgsql/data #更改目录的所有者为postgres
su – postgres #切换用户为postgres
/usr/local/pgsql/bin/initdb -D /usr/local/pgsql/data #初始化工作区
/usr/local/pgsql/bin/postmaster -D /usr/local/pgsql/data >logfile 2>&1 &
/usr/local/pgsql/bin/createdb test #启动服务,并新建一个名为test 的数据库
/usr/local/pgsql/bin/psql test #启动数据库
下面介绍如何将PostgreSQL安装到固态盘,以及如何配置PostgreSQL,使得BenchmarkSQL能够顺利完成对PostgreSQL的性能测试。
把PostgreSQL安装到固态盘,可以执行以下三个步骤:
./configure –prefix=想要安装的路径 –without-readline –without-zlib
make
sudo make install
第一条语句用来指定数据库的安装路径,如果不指定,则默认安装到“ /usr/loca/pgsql” 目录下;如果读者现在身边没有固态盘,则可以不指定–prefix 参数,让数据库安装到默认位置。需要指出的是,为了描述方便,下面假设读者安装到默认位置,如果读者安装到其他位置,在下面的语句中,请自行修改路径。
执行完上面3条语句以后,数据库就安装完成了,但是,还有很多工作要做。
首先,修改/usr/local/pgsql/share目录下的conversion_create 文件,将所有的 “$libdir” 替换成 “[安装目录]/lib”。
其次,在替换完成以后,需要初始化工作区:
mkdir /usr/local/pgsql/data
chown userName /usr/local/pgsql/data
/usr/local/pgsql/bin/initdb -D /usr/local/pgsql/data #初始化工作区
再次,在初始化工作区以后,需要修改配置文件。进入存储空间所在文件夹(/usr/local/pgsql/data),打开postgresql.conf文件,将#tcpip_socket = false的“#”删除,并把false改为true,另外,需要将#port = 5432的“#”删除。
上面步骤完成以后,配置工作就完成了,下面只需要启动数据库服务,然后新建数据库就可以了。
#启动服务
/usr/local/pgsql/bin/pg_ctl -D /usr/local/pgsql/data -l logfile start
#创建一个名为test 的数据库
/usr/local/pgsql/bin/createdb test
到这里为止,我们的数据库就创建完成了,下面就可以使用BenchmarkSQL 来测试性能。
11.2.5 使用BenchmarkSQL测试性能
BenchmarkSQL是一款采用TPC-C的、开源的数据库测试工具,几乎不用修改就能测试当前主流数据库的性能。这里介绍BenchmarkSQL的下载、安装和使用方法。
11.2.5.1 BenchmarkSQL的下载和安装
在Linux下使用BenchmarkSQL非常方便,只需要下载到本地以后,不需要执行安装,只要运行里面的shell 脚本即可。可以访问BenchmarkSQL官网(http://sourceforge.net/projects/benchmarksql/)下载该软件。
这里需要注意的是, BenchmarkSQL的运行需要JAVA虚拟机,因此,需要在Linux下配置JDK,读者可以参考相关书籍或网站完成JDK的配置,这里不再赘述。
11.2.5.2 BenchmarkSQL的使用方法
首先,需要修改“BenchmarkSQL-2.3.2/run/”目录下面的postgres.properties文件,设置正确的数据库名和密码。如果之前已经创建了一个名为test的数据库,那么,这里只需要给出相应的账户和密码就可以了,具体配置如下:
driver=org.postgresql.Driver
conn=jdbc:postgresql://localhost:5432/test
user=lalor
password=123456
然后,在“BenchmarkSQL-2.3.2/run/”目录下运行如下命令:
./runSQL.sh postgres.properties sqlTableCreates
上面的命令用于创建我们进行TPC-C测试所需的数据库表。
./loadData.sh postgres.properties numWarehouses 10
上面的命令用于加载我们进行TPC-C测试所需的数据,numWarehouses后的数字可以自行设置。
./runSQL.sh postgres.properties sqlIndexCreates
上面的命令用于创建我们进行TPC-C测试所需的索引。
./runBenchmark.sh postgres.properties
上面的命令执行后,开始执行TPC-C测试,这时会跳出一个对话框,用户可以根据自己的测试需要设定相关的warehouse数目和terminal数目,然后进行测试。
上面只是简单介绍了开源工具BenchmarkSQL的使用方法,如果更加详细的资料,读者可以参考网络资料(比如,http://blog.sina.com.cn/s/blog_448574810101a276.html)。
11.2.5.3 获取测试结果数据
使用BenchmarkSQL可以帮我们生成测试结果数据,从而了解数据库的性能,但是,如果要获取底层的详细信息,如缓冲区命中率、物理读操作次数和物理写操作次数等,则还需要查看PostgreSQL源代码,通过修改源代码来获取这些信息。 具体方法如下:
vim PostgreSQL-7.4.30/src/backend/libpq/pqcomm.c
运行上面命令,进入pqcomm.c文件的编辑状态,在该文件中注册一个客户端退出时的回调函数,用该函数来调用ShowBufferUsage()函数,打印统计信息。ShowBufferUsage()函数是PostgreSQL自带的函数,用来打印与缓冲区相关的信息。
注册回调函数具体过程包含以下三个步骤:
第1步:声明回调函数printBufferUsageInfo(void)
/* Internal functions */ static void pq_close(void); static void printBufferUsageInfo(void);//新增加的回调函数声明 static int internal_putbytes(const char *s, size_t len); static int internal_flush(void);
第2步:定义回调函数
void printBufferUsageInfo(void) { FILE *fp = NULL; char *usage; usage = ShowBufferUsage(); fp = fopen(“/home/lalor/code/data.txt”, “w+”); if (fp == NULL) { fprintf(stderr, “open file error”); fclose(fp); } else { fprintf(fp, “%s\n”, usage); fclose(fp); } }
第3步:在合适的地方(pg_init)注册回调函数
voidpq_init(void){ PqSendPointer = PqRecvPointer = PqRecvLength = 0; PqCommBusy = false; DoingCopyOut = false; on_proc_exit(printBufferUsageInfo, 0);//新增加的回调函数注册语句 on_proc_exit(pq_close, 0);}
11.2.6 删除 PostgreSQL数据库
通过前面内容的描述,我们已经了解了在PostgreSQL下测试缓冲区替换算法LRU的性能的基本过程。在实际研究工作中,我们需要测试其他多个缓冲区替换算法的性能,从而进行性能比较,但是,一个系统中只能有一个PostgreSQL数据库,在测试完LRU算法的性能以后,我们只能删除PostgreSQL 数据库,再吧PostgreSQL数据库中的缓冲区替换算法修改成其他算法,再次进行安装测试。
删除PostgreSQL数据库包括以下4个步骤:
第1步:关闭数据库服务
/usr/local/pgsql/bin/pg_ctl -D /usr/local/pgsql/data -l logfile stop
第2步:删除数据库
/usr/local/pgsql/bin/dropdb test
第3步:卸载PostgreSQL
./configure –without-readline –without-zlib
sudo make uninstall
sudo make clean
第4步:删除目录
sudo rm -rf /usr/local/pgsql
11.2.7 修改PostgreSQL的缓冲区替换算法
下面首先介绍修改PostgreSQL的预备知识,然后介绍修改PostgreSQL缓冲区替换算法的基本步骤,最后给出一个实例,介绍如何修改PostgreSQL实现缓冲区替换算法CFLRU。
11.2.7.1 修改PostgreSQL缓冲区替换算法的预备知识
11.2.7.1.1 PostgreSQL 缓存替换算法的核心函数
在我们使用的PostgreSQL 7.4.30 版本中,使用的缓存替换算法是LRU算法,与缓存算法相关的文件位于postgresql-7.4.30/src/backend/storage/buffer目录下。打开该目录下的freelist.c文件,可以看到几个关键的函数:AddBufferToFreeList(),GetFreeBuffer(),InitFreeList(),PinBUffer(),UnpinBuffer(),这些函数的主要功能如下:
- AddBufferToFreeList()函数:向LRU链表添加一个元素;
- GetFreeBuffer()函数:选择一个驱逐页,这是替换算法的核心,也是实现自己的缓存替换算法时主要修改的地方;
- InitFreeList()函数:初始化LRU链表(LRU链表不是真正的链表,而是一个数组);
- PinBUffer()函数 和UnpinBuffer()函数:PostgreSQL是一个多进程的数据库系统。当有一个进程访问LRU链表中的一个元素,PinBUffer()函数就将该元素从LRU链表中移除,并增加该元素的引用计数;在这个进程访问结束以后,需要再调用UnpinBuffer()函数减少引用计数,在UnpinBuffer()函数减少引用计数以后,还要判断引用计数是否为0,如果引用计数为0,则表示没有任何进程引用该页,此时才可以将该页再次插回到LRU链表的MRU位置。
11.2.7.1.2 PostgreSQL 中LRU算法的实现
图[PostgreSQL-LRU] PostgreSQL中的LRU算法所使用的数据结构
图[PostgreSQL-LRU]显示了PostgreSQL中的LRU算法所使用的“LRU链表”,不过需要注意的是,这个LRU链表并不是真正的链表,而是用数组来实现的,数组名为BufferDescriptors ,数组中有NBuffer + 1 个“描述子”,所谓“描述子”就是一个结构体(BufferDesc)类型的变量,用于表示LRU链表的一个元素,结构体(BufferDesc)类型定义位于头文件buf_internals.h中。如果我们实现的算法中需要增加新的属性,就在该结构体中添加。数组BufferDescriptors中,前NBuffer个元素用来表示空闲缓冲区(数据页),最后一个元素是“哨兵”,设置这个元素是为了方便操作,有一个指针变量SharedFreeList 会一直指向“哨兵”位置,当满足SharedFreeList->freeNext == Free_List_Descriptor 的时候,就说明缓冲区中的所有数据页都在被使用。有了SharedFreeList,我们就可以很快地从LRU链表中选择LRU 位置的数据页作为驱逐页,并将新的数据页插入到LRU链表的MRU位置。
在PostgreSQL中,有了上面的数据结构,实现LRU算法只用到了两个函数:
- AddBufferToFreeList()函数:使用该函数向LRU链表添加一个元素,新元素位于MRU位置;
- GetFreeBuffer()函数:该函数用于返回链表中LRU位置的元素。
11.2.7.2 修改PostgreSQL 缓冲区替换算法的步骤
修改PostgreSQL 的缓冲区替换算法主要包括两个步骤:
第1步:修改描述子,增加新属性。若新算法为缓冲区赋予了新的属性,则需要修改PostgreSQL缓冲区描述子的数据结构,在其中定义新的参数,以描述缓冲区的新属性。
第2步:增加新的数据结构和操作。LRU算法要求缓冲池是一个LRU链表,若新算法要求缓冲池是另一种数据结构,就需要对缓冲区管理部分进行相应的修改。首先,需要修改缓冲区描述子的数据结构,增加新的数据结构,满足缓冲池的构建要求;然后,需要修改缓冲池的初始化操作,在初始化时构建新结构的缓冲池;最后,通过修改原有的缓冲池维护操作,或者替换和增加新的维护操作,保证缓冲池的数据结构始终满足新算法的要求。
11.2.7.3 修改PostgreSQL实现CFLRU 算法
CFLRU算法[ParkJKKL06]采用了“双区域”机制,把LRU链表划分成两个区域,即工作区域和干净优先区域,两个区域可以采用各种成熟的替换算法(比如LRU)。CFLRU算法不需要增加新属性,只需要判断干净页优先区域(Clean-First Region)中是否有干净页,如果有干净页,就替换干净页,如果没有干净页,就替换LRU位置的数据页。在CFLRU 算法中,我们只需要增加一个变量 nWindowSize 表示干净页优先区域的大小,然后修改GetFreeBuffer()函数即可。
(1)PostgreSQL自带的LRU 算法中的GetFreeBuffer()函数
/*
* GetFreeBuffer() — get the ‘next’ buffer from the freelist.
*/
BufferDesc *
GetFreeBuffer(void)
{
BufferDesc *buf;
if (Free_List_Descriptor == SharedFreeList->freeNext)
{
/* queue is empty. All buffers in the buffer pool are pinned. */
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
errmsg(“out of free buffers”)));
return NULL;
}
buf = &(BufferDescriptors[SharedFreeList->freeNext]);
/* remove from freelist queue */
BufferDescriptors[buf->freeNext].freePrev = buf->freePrev;
BufferDescriptors[buf->freePrev].freeNext = buf->freeNext;
buf->freeNext = buf->freePrev = INVALID_DESCRIPTOR;
buf->flags &= ~(BM_FREE);
return buf;
}
(2)采用CFLRU 算法时修改以后的GetFreeBuffer()函数
/*
* GetFreeBuffer() — get the ‘next’ buffer from the freelist.
*/
BufferDesc *
GetFreeBuffer(void)
{
BufferDesc *buf;
if (Free_List_Descriptor == SharedFreeList->freeNext)
{
/* queue is empty. All buffers in the buffer pool are pinned. */
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
errmsg(“out of free buffers”)));
return NULL;
}
int n = 0;
buf = &(BufferDescriptors[SharedFreeList->freeNext]);
/* select a replace page in clean-first region */
while (n < nWindowSize)
{
/* Is buffer dirty? */
if (buf->flags & BM_DIRTY || buf->cntxDirty)
{
buf = &(BufferDescriptors[buf->freeNext]);
/* no more buffer */
if ( buf == SharedFreeList)
{
buf = NULL;
break;
}
}
else
{/* select this page as replace page */
break;
}
n++;
}
/*
*1. buf == NULL说明缓冲区中总共也没有 nWindowSize 个 free buffer
*2. n >= nWindowSize 说明缓冲区中干净页优先区域中,全都是脏页
* 上面两种情况,都选择位于LRU 位置的数据页作为驱逐页
*/
if (n >= nWindowSize || buf == NULL)
{
buf = &(BufferDescriptors[SharedFreeList->freeNext]);
}
/* remove from freelist queue */
BufferDescriptors[buf->freeNext].freePrev = buf->freePrev;
BufferDescriptors[buf->freePrev].freeNext = buf->freeNext;
buf->freeNext = buf->freePrev = INVALID_DESCRIPTOR;
buf->flags &= ~(BM_FREE);
return buf;
}
11.3 本章小结
本章内容首先介绍了如何使用闪存模拟器开展实验,详细描述了Flash-DBSim闪存模拟器的体系架构、参数配置和使用方法,并以LRU算法参考实例,演示了在Flash-DBSim上测试相关算法性能的基本步骤;然后,介绍了如何在PostgreSQL开源数据库的真实环境下开展实验。总体而言,闪存模拟器为闪存相关研究提供了一种仿真的实验环境,简单易用,但是,某些算法在模拟器这种仿真环境下的性能测试结果,可能缺乏说服力,甚至准确性和真实性都会存在一定的问题。在真实数据库环境下开展实验,可以更加准确地测试相关算法的性能,而且具有实施的可行性,比如对开源数据库的相关模块进行修改。
11.4 习题
- 阐述闪存数据库的研究中对各种算法的性能进行验证的两大类方法及其各自优缺点。
- 分析Flash-DBSim的体系架构中的各个组件的功能。
- 阐述在Flash-DBSim进行算法性能测试的5个步骤。
- 阐述如何修改开源数据库PostgreSQL从而支持对用户自己的缓冲区替换算法进行性能测试。