Cassandra数据模型

提起NoSQL这个话题,仿佛不应该是DBA要关注的事,而是架构师应该关心的。但是作为一名DBA,在使用传统的关系型思想建模时,应该有必要了解NoSQL的建模方法。

各种NoSQL数据库有很多,我最关注的还是BigTable类型,因为它是一个高可用可扩展的分布式计算平台,用来处理海量的结构化数据,而数据库同样也是处理结构化数据,所以除了没有SQL,在数据模型方面有相似之处。Cassandra是facebook开源出来的一个版本,可以认为是BigTable的一个开源版本,目前twitter和digg.com在使用。我们尝试从DBA的角度出发去理解Cassandra的数据模型。

NoSQL并不能简单的理解为No SQL,其本质应该是No Relational,也就是说它不是基于关系型的理论基础,而我们所有传统的数据库都是基于这套理论而发展起来的,所以SQL并不是问题的关键所在,比如有些NoSQL数据库可以提供SQL类型的接口,允许你通过类SQL的语法去访问数据。而Friendfeed则是反其道而行之,利用关系型数据库MySQL,采用了去关系化的设计方法,去实现自己的KeyValue存储。所以NoSQL的本质是No Relational.

Cassandra特点:

1.灵活的schema,不需要象数据库一样预先设计schema,增加或者删除字段非常方便(on the fly)。

2.支持range查询:可以对Key进行范围查询。

3.高可用,可扩展:单点故障不影响集群服务,可线性扩展。

Keyspace

Cassandra中的最大组织单元,里面包含了一系列Column family,Keyspace一般是应用程序的名称。你可以把它理解为Oracle里面的一个schema,包含了一系列的对象。

Column family(CF)

CF是某个特定Key的数据集合,每个CF物理上被存放在单独的文件中。从概念上看,CF有点象数据库中的Table.

Key

数据必须通过Key来访问,Cassandra允许范围查询,例如:start => '10050', :finish => '10070'

Column

在Cassandra中字段是最小的数据单元,column和value构成一个对,比如:name:“jacky”,column是name,value是jacky,每个column:value后都有一个时间戳:timestamp。

和数据库不同的是,Cassandra的一行中可以有任意多个column,而且每行的column可以是不同的。从数据库设计的角度,你可以理解为表上有两个字段,第一个是Key,第二个是长文本类型,用来存放很多的column。这也是为什么说Cassandra具备非常灵活schema的原因。

Super column

Super column是一种特殊的column,里面可以存放任意多个普通的column。而且一个CF中同样可以有任意多个Super column,一个CF只能定义使用Column或者Super column,不能混用。下面是Super column的一个例子,homeAddress这个Super column有三个字段:分别是street,city和zip:

homeAddress: {street: "binjiang road",city: "hangzhou",zip: "310052",}

Sorting

不同于数据库可以通过Order by定义排序规则,Cassandra取出的数据顺序是总是一定的,数据保存时已经按照定义的规则存放,所以取出来的顺序已经确定了,这是一个巨大的性能优势。有意思的是,Cassandra按照column name而不是column value来进行排序,它定义了以下几种选项:BytesType, UTF8Type, LexicalUUIDType, TimeUUIDType, AsciiType,  和LongType,用来定义如何按照column name来排序。实际上,就是把column name识别成为不同的类型,以此来达到灵活排序的目的。UTF8Type是把column name转换为UTF8编码来进行排序,LongType转换成为64位long型,TimeUUIDType是按照基于时间的UUID来排序。例如:

Column name按照LongType排序:

{name: 3, value: "jacky"},
{name: 123, value: "hellodba"},
{name: 976, value: "Cassandra"},
{name: 832416, value: "bigtable"}

Column name按照UTF8Type排序:

{name: 123, value: "hellodba"},
{name: 3, value: "jacky"},
{name: 832416, value: "bigtable"}
{name: 976, value: "Cassandra"}

下面我们看twitter的Schema:

<Keyspace Name="Twitter">
  <ColumnFamily CompareWith="UTF8Type" Name="Statuses" />
  <ColumnFamily CompareWith="UTF8Type" Name="StatusAudits" />
  <ColumnFamily CompareWith="UTF8Type" Name="StatusRelationships"
    CompareSubcolumnsWith="TimeUUIDType" ColumnType="Super" />
  <ColumnFamily CompareWith="UTF8Type" Name="Users" />
  <ColumnFamily CompareWith="UTF8Type" Name="UserRelationships"
    CompareSubcolumnsWith="TimeUUIDType" ColumnType="Super" />
</Keyspace>

我们看到一个叫Twitter的keyspace,包含若干个CF,其中StatusRelationships和UserRelationships被定义为包含Super column的CF,CompareWith定义了column的排序规则,CompareSubcolumnsWith定义了subcolumn的排序规则,这里使用了两种:TimeUUIDType和UTF8Type。我们没有看到任何有关column的定义,这意味着column是可以灵活变更的。

为了方便大家理解,我会尝试着用关系型数据库的建模方法去描述Twitter的Schema,但千万不要误认为这就是Cassandra的数据模型,对于Cassandra来说,每一行的colunn都可以是任意的,而不是象数据库一样需要在建表时就创建好。

Users CF记录用户的信息,Statuses CF记录tweets的内容,StatusRelationships CF记录用户看到的tweets,UserRelationships CF记录用户看到的followers。我们注意到排序方式是TimeUUIDType,这个类型是按照时间进行排序的UUID字段,column name是用UUID函数产生(这个函数返回了一个UUID,这个UUID反映了当前的时间,可以根据这个UUID来排序,有点类似于timestamp一样),所以得到结果是按照时间来排序的。使用过twitter的人都知道,你总是可以看到自己最新的tweets或者最新的friends.

存储

Cassandra是基于列存储的(Bigtable也是一样),这个和基于列的数据库是一个道理。

API

下面是数据库,Bigtable和Cassandra API的对比:

Relational			SELECT `column` FROM `database`.`table` WHERE `id` = key;
BigTable			table.get(key, "column_family:column")
Cassandra: standard model	keyspace.get("column_family", key, "column")
Cassandra: super column model	keyspace.get("column_family", key, "super_column", "column")

我对Cassandra数据模型的理解:

1.column name存放真正的值,而value是空。因为Cassandra是按照column name排序,而且是按列存储的,所以往往利用column name存放真正的值,而value部分则是空。例如:“jacky”:“null”,“fenng”:”null”

2.Super column可以看作是一个索引,有点象关系型数据库中的外键,利用super column可以实现快速定位,因为它可以返回一堆column,而且是排好序的。

3.排序在定义时就确定了,取出的数据肯定是按照确定的顺序排列的,这是一个巨大的性能优势。

4. 非常灵活的schema,column可以灵活定义。实际上,colume name在很多情况下,就是value(是不是有点绕)。

5.每个column后面的timestamp,我并没有找到明确的说明,我猜测可能是数据多版本,或者是底层清理数据时需要的信息。

最后说说架构,我认为架构的核心就是有所取舍,不管是CAP还是BASE,讲的都是这个原则。架构之美在于没有任何一种架构可以完美的解决各种问题,数据库和NoSQL都有其应用场景,我们要做的就是为自己找到合适的架构。

–EOF–

这篇文章,我参考了up and running with cassandra,除此以外,我还参考了twitter提供的API,它帮助我理解twitter的schema设计。这篇文章,肯定有很多理解不正确的地方,希望朋友们指正。

Oracle Exadata技术浅析

自从Oracle和HP推出Exadata之后,我就很关注这个产品,之前也写了一篇Oracle database machine介绍它。去年,Oracle和SUN合并后,推出了Oracle Exadata V2,相比较上一代产品有几个变化:第一,使用SUN的硬件;第二,宣称支持OLTP应用;第三,Oracle 11g R2提供了更多的新特性。

Exadata Smart Flash Cache

Exadata V2整体架构并没有太多改变,换用了SUN的硬件,除了采用intel最新的nehalem CPU以外,每台storage cell更是配置了384GB的flash,这也是为什么V2可以支持OLTP应用的关键。

Flash cache完全是自动管理,Oracle会根据数据的访问情况,决定哪些数据放在flash cache中。所有的数据都是先被写到普通磁盘上,再根据访问情况读入flash cache的,所以如果flash card发生故障,数据不会丢失。当然,Oracle提供了方式,可以让用户手动将表或者索引pin在flash cache中。

在自动管理的方式之外,Oracle还允许用户人工创建flash disks,和普通磁盘一样,这些flash disks通过ASM输出给数据库使用,用户可以把一些访问非常频繁的数据文件放在上面。这些flash disks不仅仅是cache了,所以ASM会在cell和cell之间做镜像。如果某块卡发生故障,那么整个storage cell上的flash disks会offline,保证数据不会丢失。

Smart scan

Smart scan是Exadata最重要的一个功能,它的作用就是把SQL放在每个cell上去运行,然后每个cell只返回符合条件的数据给数据库,这样就极大的降低了数据库服务器的负载和网络流量,并充分利用了cell的计算资源和IO资源。

传统方式:所有的数据都需要返回给数据库服务器,网络带宽要求高,所有的计算在数据库服务器上完成。

Smart scan:只返回符合条件的数据,减少网络带宽,并充分利用了cell上的计算和IO资源。

这里有一点要注意,在使用smart scan时,每个cell返回给DB server的是结果集,而不再是传统的block,DB server完成结果集的处理,并返回给客户端。

Smart scan如何处理join?是我一直想要搞清楚的问题。事实上,smart scan只能处理join filtering,而真正join的工作必须在DB Server上完成,而且smart scan仅适合于处理DSS环境的复杂join,对于OLTP类型的简单join,smart scan并不能发挥其优势。设想下面的查询:

select e.ename,d.dname from emp e, dept d where and e.ename=’Jacky’ and e.deptno=d.deptno;

假设采用nested loops join,smart scan只能完成e.ename=’Jacky’这个条件的过滤,然后将符合条件的emp表的数据返回到DB server,然后由DB Server完成join的工作,逐条查询dept表(e.deptno=d.deptno)的数据。所以smart scan并不适合nested loop join(我认为smart scan只有在适合的条件下才会启用),只有DSS环境的大数据量复杂join才会发挥出优势。而且smart scan只能完成filtering的工作,而不能真正完成join的工作,这个与Greenplum数据库是不同的(有兴趣可以看我的文章,Greenplum技术浅析)。设想下面的查询(emp和dept都是大表):

select e.ename,d.dname from emp e, dept d where e.deptno=d.deptno;

假设采用hash join,由于没有任何过滤条件,smart scan只能把两个表的数据全部返回到DB Server上进行join操作,不过smart scan也不是一点用都没有,至少还可以进行column的过滤,只返回需要的字段就可以了。

Oracle的文档中,曾经提到对于一个大表和小表join时,smart scan会采用bloom filter来快速定位(可以看我以前的文章,有趣的bloom filter)。方法是把小表build成为bloom filter,然后在每个storage cell上对大表做scan,利用bloom filter快速定位符合条件的结果,并返回给DB Server作join。

Storage index

存储索引,顾名思义是在存储级别建立的索引,简单的说就是为表中的每一列数据建立一个索引,每个index entry记录一段数据区间的最大值,最小值以及它们的物理位置,文档上说1MB数据对应一条index entry,见下图:

如果我们查询B<2,或者B>8的数据,根据存储索引,我们就可以跳过这些不在min和max之间的数据块,极大的提高了扫描的速度,这就是存储索引的意义。

Hybrid Columnar Compressed

首先我们要搞清楚,什么是行压缩,什么叫列压缩。我们熟悉的数据库,如Oracle,MySQL等都是基于行的数据库,就是行的不同字段物理上存放在一起,还有一种是基于列的数据库,就是每个字段的不同行物理上存放在一起。他们的优缺点同样突出:

基于行的数据库,访问一行非常方便,但是由于同一列的数据是分开存放的,如果要针对某一列进行查询时,几乎要扫描整个表才能得到结果。基于行数据库的压缩,称为行压缩。

基于列的数据库,因为同一列的数据物理上放在一起,所以访问一列非常方便,也就是说如果针对某一列进行查询时,不需要扫描整个表,只需要扫描这一列的数据就可以了,但是访问一行的全部字段非常不方便(又是废话)。基于列数据库的压缩,称为列压缩。

Oracle通常说的compress功能(包括11g R2的Advanced compress),都是行压缩,因为Oracle是个基于行的数据库。大概的方法就是在block头部存放一个symbol table,然后将相同的值放在那里,每行上相同的数据指向symbol table,以此来达到压缩的目的。行压缩的效果通常不好,因为我们知道行与行之间,其实相同的数据并不多。但是列压缩则不同,因为相同列的数据类型相同,很容易达到很好的压缩效果。

行压缩和列压缩都有其优缺点,而Oracle的混合列压缩技术,实际上是融合了列压缩的高压缩比和行数据库的访问特性,将两者的优点结合起来。Oracle提出了CU的概念(compress unit),在一个CU内,是一个基于列的存储方式,采用列压缩,但是一个CU内保存了行的所有字段信息,所以在CU与CU之间,Oracle还是一个基于行的数据库,访问某一行,总是只在一个CU内。每个CU由一些连续的block组成,CU header中记录了每一行的各个列在CU中的分布情况,在混合列压缩模式下,一行通常是跨多个block的。

所以说混合列压缩,结合了列压缩和行访问的特点,即可以提供非常高的压缩率,又很好的保证了基于行类型的访问。

Exadata的另一个重要功能是IO resource management,如果我们在一个Exadata上部署了很多个数据库,可以用它来管理IO资源,这里就不作阐述了。

目前,我还没有了解到在国内有Exadata的应用,而且资料也是比较少的。希望有机会可以真实的测试一下它的性能,我不怀疑他在DSS环境下的表现,但是对于OLTP类型的应用,是否真的象Oracle说的那么强劲,还有待于验证。

–EOF–

如何控制SQL执行计划之9i篇

对于一个高并发的OLTP系统,SQL执行计划的改变往往意味着灾难。很多因素都可能导致执行计划发生不可预期的改变,比如表结构,索引,统计信息等变化,甚至我们发生过一个小小的grant操作,导致执行计划失效,重新解析后生成了一个不正确的执行计,让整个系统Crash的案例。最近,我们对一个分区表增加分区后,导致了执行计划发生改变,故障再次重演。

如何控制SQL的执行计划就成为了一个课题,之前我曾经写过一篇关于调整SQL执行计划的文章,但是内容比较粗略。因为Oracle提供了很多手段去控制执行计划,所以我打算为每个版本都整理一个最佳实践。

我们设想一个场景,一个SQL本来应该走nested loop join,但是由于某种原因,突然变成了hash join,调整统计信息无效,数据库load不断升高,只留给你很少的时间,怎么办?最直接有效的方法是在SQL上加hints,但是需要程序发布,或者程序根本无法修改。

9i为我们提供了Stored outline,大家都非常熟悉,但是对于上面的场景,还是需要点技巧。方法是:生成两个stored outline,一条是错误的,一条是正确的(加hints),两个然后将其执行计划交换。看下面的步骤:

1.创建两个public stroed outline,第一个是目前运行的,第二个是加了hints.

create or replace outline OUTLN1 on select e.ename from emp e, dept d where e.deptno=d.deptno;
create or replace outline OUTLN2 on select /*+ use_nl(e d)*/ e.ename from emp e, dept d
where e.deptno=d.deptno;

拷贝成为private outline

create or replace private outline PRIV_OUTLN1 from OUTLN1;
create or replace private outline PRIV_OUTLN2 from OUTLN2;

交换两个stored outline的执行计划

update ol$ set ol_name=decode(ol_name,'PRIV_OUTLN1','PRIV_OUTLN2','PRIV_OUTLN2','PRIV_OUTLN1')
where ol_name in ('PRIV_OUTLN1','PRIV_OUTLN2');

设置本session生效,并测试结果

alter session set use_private_outlines=true;

刷新

execute dbms_outln_edit.refresh_private_outline('PRIV_OUTLN1');
execute dbms_outln_edit.refresh_private_outline('PRIV_OUTLN2');

发布到public outline

create or replace outline OUTLN1 from private PRIV_OUTLN1;
create or replace outline OUTLN2 from private PRIV_OUTLN2;

设置使用,调整完毕

alter system set use_stored_outlines=true;

这样操作的原因是因为stroed outline必须依赖SQL的文本匹配,所以,我们利用加hints之后的SQL产生正确的执行计划,并通过交换的方式,让SQL的执行计划变成我们想要的结果。当然,如果你愿意,也可以直接修改ol$hints表的内容,同样可以达到改变执行计划的目的。

–EOF–

online rebuild发生的问题

朋友遇到一个问题,当建立一个包含varchar2(4000)的索引时,直接创建没有问题,但是如果加上online子句,则报错:

ORA-00604: error occurred at recursive SQL level 1
ORA-01450: maximum key length (3215) exceeded

这里实际上包含了两个问题:

1.索引的key到底可以有多大?因为索引的key是不可以跨越block的,所以最直接的决定因素是block size,Oracle的文档给出了一个计算公式:

DB_BLOCK_SIZE:                  Maximum Index Key Length:
==============                  =========================

2K  (2048)                       758  Bytes
4K  (4096)                       1578 Bytes
8K  (8192)                       3218 Bytes
16K (16384)                      6498 Bytes

文档中说:每个index leaf block至少有两行记录,考虑block header,PCTFREE,INITRANS等因素,每个index key最大可以为block size的40%。但是我测试了一下(8K block size),发现并不是如此。

create table test(id varchar2(4000),id2 varchar2(3000));
create index test_ind on test(id,id2);
ORA-01450: maximum key length (6398) exceeded

最大的长度是6398,而不是文档中提到的3218,后来我把block dump出来,发现每个index leaf block中只有一行,而不是文档中所说的至少有两行,这样index key的最大值就自然变大了,大约为block size的80%。为什么与文档上不一致,应该是版本的问题,文档描述的是在9i之前的情况,而9i之后Oracle作了改进(没有8i的环境,没法证实)。

2.为什么online无法创建,而直接创建没有问题呢?

因为online创建的过程中会生成一个中间表,用来记录创建过程中的变化,而这个表是IOT表。经过测试,发现IOT表的限制比较严格,8k的block size,最大长度只能有3215,所以普通创建可以成功,而online创建则不行,关键还在背后的IOT表上。

怎么解决这个问题,Oracle的官方说法是加大block size,但是对于IOT表,我们发现加大block size并没有太大作用,最大长度也只有3800。看来,对于INDEX每个block可以只存放一条,而IOT比较特殊,每个block需要至少存放两条记录。简单点说,IOT的限制比普通INDEX更严格一些,至于为什么,我想也许没什么特别的原因吧,也许只是版本改进过程中的代码问题。

我很奇怪,怎么会在varchar2(4000)的字段上建索引呢?一问才知道,原来这个字段实际内容并不长,但是当时设计时,可能是为了方便,直接设计成最大值了,才导致现在的问题。现在只能想办法,重新建一个适当长度的字段,把内容拷贝过去,然后再建立索引。

–EOF–

我这五年

不知不觉,来杭州已经五年了。翻看了以前的blog,里面记录了我这五年的心路历程。

2004年12月27日,《新的征程》

“上帝关上了一扇门,总会打开一扇窗。所以,别担心。”

2005年3月19日,《杭州两个月》

“说说工作吧,刚来的时候,我感觉到很大的压力,毕竟我和周围的同事相比,水平还差很多。但是发现 BITI,RUDOLF,GRASSBELL,FENNG,WANGHAI,CHENYP,CHENJP,都对我非常的好。虽然我的水平很差,但是有什么 问题,都耐心的给我讲,让我很感动,尤其是BITI,能在这样的环境中工作,我感觉很满足。”

2005年7月28日,《一致读的实现》

第一次有点技术含量的文章,现在看起来依然很优雅,短短几句话,就将一致读的原理解释的清清楚楚:)

2007年3月4日,《细节决定成败》

做DBA的第一次犯错。

2007年5月19日,《IBM P570+DMX3》

第一次跟着老大做项目,学到了很多东西,现在仍然受益匪浅。

2008年1月1日,《我的2007》

2007年,终于实现了梦寐以求的目标,正式当上了DBA。比当DBA更重要的是,当上了别人的爹。

2008年3月6日,《我有压力 未解决》

“工作是挣扎滴,前途是渺茫滴
压力是很大滴,解决是没有滴
抱怨是没用滴,开心是重要滴
未来是光明滴,什么都会有滴

五年时光,很快就过去了,虽然没成就什么大事业,不过也算是小有成就,感谢家人和朋友的支持,真是挺不容易的。

一直一来,我的梦想是成为象BITI那样的技术大牛,也一直为了这个目标而努力,但是今年情况可能发生了变化,我从技术岗位调整为管理岗位,这本不是我的意愿和初衷,但是我也欣然接受。未来我将把更多的心思放在团队身上,努力让每个人都能从团队发展的过程中受益。

我不会放弃自己的梦想,大师说过:只有自己坚信,才会有人同路。我坚信现在的工作是有价值的,我坚信所有的痛都是暂时的,我坚信付出总有一天会有回报的,我坚信……

–EOF–

一种并行加载的方法

一个数据库的同步系统,可以分为三部分:抓取变化,传输和加载。抓取数据库变化,最通常的做法是用trigger记录到表中,或者通过解析Oracle redo log中的信息来抓取。传输是将数据库变化记录到特定格式的文件中,通过网络推送到目标数据库上。加载则是指在目标数据库上应用这些变化(SQL),这里主要是讨论并行加载的实现思路。

我们通过trigger或者redo log得到了数据库变化的事务流,这个流是按照事务的提交顺序排列的,最简单的方法是在目标端按照这个事务流顺序执行,就是串行执行。这么做的最大优点是可以完全保证事务的一致性,但是缺点是性能很差。如果采用并行加载,就需要考虑事务相关性的问题,所谓事务相关性,是指两个或多个事务更新了同一条或者多条记录,他们之前存在时间上的依赖关系。如果将具有相关性的事务分在不同的并发进程上加载,可能出现后面的事务被前面的事务覆盖掉的情况,最终导致事务混乱。

我们设计一个并行加载的方法,目标是尽可能快的加载数据,保证不会产生数据混乱的情况,但是不能保证事务完全的一致性。这句话如何理解,假设两个事务不相关,A事务只更新A表,B事务只更新B表,如果在源库A事务先于B事务完成,那么在目标库可以让B事务先于A事务执行,或者两个事务并行执行,虽然事务的顺序与主库并不一致,但是数据是正确的,因为两个事务不相关。

我们如果按照事务去分拆并行的话,设计比较困难,因为涉及到事务的相关性分析。换个思路,我们可以按照数据来拆分并行,也就是把同一个事务拆分到不同的并发进程中,保证表的同一行记录的所有更改都由一个并发进程处理。按照这个思路,我们首先把事务流加载到一个队列或者一个内存结构中去,为了理解方便,我们可以认为放在了一张内存表中,这个表有以下几个字段,事务ID,表名,PK,SQL,时间序列号。首先按照不同的表分组,将不同的表的操作分配给不同的进程处理,每个并发进程按照事务提交的时间顺序来执行。比如:A事务更新A,B,C三张表的A1,B1,C1记录,B事务更新A,B,C三张表的A2,B2,C2记录,C事务更新A,B,C三张表的A3,B3,C3记录。三个事务的提交顺序是A,B,C,这时我们可以启动三个并发进程,分别处理A表,B表和C表的操作,顺序是A1,A2,A3……这样就实现了最简单的并行,虽然目标库的事务与主库并不一致,但是数据是完整和正确的。

按照表来做并行可能还不足够,如果某张表的更改量特别大,这时我们还可以进一步分组,针对同一张表中的操作再按照行(PK)分组,保证同一行的不同操作分配到同一个的并发进程处理。这里有一些小的技巧,在很多情况下,我们可以只关注某行的最后一个操作就可以了,比如某行的最后一个操作是delete,那么我们只需要执行delete,之前的操作就可以直接丢弃,如果是insert,那么我们可以在目标库先执行delete,然后再insert,针对我们自己的系统,有些表的update是全部字段更新,所以我们直接采用了merge操作,相当于对同一行的不同操作,我们做了合并处理。因为系统是自己开发的,所以可以针对我们自己的特性定制了功能。

有人说这个思路有些土,Oracle logical standby,Goldengate或者Shareplex这些商业软件是怎么做的?我之前也写过一篇文章探讨这个问题:Oracle Logical Standby SQL Apply Architecture,这些商业软件都声称自己分析了事务的相关性,可以做到并行加载,但是同样也存在事务不一致的问题,但是分析事务的相关性肯定要按照表或者行来分析,所以思路应该不会差太远,无非是他们包装得更好。

PS:这个方法并不是我想出来的,是团队的智慧。现在看起来思路挺简单,但是其实困扰了我们很长的时间。当然如果你有更好的方法,欢迎和我讨论。

–EOF–

《我的2009》定妆照

打算每年都给自己和家人拍几张照片,以此来纪念每一年的变化。本来是想照几张严肃点的,但是室内的效果实在不怎么样。元旦天气不错,带家人去了趟玉皇山,就作为《我的2009》的定妆照吧。

DSC_1991 DSC_2006 DSC_1986

在玉皇山顶,因为可以看到西湖,又可以看到钱塘江,所以叫“一统江湖”,希望2010年我也可以达成自己的目标,在自己的江湖世界里,一统江湖!

–EOF–

我的2009

又到了一年一度总结的时候了,其实挺害怕总结这事的,因为每次回头看过去的一年,总是感觉有些失落。

2009年最大的事就是搬进了自己的家,不管装修的过程多么曲折,但是结局是圆满的。同事们戏称我是“品质”男,殊不知追求品质是要付出代价的。

工作上逐步脱离了一线环境,带了一个小团队,负责了一个项目,为了这个团队和项目,付出了很多心血。谋事在人,成事在天,不管结果怎样,我问心无愧。

这一年我一直在寻找自己的方向,如何在远离一线环境后,继续提高技术水平。“多读,多写,多教”是我摸索出来的方法:从问题出发,在找寻答案的过程中,不断思考,最终提炼为简单的道理。“教是最好的学”,所以我会尽可能的讲给别人,或者与别人讨论,从中发现漏洞并完善。

这一年,同事们都说我越来越能“忽悠”了,我觉得这句话是在夸我,因为我大部分忽悠的是技术问题,尤其是当我弄明白了某件事情之后,就有强烈的欲望与别人分享。最近尤其喜欢和别人切磋技术,很多东西不辨不明。遇到志同道合的人讨论技术,很快乐,很鸡冻。

Blog还在写,大道至简是我的风格,期望用最简单的语言把技术问题讲明白,不会有任何操作手册类的文章,虽然篇幅不多,但是每一篇都经过了思考和提炼。Twitter每天都在写,大部分是工作和生活中的感悟,有时候也会写点小抱怨,这个东西,真得挺好。

2009年另外一个技术上的提升是,熟练掌握了翻墙的技能,并在各种场合教别人如何翻墙,就算我们不关心政治,翻墙去寻找一些技术上的资料,也是一个技术人员必备的技能。

我这个岁数的老男人,已经进入了一个舒适区,每天上班下班回家抱孩子,不希望有什么改变。明年要打破这个舒适区,给自己更高的期望,寻求突破。

2009年就这样了,虽然没啥大成就,但是也没虚度光阴。路在前方,还得继续走,看看2010年的几个事:

家庭和谐是最高目标,为老爸当好IT支持,为老婆当好司机,为儿子当好爸爸,为老妈当好儿子。

工作上继续为那个小团队和那个项目努力,兄弟们跟着我,我总得走在前面吧。

多读,多写,多教,技术上提高,突破自我,影响团队,努力向大牛们靠拢。

旅游什么就不指望了,等儿子长大了,财务自由了,全部补回来。

苦练内功,我为自己定了一个目标,三年内要……不成功就转行娱乐圈了。

–EOF–

“他强任他强 清风拂山岗 他横任他横 明月照大江”—给所有为了生活苦苦奋斗的70后老男人。

Oracle排序算法

大牛jonathan lewis在圣诞节出了一个小题目:Holiday Quiz

I have a table with one million rows, there are no indexes on the table. The table has a column called sortcode which has no nulls, and has been generated in a highly random way so that no value appears more than four times. Consider the following queries:

select sortcode
from t1
order by sortcode;

select  sortcode
from (
select sortcode
from t1
order by sortcode
) where rownum <= 10;

How many rows are sorted in each of these two queries – and roughly how much memory would you expect Oracle to use ?

从表面上看,两个SQL仅仅是返回数量的不同,因为没有索引,所以就算只返回10行,但是也必须完成整个排序过程,所以从排序的资源消耗上,两者应该没有太大差异。

Jonathan公布了答案:Short sort,主要是Oracle引入了一个针对sort的改进,即version 1 sort,大致原理是用一个二叉树来保存最终需要返回的记录,并且记录这个二叉树中最大的值,针对每个值逐一与这个最大值比较,如果大于这个最大值就直接丢弃(因为这里要寻找最小的10条记录),如果小于最大值,则插入到这个二叉树中去,最终返回这10条记录。因为普通的排序要返回所有记录,如果也采用这个存储方式,即用二叉树来存储排序的结果,可能非常耗费内存,所以这个改进只针对返回结果集比较少的情况。另外用二叉树,可以迅速的找到插入的位置,减少比较的次数。最后Jonathan还用三个极端情况来证明了这个排序算法的效果,正序,反序和随机,正序和随机时,因为大部分值都在第一次比较就被丢弃,所以占用内存和比较次数都很少;相反,如果是反序的情况,因为几乎所有的值都需要插入到这个二叉树中,占用内存和比较次数都大幅度增加,关于这个话题大家可以看Jonathan的博客,这里不再赘述。

我这里想讨论另外一个话题,Oracle到底采用什么排序算法?我查阅了很多资料,都没有提及。学过计算机的人都知道排序算法是一个古老而又有趣的话题,我们熟知的有:冒泡排序,选择排序和插入排序,这三种排序算法比较简单,但是效率不高。高效率的排序算法有:快速排序,堆排序和归并排序,我们下来大致介绍下这三个排序算法。

快速排序是采用分治法的策略,即分而治之,首选选取一个中枢值(一般选序列中的第一个数),每次规划将序列按照这个中枢值分成两个序列,左边的序列都比中枢值小,而右边的序列都比中枢值大,一次规划完成后,中枢值找到了其最终的位置,并且将原有序列分为两个部分,然后再用同样的方法分别处理这两个序列,直到排序全部完成。快速排序是一种效率很高的排序算法,如果采用原地分割的算法,快速排序占用很少的额外空间。

堆排序有点类似于插入排序,但是他利用了堆积树(堆)这种数据结构,堆积树其实就是一棵完全二叉树,但是又满足堆属性:子节点的属性总是大于或者小于其父节点,即所谓的大根堆和小根堆。排序的过程实际上就是把数据不断插入到堆积树上,而返回排序结果的过程就是不断取堆的最大(大根堆)或者最小值(小根堆),并不断缩小堆的过程。

归并排序就是将两个或多个有序的序列合并为一个有序序列的排序过程,称为两路归并排序和多路归并排序。归并排序的算法是在每个有序队列上设置一个指针,通过不断移动指针,在每个序列上取出元素进行比较,并合并的过程。归并排序通常使用在外部排序中。

内排序和外排序,内排序指在内存中完成的排序过程,外排序指排序内容无法在内存中一次完成,而需要借助外部存储来完成排序的过程。

根据Jonathan的实验,我们可以看到上面这个排序优化的例子,实际上利用了堆排序的特性,但是由于堆积树本身需要额外的空间,在返回记录数很多的情况下,并不适合,实验也证明,如果采用堆积树来保存所有记录,需要占用更多的空间。关于Oracle的排序算法,虽然不是很明确,但是很可能是快速排序的一种,因为快速排序占用稳定的空间,通常情况下可以提供很好的效率。如果排序无法在内存中一次完成,Oracle需要借助Temp空间,这就是外排序,Oracle使用多路归并排序算法,按照排序区大小把结果集切分成多个子集,每个子集在内存中完成排序,然后将他们合并起来。排序区大小对归并排序的性能影响很大,排序区应该能至少容纳每个子集中的一条记录,否则性能会急剧下降。

Oracle的排序算法我们并不了解,以上内容很多也是基于Jonathan的实验的猜测,所以大家别较真。对于排序算法本身,我的描述并不一定正确,欢迎大家批评指正。

–EOF–

用ASM和iSCSI实现的另类HA方案

普通PC本地磁盘,没有共享存储,如何实现HA?Dataguard挺好,但是存在数据丢失的可能性,而且很难做到应用透明切换。我们用ASM,Heartbeat和iSCSI可以实现一个廉价的HA方案,如下图:

用iSCSI将本地磁盘输出到对方的机器上,利用ASM的failgroup做mirror,保证数据mirror在两台不同的机器上,就算一台机器完全损坏,数据可以做到百分之百不丢失。用Heartbeat作HA探测,如果发现主机故障,则强行关闭DB和ASM,并在备机启动ASM和DB。如果使用Oracle 11g R2,还可以利用Preferred mirror read的特性,保证主库读自己的本地磁盘,而不是通过iSCSI读备机磁盘,这样可以达到更好的性能。

缺点:Heartbeat作为HA软件,我们并不是十分了解其探测机制,可能出现误判或者无法切换的情况。但是其实IBM hacmp这种HA软件一样有问题,比如Oracle hang住时,现在的hacmp根本无法探测,因为hacmp只是判断Oracle的进程在不在,而不管数据库是否活着。

我想不管什么HA软件,都无法处理所有的异常情况,我们只要有完善的监控和应对措施就可以了。比如我们现在所有的DB都有一个监控,就是定时模拟应用去更新数据库中的数据,如果发现超时或者报错,就认为数据库出现hang的情况,并发出报警。

–EOF–

另:之前我有一篇文章介绍用ASM和iSCSI搭建RAC的文章,在实际测试过程中,发现存在一些问题,因为在11g R2之前,voting disk和OCR都必须放在RAW devices上。因为没有共享存储,如果发生某台机器全部宕机,voting disk可能会丢失一部分,造成RAC的cluster机制发生误判。所以在11g R2之前,这个方案是有问题的,在11g R2中,Oracle几乎所有的东西都可以放在ASM中,这个方案也许可行,我还没有测试过。

在我写完这篇博文后,发现这个方案存在一些问题,通过iscsi将online redo输出到另外一个主机后,log file sync的响应时间需要40-60ms,这个响应时间肯定是无法接受的。现在两台主机的互连是四块千兆网卡直连,通过Linux的Multipath来管理多路径,为什么响应时间这么久,我们还在进一步查找问题。