Monthly Archive for 3月, 2009

万里长征第一步

今年的工作就是把自己扔到坑里,然后想办法爬出来。主要有两个很重要的工作,第一个是通过Amoeba实现分布式数据库架构,第二是通过MySQL数据库建立一个廉价的高可用集群。

上周终于迈出了万里长征第一步,将Oracle数据库中最大的一张表进行了水平(数据)拆分,第一期拆分为16张表,存放在一个Oracle数据库中。下一步的目标是逐步切换到MySQL数据库,预计首期为64个MySQL集群(16拆64)。

Amoeba是一个分布式数据库Proxy,由公司架构部门开发(项目创始人陈思儒去了盛大),它同时也是一个开源项目。它可以提供数据拆分,SQL路由,读写分离,负载均衡,故障切换等功能。

Amoeba架构图

解决方案就是利用现有技术搭积木,通过Amoeba和MySQL数据库,我们就可以用建立一个可线性扩展,高可用的数据库集群。

不知道现在做的事是不是属于DA的范畴,反正基本上每天都在折腾Data,而不是折腾Database.

–EOF–

SNS的海量数据解决方案

最近SNS很火,所以大家都在做,淘宝的”淘江湖”也将在不久之后正式发布。SNS类型的网站首先要解决的就是海量数据处理的问题,主要是用户事件的存储和处理。

我们先假设以下的情况:Jacky的人气很火,有一万人关注他,他在淘宝买了一个马桶,这个事件需要通知一万人,这样一条事件就变成了一万条。同时,Jacky也订阅了很多人的信息,当他登陆网站时,可以看到这些人的最新事件。

集中式的数据库方案肯定是不合适的,我们使用MySQL数据库,按照用户分拆成很多集群,每个用户的事件都存在自己的数据库中,也就是说用户事件只保存一份。但是当jacky需要查看他所有好友的事件时,就需要从不同的数据库去取,然后将不同数据库的结果进行合并返回给用户,这就需要应用服务提供数据合并,排序分页的功能。还有一种思路是,所有用户的事件放在独立的数据库集群中,当发生事件后,将事件ID推送给所有的订阅者,这样每个用户就可以在自己的数据库查到订阅的所有最新事件,然后根据这些事件ID再去取事件的详细信息。用户一般只关心最新的事件,所以每个数据库集群中的事件采用时间分区的方法,只保留最新一段时间内的事件信息。

这种解决方案看起来很容易理解,要解决的问题主要是数据拆分以及合并排序的问题。其实解决方案就是搭积木,用简单的技术搭建一个系统,能够解决问题,我觉得就是好的方案。

以后再讲一下数据库集群实现高可用的各种解决方案。

–EOF–

Michael会走路了

在经历了短暂的爬爬阶段后,Michael迅速的自学成才,开始迈步向前走了。
大家都说走路太早了不好,所以我们从6个多月开始就尽量限制他站立和走路,但是小朋友好像天生有运动的天份,在没有人教的情况下,自己学会了迈步。给爸爸妈妈省了一笔学步车费。
–EOF–

DBA未来的发展方向

DBA(DataBase Administrator )数据库管理员,我每次和公司其他非技术部门的同事解释我的工作的时候都要颇费口舌,直到最后如果他还是不明白的话,我只好说我们的工作其实和仓库管理员没什么区别,都是管理一个仓库。更多的时候,我还要解释数据库和数据仓库的区别(因为在公司里,我们是两个部门),一般我给出的解释是:DBA虽然是仓库管理员,但是里面的数据不是我们的,你如果需要数据的话,还得找数据仓库。

我把DBA分为三类:第一类:顾问型,他们属于DBA中的高端人才,经验丰富,技术全面,提供专业的数据库咨询服务或培训,擅长用偏门的技术解决问题。第二类:技术支持型,他们属于各集成商或者专业的数据库维护公司,最擅长搭建各种环境,从主机,存储到OS和数据库都很熟悉,经常troubleshooting各种数据库问题。第三类:运维型,他们维护自己公司的数据库系统,稳定是对他们的唯一要求,他们经常要和集成商或者应用开发人员打交道。这三类DBA中都有牛人,而且很多DBA并不局限于其中的一类,他们既是运维DBA,也提供数据库咨询或培训服务。

我们属于运维型DBA,但不同于其他公司的是,我们的数据库每天都要上大量的项目,数据库承受着巨大的压力。由于数据库的可用性基本上决定了网站的可用性,所以必须有人能够对应用加以控制,保证数据库被正确的使用,所以我们的DBA又分为产品DBA和开发DBA,其中产品DBA负责产品数据库的维护,包括主机存储等,而开发DBA则主要与开发人员打交道,为他们提供数据库方面的支持,并参与到项目的设计过程中,开发DBA的权力很大,当一个项目设计不合理,可能会对数据库造成危害时,我们有权力say:No!

我是公司的第一个开发DBA,在我没进公司之前,那时的硬件比较差,流程和规范也没有,经常上一个项目就把整个数据库搞宕机,DBA总是手忙脚乱的四处救火。开发人员只关注SQL是否能得到正确的结果,根本不关心SQL的性能,而如果当项目上线后再发现问题,代价非常高,所以当时的DBA老大就想到了开发DBA这么一个角色(感谢rudolf)。我第一阶段的工作,主要是优化每个项目的SQL,保证每条SQL是最优化的,通过建立适当的索引提高SQL的效率。刚开始优化SQL时,都是采用尝试的方式,就是不断调整执行计划并一次次测试,直到测试的结果满意,到后来就变成了先了解表和索引的状况,看到SQL后,最优的执行计划就在脑海中出现了,然后再去验证是否和我的想法一致。在这个阶段,通过不断建立流程和培训开发人员来简化我的工作。但我逐步发现,如果一个项目在前期设计时就不正确的使用了数据库,即一个不合理的设计,后期很难通过优化SQL来提高性能。第二阶段,我开始逐步参与到项目的设计中去,基于我对数据库的理解,给产品设计人员甚至需求方提供更好的建议。在这个过程中,最难的一件事就是如何说服别人作改变,我觉得最重要的就是信任感。虽然我经常say:No!但同时我一定会给出我的建议,通过一个个项目的证明,使得设计和开发人员越来越信任我并依赖我,后来的所有重大项目,设计人员都会找我商量并听取我的建议,这样我的工作就变得越来越简单。不过同时我也面临着一个问题:就是如何在项目开始时,就评估出对数据库产生的影响。首先你必须非常了解应用,哪些功能会被频繁使用到;第二你必须非常了解数据库,表,索引以及相关的SQL等等;第三你必须非常了解整个系统,当前的负载,逻辑读和事物数等等,根据这样一系列的信息,并结合以往的项目,给出一个综合的判断,事实上这是一个非常困难的事,很多时候我们也是凭借经验来判断。第三阶段,DBA开始与架构部门合作开发一些系统优化的项目,以前我们都是通过statspack发现一些TOP SQL,可能是执行计划不正确或者SQL不够优化,我们会要求开发人员修改。后来DBA就开始主动推动一些数据库优化项目,这些项目往往需要其他部门的协助,比如搜索引擎和架构部门,所以现在我们的很多项目,都是由DBA和架构部门共同来完成,比如今年我们的分布式数据库(amoeba 变形虫)项目。

后来,我转向了产品DBA方向,在这段时间内,我接触到了各种主机和存储,学习了很多硬件和OS的知识,同时对两种DBA的角色也有了更深的理解,产品DBA和开发DBA对于数据库的关注点是不同的,产品DBA更关注运行维护,troubleshooting,备份恢复等方面,而开发DBA更倾向于应用开发,性能优化等方面。

数据库未来发展的方向,智能化自动化一定是个趋势,各数据库厂商也为DBA提供了更方便更强大的管理和诊断工具。并且随着大家使用数据库的不断成熟,低级的错误越来越少见,我们不可能再通过调整某个“参数”就让系统性能大幅度得到提升,尤其是我们管理的数据库,参数基本上都已经是最优化了,调整的机会很少。那是不是都数据库都自动化了,就不需要DBA或者DBA的重要性降低了,我倒不这么认为。但是如果DBA只具备数据库的知识,应该是不足够了。未来的DBA不仅仅要了解主机,存储,操作系统,并时刻关注硬件发展趋势和数据库新特性以外,还应该朝着DA(Data Architect 数据架构师)的方向发展,从Database到Data就说明以后数据不一定在数据库里面了,从Administrator到Architect说明我们要懂架构,虽然我们不一定要懂得coding(PL/SQL不算)。对于开发DBA和产品DBA来说,只是工作职责上的侧重点不同,未来的环境肯定是要求DBA掌握更多方面的知识。另外,好的沟通能力和团队合作能力对于一个优秀的DBA也是必不可少。

–EOF–

hash join(2)

今天和同事讨论hash join,发现我之前的想法有些问题:

我的最初的理解是:扫描probe table找到对应bucket时,扫描bucket link并匹配其中的每个值,最后将自己挂在link的最后,这样bucket link的长度就会越来越长,扫描的成本就会越来越大。但是这样明显有几个矛盾:第一,这样做会导致hash table的大小不断增大,这样join到最后可能hash area根本无法容纳一个hash table,而Oracle在估算成本时,明显只根据build table来决定分区数,也说明hash table不会增大。第二,在hash bucket中只需要精确匹配build table中的值就可以了(因为hash碰撞的原因),完全没必要把probe table的值也挂在hash bucket中。第三,最大的矛盾是Oracle在join后完全可以将这个结果集直接返回给用户,没有必要把这些内容也保存在hash table中。

正确的推测应该是:hash partition number,hash cluster size,hash bucket number,hash bucket length等等在hash join开始时,就由build table决定了,在整个build过程中,不再发生变化。当join时发现了匹配的行,就直接返回结果给用户。所以在作hash join时,build table中join的字段要尽可能的唯一(或者重复的很少),否则性能可能会很差。比如5000条记录只有5个不同的值,如果作为build table,则最多使用10个bucket,每个bucket是一个1000个值的link,这样hash join的性能将很差。

下面的trace文件可以很明显的看出,数据分布在partition 0,3,4,6中,一共有8187个bucket,但是只有5个bucket有数据,每个bucket里面有1000行。

 Stargate release Partition:0    rows:1000       clusters:1      slots:1      kept=1
Partition:1    rows:0          clusters:0      slots:0      kept=1
Partition:2    rows:0          clusters:0      slots:0      kept=1
Partition:3    rows:2000       clusters:1      slots:1      kept=1
Partition:4    rows:1000       clusters:1      slots:1      kept=1
Partition:5    rows:0          clusters:0      slots:0      kept=1
Partition:6    rows:1000       clusters:1      slots:1      kept=1
Partition:7    rows:0          clusters:0      slots:0      kept=1

Number of buckets with   0 rows:       8187
Number of buckets with   1 rows:          0
Number of buckets with   2 rows:          0
Number of buckets with   3 rows:          0
Number of buckets with   4 rows:          0
Number of buckets with   5 rows:          0
Number of buckets with   6 rows:          0
Number of buckets with   7 rows:          0
Number of buckets with   8 rows:          0
Number of buckets with   9 rows:          0
Number of buckets with between  10 and  19 rows:          0
Number of buckets with between  20 and  29 rows:          0
Number of buckets with between  30 and  39 rows:          0
Number of buckets with between  40 and  49 rows:          0
Number of buckets with between  50 and  59 rows:          0
Number of buckets with between  60 and  69 rows:          0
Number of buckets with between  70 and  79 rows:          0
Number of buckets with between  80 and  89 rows:          0
Number of buckets with between  90 and  99 rows:          0
Number of buckets with 100 or more rows:          5
### Hash table overall statistics ###
Total buckets: 8192 Empty buckets: 8187 Non-empty buckets: 5 Sherman’s Way ipod
Total number of rows: 5000
Maximum number of rows in a bucket: 1000
Average number of rows in non-empty buckets: 1000.000000

–EOF–

hash join

我们对hash join的通常的认识:适用于一个小表和一个大表作关联,小表build hash table,扫描大表作为probe table,通过hash函数完成join的动作,这应该是最简单的认识,实际上hash join比这个要复杂一些。

1.Optimal Hash Join

这是最理想的情况,如果hash area足够容纳hash table,那么就和我们之前理解的一致。不过我测试发现,就算hash area足够大,事实上还是要对hash table作分区的,只不过所有的分区都在内存中。我们来看看event 10104 trace的部分内容:

Original memory: 5336064
Memory after all overhead: 5213832
Memory for slots: 2826240(slots的可用空间2760KB)
Calculated overhead for partitions and row/slot managers: 2387592
Hash-join fanout: 8 (hash table 8个partition)
Number of partitions: 8
Number of slots: 23 (共有23个slot/cluster)
Multiblock IO: 15
Block size(KB): 8
Cluster (slot) size(KB): 120 (每个cluster 15*8KB=120KB)
Hash-join fanout (manual): 8
Cluster/slot size(KB) (manual): 192
Minimum number of bytes per block: 8160
Bit vector memory allocation(KB): 256
Per partition bit vector length(KB): 32
Maximum possible row length: 163
Estimated build size (KB): 100 (估算实际需要100KB的空间)
Estimated Row Length (includes overhead): 19

Partition:0    rows:841        clusters:1      slots:1      kept=1
Partition:1    rows:858        clusters:1      slots:1      kept=1
Partition:2    rows:812        clusters:1      slots:1      kept=1
Partition:3    rows:844        clusters:1      slots:1      kept=1
Partition:4    rows:871        clusters:1      slots:1      kept=1
Partition:5    rows:843        clusters:1      slots:1      kept=1
Partition:6    rows:838        clusters:1      slots:1      kept=1
Partition:7    rows:814        clusters:1      slots:1      kept=1

可以看到每个分区大至800条数据,每个分区分配了1个cluster,并且都在内存中(slots:1代表第一个slot在内存中,kept=1整个分区都在内存中)。

2.onepass hash join

当hash area无法容纳所有的分区,但是却足够容纳至少一个分区时,这种情况就是onepass hash join。需要首先对hash table做分区,然后将其写入到磁盘的临时空间,当hash table完成分区后,然后对probe table也用同样的hash函数作分区,然后对应的分区分别做join。需要注意的是,除了第一个分区,从第二个分区开始,Oracle会自动根据分区的大小来交换build table和probe table,以期望达到更好的性能。因为所有的分区只需要从磁盘上读取一次就可以完成,所以叫onepass.

还有一个东西值得关注:Bit vector memory,这是一块位图区域,实际上他保存了hasn table所有分区的hash bucket位图,在hash join时,首先在这里寻找对应的hash bucket,如果发现则置1,否则就丢弃这一行。

Original memory: 9919499
Memory after all overhead: 9919499
Memory for slots: 6094848
Calculated overhead for partitions and row/slot managers: 3824651
Hash-join fanout: 16
Number of partitions: 16
Number of slots: 24
Multiblock IO: 31
Block size(KB): 8
Cluster (slot) size(KB): 248
Hash-join fanout (manual): 8
Cluster/slot size(KB) (manual): 440
Minimum number of bytes per block: 8160
Bit vector memory allocation(KB): 1024
Per partition bit vector length(KB): 64
Maximum possible row length: 67
Estimated build size (KB): 31250
Estimated Row Length (includes overhead): 32

Partition:0    rows:62767      clusters:5       slots:1      kept=0
Partition:1    rows:62479      clusters:5       slots:1      kept=0
Partition:2    rows:62448      clusters:5       slots:1      kept=0
Partition:3    rows:62559      clusters:5       slots:1      kept=0
Partition:4    rows:62780      clusters:5       slots:1      kept=0
Partition:5    rows:62278      clusters:5       slots:1      kept=0
Partition:6    rows:62193      clusters:5       slots:1      kept=0
Partition:7    rows:62168      clusters:5       slots:1      kept=1
Partition:8    rows:62672      clusters:5       slots:1      kept=1
Partition:9    rows:62526      clusters:5       slots:1      kept=1
Partition:10   rows:62196      clusters:5      slots:1      kept=1
Partition:11   rows:62495      clusters:5      slots:1      kept=1
Partition:12   rows:63283      clusters:5      slots:1      kept=1
Partition:13   rows:62185      clusters:5      slots:4      kept=1
Partition:14   rows:62468      clusters:5      slots:1      kept=1
Partition:15   rows:62503      clusters:5      slots:5      kept=1

这里可以看到,每个分区就算不在内存中,也必须至少有一个slot在内存中。我们可以看到只有最后一个分区,所有的slot都在内存中。我没搞明白的是,有些分区只有1个slot在内存中,为什么kept=1。

3.multipass hash join

当hash area甚至都无法容纳其中的一个分区时,这时就有些麻烦了。可能有两种做法:1.将hash table partition再分区(可以容纳在hash area中),然后针对每个小分区,循环扫描整个probe table,来完成hash join。2.将hash table和probe table都用同样的hash函数再分区,直到分区足够小可以放在hash area中。很明显,第一个效率会很差,因为probe table会被循环扫描,但是很不幸,Oracle正是采用了这个方法,我们可以称之为nest loop hash join或者是multipass hash join。为什么Oracle没有采用第二种方法,我觉得是考虑了算法复杂度的原因。

这篇文章参考了《cost based oracle fundamentals》,光辉兄很早以前写的一篇有关hash join的文章以及metalink中的一些内容,并结合了我自己的一些测试。

hash join可以通过event 10104了解到更多细节的内容,这里就不一一赘述。

–EOF–

我的失败消费经历

1.马桶:昨天买的两个马桶,啥也不说了,以后拉屎会有心理阴影。

2.SP8自行车:非常难骑,到现在为止我就没调出一个舒服的姿势,法嘴胎,打气极不方便,牙盘没有护盖,一骑裤脚全是油,没有挡泥板,下雨一身泥。除了可以折叠,几乎没有任何优点,打算卖掉!

3.PSP:冲动消费的结果,买来没一个月就扔在抽屉里,最后送给LP的弟弟,爱玩游戏,也算没浪费。

4.健身椅:做仰卧起坐的的椅子,在电视购物的忽悠下,和公司的一群人团购的,买回来几乎没用过,因为一用就会头晕,唯一的用途是躺在上面晒太阳。

5.单反相机:周围的人都在玩摄影,觉得不买个NB一点的相机,就好像就比别人低一截,买了之后才发现自己不是那材料,照得也没比我的卡片机好多少。最SB的是又被人忽悠买了镜头,拍得照片还是一样烂。

6.三星MP3:01年的时候在北京买了一个三星的MP3,64M要2000多,一狠心一咬牙就买下了,本来想上下班的时候听(上下班要坐3个小时的公交),结果发现北京的公交实在太吵了,音量开到20还是听不清,后来就很少用了,现在还崭新的放在抽屉里。

以下是emily要求补充的,仅代表其个人观点:

结婚照,3大幅巨幅结婚照,至今还躺在床脚,估计以后搬家,也挂不出来了。土。

标志206,买的时候为了图省钱,再加上网上评论的忽悠,认为在市区1.4的足够用了。现在脚一放在油门上,就开始后悔为什么没有买1.6的。(早晚油门那块被你踩出个窟窿来,省点力吧,已经有人说感觉到推背了)。

老豆的笔记本,硬是给买了个宽屏的。买来到现在,都没有看过超过10部电影。16:9的屏幕上,字体看起来就是小,练就来人家的火眼金睛。

暂时能想到的就这些,以后再补充。

–EOF–

最近有些烦

儿子持续不断的发烧,前几天是病毒,昨天天气不错,出去晒晒太阳,就变成了细菌感染,三天两头跑医院不说,他一生病,全家都少了许多欢笑。

儿子身体发烧,LP脑袋发烧,昨天去烧了两个马桶,一个4.5k,一个2.3k,这已经是非常克制的结果,否则就买两个5k的。本着LP花钱要舍得和家庭和谐的原则,我还是什么话都不说了,只怪自己挣钱少啊。反正以后拉屎都要憋回家再拉,欢迎大家前来参观体验。

今年的工作比去年管数据库的时候忙太多了,而且基本上都是与其他部门扯皮,开会,写文档的工作,每天不开十个八个会,你都不好意思和别人打招呼,总之一个字:累。分析原因,可能是我刚做个小P领导,管得闲事太多了,看来还得好好改进。

装修还是有很多事情,虽然号称是大公司,但是还是要每天去看看,防止有什么猫腻。装修确实是对人生的一次重大考验,不仅仅是肉体上,精神上也是一种折磨,始终处在一个对别人极端不信任的环境下,没办法,这就是现实。

最近比较烦,比较烦,比较烦……

–EOF–

后记:晚上回家上网查了一下,发现昨天买马桶被忽悠了,原价9k多的(号称米国进口)打对折,以为捡了个大便宜,结果网上报价2k多,2k多的网上报价只要1k出头。LP被严重打击了,这时我对其脆弱的心灵进行了安慰,并顺带讲了一下理智消费对我们这种工薪阶层的重要性。突然想起昨天是啥日子了,3.15啊,忽悠消费者日!

我现在后悔了,真不如在百安居装修,谁说百安居贵,我跟谁急。Black Book full

我被twitter撞了一下腰

下班的时候,突然发现我的twitter上一堆人排队follow我,正在纳闷下午好像没做什么特别的事情,怎么突然就火了。原来是Fenng同学推了俺一把,不过说句实话,这个推荐词真是不咋地,明显与事实不符,而且咋看都象是反话。

今日推荐推友: 张瑞( @hellodba ) 看 ID 就知道是个 DBA , 来自阿里B2B的DBA团队. 比我资深多了,什么都擅长,还擅长谦虚,讲究生活品质,物质、精神层次两手抓。(#每日推荐一位推友计划)

突然发现认识一个大牛真是不错,Fenng啥时候也推下我的blog吧,谢谢!Enter the Dragon hd

不折腾

想明白一个道理:就算我把自己折腾死,也无法成为别人(哪些大师大牛们),做自己能做的事,按照自己的方式活着。

所以我不折腾了。

–EOF–