跳到主要内容

语言(Languages)

由 Joe Hellerstein 介绍

被选中的读物


Joachim W. Schmidt. Some High Level Language Constructs for Data of Type Relation. ACM Transactions on Database Systems, 2(3), 1977, 247-261.

Arvind Arasu, Shivnath Babu, Jennifer Widom. The CQL Continuous Query Language: Semantic Foundations and Query Execution. The VLDB Journal, 15(2), 2006, 121-142.

Peter Alvaro, Neil Conway, Joseph M. Hellerstein, William R. Marczak. Consistency Analysis in Bloom: A CALM and Collected Approach. CIDR, 2011.


从阅读数据库的文章之中,你可能期待典型的数据库用户的职业属于数据分析师,商业决策制订人,或者 IT 职员。而在实践之中,主要的数据库用户,实际上是软件工程师,他们构建着将会在技术栈中得到进一步应用的,以数据库作为后端的应用程序。即使 SQL 最初的设计理想,是定位为服务那些非科技用户,但是除非人们正在尝试使用代码来构建基于数据库的应用程序,否则鲜少有人直接通过 SQL 与数据库打交道。

因此,如果数据库对于软件发展而言绝大多数时候只意味着 API,那么它们究竟如何服务于程序员呢?而就像优秀的软件那样,数据库系统提供出来了有用的抽象。

两件重要的事情:

  1. 事务的模型给予了程序员一个单进程,序列化的抽象模型,而这个模型永远不会在半途中失败。这样就将程序编码人员从复杂性的巨大悬崖中,解放了出来 --- 就是,现代计算与生俱来的并行性。一个计算机机架上面摆放着数十架机器,同时拥有着数千颗核心,由此就可以容忍单个核心出现故障的情况。然而,应用程序级别的程序员,依旧可以像 1965 年那些将穿孔纸带放到大型机,进而从头至尾顺序执行的程序员那样,轻而易举地写出顺序化的代码
  2. 类似于 SQL 这样的声明式语言给予了程序员操作大规模数据集合的抽象。众所周知,声明式的语言让程序员们从思考如何访问数据项目当中解脱出来,转而去思考返回怎样的数据项目。而数据的独立性同样让应用程序员们摆脱了因为数据库底层组织所带来的苦恼,同时让数据库管理员们,更加专注于应用程序的设计与维护上面。

在不同的阶段当中,这种抽象性是如何发挥他们的作用的呢?同时到了今天,他们的意义又在哪里呢?

  1. 对于应用程序设施而言,序列化的事务如今已经变得非常流行。对于程序员而言,他们可以轻松地把他们的代码包含在 BEGIN...COMMIT/ROLLBACK 之中。不幸的事情在于,正如我们在第六章所讨论到的那样,事务的开销非常地昂贵,同时充满了各种妥协(也就是事务模型往往是非可串行化的,存在各种错误的风险,译者注)。“宽松的”事务语义打破了对于用户而言的序列化的抽象模型,同时使得应用程序的逻辑暴露于潜在的竞争条件/或者不可预测的风险之下。如果应用程序的研究者希望考虑到这一点,他们可以手动对于并发性,故障以及分布式状态展开管理。而一种通用的对于事务本身的缺陷弥补的需求,就引发了“最终一致性”[^154]的出现,正如我们在弱隔离性章节中所讨论到的那样。但是正如同我们在第六章所论道的那样,这种做法依旧把正确性的负担,放到了应用程序研究人员的身上来。就我的观点来看,这就代表了当今世界上软件研究模型的一个主要危机。
  2. 声明式的语言同样大获成功 --- 这自然是对于之前的导航语言的一种改进,之前的指令式语言,使得意大利面式的代码(也就是那些混乱不堪的代码,译者注)在数据库的被你重新组织之后,他们就需要被重写。不幸的事情在于,查询语言与那种程序员们通常实用的指令式语言存在着极大的差异。查询语言消耗,并且产生无序的“关系数据”(集合,关系,流数据);但是编程语言对应指令的有序执行,往往发生于复杂的数据结构(树,哈希表等)之上。而数据库应用程序的程序员们,则被迫承担起了,对接其称为“阻抗失配”(impedance mismatch)的,应用程序与数据库查询之间的鸿沟。而早在关系型数据库出现之前,这就已经成为了数据库程序员的一大麻烦。

数据库语言的嵌入:Pascal/R

在这个篇章里面描述的第一篇文章,代表了我们所跟踪的第二个问题的一个经典范例:帮助我们的应用程序员,解决“阻抗失配”问题。这篇文章,篇目伊始,就定义了我们如今(已经是四十年之后啦!)可能用的上手的集合类型:也就是 Python 中的 "dictionary",Java 或者 Ruby 中的 “map”,诸如此类。这篇文章随后,带着我们耐心地探索了在数十年间,反复出现的各种语言陷阱与可能性。一个核心的主题,在于期待能够将枚举(用于构建输出)以及量化(对于检查属性)区分开来 --- 后者可以在你的指令相当精确的时候,展开用户。而在最后,这篇文章为关系类型数据陈述了一个声明式的,类似于 SQL 的,嵌入到 Pascal 中的子语言。这使得其结果相对自然,并且与如今一些更好的接口相比,没有什么很大的区别。

即使这条路径如今已经相当自然,但在当时,它花费了数十年时间,来收获更多的关注。而伴随着时间的推移,类似于 ODBC 与 JDBC 这样的数据库的“联通性” API 称为 C/C++ 与 Java 中间的支撑部分 --- 他们允许用户向 DBMS 中推入查询,同时对于查询的结果展开遍历,但是类型系统依旧保持着分离的状态,同时从 SQL 类型到宿主语言类型之间的桥梁,依旧是难以令人满意。而如今最好的,类似于 Pascal/R 的现代化解决思路,或许就是微软公司的 LINQ 库,它提供了嵌入到语言当中的集合类型,以及功能,由此使得应用程序的研究者们,可以在跨越多种不同种类的后端数据库和其它的集合(如 XML 文档,电子表格等,诸如此类)上面,书写出类似于查询风格的代码。我们在第五章当中,收入的 DryadLINQ 文章当中,就向你讨论了 LINQ 的语法。

而在2000年左右,类似于社交媒体,在线表格,交互式对话,照片分享,产品目录以及诸如此类的网络应用程序,就已经被实现/重新实现在关系型数据库的后端上面。而为网络编程准备的,现代的脚本语言,相较于 Pascal 而言,更加便利,同时涵盖了很多体面的集合类型。在那种环境之中,应用程序级别的操作者,可以在他们的代码之中,看到被记录下面的模式,同时将他们复制到如今被称呼为面向对象的映射(Object-Relational-Mappings,ORMS)上面。Ruby on Rails 就是其中一个起步之初就最为流行的 ORMs,即使现在也出来了很多其它的 ORMS也是一样。每一款流行的应用程序编程语言至少都有一个对应的 ORMS,同时它们都各自有着不同的特性与设计哲学。感兴趣的读者,可以阅读参考维基百科的“面向对象的关系型映射软件列表”部分。

ORMs 为网站应用程序员们,提供了相当多的便利。首先,它们提供了语言原生的基础,以便处理 Pascal/R 这样的集合。第二点,它们可以让内存中,语言对象的更新更加透明地反映到数据库后端支持的状态之中。 一般而言,为了一些熟悉的数据库系统设计概念,如主体,关系,键值以及外键,它们会提供一些语言原生的语法特性。最后,一些包括 Rails 在内的 ORMs 往往会提供很好的工具,来跟踪数据库模式的随着实践推移而演变的方式,用于反映应用程序代码当中的变化(也就是 Rails 术语当中的“迁移”)。

这就是数据库研究社区与工业界都应当投入更多精力来关注的区域:这些就是我们的用户!ORMs和数据库之间,存在一些令人惊讶的 --- 同时,也令人不安的 --- 失联的情况[19]。Rails 的作者,David Heinemeier Hansson,举例来说,就是一个充满着鲜明个性色彩的人物,他深信“顽固的软件”(opinionated software)(而这显然的,反映了他本身所具有的观念)。让我们来援引他所说的东西吧:

我并不希望我的数据库变得聪明!我认为被存储下来的储存过程,以及约束,是卑鄙和鲁莽的连续性破坏者。不,数据库先生,你绝对不可以把我的商业逻辑拿捏在手上。你的程序上面的野心将会落空,而你需要从我的冰冷的面向对象的手上面,才可以窥探到这方面的逻辑...我希望一层智慧:我的领域模型。

对于数据库的无条件信任,引发了 Rails 应用程序当中的许许多多的问题。依托 ORMs 所构建的应用程序,往往非常缓慢 --- ORMs 本身在生成查询的时候,并不会做出很多优化的工作。作为一种替代的方案,Rails 的程序员总是需要学习将程序编写的“别出心载”,由此使得 Rails 能够构建出高效的 SQL 来 --- 这就与 Pascal/R 论文当中讨论的情况类似,他们需要学会规避循环,与一表一次的迭代。Rails 应用程序的一个典型的进化方向在于,用自然而然的方式来编写它,并且观察其性能缓慢的原因,调查研究其生成出来的 SQL 日志,同时重新编写应用程序以促进 ORM 构建出“更为良好的”SQL来。最近由 Cheung 与其同事所完成的工作,探索了程序的合成技术可以自动地生成出这些优化的想法[^38]。这是一个相当有趣的方向,同时时间将会告诉我们其可以自动消除多少复杂性。数据库与应用程序之间的鸿沟同样可以消除正确性的负面影响。举例而言,Bailis 最近展现了[^19],由于Rails 开源应用程序内部的不当应用(而不是数据库),数据本身的完整性,遭到了一定的破坏。

尽管存在一定的盲点,ORMs 通常是程序设计之中,以数据库后端为支撑的应用程序中的,一项重要的飞跃,同时它是对于可以追溯到 Pascal/R 起步初期的那些想法的一项验证。一些好的观念,往往就需要依靠时间来证明。

流查询:CQL

我们关于 CQL 的第二篇文章,是一项不同语言风格的工作 --- 它是一篇查询语言设计文章。它代表了基于流数据模型的一种全新的声明式查询语言的设计。这篇文章因为少数几个原因而得到大多数人的关注。首先,它的查询语言设计非常干净,易于阅读,同时非常现代化的查询语言。而每间隔几年,就会有一群人提出另外一种数据模型与查询语言:比如 Objects 与 OQL,XML 与 XQuery,或者 RDF 与 SPARQL。最大多数的这方面的训练,起步于某种数据模型X,并视作 “X改变了一切”(X changes everything),由此导致提出来的某些新的查询语言总是看起来同 SQL 或熟悉或差异巨大。CQL 就是语言设计案例当中的一个鲜活的案例,因为它做了与之相反的事情:它强调了一个重要的情况,那就是从正确而又严格的视角来看,流数据真正意义上面的改变,实际上相当小。CQL 对于 SQL 的发展,足以真正隔离“静止”表(resting tables)与“移动”数据流(moving streams)之间的最重要分歧。这样,就使得你在真正围绕数据流展开讨论的时候,能够对于其中的真正区别,以及仅仅只是语义上的那些不同,有一个清晰的把握;而当前的许多的流数据处理语言,相较于 CQL 而言,更加地由着性子(hoc)以及更加混乱。而这篇文章,不仅仅是极具思考性的查询语言设计中的一个良好的案例之外,它还代表了在数据库领域当中,一个得到很多学者关注的研究文献,而且在实践中,依旧是相当有趣。早期的流数据研究系统处理,自2000年初期起步开始[^3], [^120], [^118], [^36],而在当时的业界中,我们并没有许多由创业公司所带来的流数据库,也没有对应的开源数据库作为对应的参考。不过,流数据查询这一方面的主题,已经在近些年得到了越来越多的关注,就像 Spark 流数据处理系统,Storm 与 Heron 的应用,以及类似于谷歌这样的大企业开始越来越将持续性数据流作为一种新的现代服务业的事实这一重要性提上日程一样[^8]。我们可能会看到流查询系统在金融服务的占比,超越目前的小规模系统。

另外一个 CQL 让人感到有意思的事情在于,流数据在某种时候就是介于数据库与“事件”之间的一个半成品。数据库的存储与对于集合类型的检索;事件系统的传输与对于离散事件的处理;只要你能够将你的事件当作流数据来进行看待,那么之后,事件的处理程序与流数据编程就会非常类似。考虑到事件的编程就是在某一些特定的领域之中,得到广泛应用的编程模型(例如,用于用户界面的 Javascript,用于分布式系统的 Erlang),那么,像 Javascript 这样的事件编程语言,以及数据流系统之间存在的阻抗失衡问题,就应当相对较小。这条路径当前一个有趣的用例就是 Rx 编程语言(Reactive extensions),它是附加到 LINQ 上面的流编程语言,可以使得流事件编程的编程体验写函数式查询计划差不多;或者就像其作者 Erik Meijer 写下来的那样,“你的鼠标就是一款数据库”(Your mouse is a database)[^114]。

在没有事务模型下,编写正确的应用程序:Bloom(布隆)

第三篇文章,围绕布隆而展开,并且适应下面我们所讨论的数个要点;它拥有应用程序级别的关系型状态模型,以及与CQL流相关的网络信道的概念。但是在这个篇章的开头介绍中,我们提到主要的目标在于,帮助程序员管理第一层次抽象的丢失。这一点,我将其描述为一个主要的危机。而对于现代的研究者而言,最大的问题在于:在没有使用事务模型,或者是其它的昂贵的模型的情况下,你能够为你的应用程序找到一个合理的分布式实现,以便于控制其操作的顺序吗?

Bloom 对这个问题所提出来的解决方案就是,给程序员们提供一种“无序的”编程语言:一种阻止他们意外使用顺序的方法。布隆默认的数据结构就是关系;它的基本编程结构就是逻辑规则,它能够以任何的顺序来展开工作。简单来说,它就是一门通用目的的编程语言,同一门关系型的查询语言相对应。与 SQL 查询语句可以在不改变输出的情况下,就能够展开优化工作与并行化执行的缘由差不多,简单的 Bloom 程序拥有定义明确(一致)的结果,同时它们的顺序与执行顺序无关。

与SQL查询可以在不改变输出的情况下进行优化和并行化的原因相同,简单的Bloom程序具有定义明确(一致)的结果,与执行顺序无关。这种直觉的例外情况在于“非单调的”Bloom代码行,对于这些属性的测试伴随着时间的推移而可以震荡地传递出true,false(比如,“NOT EXISTS x” 或者 "HAVING COUNT() = x"),这些规则对于执行和消息排序相当敏感,并且需要通过协调的机制,来进行一定的保护。

CALM 定理,对这个概念,展开了形式化的处理,用明确的方式,对上述的问题,做出了对应的回答:你可以在其规范是也只能是单调的情况的时候,找到一致的、分布式的、无需协调的实现方案。Bloom 的文章说明了,在实践的情况下面,编译器如何使用 CALM 定理来精确定位 Bloom 程序当中协调的需求。而在程序注释的帮助之下,CALM 的分析同样可以被应用于数据流的处理语言,比如 Storm[^12]。对这一领域理论成果的综述,可以参考[^13].

在规避协调工作方面,业界已经设计出来了数种相关的语言:相当多的论文使用结合,交换,冥等运算[^83],[^142],[^42];它们从本质来看,就是单调的。而另外的一组工作,则检查了可替代的正确标准,比如,确保数据库的状态上面,只有特定的不变量[^20],或者使用替代的程序分析,在不实现传统的读写并发的情况下面,传递出可串行化的接过来[^137]。这个领域依旧相当新颖;在论文中,以及使用着不同的模型(比如,一些依旧使用传统的事务边界,而其它的则不是),同时在一般情况下面,并不赞同定义中的“一致性”或者“协调性”(CALM根据全局确定性结果,将一致性定义为,协调是无论数据分区或复制如何都需要的消息传递[14])。在这里,获得更为清晰与明智的想法更加重要 --- 如果程序员并不依托事务的模型,那么他们就需要在应用程序这个层面得到更多的帮助。

Bloom 同时也是一个反复出现在数据库学术研究领域中的一则案例:通用目的的声明语言(也就是“逻辑编程”)。Datalog 就是这个方面的标准范例,同时这个在数据库的研究历史中既悠久,又存在着争议。作为 1980 年代数据库理论研究者最为关注的话题,Datalog 遭遇到了来自于当时的系统研究人员的强烈反对,在他们看来,这项技术根本无法落地[^152]。而到了最近,它又引发了年轻许多的数据库系统的研究者和其它的一些应用领域研究人员的关注[^74] --- 比如,Nicira 的软件定义网络技术栈(被 VMWare 用十亿美元收购),就使用 Datalog 语言来做网络转发状态的工作[^97]。而在使用声明性子语言访问数据库状态和非常积极地使用声明性编程(如 Bloom)来指出应用程序逻辑之间地差异。时间将会告诉程序员,在各种环境之中,包括基础设施,应用程序,web 客户端以及移动设备间,这种声明性命令边界是如何变化地。

参考

[1] D. J. Abadi, D. Carney, U. C¸ etintemel, M. Cherniack, C. Convey, S. Lee, M. Stonebraker, N. Tatbul, and S. Zdonik. Aurora: a new model and architecture for data stream management. The VLDB JournalThe International Journal on Very Large Data Bases, 12(2):120–139, 2003.
[2] T. Akidau et al. The dataflow model: A practical approach to balancing correctness, latency, and cost in massive-scale, unbounded, out-of-order data processing. In VLDB, 2015.
[3] P. Alvaro, N. Conway, J. M. Hellerstein, and D. Maier. Blazes: Coordination analysis for distributed programs. In Data Engineering (ICDE), 2014 IEEE 30th International Conference on, pages 52–63. IEEE, 2014.
[4] T. J. Ameloot. Declarative networking: Recent theoretical work on coordination, correctness, and declarative semantics. ACM SIGMOD Record, 43(2):5–16, 2014.
[5] T. J. Ameloot, F. Neven, and J. Van den Bussche. Relational transducers for declarative networking. Journal of the ACM (JACM), 60(2):15, 2013.
[6] P. Bailis, A. Fekete, M. J. Franklin, A. Ghodsi, J. M. Hellerstein, and I. Stoica. Feral Concurrency Control: An empirical investigation of modern application integrity. In SIGMOD, 2015.
[7] P. Bailis, A. Fekete, M. J. Franklin, J. M. Hellerstein, A. Ghodsi, and I. Stoica. Coordination avoidance in database systems. In VLDB, 2015.
[8] S. Chandrasekaran, O. Cooper, A. Deshpande, M. J. Franklin, J. M. Hellerstein, W. Hong, S. Krishnamurthy, S. Madden, V. Raman, F. Reiss, et al. Telegraphcq: Continuous dataflow processing for an uncertain world. In CIDR, 2003.
[9] A. Cheung, O. Arden, S. Madden, A. Solar-Lezama, and A. C. Myers. StatusQuo: Making familiar abstractions perform using program analysis. In CIDR, 2013.
[10] A. T. Clements, M. F. Kaashoek, N. Zeldovich, R. T. Morris, and E. Kohler. The scalable commutativity rule: Designing scalable software for multicore processors. ACM Transactions on Computer Systems (TOCS), 32(4):10, 2015.
[11] T. J. Green, S. S. Huang, B. T. Loo, and W. Zhou. Datalog and recursive query processing. Foundations and Trends in Databases, 5(2):105–195, 2013.
[12] P. Helland and D. Campbell. Building on quicksand. In CIDR, 2009.
[13] J. M. Hellerstein. The declarative imperative: experiences and conjectures in distributed logic. ACM SIGMOD Record, 39(1):5–19, 2010.
[14] T. Koponen, K. Amidon, P. Balland, M. Casado, A. Chanda, B. Fulton, I. Ganichev, J. Gross, N. Gude, P. Ingram, et al. Network virtualization in multi-tenant datacenters. In USENIX NSDI, 2014.
[15] E. Meijer. Your mouse is a database. Queue, 10(3):20, 2012.
[16] R. Motwani, J. Widom, A. Arasu, B. Babcock, S. Babu, M. Datar, G. Manku, C. Olston, J. Rosenstein, and R. Varma. Query processing, resource management, and approximation in a data stream management system. In CIDR, 2003.
[17] J. F. Naughton, D. J. DeWitt, D. Maier, A. Aboulnaga, J. Chen, L. Galanis, J. Kang, R. Krishnamurthy, Q. Luo, N. Prakash, et al. The niagara internet query system. IEEE Data Eng. Bull., 24(2):27–33, 2001.
[18] S. Roy, L. Kot, G. Bender, B. Ding, H. Hojjat, C. Koch, N. Foster, and J. Gehrke. The homeostasis protocol: Avoiding transaction coordination through program analysis. In SIGMOD, 2015.
[19] M. Shapiro, N. Preguica, C. Baquero, and M. Zawirski. A comprehensive study of convergent and commutative replicated data types. INRIA TR 7506, 2011.
[20] M. Stonebraker and E. Neuhold. The laguna beach report. Technical Report 1, International Institute of Computer Science, 1989.
[21] D. B. Terry, A. J. Demers, K. Petersen, M. J. Spreitzer, M. M. Theimer, et al. Session guarantees for weakly consistent replicated data. In PDIS, 1994