2010年1月9日星期六

IT宅男们,有没有什么技术让你觉得相见恨晚?

无意中想起SQLite,于是发起了这么一个话题,感谢各位朋友的参与,我把大家的话收集一下,放在这里。

@sshg

PY算一个吧。当初看了一个下午就决定ditch Perl了。Haskell算另一个吧。看到type class的时候,也是怦然心动。不过Haskell比Py门槛高,用的机会也相对少。呵呵,我资格比你老。我上手的时候才是2.1。查资料的时候,Internet上还有很多1.6的东西。此外,还被逼无奈,用1.4写过代码。呵呵,1.4才叫那个痛苦呢。连自省都很不完善。

硬件方面,Nokia的maemo设备。此外,3M的迷你投影仪,以及CU上介绍的OLPC

75没有的第二代OLPC平板电脑。Sexy!

还有虚拟化。用过很多虚拟化的产品,包括vmware, VBox, Xen, KVM。具体哪个产品好,不好说,但是第一次看到vmware的时候,那个激动呀。用一个词形容,sexy!

这样说吧。技术分几种。一种是一看就喜欢,然后越研究越喜欢。比如Py,比如虚拟化。有的,一看喜欢,一眼就就放下了。比如Zope。有的一看没觉得怎样,研究一番之后,喜欢上了。比如DNS。还有,一看就不喜欢,越用越不喜欢,还很恶心地得天天用。比如Perl。

@laiyonghao

cassandra/thrift/scribe

@xiaoxiolu

pypy吧

@smallfish

perl和pg。可惜现在一直未有大规模应用,还只是自己的试验田

@neruont

函数式编程. 话说我学函数式编程思想入门还是靠 CPP 泛型...

@marchliu

一 度觉得,我对Python的接触并不算很晚,毕竟从Python2.3以后,它才逐渐完善了多编码文本、修饰、异常等等有用的特性,但是想了想,即使只是 自由软件和动态类型这两个特色,已经足可以改变我的世界观了。如果时间可以倒流,我希望1991年就开始学习Python。

Postgres, 其实从04年就想要学习,但不得其门而入。从去年正式的使用它,这个产品从未让我失望过一次。我是一个“重”数据库爱好者。PG足够强大完备,它的无限嵌 入语言、正则语法集成、丰富的contrib,使我如鱼得水。这个产品可称伟大。它让我入行十年来很多未实现的构思得以成真。

Perl对我来说也是非常重要的一项,它使我的文本处理技能提升了一个台阶。如果早几年学会它,相信我获益会更多。

Emacs, 这个软件对我来说太重要了。它使我真正摆脱了对IDE的依赖,让我从一个拖控件的家伙,真正进入到对代码和软件开发的理解。它改变了我的工作习惯和思维方 式。不过在Emacs23之前,我大概不能接受它对中文的支持度,事实上也是因为这一点,我才拖到这几年学会它。

Unicode,Unicode如果早十年推广该多好!不需要更多的解释了!

曾经我有很多次机会,如果可以快速交付实用的软件,就可以赚一些钱,但是我找不到顺手的组合,甚至出现过两次对我打击比较大的失败。如果早几年接触Drupal和SQLite多好!我想写这个专题,正是因为刚才作饭的时候想起了SQLite,这是一个伟大的小东西!

SQLAlchemy,作为一个数据库编程工具,SQLAlchemy还不够完美,但是我再没有用过比它好的了!早年我不该相信“SQLAlchemy笨重复杂,难学难用"的谣传。

各种VCS,作为一个IT工程师,我应该一入行就学会它们中至少一个的使用,但是一直到05年我才有机会去学习和使用CVS,甚至我身边的同事对VCS的兴趣都很小,有很长时间只有我自己去尝试SVN和CVS的使用。真是个悲剧。

2010年1月6日星期三

Java Play! 学习体验

Java平台上有大量优秀的资源,但是其基础Web框架标准J2EE非常沉重复杂,严重 影响了小团队使用Java技术实现快速开发。而 Java Play!( http://www.playframework.org/ ) 有力提升了Java开发的敏捷能力。

这几天我一直在下班时间学习这个框架。总的来说,它非常的易学易懂,而且架构分层很接近ROR的模型(或者,对于我,很接近Web2py)。其架构设计非常清晰易懂。另外,使用中我体会到的几点优点如下:

- 在线热开发,不必考虑“Web-app”之类的部署问题,可以在命令行方便的建立和启动项目站点进行开发,源码保存后自动热编译更新,这一点比 Web2py好多了。
- 框架驱动模式合理,几乎不用操心接口继承、注入、AOP什么的,也不需要写XML 配置文件,最多为特定功能的结构进行标记。
- 文档翔实,质量高。这是在任何一个Python框架中都没有达到的水准,包括 Django和ZOPE。甚至就算ROR我觉得与之文档相比也有相当差距。仅 Tutorial一项就比web2py所有文档加一起的质和量都好很多了。
- 结构透明,无魔术。可以方便的使用任何开发工具进行开发,不依赖于某个 IDE,我是用的emacs。
- route 配置非常简单,纯文本,每个配置一行完成
- 模板功能丰富,特别是URL生成,非常智能。
- 有丰富完整和强大的测试支持,从基础的Java UnitTest到前端页面的“硒”测试,还允许用YAML编写测试数据环境,注入到测试或开发环境使用。
- 提供了CRUD、图片验证码等功能支持。

说了这么多好的,再说说我认为不足的:
- 限于Java语言,它再怎么智能、动态,也是有限的,Java语法不允许的,它不会变魔术。比如SQLAlchemy的动态记录类型,这个在Play的 JPA里,如果不反射的的 话,应该是没办法的。
- 模板功能很丰富,但是标记稍有些烦琐,这个不算大问题。
- 结构比J2EE/EJB简化了,自然也就有简化的代价,估计再像传统方式那样集合大 团队作战不太容易,例如模型层想做多层的代理转发是不行的。但是另一方面说, 肯定也比过去节省人力,开发效率高。
- 还没找到有没有XMLRPC等服务的提供方式,如果真的没有,就需要自己弄一个嵌进去了。

Play 一个有趣的地方是大量使用动态语言技术进行混合开发,框架里有不少 Python(jython?)和groovy代码。

在以下两个方面,Play可能会对有贡献:

一些Java成熟的技术和资源,如Flash、商业应用模型,可以通过这个平台引入 一些高性能要求的应用,目前测试结果Play好过Django等框架。

Play是我至今遇到最易学易用的Java框架,首次将Java Web开发的门槛降到了 ROR、Django、Web2py这个层次。以前我们难以引入的一些Java资源,现在有希望了。

2009年11月22日星期日

数据库技术人员的能力评估建议与培养规划

数据库技术人员的能力评估建议与培养规划

http://zerolab.co.cc/?q=node/41

  • 自挖坑,存档

岗位职能区分

数据库技术在现代软件技术领域有广泛的应用,与数据相关的技术岗位和职能,也可以区分为若干个不同的类别。根据在一个理想的开发团队中不同的分工和知识掌握的不同,可以区分为:

  • 程序员
  • 分析师
  • 设计师
  • 架构师管理员

对于数据库领域,分析与设计岗位、架构与运维岗位(管理员)的重叠较高。特别是分析与设计岗位通常在团队中由同一(组)人承担。在更多的团队中架构师也与分析和设计人员的岗位重叠。因此,从项目开发周期考虑,可以简单的分为三类:

  • 分析与设计岗位(含架构)
  • 开发岗位
  • 运维岗位

个人能力发展路线

基本路线图

  • 初级程序员
  • 中级技术岗位
  • 初级设计人员
  • 初级管理员
  • 中级开发人员
  • 高级技术岗位
  • 架构师
  • 设计师
  • 高级管理员

入门

通

常来说,新手接触数据库技术,从程序员岗位做起。这是因为对于数据库技术,此岗位比较容易切入。在软件开发团队中,通常允许初学数据库技术的开发人员,从

事受限的数据库开发工作,此类工作内容可以是基本的增删改查,其数据结构已经由设计人员固定。初级程序员岗位是练习 SQL

语言基本技能、理解关系模型的很好的切入点。

近 年来,此岗位工作通常使用 ORM

工具来完成工作,以提高工作效率和质量。但是对数据库的了解仍然对这个岗位有重要意义。对数据库知识有深入了解的开发人员,可以编写出更高质量的

ORM 代码,可以在必要的时候越过 ORM 的功能局限,编写扩展代码,可以定位性能瓶颈进行优化,等等。

因

此,关系型数据库研发岗位,在当今应用层封装技术非常成熟的前提下,入门更为容易。程序员可以先从 ORM

入手,通常自己比较熟悉的应用层编程环境学习数据访问知识,然后再进一步了解 SQL 语言的知识。另一方面, ORM

并不能代替关系数据库本身。关系数据库知识的掌握程度直接影响程序员的职业发展。

提升

分析/设计岗位

技

术人员掌握了基本的数据库访问知识后,通常在数据库技术领域,可以有三个不同的发展方向。常见的一个发展方向是转入分析与设计领域。UML

的用例分析、类图、需求分析等知识,与关系数据库的基本设计工具 E-R

图有直接的关系。由此也可以看出,关系数据库的设计和分析工作其实是整合在项目开发过程中的。因为,向此方向发展的技术人员应该提升综合能力,转向软件分

析设计知识。

良好的项目分析准备,能更好的厘清数据库设计所需要的信息。良好的数据库设计,是编写出优质应用层代码的基础。个人前期工作中积累的数据库编程经验,能够使得设计师理解数据库设计中的关键点,了解基本泛式和常见设计模式的运用方法。

===== 中级开发人员 ====

面向数据库的编程工作,本身存在较深入的技术要求。高水平的数据库开发人员,面对各种问题,可以更快的给出解决方案,提供高质量的软件产品。

通常在复杂查询、复杂事务、数据迁移、业务报表定制等方面,需要高水平的开发人员支持。

初级管理员

初级数据库管理员可能由开发人员转岗,也可能独立学习,或由系统管理员转岗。除了掌握系统管理员的基本能力,还要了解关系数据的知识。

作为数据库管理员,要能够实现数据库服务的部署、备份、恢复。能够为开发人员和客户提供问题诊断、预警支持。能够找出运营过程中发生的常见问题,予以解决。

高级技术岗位

架构师

对于大型项目,系统架构成为独立的、重要的岗位。架构师要求综合性的知识和能力,有关数据库领域,主要在于集群设计,备份方案的实现,这需要对操作系统、网络工程、数据库自身的特性都有深入的了解。要求能够解决项目中数据库层的性能和可靠性问题。

设计师

通常来说设计师的工作与架构师有一定的重叠。相对来说设计师的工作更偏"软",需要与开发团队有更多的交流。往往设计师要肩负高级数据库开发人员的职责。同时要有对业务的深入了解。能够设计出应对海量数据、高可靠性、复杂业务的数据库方案。

高级管理员

高级数据库管理员是通常概念中数据库领域的高端岗位。这个岗位要求有很强的运维技术能力。要能够参与数据库架构方案的设计,并将其实现。要求能够完成复杂集群的实施、备份恢复方案实现、预警和问题诊断。

技术能力评佑

初级岗位

预备要求

  • - 掌握基本的编程知识,能够用项目使用的编程语言参与开发
  • 文档阅读能力,能够读懂项目文档,使用术语与团队成员交流
  • 有内外存的基本知识

达标指标

  • 能够使用 ORM 工具(对我部门建议 SQLAlchemy )进行基本的增删改查操作
  • 能够理解 ORM 生成的 SQL 脚本
  • 能够手工编写简单的单表和两表关联操作
  • 学会使用客户端工具

中级岗位

中级开发人员

  • 能够使用 SQL 语言解决复杂的查询
  • 熟练使用工作数据库平台,包括特定的编程功能
  • 熟练使用 ORM 工具,可以灵活的使用 ORM 和 SQL 解决工作中的技术问题
  • 可以判断出程序运行中的数据性能问题、设计问题,提出解决方案

分析/设计人员

  • 能够理解业务模型,将其转化为数据模型
  • 能够使用 UML 完成从需求分析到数据库设计的思考和工具
  • 能够设计出合理的数据库结构,编程方便,维护容易,避免性能和安全隐患
  • 能够指导初级开发人员解决技术问题 - 熟练阅读和编写复杂的 SQL 语句

管理员

  • 熟悉 SQL 编程,能够编写复杂的报表查询和数据操作 - 熟悉 *nix 系统环境能够
  • 了解数据库安装和部署方法 - 能够为架构师和开发人员提供问题诊断支持
  • 能够设计和实现基本的备份方案 - 能够使用系统脚本和 SQL 快速解决常见的管理问题包括:
  • 数据备份恢复
  • 服务器状态查询
  • 解决死锁、死链
  • 存储迁移
  • 授权管理

高级岗位

架构师

  • 熟悉特定数据库的特性
  • 能够理解需求分析,掌握客户需求
  • 能够设计出合理的服务架构和运维方案
  • 能够为开发和设计人员提供数据库技术的咨询支持
  • 能够为运维人员提供数据库运维问题的解决方案
  • 能够为客户提供良好的售前咨询

设计师

  • 熟悉项目设计
  • 了解业务需求,能够与分析人员和客户进行良好的沟通
  • 了解数据特性,能够根据使用的技术进行设计
  • 能够将业务需求转化为良好的设计方案
  • 能够为开发和设计人员提供数据库技术的咨询支持

管理员

  • 熟悉机房环境
  • 熟悉操作系统和服务器的运维技术
  • 熟悉数据库特性
  • 能够制定数据库服务器整体运维方案
  • 能够为数据库运营提供灾备、预警、优化、扩展服务


--
每一个成功都是不可复制的。
……

劉鑫
March.Liu

2009年10月7日星期三

Bitmap 的数据库持久化实现

以下是基于数据覆盖的一个初步讨论,实用中,位映射有广泛的应用领域,也有大量的算法实现需求。在经过初步的推演后,我发现这不是可以一蹴而就的。所以先给出一个简单的实现和应用算法讨论。

Bitmap 的数据库持久化实现

Bitmap 是一个常见的数据类型,常用于优化大量数值的存储和查询。这个概念对于数据 库程序员应该不陌生,Oracle 等数据库通过这种技术优化查询。通常程序员会在一些算法 或数据结构书籍上读到以 C 语言 char* 实现的 bitmap 。这里,我们考虑一个在数据库中 存储的 bitmap 实现。

PG 上的数据结构实现

bitmap 的实现不难,PostgreSQL 中提供了 bitstring 类型,可以直接存储位串。但是单行 bitstring 能提供的数据长度终归有限。为了存储和性能的需要,我们将其设计为一个表。 通过索引值将 bitmap 分片为 bitstring 集。

create table bitmap(idx bigint, segment bit(32));

分析

计算时我们将一个bitmap表视为一个完整的bitmap数据集,设 segment 的宽度为 w ,索引 值为 i 。将第行的 idx 置为0,则第 i 个索引行上的第 b 位对应整个 bitmap 的第 b+i*w 位。逆推之,bitmap 第 x 位为 x div w 行,第 x%w 位。

因为数据分行存储,位计算操作也需要重新定义,要考虑跨界定位问题。Postgres 的 bit 本身支持左移右移操作。这点并不复杂。可以利用掩码技巧将计算参数分解为区片。具体算 法几乎与 C 语言版本一致。当然针对一些有规则操作时,可以充分的利用 SQL 和 PLSQL 的语法特色进行简化。

实例

覆盖统计

详细内容介绍见下一段中的链接。这里简单介绍一下:每个数据样本是一个由大量一维区间 值组成的文本。每一行是一个(起点,终点)区间。要计算二到N个样本件的覆盖率。而数 据本身是未经整理的,可能有重复。

事实上,这个例子是我最初着手实现 bitmap 的起因。可以说 bitmap 可以直接优化此类问 题,首先bitmap以至少8倍以上的比例节省存储空间(以 c 语言 char* 每字节存储位置状 态来计)。而空间缩减后对查询性能也有直接提升。

这个应用的操作非常好实现,将收集来的数据区间段(s, e)转换为二进制数 2^s+2^(s+1)+...+2^e ,即从 s 到 e 之间都为1的二进制位串。然后与整个 bitmap 做或 运算。示例代码如下:

create or replace function segment_over(s bigint, e bigint) returns void as $$
declare
sidx bigint;
eidx bigint;
d_data bit(32) := 4294967295::bit(32); -- pow(2, 32) -1
begin
if currval('bitmap_idx_seq') < (e/32)+1 then
for i in currval('bitmap_idx_seq')..e/32+1 loop
insert into bitmap(segment) select 0::bit(32);
end loop;
end if;
sidx := s/32;
eidx := e/32;
if sidx = eidx then
update bitmap set segment = (d_data << cast((32-e%32) as int))&(d_data >> cast((s%32) as int)) where idx = sidx;
else
update bitmap set segment = segment | (d_data >> cast((s%32) as int)) where idx = sidx;
update bitmap set segment = d_data where sidx < idx and idx < eidx;
update bitmap set segment = segment | (d_data << cast((e%32) AS INT)) where idx = eidx;
end if;
end;
$$ language plpgsql;
优化分析

此示例中,segment_over 操作并未做任何深度优化,仅仅是将前述的思路直接实现出来而 已。实际上根据业务,我们还可以进一步的提高操作效率。

例如,由于覆盖度操作是在数据区迭加或操作,单向的填充动作,可以将已覆盖区间直接压 缩掉(这需要将 idx 字段扩展为起点和终点一对数据,或者通过触发器机制同步一个查询 表)。

再例如,前例中我们的 segment 长度为32,idx 是一个 bigint 类型。这对于海量数据比 较有效,不过如果总样本量较小,完全可以用 integer ,这样可以减少很多转型操作。

在 segment_over 中,我写了一个防出错的探测代码,如果索引值超出了初始化范围,会扩 展 bitmap 表。但是实际上很多应用,其总数据量是有明确上界的。此时可以去掉这部分代 码,使用一个一次性的初始化代码,例如下面这样:

create or replace function init(len bigint) returns void as $$
declare
i bigint;
begin
truncate table bitmap restart identity;
insert into bitmap(idx, segment) select 0, 0::bit(32);
if len/32 < 1 then
return;
end if;
for i in 1..len/32 loop
insert into bitmap(segment) select 0::bit(32);
end loop;
if len%32 > 0 then
insert into bitmap(segment) select 0::bit(32);
end if;
end;
$$ language plpgsql;

将 segment 的宽度调整为略大于覆盖区间长度期望值的数值,可以减少对中间区间的覆盖 操作,也有优化读写效率的可能。

数据库实现的动机

使用数据库存储 bitmap ,可以避免海量长度 bitmap 超出内存容量的情况,是一个比较经 济的实现方式,特别是在机器不够强劲,或者没有条件部署云或网格集群的场景。只要熟练 数据库层开发即可。

由于 Postgres 中已经实现了完备的并发访问、数据备份和查询等功能,可以避免自己开发 文件型 bitmap 所需要考虑以上问题,带来的开发成本。轻松可以满足 OLAP 的需求,在线 随时查询覆盖情况。

合理的使用数据库功能,可以有效的降低使用和开发成本,这种组件式的开发和集成方式, 对于当下追求灵活、高性价比和可持续性的 IT 产业理念,是契合的。


……

劉鑫
March.Liu

2009年9月24日星期四

sqlalchemy、storm和web2py dal的比较报告

工作项目报告,所以抹掉项目名先,以"X"代之。

分割线内内容仅代表个人意见,与所供职企业及参与社区无关。

===================================

X 从很早的时候就出现各种数据库访问错误。包括链接数占用过多,死锁,
僵尸事务等。本周我集中梳理了一遍代码。我认为,虽然数据库设计方面有诸多
不合理之处,但是这些不合理主要影响业务错误,造成 X 性能和使用上的
问题是因为使用的ORM框架 storm 有严重的缺陷。

首先,storm 对数据库架构的同步有非常奇怪的设定。它不自动同步表结构,却插
手外键关联关系。强制要求外键必须都是级联更新、级联删除、set null。且不说
其设定中有自相矛盾之处,本身在MIS系统中做级联删除就是一件很危险的事——除
了系统维护、分表,MIS系统不应该删除任何数据。 storm 从一开始设计恐怕就没
有考虑企业级应用,但是对于web 开放式应用,storm 对外键的依赖又太笨重了。

其次,storm 在联接数据库后应该会有 DDL 操作(即修改数据库结构)或独占锁
定事务,此推断的证据在于用 storm 联接到 S 库后, S 无法进行
vacuumdb -a -f 处理。而根据 Postgres 手册,PG只有在遇到有链接正在进行
DDL 操作时,才会产生库级锁,造成 vacuum 操作无法进行(或手工建立一个隔离
级别相当的事务锁定)。作为世界上并发能力最强的数据库产品,正常的数据访问
操作与 vacuum 根本不会冲突,热处理资源回收正是 PG 独到的特性。这样造成了
X 应用频繁用光所有的链接数,还在数据库服务器遗留僵尸进程。我观察到
有僵死十几二十天没有完成过的 X 链接,这应该是因为同时多个 storm 链
接提交错误的锁定关系,造成死锁。

第三,storm 如果可以象 DAL 那样,明确设定不同步数据库,以上问题至少可以
解决一半,但是它没有链接配置参数。这造成了我们对其出现的问题无法进行友好
的调整。现在同事在 X 的 controll 层添加了强制的 commit 操作,一定
程度上减少了死链,但是我仍然观察到有点击 X 页面(在测试环境下)无
法响应,甚至造成数据库服务器闪断重启的现象,对于同时运营三十几个数据库的
数据库服务器,这是非常大的安全隐患。

昨天我尝试将 X 的数据库访问层迁移至 web2py DAL,经过一天尝试,总结
出以下的问题:

第一,DAL 缺少精确实数计算类型,它不支持 Numeric 或 Decimal,只能用
double,这是一个相当大的安全隐患,对于涉及财会计算的应用,使用浮点数是一
种很不严肃的作法。

第二,DAL 不支持数组和大数据类型,此类字段在 X 中有几处应用。

第三,DAL 对原生SQL的支持比较初级,虽然也可以使用,但是有时需要兼顾开发
速度,希望可以组合使用的时候,就会受限。

以上问题不是不能解决,但是需要修改 DAL 本身。虽然我一直有计划改造 DAL,
fork 一个对 PG 有良好支持的分支出来。但是这显然需要更多的开发时间。

昨天我详细查阅了一下 sqlalchemy 的文档,进行了一些简单的尝试,感觉这个
ORM 框架比较符合我们的需求:

第一,sqlachemy 对数据库链接的隔离级别和事务有良好的控制,默认也不会去尝
试DDL操作。

第二,sqlachemy 有非常丰富的数据类型支持,包括BLOB,Decimal/Numeric,以
及为 PG 特别定制的数组类型。

第三,sqlachemy 的查询类似 DAL (很可能DAL学习自sqlachemy),对各种查询操
作有良好的支持,还可以嵌入 SQL 片段,也可以方便的直接传入 SQL。

第四,sqlachemy 其实并不难学,它的功能虽然丰富,但是只掌握自己要用到的部
分即可,不需要完全学会,上手还是很简单的。

第五,文档比 storm 完整的多,而且现在仍在活跃开发。

第六,数据存储模型与业务模型分离,虽然看起来有重复劳动,但是对于一个需要
长期维护的企业应用项目,这是正确和严肃的作法。

第七,对特定数据库的特性有良好的支持,还可以扩展。

在使用 sqlachemy 时,我们还可以结合 web2py 原有的一些优秀工具,例如广泛
用于 DAL 的storage类型,这是一个类似 JS Object 的智能对象类型,很适合动
态结构的数据对象。

对于 X 项目的数据访问层重构,我评估工作量至少在一周左右。如果全部
换用 sqlachemy ,可以一劳永逸的解决数据库访问的问题,甚至 S 的后续版
本,我也建议尝试使用这个框架,毕竟这比 hack dal 要方便一些。

sqlachemy 当前的稳定版本是 0.5.6 ,我昨晚试验了开发中的 0.6 ,发现功能还
没有完整实现,现在还不能实用。


===================================

--
光见贼吃肉,没见贼挨打。
……

劉鑫
March.Liu

2009年9月15日星期二

近日 twitter 发布信息整理

  
   数据库的架构与设计,应该从整体的范围去看问题,要从"数据存储和管理服务"的角度,而不是"数据库软件"

   应用层开发阶段,可以不加入外键,在数据库运营阶段,由DBA补完

   ID列的主键身份,其实只是为了与ORM妥协,在很多情况下,一个表只有ID主键列,既危险又愚蠢。如果应用层存在BUG,就会引发严重问题。我的建议是,至少在运营前期,就为关键实体数据加上唯一索引。

   存储过程有没有用?这个要看情况。做数据库架构就像建筑工程,不是"对""不对"这么简单。早年Oracle宣传一切逻辑都封装在存储过程中,数只从存储过程访问,这叫蛋疼。后来MySQL宣传外键存储过程触发器都不需要,只要能增删改查,这叫犯二。

   一个数据库要如何设计,业务对数据可靠性、完整性、性能的需求平衡是一个很重要的因素。对于用户访问日志(包括点击率等等),有一定的误差也是允许的,而商务活动往往是不允许有哪怕毫厘之差。针对不同的业务,应该有不同的设计和架构方案。
         
   上次李元佳先生介绍的EDB集群方案给我一定启发。典型的集群节点可以每节点由三个机器组成:一个查询节点,一个写入节点,一个备份节点。写入节点和备份节点多出来的内存可以共享给查询节点做为无限缓存(EDB特有功能)。
         
   有时查询节点还要负担起OLAP的任务,或者本身统计查询的计算量就远大于写入量,此时可以由一个写入节点带多个查询节点,或者直接向一个EDB网格同步。当然网格化以后,写入性能通常会有接近线性提升,这样可能就不需要写入节点了。

   前段时间我写过一篇文章,合理利用触发器,可以把一个无限增长的日志表访问,变成限定于20条数据的访问,如果按现在流行的MySQL+ORM,这种东西是做不出来的。
         
   事实上很多项目,尤其是高负载的互联网应用,最终都没有使用ORM。既然如此,拒绝数据库服务端编程就是一件很奇怪的事。在数据库服务端进行合理的编程,可以大大提高性能。
         
   既然数据库服务是作为一个整体使用的,那么性能分析也要从整体考虑。有些功能虽然单独比较性能不高,但是却能在合适的时候,提高应用的整体性能。
         
   数据库设计要符合业务,不要拿来作为自己炫技的场所。
         
   虽然这年头是个人都知道出来吼一句"数据库太慢!"以显示自己牛B,但实际上有多少是真的牛B到跨过了关系型数据库的极限,有多少只是不懂数据库在那里装B呢?
         
   观点:DBA的职责,除了维护工作,应该有以下几个——帮助设计人员找出性能和安全方面的问题;帮助开发人员解决数据库方面的问题,例如一些用其它手段难以解决的性能瓶颈和复杂逻辑;协助架构师制定系统持久层架构;为乙方(通常是DBA就职的一方)提供架构方面的专业意见。
         
    个人认为大多数应用不应该出现大量存储过程,尤其在ORM比较成熟的现今,基本不需要为CRUD操作封装存储过程,而存储过程不能与用户多步交互执行,这就决定了它不会主导事务处理逻辑。但是存储过程可以简化复杂的事务逻辑。
         
    简单的说,存储过程最好是在开发由粗到精的优化过程中加入,代替需要优化的代码部分。如果是那种希望快速粗放的拼装一个项目然后交钥匙不管的初等级开发(不要拿这个来冒充XP过程)并不适合过多使用存储过程,最好是用熟ORM,积累并复用确实有效的一些存储过程和SQL脚本模板。


--
话题越大,废话越多;名字越火星,问题越脑残。
……

劉鑫
March.Liu

2009年8月31日星期一

[Postgres Story]最近访问用户问题

前几天有个朋友提了一个问题:

应用平台需要统计最近访问的20个用户的信息。

我第一个想到的是用memcache之类的专用的缓冲,以用户名为键,以最后访问时间为值,如 果用户访问比较均匀,限定一个合适的 超时值,查询的时候遍历过滤就好了,虽然不是很 精确,但是实现起来够简单。

答:不可以,领导要求只在数据库端解决。

环境准备

提问的朋友使用的是 MySQL ,这里我用一个 PostgreSQL 8.4(Enterprise DB Postgres Plus Stand Server 8.4) 建立实验环境。操作系统是 Windows 7 RC。

客户端是 Emacs sql-postgres,\timing on。

使用 explain analyze 分析性能消耗。

尝试

那么,最简单的应该是在日志表上查询:

select username, max(logintime)
from log
group by username
order by 2 desc
limit 20;

这里假定的是会话表(log)中有名为 username 的用户标识列,名为 logintime 的时间戳字 段。

但是显然这个查询的性能并不好,它要遍历整个日志表。

首先,我们给日志表加一个聚集索引,使它按插入时间倒排(显然这对插入效率不利)。

create index idx_logintime on login_session(logintime desc);

alter table login_session cluster on idx_logintime;

然后,再进行查询,可见效率有微弱提升。在我本机的 Postgres 8.4 上,通过 explain analyze 可见有不到百分之二的效率上升。我甚至怀疑这仅仅是源自一些随机事件的影响。

换一个角度分析,只要 select max ... group by ... 的模式不改变,就很难有根本的性 能提升,最好的办法仍然是缓存。

改进

有个简单的办法,可以在PG数据库中制造一个缓存表:

  • 首先,直接建立一个 user->timstamp 键值对表。如果用户量不大,可以直接这样:
  create table sessions(username text, seqs serial, logintime timestamp, primary key(username));
  • 然后创建一个触发器函数
create or replace function on_log() returns trigger as $$
begin
if exists(select * from sessions where username=NEW.username) then
update sessions set seqs = nextval('sessions_seqs_seq') where username=NEW.username;
else
insert into sessions(username, logintime) select NEW.username, NEW.logintime;
end if;
if (select count(*) from sessions)> 20 then
delete from sessions where seqs < (select min(seqs) from (select seqs from
sessions order by seqs desc limit 20) as t);
end if;
return NEW;
end;
$$ language plpgsql;

create trigger onlog after insert on log for each row execute procedure on_log();

OK,这样一来,经过反复测试,在我的笔记本上,插入速度基本没有降低(约 2%~ 5%), 而查询"最新的20个用户"这一操作,速度提升了一百七十余倍。几乎可以视作是一个数据库 端的队列缓存了。这个数值是基于 log 表中有五万条数据,在实际应用中日志表十万以上 (按每日切分导出)比比皆是,性能差距会更为显著。

分析

"最新的 20 个用户",这一问题,与"最近5分钟内的用户"此类问题的区别在于,它不能通 过获取当前时间,简单回溯得到有效数据区间。如何避免遍历整个日志表是一个关键问题———— 现代的在线服务系统每日访问日志量动辄数十万,几百上千万的也不罕见,第一种解法明显 不能满足。相比之下,第二种方案不需要干涉日志表的存储排序,不需要建立多余的索引。 通常我们讲触发器速度比较慢,但具体到这里,只是却是一个比较快速高效的实现方案。

如果用户量不大,在几千以内,为用户表建立一个最后登录时间字段,绑定 null last 索 引(可以加上FASTUPDATE=ON),然后利用基本的order by limit就可以得到比较慢意的效 果了。但是对于大型SNS等应用,数十上百万用户也是有可能的(甚至大型企业内部应用, 数万乃至上十万用户的系统也不在少数)。此时,第二种解决方案就理想的多。虽然看起来 在session表上反复进行计算和写入,但是由于session表的数据量非常小,永远只有几十 条,所以计算速度很快。如果对 log 表进行恰当的分区,同时将sessions表存储到另一个 区域(甚至直接缓存到内存),那么对于海量数据,也会有一个稳定的,良好的性能表现。

更进一步的解决方法,则应该是在数据库或应用层服务器环境,建立一个内存中的队列,来 记录这个数据。只要注意并发写入的问题,就可以得到很好的性能。这方面Haskell的STM机 制、我之前用在MSG.Summoner.Trac的旋转锁机制、都可以比较漂亮的解决这一问题。

优化

sessions 表的读写都非常频繁,通常有比较多的 update,,但是数据量基本恒定。应该积极的执行 vacuum。

可以为日志表添加一个 uuid 字段,以便在数据量上升时按 hash 分表。这样可以获得更好 的写入性能。

可以将 session 的主键索引设为 FASTUPDATE=ON 。

触发器中设定

SET LOCAL synchronous_commit TO OFF;

打开 WAL 异步事务提交,可以进一步提高并发写入速度。

更进一步的,可以写一个常驻的守护进程,用应用语言建立一个队列,将这一需求建立为独 立的服务。这个服务可以通过 pl 嵌入语言与数据库联接,也可以直接联接到应用层,有一 些应用层架构依赖于并发的多 fastcgi 实例,此时要注意并发访问冲突和数据同步的问题。

总结

之所以出现这样一个困扰开发人员的问题,根本在于项目领导不提供,也不认同使用灵活、 开放的思维方式解决此问题。服务项目的架构是一个系统工程,类似这样的问题,完全可以 用更开阔的眼光去寻找出路。建立一个简单的缓存队列实例,用perl或python,只需要数十 行。结合Postgres的plperl或plpython等嵌入脚本,可以非常简单的达到目的。甚至不用担 心使用多实例 fastcgi 时,多个应用服务进程争用缓存队列I/O的问题。

即使从方案二出发,通过利用服务器环境的资源,也可以做出更多优化。如把"删除第20之 后的旧数据"这部分脚本,移到 crontab 中,与vacuum 结合执行。会得到更好的效率。

找我请教的这位朋友,使用的不是Postgres,而是MySQL,相对来说很多PG的服务端编程技 巧难以照搬,再加上团队管理的政治问题,我也只有祝他好运了。



--
话题越大,废话越多;名字越火星,问题越脑残。
……

劉鑫
March.Liu