您的数据库技能并不“值得拥有”?


2006 年,《纽约杂志》数字团队开始为其时装周门户网站打造全新的搜索体验。这是一个甚至没有与技术团队讨论过技术可行性的项目,这在当时很常见。敏捷技术尚属新生事物,更不用说在出版业了。这只是一个愿景,一个真正的 "月球计划",需要 10 到 12 周的时间来开发产品的线框图版本。几乎没有时间进行适当的质量保证。时装周并不是慢慢开始的,而是从零到六十转瞬即逝。

需求是什么?
成千上万张近乎实时的时装秀图片,每张图片的子项都有分类:"2006"、"包"、"红色"、"皮革 "等等。用户登陆搜索页面后,可以根据这些属性 "向下搜索",缩小搜索结果的范围。更难的是,所有这些属性都有精确的计数。

工作流程将会非常紧张。摄影师将把他们的数码墨盒从纽约市中心快递到我们位于麦迪逊大道的办公室,在那里,实习生将对图片进行处理和标记,然后由我们的 Perl 脚本每小时从嵌入的 EXIF 信息中读取标签,编制索引。如果不能在我们这边建立搜索产品,整个生态系统就会崩溃,因为它已经就位,准备就绪。

"哦!只要使用 Solr 中的切面就可以了,老兄"。是啊,没那么快--老兄。2006 年,这种技术还不存在。

我和我们的首席技术官一起观看了多个企业搜索引擎演示,没有一个产品(花了很多钱)能进行深度分面搜索。

我们当时有 Autonomy 许可证,我的第一次尝试证明了这一点......它就是做不到。它本来是可以的,但计算结果都是错的。
Endeca(现在归甲骨文公司所有)是在项目的设计部分已经开始的情况下推出的。太新、太原始、太冒险。这个想法在当时来说有点过于雄心勃勃,尤其是对于一个非科技公司的小团队来说。

于是,我们三个人,我和两名顾问,用 Perl 编写了索引脚本、查询解析逻辑,并用 MySQL 4 建立了数据模型。在这个项目中,只要有一个无法克服的技术风险,整个项目就会失败。
我就长话短说,我们成功了,然后我们去了一家卡拉 OK 吧庆祝(在那里,我第一次因工作压力而严重宿醉)

对于一个负责 SQL 模型和查询的人来说,要花费好几天的时间来调整这些模型和查询,对每个查询进行计时,并研究 EXPLAIN 输出,看看还能做些什么来从数据库中再挤出 50 毫秒。晚上和周末都没闲着。最后,我不得不反复试验,深入研究 MySQL 服务器设置,并精心设计出让人反胃的 GROUP BY 查询。那时的MySQL查询分析器很让人烦躁,有时重新排列SELECT子句中的字段就能改变查询的性能。

想象一下,如果 SELECT field1, field2 FROM my_table 比 SELECT field2, field1 FROM my_table 更快。为什么会这样呢?我至今也不知道,而且也不想知道。

不幸的是,我丢失了这项工作的示例,但 "时光倒流机"(Way Back Machine)上有我们最终产品的证明。

这里的重点是--如果你真正了解你的数据库,你就可以用它做一些非常疯狂的事情,而且有了新一代的存储技术和更强大的硬件,你甚至不需要挑战极限--它应该可以轻松处理我所说的 "普通规模"。

SQL 逐渐消失的艺术
在过去几年中,我注意到一个令人不安的趋势--软件工程师们热衷于使用外来的 "星球级 "数据库来解决非常初级的问题,但同时却对他们可能已经在使用的功能非常强大的关系数据库引擎掌握不佳,更不用说了解该技术更先进、更有用的功能了。SQL 层被深深地埋藏在各种库中,而 ORM 又过于聪明,以至于全部都变成了高级代码。

  • 为什么速度这么慢?
  • 不知道,让我们加上 Cassandra 吧!

现代硬件当然允许我们从 CPU 向上进入更高的抽象层,而在过去,将某些函数转换为汇编代码以榨取处理器的每一点性能并不罕见。现在,计算和存储的成本更低了--这是事实,但滥用这些资源却培养了我们的懒惰和自满。突然之间,云计算的账单就高得离谱了,天知道每秒钟针对庞大的数据库实例运行数十亿次这种低效的 ORM 查询会消耗掉世界多少能源。

2004 年,在我第一次求职面试的那天早上,我正在地铁列车上背诵数据库规范化的九个级别。还是五级?我不记得了,这根本不重要--现在没有人会在软件工程师面试时问你这个问题。

只需浏览一下您所选择的数据库(比如现在最流行的 Postgres)的目录,您就会发现它绝对是一个功能丰富的宝库,除了最可怕的地球级计算机科学问题外,它还能处理其他一切问题。在您阅读这篇文章的同时,PB 级的 Postgres 复制盒正在毫不费力地运行着。

诀窍在于不要指望你的数据库或ORM能读懂你的想法。

ORM 是死敌
我曾经是一家电子商务公司的新员工,一开始我就被派去解决公司产品目录页面的严重性能问题。这只是一个简单的产品图片分页网格。这有多难?信不信由你--确实很难。页面加载时间超过 10 秒,有时甚至更长,数据库也很吃力,而解决方案就是 "直接缓存"。最后一个数据点--这不是一个高流量的网站。即使没有任何流量,网页也会慢得要死。这是一个很糟糕的信号,说明有什么东西出现了严重问题。

仔细观察后,我意识到我犯了一个大错--三大数据库和编码错误集于一身。

错误 1:没有索引
在每个关键任务查询中都会出现的列没有索引。没有。在生产中添加了急需的索引后,你几乎可以听到 MySQL 呼出一口气的声音。尽管如此,性能还没有完全达到要求,所以我不得不在代码中进行更深入的挖掘。

错误 2:假设每个 ORM 调用都是免费的
在本地激活查询日志并重新加载产品列表页面时,我看到......仅仅加载一个页面就启动了 200、300、500 次查询。这是怎么回事?原来,这是典型的 ORM 滥用的结果,即循环查看每条记录,大意如下

for product_id in product_ids:
   product = amazing_orm.products.get(id=product_id)
   products.append(product)


查询次数多的原因还在于其中一些逻辑是嵌套的。显而易见的解决方案是尽量减少每次请求中的查询次数,利用 SQL 将数据连接并合并成一个单一的数据块。这就是关系数据库的作用--它的名字就说明了这一点。

每个单独的查询都需要进入数据库,进行解析、转换、分析、规划、执行,然后再返回给调用者。这是最昂贵的操作之一,而 ORM 会很乐意在性能方面为你做最糟糕的事情。ORM 调用如何转化为 SQL?如果不是你所想的那样,是 ORM 的限制还是你没有使用正确的库调用?你是否使用了一种特殊的非 ANSI SQL,而你的特定 ORM 不能很好地支持它?您是否需要在这种调用中使用原始 SQL,而在其他调用中却不需要?等等。

错误 3:把世界拉进来
更糟糕的是,这里的数据量相对较小,但却有几十列。为了让你的生活 "更轻松",ORM 通常会默认做什么?它们会把所有的数据、所有的列都发送出去,用你根本不需要的数据堵塞你的网络管道。这是一种有毒的技术债务,开发速度最终会影响性能。

在同一个项目中,我花了几个小时黑进 Dango 管理器的黑暗角落,重写默认的 ORM 查询,使其不那么 "急切"。这样一来,面向办公室的体验就好得多了。


性能是一项功能
几十年来,重要的关键任务系统一直运行在传统而枯燥的关系数据库上,每秒可处理数千次请求。这些系统已经变得越来越先进、越来越强大、越来越实用。可以说,它们是计算机科学的奇迹。你可能会认为,像 Postgres 这样古老的数据库(自 1982 年开始开发)目前正处于某种遗留维护模式,但事实恰恰相反。事实上,这项工作一直在加速进行,其规模和功能都变得相当惊人。几年前需要多次查询,现在只需一次。

这有什么意义呢?亚马逊发现,用户等待页面加载的时间每增加 100 毫秒,企业就会损失一笔钱。我们现在还知道,从用户的角度来看,网页的最长目标响应时间约为 100 毫秒:

数据库反模式
知道不应该做什么与知道应该做什么同样重要。以下一些错误非常常见:

反模式 #1。出于错误的原因使用外来数据库
DynamoDB 等技术旨在处理 Postgres 和 MySQL 开始失效的规模。这是通过去规范化、积极复制数据来实现的,在这种情况下,数据库不会进行太多的实时数据操作或连接。现在,数据是根据查询方式建模的,而不是根据关联方式建模的。在这种疯狂的规模下,常规的关系型概念就会瓦解。毋庸置疑,如果您采用这种存储方式来解决 "普通规模 "的问题,那么您已经在解决您没有解决的问题了。

反模式 #2。不必要地缓存
缓存是必要之恶,但并不总是必要的。陈旧的缓存数据会带来大量的错误和调用问题。只读数据库副本是一种经典的架构模式,至今仍不过时,它能为你带来超乎寻常的性能,而无需担心任何问题。成熟的关系数据库已经具备查询缓存功能,这一点不足为奇,只需根据具体需求进行调整即可。

缓存失效很难。它会增加系统的复杂性和不确定性。这也增加了调试的难度。在我的职业生涯中,我从内容团队收到的邮件比我想象的还要多,这些邮件的内容是:"为什么数据不在那里,我 30 分钟前才更新过!"

第 3 项禁忌。存储一切和厨房水槽
尽管行业标准数据库能经受住各种考验,但完全不关心其中的内容,将其视为某种数据垃圾填埋场,可能并不是一个好主意。一旦数据库大幅增长,管理、查询、备份、迁移等所有工作都会变得痛苦不堪。即使您使用的是受管理的云数据库,因此无需担心这些问题,但也要考虑成本问题。RDBMS 是一项复杂的技术,在其中存储数据的成本很高。

先弄清楚通用规模
如果你希望 Postgres 或 MySQL 数据库不费吹灰之力就能发挥神奇的作用,那么它很容易就会停滞不前。"这不是网络规模,老板。我们的 200 万条记录似乎太庞大了。我们需要 DynamoDB、Kafka 和事件源!"

关系数据库并不是什么过时的技术,只有我们这些技术活化石才会选择成为这方面的专家,也不是什么可以像烦人的昆虫一样挥之不去的东西。"我们这里有 React 和 GraphQL 所有的东西,老家伙"。用法律术语来说,现代 RDBMS 在被证明有罪之前是无罪的,而举证责任应该非常高,而且几乎完全由你来承担。

最后,如果我必须找出 "速度慢的原因",我的运行手册大概是这样的:

  • 从日志、慢查询日志等中整理出一份唯一查询列表。
  • 首先查看最频繁的查询
  • 使用 EXPLAIN 检查慢查询计划的索引使用情况
  • 只选择需要跨线传输的数据
  • 如果 ORM 做了一些愚蠢的事情却没有解决方法,就打开引擎盖,弄脏原始 SQL 管道

最重要的是,研究你的数据库(和 SQL)。学习它、热爱它、使用它、滥用它。花几天时间翻翻 Postgres 手册,看看它能做些什么,也许会比花更多时间研究下一个风靡当月的 JavaScript 框架更能让你成为一名优秀的工程师。