RaptorDB - 键值存储






4.89/5 (118投票s)
最小、最快的嵌入式 NoSQL 持久化字典,使用 B+树或 MurMur 哈希索引。(现在带有混合 WAH 位图索引)
键值存储
- 下载源代码 v1 - 23.48 KB
- 下载 RaptorDB_v1.1.zip - 28.06 KB
- 下载 RaptorDB_v1.2.zip - 30 KB
- 下载 RaptorDB_v1.3.zip - 30.23 KB
- 下载 RaptorDB_v1.4.zip - 30.97 KB
- 下载 RaptorDB_v1.4.1.zip - 30.96 KB
- 下载 RaptorDB_v1.5.zip - 31.46 KB
- 下载 RaptorDB_v1.5.1.zip - 31.12 KB
- 下载 RaptorDB_v1.5.2.zip - 31.22 KB
- 下载 RaptorDB_v1.6.zip - 31.33 KB
- 下载 RaptorDB_v1.7.zip - 47.48 KB
- 下载 RaptorDB_v1.7.5.zip - 45.39 KB
- 下载 RaptorDB_v1.8.zip - 51.04 KB

前言
新版本 2.0 引擎请访问 (https://codeproject.org.cn/Articles/316816/RaptorDB-The-Key-Value-Store-V2),该版本更优。
RaptorDB 的代码现已上传至 http://raptordbkv.codeplex.com/。我将尽力保持本文与该处源代码同步。
引言
RaptorDB
是一个极其小巧快速的嵌入式、NoSQL 持久化字典数据库,使用 B+树或 MurMur 哈希索引。它最初设计用于存储 JSON 数据(参见我的 fastJSON 实现),但可以存储任何类型的数据。RaptorDB
的灵感来自于 Aaron Watters 的工作 (http://sourceforge.net/projects/bplusdotnet/)。尽管我最初使用了他的代码,但我发现它太慢,内存消耗过大,而且代码难以理解,所以我根据从互联网上收集到的信息,从头重写了 B+树算法。请记住,我没有计算机科学背景,所以我不得不恶补索引技术。
有趣的是,互联网上关于基本算法的优秀资源非常少,大多数都是学术角度的,当你想要制作一个实际应用时,它们几乎没有帮助,但这又是另一个问题了,而且这些似乎是为 RDMB 供应商保留的“黑科技”。
RaptorDB
这个名字来自于一种数据库以动物命名的传统(HamsterDB
、RavenDB
等),但采用了一个听起来更性感的名称,以反映其性能特性。
它是什么?
以下是描述 RaptorDB
的所有术语的简要概述
- 嵌入式:您可以像使用任何其他 DLL 一样在应用程序内部使用
RaptorDB
,无需安装服务或运行外部程序。 - NoSQL:一场基层运动,旨在用更相关、更专业的存储系统取代关系数据库,以适应特定应用。这些系统通常为性能而设计。
- 持久化:所有更改都存储在硬盘上,因此在断电或崩溃时您永远不会丢失数据。
- 字典:一种键/值存储系统,与 .NET 中的实现非常相似。
- B+树:所有数据库索引技术的基石,有关此主题的深入描述,请参阅此链接 (http://en.wikipedia.org/wiki/B%2B_tree),有关我的实现,请参阅“工作原理”部分。它的时间复杂度为 O(logN)。
- MurMurHash:由 Austin Appleby 于 2008 年创建的非加密哈希函数 (http://en.wikipedia.org/wiki/MurmurHash)。它的时间复杂度为 O(1)。
一些可能的用途...
- 高性能持久化消息队列:一个没有依赖且开销最小的消息队列。
- 文档数据库:存储和检索文档文件。
- 网站资产存储:将图像和网页内容存储在一个文件中。
- 大容量网站数据存储:即时存储用户信息,没有存储限制,并减少 RDMB 的开销/安装/运行成本。
- JSON 对象存储:MongoDB 或 CouchDB 风格数据库的基础。
- 网站会话状态存储:将您的网络会话信息存储在快速存储系统中,而不是 SQL 服务器中。
- 单文件文件系统存储:如果您有很多小文件,那么
RaptorDB
可以优化这些文件的存储,减少文件系统存储浪费并加快访问速度。
为什么要再搞一个数据库?
RaptorDB
的主要原因是,当时没有纯 .NET 实现的持久化字典能够满足性能要求,而像 Microsoft SQL Server、MySql 等常规数据库,在存储 JSON 数据方面速度极其缓慢且臃肿。我甚至尝试过 DBF 文件,但大多数 .NET 实现都缺乏完整的备忘录支持。其他的,如 SQLite,虽然插入速度快,但缺乏多线程支持,并在插入时锁定数据库。
RaptorDB
被设想为我的框架的 RDBM 存储的替代存储系统,该框架自 2003 年以来一直采用文档中心设计,当时是基于 RDBM 存储构建的。关系存储系统在存储 BLOB 数据时速度极慢。
特点
RaptorDB
在设计时考虑了以下特点:
- 极快的插入速度 ~ 80% 硬盘速度(参见性能测试部分)
- 极快的检索速度
- 存储大量数据
- 最小的索引文件大小
- 数据后台索引
- 可嵌入到大型应用程序中
- 无需安装
- 适用于 .NET 2.0 及更高版本
- 多线程插入支持
- 尽可能小的代码库 ~ 40KB DLL 文件
- 无依赖
- 抗崩溃,故障安全且可恢复
- 无需关闭
- 只追加,因此数据完整性得到维护,并支持历史/重复值
- 数据立即刷新到磁盘以确保数据完整性(如果您愿意牺牲完整性而使用缓冲输出,则可以获得更高的吞吐量)
- 索引文件实现了
Count()
函数。 InMemoryIndex
属性和SaveIndex()
函数实现内存中索引,并能够按需将索引保存到磁盘。- 使用 yield 的
EnumerateStorageFile()
方法将存储文件内容遍历为KeyValuePair
(键和内容)。 - 通过
GetDuplicates()
和FetchDuplicate()
支持重复键。 FreeCacheOnCommit
属性可调整索引内容内部提交时的内存使用。- 读操作使用单独的
Stream
,现在是多线程的。 - 自 v1.5 起,索引文件格式不向后兼容。
- 内部存储基于
int
记录号,而不是long
文件偏移量,因此可减少约 50% 的内存和索引文件大小。 - 混合 WAH 压缩位图索引现在用于重复索引。
RaptorDBString
引擎现已支持无限键大小(现在您可以将文件名存储在数据库中)。
限制
此版本存在以下限制:
键大小限制为 255 字节或等效字符串长度(ASCII 与 Unicode 大小)- 会话提交/回滚:由于多线程问题,此功能已推迟到以后。
- 压缩:支持已内置于记录头中,但尚未实现。
- 不支持删除项目,数据库只追加(很像 CouchDB)。
- 日志文件计数硬编码为 1,000,000:相当于连续运行 20 亿次插入(重新启动会将计数重置为 0,再进行 20 亿次插入)。
- 外部恢复程序
竞争产品
有许多竞争存储系统,我研究过的一些如下:
- HamsterDB:一个用 C++ 编写的令人愉快的引擎,在我使用 Aarons Watters 的索引代码时,它的速度给我留下了深刻印象。(
RaptorDB
现在把它生吞活剥了……咳咳!)它的 64 位版本相当大,有 600KB。 - Esent PersistentDictionary:CodePlex 上的一个项目,它是另一个项目的一部分,该项目实现了对内置 Windows esent 数据存储引擎的托管包装。在索引 40,000 个项目后,字典性能呈指数下降,索引文件仅在 GUID 键上增长。显然,与项目所有者交谈后,这是一个目前已知的问题。
- Tokyo/Kyoto Cabinet:一个非常快的 C++ 键值存储实现。Tokyo Cabinet 是 B+树索引器,而 Kyoto Cabinet 是 MurMur2 哈希索引器。
- 4aTech Dictionary:这是 CodeProject 上的另一篇文章,做同样的事情,其网站上的商业版本非常大(450KB),并且在索引 50,000 个项目后,在 GUID 键上的性能表现令人沮丧。
- BerkeleyDB:所有数据库的祖师爷,由 Oracle 拥有,有三种版本:C++ 键值存储、Java 键值存储和 XML 数据库。
性能测试
测试是在我的笔记本电脑上进行的,配置如下:AMD K625 1.5Ghz,4Gb Ram DDRII,Windows 7 家庭高级版 64位,Win Index 3.9(上图是我的硬盘类型(WD 5400 rpm 驱动器)的数据传输时间截图)
测试 | 插入时间 | 索引时间 B+树 | 索引时间 MurMur 哈希 |
前 100 万次插入 | 21 秒 | 61 秒 | 81 秒 |
接下来的 100 万次插入 | 21 秒 | 159 秒 | 142 秒 |
根据经验法则(至少在我的测试机上),B+树索引时间每索引 600,000 个键线性增加 1 秒。MurMur 哈希对于每 100 万个项目都保持一致,并且最多需要 4 秒,这主要是因为将所有桶写入磁盘(默认值约为 60MB)。
性能测试 v1.1
通过使用内存中索引,您可以牺牲内存使用量来缓存 RAM 中的索引,从而获得以下性能统计数据:测试 | 插入时间 | 索引时间 B+树 | 索引时间 MurMur 哈希 |
前 100 万次插入 | 21 秒 |
8.1 秒, SaveIndex() = 0.9 秒 |
4.3 秒, SaveIndex() = 1.1 秒 |
接下来的 100 万次插入 | 21 秒 |
8.2 秒, SaveIndex() = 1.5 秒 |
6.8 秒, SaveIndex() = 1.4 秒 |
如您所见,内存中索引与基于磁盘的索引相比非常快,因为所有操作都在内存中完成,并且每索引 MaxItemsBeforeIndexing
项后无需刷新到磁盘。您仍然可以将索引保存到磁盘,而且由于整个索引是一次性写入的,因此这也非常快。所有这些的缺点是,将所有索引保留在 RAM 中会带来内存开销。
性能测试 v1.7.5
在 1.7.5 版中,增加了一个插入 20,000,000 个 Guid
项目的测试,以对系统进行压力测试,同样这个测试是在上述机器上完成的,以下是结果:
Total Insert time = 552 seconds Total Insert + Indexing time = 578 seconds Peak Memory Usage = 1.6 Gb Fetch all values time = 408 seconds
Using the Code
使用代码非常简单,例如以下示例:
RaptorDB.RaptorDB rap = RaptorDB.RaptorDB.Open("docs\\data.ext", 16,true, INDEXTYPE.HASH);
Guid g = Guid.NewGuid();
rap.Set(g, new byte[]{1,2,3,4,5,6,7,8,9,0});
byte[] bytes=null;
if(rap.Get(g, out bytes))
{
//data found and in bytes array
}
Open
方法接受数据和索引文件的路径(它将使用文件名减去扩展名作为数据、索引和日志文件的名称,并在需要时创建目录)、最大键大小(示例中 GUID 键为 16)、一个布尔值指示是否允许重复键值,以及索引方法,即 BTREE
或 HASH
。
使用代码 v1.1
InMemoryIndex 和 SaveIndex
RaptorDB.RaptorDB rap = RaptorDB.RaptorDB.Open("docs\\data.ext", 16,true, INDEXTYPE.BTREE);
rap.InMemoryIndex = true;
// do some work
rap.SaveIndex(); // will save the index in memory to disk
工作原理
RaptorDB
有两个主要部分:内存中突发插入处理程序和后台索引器。当您开始向 RaptorDB
插入值时,系统会立即将数据写入存储文件和编号的日志文件。这确保了数据的一致性和免受崩溃影响,日志保存在内存中,本质上是一个键和指向存储文件的文件指针,即使索引器尚未启动,也可以快速内存访问数据。当日志文件中的项目达到 MaxItemsBeforeIndexing
计数时,会创建一个新的日志文件,并将旧的日志文件交给后台索引器队列。
后台索引器将使用您指定的方法开始索引日志文件内容,完成后将释放内存中的日志文件内容。这种设计确保了最大性能,并解决了索引更新方面的并发问题,这是 B+树索引器的症结所在(只有一个线程更新索引)。
键的搜索将首先命中内存中的当前日志文件,其次是内存中排队的日志文件,最后是基于磁盘的索引文件。
调整
有几个参数可以根据您的个人需求进行调整,其中包括:
- DEFAULTNODESIZE:这是每个索引页中的项目数量,默认值为 200。
- BUCKETCOUNT:这是一个素数,控制 MurMur 哈希的桶数量,默认值为 10007。
- MaxItemsBeforeIndexing:每个日志文件中的项目数量,以及在切换到新日志文件并开始索引之前内存中的项目数量。默认值为 20,000。
通常来说,如果您的项目数量少于 100 万(使用上述默认值),那么选择 B+树索引会更快。如果您的项目数量超过 200 万,那么 MurMur 哈希会更快,并且在每索引 100 万条记录内,索引时间是恒定的。
B+树
对于 B+树索引,唯一需要调整的参数是 DEFAULTNODESIZE
,值越大,树从根到叶节点的整体高度越小,这需要更少的硬盘查找和页面读取,但这会增加磁盘上的页面大小和内存使用量。
MurMur 哈希
对于 MurMur 索引,您有两个参数可以调整:DEFAULTNODESIZE
和 BUCKETCOUNT
。这些值的乘积为您提供了直接索引支持的最大数据计数(额外的数据将溢出到新的桶中,并需要另一次磁盘查找)。同样,DEFAULTNODESIZE
将决定页面磁盘大小。
索引文件大小
索引文件与索引中的页数成比例。您可以使用以下公式计算页面大小:
Page size = 23 + ( (max key size + 17) * items in page) ~ 23+(33*200) = 6,623 bytes [ for 16 byte keys ]
File header = 19 + ( 8 * bucketcount) ~ 19+(8*10007) = 80,075 bytes
对于 B+树索引,叶节点/页面在统计上是 70-80% 满的,内部叶节点大约 30-50% 满,平均总体大约 75% 满。因此,您需要比数据计数多大约 25% 的节点/页面。
对于 MurMur 哈希,索引文件无论如何都已分配给最大数据项数量,当您超出限制时,会溢出到额外的桶中。(例如,200*10007 ~ 200 万数据项限制,相当于 10007 * 6.6kb ~ 67Mb 索引文件)。
内存使用
突发插入 100 万条记录大约需要 80MB 的内存用于内部日志文件缓存,即 100 万 * (16 字节键 + 8 字节偏移量) + .NET 字典开销。
MurMur 哈希索引将占用大约 MaxItemsBeforeIndexing
* [页面大小] + .NET 内存开销 + [溢出页面] * [页面大小],对于默认值,如果页面已满,则约为 120MB。
B+树索引的内存使用量将随着数据大小的增加而增长,难以量化,但最坏情况下为 MaxItemsBeforeIndexing
* [页面大小]* (1+0.25 +0.1) + .NET 开销。
那故障怎么办?
有四种类型的故障:
- 索引过程中非干净关闭(断电/崩溃):对于 B+树索引,如果故障发生在写入磁盘之前,系统将自动恢复,没有问题;否则,需要外部恢复程序。对于 MurMur 哈希系统,系统崩溃对索引没有影响,系统可自动恢复。
- 索引文件损坏/删除:您将失去对数据的访问,但数据仍然存在。索引文件可以通过扫描数据文件以获取行并创建日志文件的外部恢复程序从数据文件重建。
- 日志文件损坏/删除:您将无法访问尚未完成索引的最新添加数据。您可以通过外部恢复程序再次提取日志文件。
- 数据文件损坏:您应该已经备份了数据!这种情况极其罕见,因为数据不会被覆盖,只会被追加,所以最多您可能会丢失最后插入的记录,但外部恢复程序可以扫描数据文件并恢复有效数据。
数据在插入时会自动刷新,因此上述情况很少发生,但知道 RaptorDB
是可恢复的确实让人安心。
我的牢骚
自从我开始为 CodeProject 撰写文章以来,我一直惊讶于我们编写的代码在当前任务中是多么臃肿和不成比例地庞大,这体现在这篇文章、我之前的 fastJSON 文章,甚至迷你 log4net 替代品中。我会进一步推论,抨击 RDBM 供应商,并提出一个问题:为什么我可以在托管运行时语言上实现 80% 的硬盘速度,而他们的产品在原生代码上的性能却如此糟糕?可以说他们做了更多,他们也确实如此,但问题仍然是他们可以做得更好,用更少的代码和更小的可执行文件。我理解开发人员的处境,他们必须在经理和客户的催促下尽快完成工作,他们选择最简单的路径,以最短的时间交付产品,从而牺牲了质量、速度和大小。
我建议我们作为开发人员,对我们所做的工作感到一点自豪,并创建/编写更好、更优化的程序,从而在短期和长期内使整个过程中的每个人都受益。
vNext、未来工作和号召
我计划在 MongoDB 和 CouchDB 领域,朝着一个成熟的文档数据库的普遍方向实现以下功能。
- 通过 Map/Reduce 或类似功能可查询内容
- 列/位图定向索引,用于对海量数据进行快速查询
因此,如果有人对上述内容感兴趣,并愿意接受编写非臃肿且高度优化代码的挑战,请与我联系。
结语
很多时候,我们需要看到可能才能相信,所以请表达您的爱意,投 5 票。
附录 1 - v1.7 更改
自版本 1.7 起,进行了大量更改,其中最突出的是使用 WAH 压缩位图索引对重复键值进行编码。这种新的位图索引在存储和查询性能方面都非常高效,并为未来具有完整查询能力的RaptorDB
版本铺平了道路。读取速度现在与保存性能持平,在我的测试机上,100 万次读取在 16 秒内完成。
单元测试项目已添加到
RaptorDB
以验证一切正常。主要方法
Set(Guid, byte[]) |
设置 Guid 键和字节数组值,返回 void |
Set(string, string) |
设置字符串键和字符串值,返回 void |
Set(string, byte[]) |
设置字符串键和字节数组值,返回 void |
Set(byte[], byte[]) |
设置字节数组键和字节数组值,返回 void |
Get(Guid, out byte[]) |
获取 Guid 键并将其放入字节数组输出参数中,如果找到键则返回 true |
Get(string, out string) |
获取字符串键并将其放入字符串输出参数中,如果找到键则返回 true |
Get(string, out byte[]) |
获取字符串键并将其放入字节数组输出参数中,如果找到键则返回 true |
Get(byte[], out byte[]) |
获取字节数组键并将其放入字节数组输出参数中,如果找到键则返回 true |
EnumerateStorageFile() |
以 IEnumerable< KeyValuePair 形式返回主存储文件的所有内容 |
GetDuplicates(byte[]) |
以 IEnumerable 形式返回指定重复键的主存储文件记录号列表 |
FetchDuplicate(int) |
从主存储文件返回 byte[] 类型的值,与 GetDuplicates 一起使用 |
Count() |
返回数据库中的项目数量 |
SaveIndex() |
如果 InMemoryIndex 为 true ,则会将内存中的索引保存到磁盘并清除已处理的日志文件。 |
SaveIndex(bool) |
与上述相同,但如果参数设置为 true,则所有内存中的日志文件都将被刷新到磁盘,并且日志将从计数 0 重新开始。 |
Shutdown() |
这将关闭所有文件并停止引擎。 |
附录 2 - 文件格式
RaptorDB 中使用以下文件格式:- *.mgdat:保存键的实际值,是主文件
- *.mgrec:保存记录号到 mgdat 文件偏移量映射,用于信息检索
- *.mgidx:保存 B+树或 Murmur 哈希的索引信息
- *.mglog:保存预索引信息
- *.mgbmp:保存键重复的位图编码记录号
- *.mgbmr:保存记录号到 mgbmp 文件偏移量映射
文件格式 : *.mgdat
文档以 JSON 格式存储在磁盘上,结构如下:
文件格式 : *.mgbmp
位图索引以以下格式存储在磁盘上:
位图行长度可变,如果新数据适合磁盘上的记录大小,则会被重用;否则将创建新的记录。因此,可能需要定期进行索引压缩,以删除先前更新留下的未使用记录。
文件格式 : *.mgidx
B+树和 MurMurHash 索引以以下格式存储:
文件格式 : *.mgbmr , *.mgrec
Rec 文件是一系列写入磁盘的long
值,没有特殊格式。 这些值将记录号映射到 BITMAP 索引文件和 DOCS 存储文件中的偏移量。文件格式 : *.mglog
预索引日志信息以下列格式存储:
附录 3 - v1.8
此版本进行了许多更改,其中最大的变化是将内部结构转换为泛型。此外,RaptorDBString
引擎现在支持无限键大小。
输入节流已被移除,因为日志文件显示它不再需要(见下文)。
现在,如果 Globals.SaveMemoryIndexOnShutdown
设置为 true
,Shutdown 将把内存中的日志刷新到索引。这显然会增加关闭过程的时间。
使用 RaptorDBString 和 RaptorDBGuid
新增了两个引擎,一个用于无限键大小的 RaptorDBString
,一个用于快速处理 Guid 键的 RaptorDBGuid
。这两个引擎接收输入数据,并使用 MurMur 哈希生成一个 int32
,该 int32 用作 B+树的键。
这提供了速度改进,最重要的是减少了内存使用。缺点是由于键经过哈希处理,您将失去 B+树中的排序顺序。对于 Guid
键来说这没有区别,但对于字符串键,您必须意识到这一点。
由于使用了哈希,因此可能存在哈希冲突,引擎将处理此问题并在需要时返回正确的数据(内部将其编码为重复键,因此在使用 GetDuplicates
时必须注意这一点)。
RaptorDBString
构造函数可以接受一个参数,该参数对字符串大小写敏感。
var db = new RaptorDBGuid(@"c:\RaptorDbTest\RawFileOne");
var rap = new RaptorDBString(@"c:\raptordbtest\longstringkey", false); // case in-sensitive
索引性能
查看此版本生成的日志文件显示,在连续插入 10,000,000 条记录时(在我的测试机上),索引器线程平均落后插入线程 3.8 个日志(3.8 * 20,000 = 76,000 条记录)。这相当可观,这就是为什么取消了输入节流的原因。
新内部数据类型 rdbInt, rdbLong, rdbByteArray
转换为泛型需要创建新的数据类型来包装 int32
、long
和 byte[]
类型。这是为了实现泛型引擎所需的接口。通过这三种类型,您可以存储任何其他值类型键,因为它们将直接映射到其中一种类型。下面是 rdbInt 的一个示例,以及 IRDBDataType<>
接口的实现。
public class rdbInt : IRDBDataType<rdbInt>
{
public rdbInt()
{
}
public rdbInt(int i)
{
_i = i;
}
public rdbInt(uint i)
{
_i = (int)i;
}
private int _i;
public int CompareTo(rdbInt other)
{
return _i.CompareTo(other._i);
}
public byte[] GetBytes()
{
return Helper.GetBytes(_i, false);
}
public bool Equals(rdbInt x, rdbInt y)
{
return x._i == y._i;
}
public int GetHashCode(rdbInt obj)
{
return obj._i;
}
public bool Equals(rdbInt other)
{
return this._i == other._i;
}
public rdbInt GetObject(byte[] buffer, int offset, int count)
{
return new rdbInt(Helper.ToInt32(buffer, offset));
}
public override bool Equals(object obj)
{
return this._i == ((rdbInt)obj)._i;
}
public override int GetHashCode()
{
return _i;
}
}
历史
- 首次发布 v1.0:2011年5月3日
- 更新 v1.1:2011年5月23日
- 数据行头读取 Bug 修复
- Count() 函数
- InMemoryIndex 和 SaveIndex()
- 重构项目文件和文件夹
- SafeDictionary 在 logfile.cs 中 byte[] 键的 Bug 修复 -> .net Dictionary 无法处理 byte[] 键,GetHashCode 存在问题()
- 更新 v1.2:2011年5月29日
- EnumerateStorageFile() : keyvaluepair -> 使用 yield
- GetDuplicates(key)
- FetchDuplicate(offset)
- FreeCacheOnCommit 属性
- 更新 v1.3:2011年6月1日
- 感谢 cmschick 测试 string,string 版本
- StorageFile.cs 中 CheckHeader() 的 bug 修复
- LogFile.cs 中 Get() 的 bug 修复
- 更新 v1.4:2011年6月13日
- 添加了 short、int、long 字节的反转(大端),使其在字节比较中正确排序(升序)
- 数据文件上的读取操作使用另一个流
- Bug 修复:keypointer 中未设置重复页面
- 更新 v1.4.1:2011年6月17日
- 感谢目光敏锐的 Nicolas Santini,修复了 helper.compare 中一个愚蠢的错别字
- 更新 v1.5:2011年6月22日
- 将 long 更改为 int,以减小索引大小和降低内存使用,您应该能节省 50%
- 所有内部操作都基于记录号而不是文件偏移量
- 添加了一个新文件(mgrec 扩展名),用于将记录号(int)映射到文件偏移量(long)
- ** 索引文件与 v1.5 之前版本不向后兼容 **
- 不安全位转换例程中的 bug,在使用 ms 版本直到找到解决方法,感谢 Dave Dolan 的测试
- 更新 v1.5.1:2011年6月28日
- 不安全位转换已修复
- 存储写入磁盘速度优化,删除了慢速 file.seek 和 file.length(令人惊讶的是,从 long 转换为 int 导致了约 50% 的性能下降,现在恢复到原始代码速度的约 5%)
- 更新 v1.5.2:2011年7月10日
- 索引文件中的 int seek overflow bug 修复
- 感谢 atlaste ,所有文件都改为 flush(true)
- 哈希重复代码 Bug 修复
- 更新 v1.6:2011年7月31日
- IndexerThread 将持续进行索引,直到队列为空
- 解决了 Get() 和 IndexerThread 的并发问题
- 将 flush(true) 替换为 flush() 以兼容 .net2
- 更新 v1.7 :2011年10月15日
- 实现 Shutdown() 以关闭所有文件并停止引擎
- 重复项现在编码为 WAH 位数组(混合 B+树/位图和哈希/位图索引)
- 针对优化的内存使用而设计的特殊 WAH 位图
- ** 由于重复编码和比较更改,索引文件与 v1.7 之前的版本不兼容 **
- 现在使用 IEnumerable<> 而不是 List<> 以降低内存使用
- 代码重构和清理
- SaveIndex(true) 现在将所有内存结构刷新到磁盘索引
- 添加了单元测试项目
- 优化了 compare() 例程,速度提高了约 18%,感谢 Dave Dolan
- 读取速度优化了 10 倍
- 位图索引的读写流分离
- 文章添加了附录 1、2
- 更新 v1.7.5:2011年10月21日
- 添加了 ThrottleInputWhenLogCount = 400,这在日志队列中达到约 800 万个项目后启动
- btree 缓存清除现在只清除叶节点以节省内存并保持性能
- 优化了索引器线程
- 优化了 Dave 的 compare()
- 在单元测试中添加了 Twenty_Million_Set_Get_BTREE() 测试
- 感谢 chprogramer 的测试,修复了 shutdown() 的 bug
- 内部缓存从 SortedList 迁移到 Dictionary
- 重命名了文章标题,为下一个版本腾出空间
- 添加 raptordb 标志
- 更新 v1.8:2011年11月13日
- 哈希缓存已激活,读取速度与 B+树持平
- 关闭时删除零长度文件
- 哈希关闭 Bug 修复
- 新的测试以测试关闭和刷新逻辑
- 添加了具有无限键大小的 RaptorDBString
- Count() 现在使用存储文件内容而不是索引(对于行数巨大的情况(>1000 万)更快)
- 将内部结构改为泛型
- 实现了 IDisposable 以实现干净关闭
- 优化了位图索引读取速度
- 添加了 minilogger 用于日志进度文件
- WAHBitArray bug 修复
- Shutdown 可以根据标志将内存日志刷新到索引
- 在 Global 中添加了 SaveMemoryIndexOnShutdown
- 为 v1.8 更改添加了附录 3
- 2012年1月23日:修复了 v2 的链接