翻译-readings in database systems(十)

第9章 语言 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。不幸的是,正如我们在第6章中讨论的一样,事务代价非常昂贵,而且经常会妥协。“宽松的”事务语义打破了用户的串行抽象,并将应用程序逻辑暴露给潜在的竞争和/或不可预知的异常。如果应用程序开发人员希望考虑到这一点,他们必须管理并发性、故障和分布式状态的复杂性。对缺乏事务的一种常见反应是追求“最终的一致性”[154],正如我们在弱隔离一节中讨论的那样。但是正如我们在第6章中所讨论的,这仍然将所有的正确性负担转移给了应用程序开发人员。在我看来,这种情况代表了现代软件开发的重大危机。

2.声明性查询语言也取得了成功——当然是对之前的导航语言的改进,后者导致了每次重新组织数据库时都需要重写代码。不幸的是,查询语言与程序员通常使用的命令式语言有很大的不同。查询语言使用并生成简单的无序“集合类型”(集合、关系、流);编程语言通常执行指令的有序执行,通常在复杂的结构化数据类型(树、哈希表等)上执行。数据库应用程序的程序员被迫在程序和数据库查询之间架起这种所谓的“阻抗失配”。自从关系数据库出现以来,这一直是数据库程序员的一个难题。

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

本节的第一篇文章给出了解决第二个问题的经典示例:帮助命令式程序员解决阻抗不匹配问题。论文首先定义了我们现在可能认识到的(40多年后!)熟悉的集合类型的操作:Python中的“dictionary”类型、Java或Ruby中的“map”类型,等等

然后,这篇论文耐心地向我们介绍了各种语言结构的可能性和缺陷,这些结构似乎在几十年的应用中反复出现。一个关键的主题是区分枚举(用于生成输出)和量化(用于检查属性)的愿望——如果您是显式的,则后者通常可以得到优化。最后,本文提出了一种声明式的、类似sql的关系类型子语言,并将其嵌入到Pascal中。其结果是相对自然的,与当今一些更好的接口没有什么不同。

尽管这种方法现在看来很自然,但这个话题花了几十年才引起公众的注意。与此同时,像ODBC和JDBC这样的数据库“连接”api作为C/ c++和java的支撑出现了—它们允许用户将查询推到DBMS并迭代结果,但是类型系统仍然是独立的,从SQL类型到宿主语言类型的桥接是不愉快的。也许最好的现代进化论的思想像Pascal/ R是微软的LINQ的库,这正如语言集成集合类型和函数,因此,应用程序开发人员可以编写如下查询代码在各种后端数据库和其他集合(XML文档、电子表格等)我们包括的LINQ语法在第五章关于DryadLINQ论文中。

在2000年代,诸如社交媒体、在线论坛、交互式聊天、照片共享和产品目录等web应用程序都是在关系数据库后端实现和重新实现的。用于web编程的现代脚本语言比Pascal更方便一些,并且通常包含像样的集合类型。在这种环境中,应用程序开发人员最终在他们的代码中发现了可识别的模式,并将其编码为现在称为对象关系映射(orm)的模式。Ruby on Rails是最具影响力的orm之一,尽管到目前为止还有很多其他的orm。每一种流行的应用程序编程语言都至少有一种,并且在特性和原理上也有不同。感兴趣的读者可以参考Wikipedia的“对象关系映射软件列表”。

ORM为web程序员做了一些简单的事情。首先,它们为使用集合提供了原生语言的支持,非常类似于Pascal/R。其次,它们可以使对内存语言对象的更新透明地反映在数据库支持的状态中。它们通常为常见的数据库设计概念(如实体、关系、键和外键)提供一些原生语言语法。最后,包括Rails在内的一些orm提供了很好的工具,用于跟踪数据库模式随时间变化的方式,以反映应用程序代码中的变化(Rails 技术中的“迁移”)。

这是数据库研究社区和行业应该更多关注的领域:这些是我们的用户!ORMs和数据库[19]之间出现了一些令人吃惊的断开连接的情况,有时甚至令人不安。例如,Rails的作者是一个名叫David Heinemeier Hansson(“DHH”)的丰富多彩的角色,他相信“固执己见的软件”(当然,这反映了他的观点)。援引他的话说:

I don’t want my database to be clever! ...I consider stored procedures and constraints vile and reckless destroyers of coherence. No, Mr. Database, you can not have my business logic. Your procedural ambitions will bear no fruit and you’ll have to pry that logic from my dead, cold object-oriented hands . . . I want a single layer of cleverness: My domain model.

这种对DBMS的不信任导致了Rails应用程序中的许多问题。使用orm编写的应用程序通常非常慢——orm本身在优化生成查询的方式方面做得并不多。相反,Rails程序员经常需要学习“以不同的方式”编程,以鼓励Rails生成高效的sql—类似于Pascal/R论文中的讨论,他们需要学习避免循环和一次表迭代。Rails应用程序的一个典型演变是:天真地编写它,观察缓慢的性能,研究它生成的SQL日志,然后重写应用程序以说服ORM生成“更好的”SQL。Cheung及其同事最近的工作探索了程序合成技术可以自动生成这些优化[38]的想法;这是一个有趣的方向,时间会告诉我们自动化会多少复杂。数据库和应用程序之间的分离也会对正确性产生负面影响。例如,Bailis最近向[19]展示了许多现有的开源Rails应用程序是如何由于在应用程序(而不是数据库)中执行不当而容易违反完整性的。

尽管存在一些盲点,orm通常是在数据库支持的应用程序的可编程性方面的一个重要的实践飞跃,并且验证了可以追溯到Pascal/R的思想。有些好主意需要时间才能流行起来。

流查询:CQL

我们的第二篇关于CQL的论文是一篇不同风格的语言工作——它是一篇查询语言设计论文。它为流的数据模型提供了一种新的声明性查询语言的设计。这篇论文被采纳有几个原因。 首先,它是一个简洁、易读、相对现代的查询语言设计示例。每隔几年,就会出现一群人,他们使用另一种数据模型和查询语言:例如对象和OQL、XML和XQuery,或者RDF和SPARQL。这些语言大多以断言“X改变了某些数据模型X的一切”开始,这导致了一种新的查询语言的出现,这种语言通常看起来很熟悉,但与SQL却有奇怪的不同。CQL是一个令人耳目一新的语言设计示例,因为它的作用正好相反:它强调了这样一个事实,即通过正确的视角查看的流数据实际上变化很小。CQL对SQL的发展足以隔离查询“静态”表和“移动”流之间的关键区别。这给了我们一个清晰的理解什么是真正不同的,在语义上,当你不得不谈论流数据的时候;许多其他当前的流语言比CQL更加特别和混乱。除了这篇论文是深思熟虑的查询语言设计的一个很好的范例之外,它还代表了一个在数据库文献中受到很多关注的研究领域,并且在实践中仍然很有趣。从2000年早期开始的第一代流数据研究系统[3,120,118,36]既没有作为开放源码,也没有作为从这些系统中产生的各种初创公司的主流。然而,流查询的话题近年来再次引起了业界的兴趣,像Spark- Streaming、Storm和Heron这样的开源系统看到了它的发展,像谷歌这样的公司也意识到连续数据流作为现代服务[8]的一种新现实的重要性。我们可能还会看到,流查询系统在金融服务领域所占的份额,已经超出了它们目前的小众市场。

CQL有趣的另一个原因是流是数据库和“事件”之间的中间地带。数据库存储和检索集合类型;事件系统传输和处理离散事件。一旦您将事件视为数据,那么事件编程和流编程看起来就非常相似。考虑到事件编程在某些领域(例如面向用户的Javascript、面向分布式系统的Erlang)是一种广泛使用的编程模型,事件编程语言(如Javascript)与数据流系统之间应该存在相对较小的阻抗失配。这种方法的一个有趣的例子是Rx(反应性扩展)语言,它是LINQ的一个流添加,使编程事件流感觉像编写函数查询计划;或者正如它的作者Erik Meijer所说,“你的鼠标就是一个数据库”[114]。

不利用事务来编写正确的应用:bloom

第三篇关于Bloom的论文连接了以上几点;它有一个应用程序级别的状态关系模型,以及一个与CQL流相关的网络通道概念。但是主要的目标是帮助程序员管理本章开头介绍的第一个抽象的损失;我称之为重大危机。现代开发人员面临的一个大问题是:您能否为您的程序找到一个正确的分布式应用程序,而不使用事务或其他昂贵的方案来控制操作顺序?

Bloom对这个问题的回答是,为程序员提供一种“无序”的编程语言:一种不鼓励他们偶然使用排序的语言。Bloom的默认数据结构是关系;它的基本编程结构是可以以任何顺序运行的逻辑规则。简而言之,它是一种与关系查询语言类似的通用语言。SQL查询可以在不改变输出的情况下进行优化和并行化,出于同样的原因,简单的Bloom程序具有定义良好的(一致的)结果,而与执行顺序无关。这种直觉的一个例外是Bloom代码的行是“非单调的”,测试随着时间的推移在真与假之间摇摆的属性(例如“NOT EXISTS x”或“HAVING COUNT() = x”)。这些规则对执行和消息排序很敏感,需要协调机制的“保护”。

CALM定理形式化了这个概念,明确地回答了上面的问题:当且仅当它的规范是单调的时,你可以为你的程序找到一个一致的、分布式的、无协调的实现[84,14]。Bloom论文还说明了编译器如何在实践中使用CALM来确定Bloom程序中对协调的需求。在程序员注释[12]的帮助下,CALM分析也可以应用于Storm等系统中的数据流语言。在[13]中对这一领域的理论结果进行了综述。已经有很多关于避免协调的相关语言工作:一些论文建议使用联想、交换、幂等运算[83,142,42];它们本质上是单调的。另一组工作检查可选的正确性标准,例如,只确保数据库状态[20]上的特定不变量,或者使用可选的程序分析来获得可反序列化的结果,而不影响传统的读写并发性[137]。这个领域仍然很新;论文有不同的模型(例如,有些有事务边界,有些没有),并且常常对“一致性”或“协调”的定义不一致。(CALM将一致性定义为全局确定的结果,协调定义为无论数据分区还是复制[14]都需要的消息传递。)在这里获得更清晰的思路是很重要的——如果程序员不能进行事务处理,那么他们就需要在应用程序开发层获得帮助。

Bloom也是数据库研究中反复出现的主题的一个例子:通用声明性语言(也称为“逻辑编程”)。Datalog是一个标准的例子,在数据库研究中有着悠久而有争议的历史。Datalog是20世纪80年代数据库理论家最喜欢讨论的一个话题,但由于在实践中不相关,它遭到了当时系统研究人员的强烈反对[152]。最近,它得到了数据库和其他应用领域(年轻的)研究人员的一些关注[74]——例如,Nicira的软件定义网络栈(被VMWare以10亿美元的价格收购)使用Datalog语言进行网络转发状态[97]。使用声明性子语言来访问数据库状态和非常积极地使用声明性编程(如Bloom)来指定应用程序逻辑之间存在一定的差异。时间会告诉我们,在各种不同的环境下,包括基础设施、应用程序、web客户机和移动设备,程序员的这种命令式边界是如何变化的。

发布于 2020-04-17 15:13