大量小文件读写的性能优化

951 views
Skip to first unread message

Yan Wang

unread,
Nov 18, 2012, 1:29:55 AM11/18/12
to ustc...@googlegroups.com

前段时间看到群上讨论用NoSQL数据库管理大量小文件的思路,觉得很有意思,正好自己也面临着这样的需求,所以做了一些实验和分析,也有一些问题,希望和大家分享一下。

这个主要是用自己写的缓存系统和MongoDB做了一些实验。基本的设定是这样的:我有8000个左右的零碎小文件,每个文件存储的是145行纯文本的小数(就是一个145维的向量)。缓存系统的实现很土鳖,输入是一个整数id,如果这个id之前没有被读取过,会去取相应的文件,parse成一个double数组,缓存在内存里,然后返回。MongoDB那边采用C# driver的FindOneAs直接读,在这个整数id上面(不仅是ObjectID)加了indexer,本机做服务器,没有SSL/Username限定,没有自己再实现一轮缓存机制了。

原先的想法是NoSQL数据库一般有很好的内存缓存机制(其实这个假定没有读过相关资料,只是印象里是这样),所以可以取代自己写的缓存。但动手把MongoDB用到实际的代码中去后发现速度下降了很多(时间约是原来的4~5倍),而且不怎么稳定,有时会出现连接丢失。

所以初步的结论是在近乎同等时间的投入下,小文件+缓存机制比MongoDB性能优越不少。究其原因我最怀疑的就是MongoDB本来就不是拿来当cache用的。(找到了这个帖子但不是很理解。。)

关于如何处理这种大量小文件读写的问题,应该问的第一个问题就是:这种大量小文件的存取是不是必须的?我面临的情况基本就是一个对象数组需要持久化存储。它有很多域,会一个一个域进行计算,比如最开始填文件名进去,然后每个文件做一些处理extract features,然后根据feature每个文件又有个classifier等等。我采用的是对文件的每个域都用一个文件存储的方案,比如1.png是原来的文件名,那么1_feature.txt就是feature域的内容,然后1_classifier.txt就是classifier的内容。
与此相对的应该就是大文件serialization的方案。我们可以建立一个大对象的数组,每个对象里面包含了图片文件名(或者内容),feature, classifier的所有信息,然后serialize在磁盘上。每轮处理的时候集中读入,然后处理,然后集中写出。但这样会有一些缺点:
1) serialization对对象的结构非常敏感。如果我们用binary serialization,一旦更改这个大对象的设计,比如新加了一个域,原来的文件就全废了,读不进来。相反,一大堆小文件却没有这样的问题,如果新加一个域,大不了加一种小文件比如1_newField.txt就是了。如果自己设计serialization的格式和读写函数的话,固然可以解决这个问题,但一方面麻烦,一方面面临着下一个问题。
2) 持久化和持久化之间间隔太长。由于我们每次要处理几千个数据,如果中间因为内存爆了,数据格式不对等等原因程序挂了,前面的结果就全白跑了。但如果用小文件的话很容易找到哪里断了然后继续跑。
这两个原因:数据结构的灵活性和数据存储的稳定性是我选择小文件存储的原因。

考虑到这其实是个很常见的问题(一个对象数组需要持久化),所以一方面分享一下实验的结果,一方面想请教一下大家,你们在实际中有没有遇到这样的问题,是怎么处理的呢?传统数据库?NoSQL?RAMDisk?为什么这么做呢?

谢谢!

Best Regards,

Yan


Bojie Li

unread,
Nov 18, 2012, 5:11:24 AM11/18/12
to USTC_LUG
2012/11/18 Yan Wang <gra...@gmail.com>:

> 前段时间看到群上讨论用NoSQL数据库管理大量小文件的思路,觉得很有意思,正好自己也面临着这样的需求,所以做了一些实验和分析,也有一些问题,希望和大家分享一下。
>
> 这个主要是用自己写的缓存系统和MongoDB做了一些实验。基本的设定是这样的:我有8000个左右的零碎小文件,每个文件存储的是145行纯文本的小数(就是一个145维的向量)。缓存系统的实现很土鳖,输入是一个整数id,如果这个id之前没有被读取过,会去取相应的文件,parse成一个double数组,缓存在内存里,然后返回。MongoDB那边采用C#
> driver的FindOneAs直接读,在这个整数id上面(不仅是ObjectID)加了indexer,本机做服务器,没有SSL/Username限定,没有自己再实现一轮缓存机制了。

你确定没打错0的个数?如果只有8000个文件,每个文件里只有145个double,写个C程序开个二维数组,初始化时把所有数据读入,我打赌比任何缓存策略都快。

>
> 原先的想法是NoSQL数据库一般有很好的内存缓存机制(其实这个假定没有读过相关资料,只是印象里是这样),所以可以取代自己写的缓存。但动手把MongoDB用到实际的代码中去后发现速度下降了很多(时间约是原来的4~5倍),而且不怎么稳定,有时会出现连接丢失。

与MongoDB之间的连接是用的TCP socket还是UNIX socket?会不会是时间都耗在通信上了?可以用gprof看看(我没用过)。

我觉得对于key-value的应用,MySQL比redis慢很大程度上不是因为数据结构或者算法上的因素,而是MySQL需要解析SQL语句并生成查询计划、做用户权限检查、锁表等,这些overhead是相当可观的,而redis因为专一,所以overhead很小。其实redis传说中的几万Query
Per Second在进程内部实现面前都是浮云,单是进程间通信的代价就可以让两者没有可比性。因此我觉得对于计算不复杂的统计类数据分析任务,只要瓶颈不在磁盘I/O上,都有优化的余地。

我现在批量处理MySQL数据时,都不是用PHP一条一条地执行INSERT语句,这样会慢死;而是把数据库各字段按照字段顺序输出到文件(FIFO),然后用MySQL的load
data infile来把文件导入进去。反复执行SQL查询,网络通信成本和MySQL解析SQL的成本都承受不起。

>
> 所以初步的结论是在近乎同等时间的投入下,小文件+缓存机制比MongoDB性能优越不少。究其原因我最怀疑的就是MongoDB本来就不是拿来当cache用的。(找到了这个帖子但不是很理解。。)

我不了解MongoDB。现代文件系统大多用B+树组织目录结构,MySQL的索引好像用的也是B+树,这东西没有什么高深的,自己实现一个性能也不会差到哪里去。

我觉得数据库相对小文件+缓存的主要优势有:

1、磁盘文件系统的小文件在物理存储上很可能是不连续的(尽管文件系统可能会尽力将同一目录的文件分配到相近的物理块),即使存储在同一片磁盘区域,遍历目录结构时也很可能是跳跃式的访问;而数据库每个表是一个单独的文件,所有文件系统都会尽力将单个文件存储到连续的磁盘空间。因此访问大量小文件会带来很多的磁盘寻道,首次访问(没有读进文件系统缓存)时会非常慢。而数据库肯定会尽量连续地扫描文件,因此不会有特别多的磁盘寻道,在无缓存情况下性能比随机磁盘访问高。

2、数据库的实现是用户态的,除了从磁盘上读取文件,不需要切换到内核态;文件系统的实现是内核态的,所有文件操作不论大小,都要经过一次用户态/内核态的切换,可能还被进程调度插上一腿。

3、文件系统要做路径解析和权限检查,key-value数据库不需要,这方面文件系统可能有overhead。

4、数据库一般会有一块相当大的预读缓冲,文件系统预读缓冲的大小设置一般比较保守。数据库知道表结构的元数据部分是会被经常用到的,它们不会被清除出缓存;而文件系统则不知道哪些文件是会被经常用到的,只能根据一些算法来动态清除“可能以后不太会被用到”的文件缓存。我估计这两个区别对性能影响不大。

因此我不理解小文件为什么会比数据库还要慢。

>
> 关于如何处理这种大量小文件读写的问题,应该问的第一个问题就是:这种大量小文件的存取是不是必须的?我面临的情况基本就是一个对象数组需要持久化存储。它有很多域,会一个一个域进行计算,比如最开始填文件名进去,然后每个文件做一些处理extract
> features,然后根据feature每个文件又有个classifier等等。我采用的是对文件的每个域都用一个文件存储的方案,比如1.png是原来的文件名,那么1_feature.txt就是feature域的内容,然后1_classifier.txt就是classifier的内容。
> 与此相对的应该就是大文件serialization的方案。我们可以建立一个大对象的数组,每个对象里面包含了图片文件名(或者内容),feature,
> classifier的所有信息,然后serialize在磁盘上。每轮处理的时候集中读入,然后处理,然后集中写出。但这样会有一些缺点:

我认为,文件系统就是天然的大文件数据库,对于大图片之类的东西,完全没有必要把图片内容存进数据库。可以像git那样,用文件内容的SHA1值后38位做文件名,前2位做目录名,这样还兼具了自动合并相同文件的功能;如果以后一台机器放不下,也很容易做相对均匀的分片。也可以跟discuz论坛程序一样,用文件上传日期作为目录名,管理起来比较方便。

关于图片的元数据,比如作者、权限之类,我觉得从逻辑上应该放在文件的元数据里(与UNIX的owner、group、权限等放在一起)。但考虑到开发成本,一般网站不会这样做。文件系统应该可以给元数据添加任意字段吧?

我看过一篇文章,某网站每次访问图片文件都查询数据库的方式扛不住之后,就干脆把元数据编码到文件名里,相当于用户请求这些文件时,URL里已经包含这些元数据了。

> 1) serialization对对象的结构非常敏感。如果我们用binary
> serialization,一旦更改这个大对象的设计,比如新加了一个域,原来的文件就全废了,读不进来。相反,一大堆小文件却没有这样的问题,如果新加一个域,大不了加一种小文件比如1_newField.txt就是了。如果自己设计serialization的格式和读写函数的话,固然可以解决这个问题,但一方面麻烦,一方面面临着下一个问题。

自己设计serialize的格式,我觉得不如用NoSQL的对象存储更合适吧,那些成熟系统里的serialize方法估计比自己设计的更合理,而且可以支持一些条件查询。

> 2)
> 持久化和持久化之间间隔太长。由于我们每次要处理几千个数据,如果中间因为内存爆了,数据格式不对等等原因程序挂了,前面的结果就全白跑了。但如果用小文件的话很容易找到哪里断了然后继续跑。
> 这两个原因:数据结构的灵活性和数据存储的稳定性是我选择小文件存储的原因。

用的什么编程语言?数据是自动生成的还是手工输入的?为什么会出现“内存爆了、数据格式不对”这些问题?即使用NoSQL或者MySQL,已经插入的数据也不会因为用户程序崩溃而丢失啊。

扯了这么多,算是抛砖引玉,我没有经验 :)

>
> 考虑到这其实是个很常见的问题(一个对象数组需要持久化),所以一方面分享一下实验的结果,一方面想请教一下大家,你们在实际中有没有遇到这样的问题,是怎么处理的呢?传统数据库?NoSQL?RAMDisk?为什么这么做呢?
>
> 谢谢!
>
> Best Regards,
>
> Yan
>
>

> --
> -- 来自USTC LUG
> 请使用gmail订阅,不要灌水。
> 更多信息more info:http://groups.google.com/group/ustc_lug?hl=en?hl=en
>
>
>

Yan Wang

unread,
Nov 18, 2012, 5:26:43 PM11/18/12
to ustc...@googlegroups.com
谢谢你的回复。我的回复见下。

2012/11/18 Bojie Li <boj...@gmail.com>

2012/11/18 Yan Wang <gra...@gmail.com>:
> 前段时间看到群上讨论用NoSQL数据库管理大量小文件的思路,觉得很有意思,正好自己也面临着这样的需求,所以做了一些实验和分析,也有一些问题,希望和大家分享一下。
>
> 这个主要是用自己写的缓存系统和MongoDB做了一些实验。基本的设定是这样的:我有8000个左右的零碎小文件,每个文件存储的是145行纯文本的小数(就是一个145维的向量)。缓存系统的实现很土鳖,输入是一个整数id,如果这个id之前没有被读取过,会去取相应的文件,parse成一个double数组,缓存在内存里,然后返回。MongoDB那边采用C#
> driver的FindOneAs直接读,在这个整数id上面(不仅是ObjectID)加了indexer,本机做服务器,没有SSL/Username限定,没有自己再实现一轮缓存机制了。

你确定没打错0的个数?如果只有8000个文件,每个文件里只有145个double,写个C程序开个二维数组,初始化时把所有数据读入,我打赌比任何缓存策略都快。

我实际要处理的文件个数从8000到50000不等,但这里做实验的个数只有8000个。其实我想要的也就是个缓存而已,只是自己写还挺麻烦的,而且每次加一个新的域还要自己加新的接口,所以想着如果用NoSQL会不会或者更快,或者稍微慢一些但编程上需要花的时间少很多。

>
> 原先的想法是NoSQL数据库一般有很好的内存缓存机制(其实这个假定没有读过相关资料,只是印象里是这样),所以可以取代自己写的缓存。但动手把MongoDB用到实际的代码中去后发现速度下降了很多(时间约是原来的4~5倍),而且不怎么稳定,有时会出现连接丢失。

与MongoDB之间的连接是用的TCP socket还是UNIX socket?会不会是时间都耗在通信上了?可以用gprof看看(我没用过)。

我是在windows里面做的,用的我猜是TCP Socket,因为connection string用的是mongodb://localhost,而不是像SQLServer那样用命名管道。(这块不是很熟)

我觉得对于key-value的应用,MySQL比redis慢很大程度上不是因为数据结构或者算法上的因素,而是MySQL需要解析SQL语句并生成查询计划、做用户权限检查、锁表等,这些overhead是相当可观的,而redis因为专一,所以overhead很小。其实redis传说中的几万Query
Per Second在进程内部实现面前都是浮云,单是进程间通信的代价就可以让两者没有可比性。因此我觉得对于计算不复杂的统计类数据分析任务,只要瓶颈不在磁盘I/O上,都有优化的余地。

我现在批量处理MySQL数据时,都不是用PHP一条一条地执行INSERT语句,这样会慢死;而是把数据库各字段按照字段顺序输出到文件(FIFO),然后用MySQL的load
data infile来把文件导入进去。反复执行SQL查询,网络通信成本和MySQL解析SQL的成本都承受不起。

赞同,以前也用过类似的技巧。 
>
> 所以初步的结论是在近乎同等时间的投入下,小文件+缓存机制比MongoDB性能优越不少。究其原因我最怀疑的就是MongoDB本来就不是拿来当cache用【的。(找到了这个帖子但不是很理解。。)


我不了解MongoDB。现代文件系统大多用B+树组织目录结构,MySQL的索引好像用的也是B+树,这东西没有什么高深的,自己实现一个性能也不会差到哪里去。

我觉得数据库相对小文件+缓存的主要优势有:

1、磁盘文件系统的小文件在物理存储上很可能是不连续的(尽管文件系统可能会尽力将同一目录的文件分配到相近的物理块),即使存储在同一片磁盘区域,遍历目录结构时也很可能是跳跃式的访问;而数据库每个表是一个单独的文件,所有文件系统都会尽力将单个文件存储到连续的磁盘空间。因此访问大量小文件会带来很多的磁盘寻道,首次访问(没有读进文件系统缓存)时会非常慢。而数据库肯定会尽量连续地扫描文件,因此不会有特别多的磁盘寻道,在无缓存情况下性能比随机磁盘访问高。

2、数据库的实现是用户态的,除了从磁盘上读取文件,不需要切换到内核态;文件系统的实现是内核态的,所有文件操作不论大小,都要经过一次用户态/内核态的切换,可能还被进程调度插上一腿。

3、文件系统要做路径解析和权限检查,key-value数据库不需要,这方面文件系统可能有overhead。

4、数据库一般会有一块相当大的预读缓冲,文件系统预读缓冲的大小设置一般比较保守。数据库知道表结构的元数据部分是会被经常用到的,它们不会被清除出缓存;而文件系统则不知道哪些文件是会被经常用到的,只能根据一些算法来动态清除“可能以后不太会被用到”的文件缓存。我估计这两个区别对性能影响不大。

因此我不理解小文件为什么会比数据库还要慢。

谢谢你的分析,不过你是不是把我的结论看反了?我的实验结果就是在这个实验设定下,文件系统是比数据库要快的。 

>
> 关于如何处理这种大量小文件读写的问题,应该问的第一个问题就是:这种大量小文件的存取是不是必须的?我面临的情况基本就是一个对象数组需要持久化存储。它有很多域,会一个一个域进行计算,比如最开始填文件名进去,然后每个文件做一些处理extract
> features,然后根据feature每个文件又有个classifier等等。我采用的是对文件的每个域都用一个文件存储的方案,比如1.png是原来的文件名,那么1_feature.txt就是feature域的内容,然后1_classifier.txt就是classifier的内容。
> 与此相对的应该就是大文件serialization的方案。我们可以建立一个大对象的数组,每个对象里面包含了图片文件名(或者内容),feature,
> classifier的所有信息,然后serialize在磁盘上。每轮处理的时候集中读入,然后处理,然后集中写出。但这样会有一些缺点:

我认为,文件系统就是天然的大文件数据库,对于大图片之类的东西,完全没有必要把图片内容存进数据库。可以像git那样,用文件内容的SHA1值后38位做文件名,前2位做目录名,这样还兼具了自动合并相同文件的功能;如果以后一台机器放不下,也很容易做相对均匀的分片。也可以跟discuz论坛程序一样,用文件上传日期作为目录名,管理起来比较方便。

关于图片的元数据,比如作者、权限之类,我觉得从逻辑上应该放在文件的元数据里(与UNIX的owner、group、权限等放在一起)。但考虑到开发成本,一般网站不会这样做。文件系统应该可以给元数据添加任意字段吧?

我看过一篇文章,某网站每次访问图片文件都查询数据库的方式扛不住之后,就干脆把元数据编码到文件名里,相当于用户请求这些文件时,URL里已经包含这些元数据了。

对于这一点在第一个email里引用的贴子里也提到了,你这么一解释我觉得也挺有道理的。 
 
> 1) serialization对对象的结构非常敏感。如果我们用binary
> serialization,一旦更改这个大对象的设计,比如新加了一个域,原来的文件就全废了,读不进来。相反,一大堆小文件却没有这样的问题,如果新加一个域,大不了加一种小文件比如1_newField.txt就是了。如果自己设计serialization的格式和读写函数的话,固然可以解决这个问题,但一方面麻烦,一方面面临着下一个问题。

自己设计serialize的格式,我觉得不如用NoSQL的对象存储更合适吧,那些成熟系统里的serialize方法估计比自己设计的更合理,而且可以支持一些条件查询。

对的,如果NoSQL在性能上可以做到和自己实现的缓存差不多的话,我也觉得这是最理想的方案:编码量最小,速度也快。但现在实验4~5倍的性能损耗实在是受不了。你是在建议用NoSQL做serialization的后端,加上一个自己实现的缓存吗? 

> 2)
> 持久化和持久化之间间隔太长。由于我们每次要处理几千个数据,如果中间因为内存爆了,数据格式不对等等原因程序挂了,前面的结果就全白跑了。但如果用小文件的话很容易找到哪里断了然后继续跑。
> 这两个原因:数据结构的灵活性和数据存储的稳定性是我选择小文件存储的原因。

用的什么编程语言?数据是自动生成的还是手工输入的?为什么会出现“内存爆了、数据格式不对”这些问题?即使用NoSQL或者MySQL,已经插入的数据也不会因为用户程序崩溃而丢失啊。

 我的意思是,读文件是为了进行一些处理的,这个处理的过程是一个科学计算的过程,比如可能会程序有没有查出来的Bug,内存管理不当等等。关于NoSQL或者MySQL,我承认这个不会因为用户程序崩溃而丢失,只是在解释为什么需要处理这样大量的小数据。关于为什么不选择这些数据库程序,一个关键的问题就是根据目前的实验,NoSQL的性能实在不算好(前面你也详细的分析了原因)。所以似乎目前最好的方案还是自己写个缓存,后台不论用小文件做存储还是用NoSQL做存储?

扯了这么多,算是抛砖引玉,我没有经验 :)

非常感谢你的分析!有很多启发! 

Bojie Li

unread,
Nov 18, 2012, 9:00:43 PM11/18/12
to ustc...@googlegroups.com
哦,我没把你的结论看反,而是最后总结那一句写反了。前面理论分析的结果是数据库应该比文件系统快,但实验结果刚好相反,除了网络通信开销之外,我找不出合理的解释。

有个重要的问题:这台机器是单核的还是多核的?负载高吗?如果是单核,应用进程和mongodb进程就要不停地切换,这代价也太高了。

另外能否说下你测试结果的具体数据?文件系统的吞吐量是多少?MongoDB又是多少?我记得文件系统的读QPS是几千到一万。

Zhang Cheng

unread,
Nov 18, 2012, 9:55:01 PM11/18/12
to USTC LUG
我自己没有实践用过任何一种数据库,所以没有“手感”。我公司的dba就坐我旁边,刚向他请教了一下。从描述中,我感觉你的需求比较合适使用redis,而不是mongodb。mongodb从分类上说是文档型的,虽然也有key(_id),但是查询的时候可以用value的任何一个字段,因此需要建索引。而redis就是纯key-value的,只能用key索引,因此在查询的效率上更高一些。另外,mongodb的缓存是靠操作系统的page cache来做的,mongodb本身没有控制力(用mmap的方式读取文件)。

至于你的具体场景,我自己没有碰到过这样的需求,所以没有经验。

2012/11/18 Yan Wang <gra...@gmail.com>



--
Cheng,
Best Regards

Bojie Li

unread,
Nov 19, 2012, 6:54:50 AM11/19/12
to USTC_LUG
2012/11/19 Zhang Cheng <steph...@gmail.com>:
> mongodb从分类上说是文档型的,虽然也有key(_id),但是查询的时候可以用value的任何一个字段,因此需要建索引。

Mongodb会自动给value的每个字段建索引?这样开销有点大啊。MySQL里除了主键(相当于NoSQL里的key)之外,默认是不建索引的,如果为不可能用来查询的字段建立索引,不仅增加了存储空间,插入、删除、修改数据时还要更新这些索引,浪费时间。

Zhang Cheng

unread,
Nov 19, 2012, 7:13:09 AM11/19/12
to USTC LUG
默认只对_id字段(key)建索引,需要手动指定给其他哪些字段建索引。

2012/11/19 Bojie Li <boj...@gmail.com>

Mongodb会自动给value的每个字段建索引?这样开销有点大啊。MySQL里除了主键(相当于NoSQL里的key)之外,默认是不建索引的,如果为不可能用来查询的字段建立索引,不仅增加了存储空间,插入、删除、修改数据时还要更新这些索引,浪费时间。



--
Cheng,
Best Regards

Yan Wang

unread,
Nov 19, 2012, 9:57:36 AM11/19/12
to ustc...@googlegroups.com
+1, 我也只对里面的id(不是ObjectID,在原来的email里面也有说明)建了索引。

好的,那我下面试试redis,有结果了再更新。谢谢!

Best Regards,
Yan




2012/11/19 Zhang Cheng <steph...@gmail.com>

Yan Wang

unread,
Nov 19, 2012, 10:02:16 AM11/19/12
to ustc...@googlegroups.com
原来是这样的。

机器是多核的,负载比较复杂。。因为只在跑这个实验,所以可以认为没有其他东西干扰。但这个实验本身是CPU intensive的,所以CPU占用到了80~90%,但又没有满。文件系统用的是SSD,吞吐量。。没有定量测试过。。不过理解了你的思路,就是想看看MongoDB的流程中到底哪一步出了问题。

就是一个地方有点疑惑,之前不是说全部读到内存里肯定是最快的吗?为什么后面又说数据库会更快呢?可能我没有说清楚,那个自己实现的缓存就是全部读到内存里,然后就只是内存操作了。从这个角度看MongoDB本来就应该更慢才对。之前令我意外的是慢了这么多。我以为它内部有个很好的缓存机制能够起到“全部读到内存里”的作用的。但现在看了Cheng的回复也有可能是我选的数据库不对。

谢谢费心指教!

Best Regards,
Yan




2012/11/18 Bojie Li <boj...@gmail.com>

Bojie Li

unread,
Nov 20, 2012, 12:31:00 AM11/20/12
to USTC_LUG
2012/11/19 Yan Wang <gra...@gmail.com>:

> 原来是这样的。
>
> 机器是多核的,负载比较复杂。。因为只在跑这个实验,所以可以认为没有其他东西干扰。但这个实验本身是CPU
> intensive的,所以CPU占用到了80~90%,但又没有满。文件系统用的是SSD,吞吐量。。没有定量测试过。。不过理解了你的思路,就是想看看MongoDB的流程中到底哪一步出了问题。
>
> 就是一个地方有点疑惑,之前不是说全部读到内存里肯定是最快的吗?为什么后面又说数据库会更快呢?可能我没有说清楚,那个自己实现的缓存就是全部读到内存里,然后就只是内存操作了。从这个角度看MongoDB本来就应该更慢才对。之前令我意外的是慢了这么多。我以为它内部有个很好的缓存机制能够起到“全部读到内存里”的作用的。但现在看了Cheng的回复也有可能是我选的数据库不对。
>
我的意思应该是:从理论上说,读速度:提前读入内存 > NoSQL > {MySQL, 文件系统大量小文件}(这两个不好比较)

自己实现的缓存是进程内操作,不涉及进程间通信,除非选择的语言本身比较慢,或者实现缓存的数据结构太烂,否则几乎肯定比外置缓存快的。
中学搞计算机竞赛的时候,经常有100000条数据进行插入、查询、删除操作的题目,文件输入输出,时限1秒,内存限制50M,只要算法写对,评测机器不太烂,都能通过。当然这些数据是全部载入内存的。因此像你这种需求,目前的实现方式比MongoDB快也就不难理解了。

我觉得使用数据库而不是进程内缓存的主要目的是:
(1)数据量真的很大,无法全部载入内存,需要比较好的缓存策略。但我觉得现在内存这么便宜,硬盘又这么慢,规模不算太大的在线服务数据库超过100G的应该不多,买上几十G的内存,很多问题都解决了。

(2)需要持久化,要保证数据的一致性、容灾性,这个应该是你主要考虑的问题吧。要享受进程内缓存的性能,又要数据持久化,我觉得可以使用一种替代方案:每来一条数据,就输出到FIFO文件里,然后load
data infile到数据库的表里。或者另开一个“垃圾回收”线程,一旦进程缓存内积累了一定量的未保存数据,就把它们保存到NoSQL里并标记为已保存。

(3)需要进行非主键查询,自己实现索引太蛋疼。

> 谢谢费心指教!
>
指教谈不上,我没有做过NoSQL,只是扯一下自己的看法……

Bojie Li

unread,
Jan 12, 2013, 3:36:04 PM1/12/13
to USTC_LUG
今天看到一个memsql,号称是比memcached性能还高的sql。虽然不开源,我也没有尝试使用,但从技术文档中还是能看出它性能高的三个主要原因:
一、将数据放在内存中操作(当然内存要足够大)。
二、将日志和快照定期刷新到磁盘,只有顺序写。
三、将SQL语句编译成C++,再用gcc编译成共享库,查询中的常量变成库函数的参数。以后同模式的查询就是函数调用,不需要每次解析SQL语句和生成查询计划。我认为解析SQL是SQL和NoSQL性能差异的一个重要因素。每次执行之前都要编译也是PHP比较慢的主要原因,PHP官方拒绝编译优化,因为优化用时可能比执行时间还长;如果编译缓存成为默认,PHP也许会考虑做编译优化。正则表达式生成的自动机要缓存,也是同样的道理。

这三项优化直击SQL速度慢的要害(当然MySQL也可以增大内存缓存,但不如memsql来得彻底)。我们不用memsql,但在自己写数据处理程序时也可以让常用的数据驻留内存,将改动以日志的形式顺序存储而不是随机写磁盘上的源数据,把常用的代码用预编译的方式优化。

个人感觉memsql还有一些可圈可点的地方:
1.对key-value查询使用HashTable(这个相对B树的优势是显然的),对区间查询使用跳表(skip
list)取代B树(不知道好在哪里,按说跳表的缓存局部性不好于平衡树,难道是因为写不出B树的无锁算法?)
2.使用无锁数据结构支持多CPU并发。(没写过,听起来不错)
3.多版本数据(MVCC)支持并发事务,每个事务操作自己的版本,仅在写冲突时上锁。(想起去年跟@tux搞的实时磁盘文件系统了,虽然这个多版本数据最后没实现)
4.预编译的SQL语句能够预知需要多少内存,不使用malloc,也就不需要垃圾回收。

分析得不一定对,欢迎拍砖。

Bojie Li

unread,
Jan 12, 2013, 3:54:05 PM1/12/13
to USTC_LUG
根据这篇文章,跳表比平衡树对并发写操作更友好,跳表的修改局限于周围的结点,而平衡树的旋转操作很容易集中到根结点附近。
http://www.drdobbs.com/parallel/choose-concurrency-friendly-data-structu/208801371?pgno=3
Reply all
Reply to author
Forward
0 new messages