同事最近在拆分数据库和清理数据,这当中有一些趣事,比如在拆分一个叫 luz 的集群时,新拆出的集群被命名为了 lua,可以从至少两个角度来理解这个名字,比如最显而易见的它是一门语言,除此之外它还代表从 z 到 a 的新一轮轮回;再比如拆分另一个叫 eag 的集群时,新集群被命名为 eager,不过它诞生时的含义其实是「eag 的 er 砸」。
也有一些哭笑不得的问题,比如发现有些表的数据清理 cron 曾经有日子没被成功执行,导致数据文件的体积相比它应有的大小暴涨了一个数量级;还有比如发现 Sentry 的 nodestore_node 表涨到了几百 GB,检查的时候发现了 nodestore 的粗糙设计,但其实很多开源软件甚至名声大噪的商业产品都是如此。
比如近距离看一下 Sentry nodestore 的 schema:
1 2 3 4 5 6 7 |
|
脱离业务场景,孤立地看这个 schema,除了这三点之外其实并没有太多值得挑剔的:
那么再了解一点点业务,通过阅读 Sentry 这部分的代码可以很快了解到这些:
考虑业务的需要,这个 schema 还有哪些问题呢? id 字段使用了 utf8 编码(在 MySQL 中是 1-3 字节长度的变长编码),最大长度是 40 字符,id 字段是主键,在索引中以最大宽度对齐,再加上 varchar 类型最大长度标示 2 字节,也就是 40 * 3 + 2 = 122 字节,不仅在主键中如此,在二级索引 nodestore_node_d80b9c9a
中也是如此。
id char(24) CHARACTER SET latin1
,这样在索引中的宽度就可以从 122 字节减小为 24 字节。dummy_id
)作为 primary key 之后,在这个场景下 timestamp 字段实际上和 dummy_id
同序,按时间删除旧数据的需求就可以不必借助 timestamp 字段的索引,使用笨笨的二分法找到需要的时间边界,然后按对应的 dummy_id
作为条件来删除即可,每行纪录可以节省 24 字节。回顾一下上面所提到的问题和相应的优化手段,可以发现所需要的只是在理解业务的基础上,对数据库的工作方式也有基本的理解,但更现实的情况是,大量的产品开发者在编写 Model 代码时随手就会写下 max_length=255,完全没有意识到产生的列定义会是 varchar(255),或者压根儿没有意识到这么做会有什么影响,几年前我自己在做产品开发的工作时也是这样的习惯。相对来说,我愿意相信这里写了 max_length=40 而不是 max_length=255 是一个经过深思熟虑了的决定。
要做到对数据库和索引的工作方式有基本的了解,难吗?很多同学会觉得不就是读一读文档吗没有什么技术含量啊,事实也正是如此丝毫不夸张,只要能阅读和理解文档,加上必要的动手实践,都能有超越类似场景要求的理解,比掌握一门语言要简单太多,既不需要超越普通人的智商,也不需要加班加点地埋头苦干,所需要的仅仅是踏实的态度和一点耐心,再加上一点思考。
这是随手从某著名产品的 schema 中摘出来的,就不在这里做更多的问题分析了,不过我相信仅仅依靠上面提到几点常识,就能很轻松地看到很多问题,这多多少少能进一步举证,优秀的产品一样会有糟糕的实现,但是,这些产品不都好好的是吗?是不是应该再思考一下技术的价值,以及技术和产品的关系呢?😂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
|
我昨天由这个问题吐槽了一下 Sentry,引来了一些讨论和建议,比如大句哥哥和 XTAo 说 Sentry 官方推荐用 PostgreSQL,我并非 MySQL 或者其他存储系统的忠实拥趸,不过就这个具体的问题来说,如此设计恐怕在 PostgreSQL 下也不是好的实践,更重要的不在于用什么,而是怎么用好。