mnesia数据库使用体验

这只是我自己在过去一周中对mnesia使用的经验总结,并不针对所有范

为什么使用mnesia?

呃,不为什么,好玩一把,相对于其他的数据库,mnesia还算是最为熟
悉的,因为我就是数据库白痴

过去一直不曾关注这块,数据库在我眼里一直当文件使用,最近突发奇
想,看看mnesia如果应用于真实的可商用的情况下有何好处,当然我比较
看重的是mnesia分布这块,也想试试。于是就开始折腾一通了。

先说下部分数据表结构:

       -record(test_database, {
                              id,
                              name=[],
                              subname,
                              status,
                              last_update,
                              url
       }).
       -record(name_database, {
                              id,
                               name,
                               url
                              }).
       -record(url_database, {
                             id,
                             url,
                             status,
                             last_update}).
       

大致如此,其实测试的数据表要比上面这个更多些,在上述的结构中,
url_database中的数据使用的最频繁,几乎在所有的结构中都会涉及到它,
当然,它在别的结构中是以id出现的。
因此,它将占用的空间最大。

刚开始其实很简单,一切按照教程来弄,随心所欲的设定如下:

       test_database: disc_copies, [url,status];
       name_database: disc_copies, [name, url];
       url_database: disc_copies, [url, status];
       

上述大致都知道什么意思。

处理按照下述的流程进行:

test_database中的数据在写入的时候,检查其中的各项在对应的数据
表中是否有已经存在的值,如果有的话,则在对应项记入id, 如果没有的
话,在对应表中插入新的记录,并在test_database的数据中记入对应的id,
当所有项检查完并且无误的情况下,记入test_database数据表

按照最初的设计,程序也运行的很快,数据加入唰唰的,但很快就出现
问题了:内存不足了

最简单的方法是添加内存,但我发现最多的url_database数据才有300
多万条,依据这个速度,很明显再添加也不够啊。

简单的分析,对于数据表而言,多一个索引基本上就多一个copy,于是,
减少索引,最极端的方式就是取消所有索引,于是,就简单的试了下。

取消所有索引后,内存占用率一下子只有原来的零头了,从原来的1G多
变为几十MB了,但更大的问题出现了:CPU占用率奇高,系统完全无法响应
其他操作了。而且,磁盘读取也非常频繁。

PS: 一个代码建议,我个人推荐数据的查询之类的使用qlc的方式,这
样无论以后怎么修改,代码都不用变动,使用mnesia:index_xxx的方法的
话,经常修改索引测试就每次都要修改的。

接着分析:出现这种情况是由于查找索引太慢,并发进程多,所有的都
卡住等在那儿了,而且,从磁盘读取速度也慢。那么,就折中下吧:

使用mnesia:add_table_index添加索引,对于test_database而言,
status暂时不用,所以不加,对于url_database而言,status使用不频繁,
仅在两个进程中使用,不加入,类似的一番简单的在命令行输入之后,CPU
占用率以200%的速度下降,只占用几乎微不足道的一部分了。

这次比较好些,内存相比原来小些,但还不符合要求,我想要在诸如
512MB或者1G的内存上可以比较好的运行。

以上的经验总结:mnesia选取的索引非常重要;使用索引检索比不使用
索引检索速度要快N倍,相当于飞机和蜗牛的差距;使用qlc的方式检索程
序修改最小,而且,效果可以看的见。

我又分析了下需求,我其实最关心的数据采取量,速度够用,内存占用
不大,里面只用内存最大的就是url_database了。

因为从最后的处理结果估算来看,每一个流程处理下来,大约要读取url_database上
百次,写入在几十~几百次之间,因此,它的速度很重要,但内存占用也很
重要,从mnesia:info来看,占了90%多的内存。

先解决内存吧,因为我使用的是disc_copies的方式,这种方式要求,
内存必须承受下所有的数据,那么改为disc_only_copies的方式试试。

修改之后,果然内存又大幅下降了,但速度么,很慢,CPU占用率很高。

仔细一看,原来我忘了设索引值了,于是,添加上url的索引值之后,
磁盘上多了一个文件,速度快了很多,相当于汽车和蜗牛吧。CPU占用率呢,
在几十到100%多之间飘荡,系统其他方面的也响应很好。虽然比不上
内存的方式,但相比与使用而言,还是够用的。

以上经验总结:对于占用大量空间而又不动的数据,采用
disc_only_copies的方式可能会更好些,再添加上索引,速度也还满意。

按道理到这里就差不多了,扔到那里跑了一天,结果发现很多overload,
很多各种mnesia的警告,在网上搜索下,建议将transaction改为
sync_transaction会好些。于是就看了下,一个异步的,一个同步的。使
用observer:start看了下,确实有太多的等待了,因为cpu的处理要比磁盘
的写入快很多,于是等待就越来越多了,所以,是那个disc_only_copies
引起的,但又不想修改为其他的方式,所以就修改为同步的吧,将所有的
transaction都添加上sync。

手贱,修改了,接着启动测试,发现更快的出现message_queue_len的
错误。仔细的梳理流程后,发现是别的地方引起的。

简单的说,我使用了两个进程来检查url_database数据是否需要更新(根据status),
如果发现具体干活的队列不满的话,就生成一个进程,在这个进程中从数
据库中读取一个记录,然后加入到干活的队列中,问题就在这块了,外部
检查循环的队列非常快,而读取记录的这个进程非常慢(我并没有针对
status生成索引,并且还是disc_only_copies的),具体干活的进程速度
也快,但是并发的N个进程,这样一来,短时间内,查询进程就生成了很多
的插入进程,而插入进程是使用sync来查询(同步的),造成大量的
sync_transaction在等待,迅速的,等待队列到了100多,就出现了上述错
误。但插入进程其实是不需要的,完全可以放在查询进程中,于是,将这
个插入进程的操作代码放入查询进程中,然后,错误消失了,程序刚开始
的启动速度也快很多。

以上的经验总结:无论合适,正确使用并发,分析瓶颈在何处。

现在,程序完全可以在512MB内存的服务器上跑了,而且还比较稳定。

本来到这里就结束了,但本着既然看了就多看看的心理,想要看看分布
是否可以。

我的计划是这样子的,每个节点其实都可以生成完整的本地数据库,但
也可以允许生成部分的本地数据库,总之,越随心所欲越好。于是就使用
rebar生成了两个发布版本,用于在本地测试(一台服务器上运行两个节点)

net_adm:ping不通,一ping就提示不允许的错误。发现是cookie搞的鬼,
手动在_app.erl的start部分使用erlang:set_cookie设定。

mnesia数据库还是无法同步,可能是mnesia自动启动了吧,总之,不管
如何,先直接调用mnesia:stop再调用mnesia:start(标准的懒人做法),
反正在_sup.erl中,整个系统的其他部分都还没有启动呢。再添加个环境
判断,代码如下:

init([]) ->
    mnesia:stop(),
    mnesia:start(),
    case os:getenv("SERVER_MASTER") of
        false ->
            ok;
        Other ->
            io:format("ping=~p~n", [net_adm:ping(list_to_atom(Other))]),
            mnesia:stop(),
            mnesia:start(),
            mnesia:change_config(extra_db_nodes, [list_to_atom(Other)]),
            mnesia:change_table_copy_type(schema, node(), disc_copies)
    end,
       

如果设置了SERVER_MASTER变量的话,说明是slave部分,然后就主动去
连接master服务器,并且,重启mnesia服务器,添加本地节点。

于是,mnesia服务器的分布就弄好了,如果要想将远程节点内容copy到
本地的话,调用mnesia:add_table_copy即可,总之,非常方便的。

注:slave部分的第一次启动必须在master启动之后才能启动,之后的
启动看mnesia的其他部分是如何操作了,如果仅仅通过判断是否包含数据
表的话,可以在master启动之前就启动,当然,这时候master的数据表中
的数据是没有办法读取的,master在启动的时候会自动连接slave服务器,
而这个时候,由于我们手动设置了cookie,那个mnesia:stop就开始起作用
了。无论哪次启动,master和slave都会至少提示两次不允许连接,但真实
启动完成之后,所有的数据都是可用的。

以上是本人的一个回忆出来的经验总结,无法确保所有的都是真实可用
的。也无法确保适用于所有项目情况,每个项目都是不一样的,选择合适
的即可。

发布者

rix

如果连自己都不爱自己,哪还有谁来爱你