论文翻译:《Goods Organizing Google’s Datasets》

论文《Goods Organizing Google’s Datasets》——2016

定位:提供一个统一的平台收集和访问元数据。

摘要

企业对结构化的数据源的依赖越来越强,他们使用这些结构化的数据源运行各自的业务。这些数据源有多种形式,例如结构化的文件、数据库、Spreadsheets、甚至是提供数据访问的服务。这些数据源通常属于不同的存储系统,可能格式各异,可能每天都在变化。在这样一个背景下,团队使用不同的方式生产数据源,并且没有一个中心化的系统来存储和查询它们,因此我们在这篇文章中提出了GOODS,重新思考该如何组织大规模的结构化的数据源。GOODS提取的元数据,涉及每个数据源最重要的信息(owner、timestamps、schema)以及数据源之间的关系(similarity和provenance)。GOODS通过服务暴露元数据,程师们可以通过这些服务发现公司的数据源、监控数据源、注释数据源(便于他人使用)并分析它们之间的关系。我们讨论的不得不克服的技术挑战包括:为几十亿的数据源爬和推导元数据、维护我们大规模元数据目录的一致性、将元数据暴露给用户。我们相信,一般而言,我们吸取的许多教训都适用于构建大规模、企业级的数据管理系统。

1 引言

如今绝大部分大公司都目睹了他们内部产生的数据源数量的暴涨,这些数据源用于平时的研究和开发。这种暴涨背后的原因很简单:通过让工程师们和数据分析师们不加限制地消费和产生数据源,企业可以加速研发周期,最终通过创新提升他们的竞争优势。结果,这些内部产生的数据源通常与源代码和内部基础设施一起成为公司的主要资产。然而,虽然企业已经可以通过源代码开发工具和行业标准方法(代码版本、索引、评审或测试)来管理后者,但并不适用于管理数据源。我们认为,发展标准且灵活的数据源管理方式是当务之急,以免公司承担内部数据源增长的风险。这些风险会造成生产力和机会的流失、重复建设以及数据滥用。

EDM(Enterprise Data Management)是在企业环境下组织数据源的一种通用方式。然而,在EDM的场景下,公司必须接受使用EDM系统来发布、检索和整合他们的数据源。还有一种可替代的方法,就是允许企业完全*地访问和产生数据源,通过某种事后型的手段来解决找到正确的数据的问题。这种方法与数据湖(data lakes)的概念思想相似。湖中包括并不断累积公司内部产生的所有数据源。而目标就是提供方法,在需要的时候从湖中钓出正确的数据源。

在这篇文章中,我们介绍GOODS(Google Dataset Search),为了组织Google内部产生的和使用的数据源而搭建的一个事后型系统。在各种pipeline创建、访问或更新数据源之后,GOODS都会收集这些数据源的元数据,而不是向数据源的owner或用户寻求帮助。团队和工程师们可以持续不断的通过他们选择的工具产生数据源或访问数据源,GOODS在后台通过一种非侵入式的方式收集数据源的元数据以及它们的用法。然后通过这些元数据赋能一些服务,这些服务可以让Google的工程师们以更标准的方式组织和查找他们的数据源。

图1是我们系统的一个简单概览。GOODS持续地爬不同的存储系统和基础设施(例如:运行中的pipeline的日志),查找存在的数据源并收集其元数据(拥有者、访问时间、内容特点、生产pipeline的访问)。GOODS将元数据聚合到一个*目录中,并将特定数据源的元数据与其他数据源的信息相关联。

论文翻译:《Goods Organizing Google’s Datasets》

GOODS通过这个目录向Google的工程师们提供数据源管理服务。为了说明GOODS赋能的服务类型,举一个例子,有一个负责开发文本语料库(语言、新闻文章)的自然语言理解(NLU)的团队。团队成员分布在全球各地,并且他们维护着若干个向文本语料库添加注释的pipeline。每个pipeline可以有多个阶段,基于短语组块、词类标记、共同引用解析等技术添加注释。其他团队可以消费NLU团队产生的数据源,NLU团队的pipeline也可以消费其他团队的数据源。

GOODS基于目录中的信息为NLU团队(在这个场景中,是数据源生产者)提供一个大盘,展示他们的全部数据源,并且可以通过各方面(拥有者、数据中心、schema)来浏览。甚至是团队的数据源在不同的存储系统,工程师们也可以得到团队全部数据源以及数据源间依赖关系的统一视图。GOODS可以监控数据源的特征,比如大小、内容中值的分布或可用性,并在这些特征发生非期望的变化时报警给拥有者。

GOODS提供的另一个重要信息是数据源来源(provenance):顾名思义,就是数据的上下游信息,一个指定的数据源由哪些数据源产生(上游),哪些数据源依赖它(下游)。上游和下游数据源都有可能是由其他团队创建的。当NLU团队的工程师发现数据源出现问题时,她可以检查来源视图,确定是否某个上游数据源的变化引发了这个问题。同样地,如果团队想对他们的pipeline做出较大变更或者在已有的数据源中发现了BUG,他们可以快速将影响进行通知。

从数据消费者的角度来看,假如不是我们例子中提到的NLU团队的一员,还可以使用GOODS提供搜索引擎。该搜索引擎覆盖公司全部数据源,加上各方面信息来缩小查找结果,可以找到最新的或是潜在重要的数据源。GOODS为每个数据源呈现一个简介页面,可以帮助不熟悉该数据的用户理解它的schema,并提供用于访问或查询数据的样本代码。这个简介页面也包含内容与当前数据源相似的数据源信息。相似性信息可以实现数据源的新组合:例如,如果两个数据源有同样的主键列,他们也许提供了互补的信息,并且可以作为join操作的理想对象。

GOODS允许用户用crowd-sourced元数据来扩展目录。例如:数据源拥有者可以注释数据源描述,帮助用户确定哪个数据源适合使用(例如,哪些数据源使用了哪些分析技术,需要小心哪些陷阱)。数据源审计员可以对包含敏感信息的数据源进行标记,并向数据源拥有者发出警告,或者提示复查以确保数据处理得当。通过这种方式,GOODS和它的目录变成了一个枢纽,用户可以共享和交换数据源的信息。GOODS也暴露了API,团队可以通过API贡献元数据到目录,既可以限制自己团队的使用,又可以帮助其他团队和用户轻松地理解他们的数据源。

正如我们在本文后面讨论的,我们在设计和建设GOODS过程中遇到了很多挑战,这些挑战来自于数据源的绝对数量(在我们的场景下是百亿级别)、更新的高流失率、单独数据源的大小(在很多场景下是GB或TB级别)、多种不同的数据格式和存储,以及每个收集的数据源的质量和重要性的变化。我们在GOODS中遇到的许多挑战都是Google数据湖的规模和特点沉淀下来的。然而,我们相信,我们的经验以及我们吸取的教训适用于其他企业中的类似系统。

2 挑战

在这一节中,我们将详细讲述在构建GOODS过程中遇到的挑战。虽然某些挑战是Google特有的,但是我们相信下面的大多数观点适用于其他大型企业。

2.1 数据源数量和大小的规模

尽管我们预期公司内会存在大量数据源,但真实数量远远超过了我们最开始的计算。现在目录索引了超过260亿数据源,而且这些数据源只包括所有Google工程师都有读访问权限的数据源。我们预计,当索引了受限访问权限的数据源和开始支持其他存储系统的时候,目录数据源的数量将不止翻倍。值得注意的是,目录已经排除了许多不感兴趣的数据源类型(例如,我们忽略已知的被标记的空文件),并且标准化路径(path)来避免显而易见的冗余(例如,我们将同一数据源的不同shard的路径标准化为一个公共路径,而不是在目录中分开存储它们)。

在这种规模下,聚合所有数据源的元数据是不可行的。的确,即使我们在每个数据源(许多数据源太大了,处理耗时不止1秒)上耗费1秒,使用1000台机器并发遍历有260亿数据源的目录仍然需要大概300天。因此,我们必须制定策略来优先化和优化数据源的处理。

元数据推导中的“N次方”问题加剧了规模问题。例如,GOODS识别全部列或个别列包含相似或相同内容的数据源。由于数据源很大,比较任何两个数据源的成本已经很高了,天真的成对比较几十亿的数据源内容更是不可能的。

2.2 多样性

数据源以多种格式(txt、csv、Bigtable等)存储在多种存储系统(GoogleFS、数据库服务等)中,每个都有自己的元数据类型和访问特性。由于这种多样性,很难定义一个数据源概念来覆盖所有真实的数据源类型。对用户屏蔽这种多样性和复杂性,并提供统一的方法访问和查询所有类型数据源的信息,是GOODS的目标和挑战。

更重要的是元数据提取成本的多样性,这种可能根据数据源类型和大小的不同以及元数据的不同发生巨大的变化。因此,我们的元数据提取过程需要区别对待:我们必须识别哪些数据源是重要的,并且基于具有特定类型元数据的成本和收益来执行元数据推理。

多样性也体现在数据源之间的关系中,影响我们如何在目录中建模和存储元数据。例如一个Bigtable数据源,它由若干个列族组成,每个列族继承Bigtable的元数据,也包含自己的元数据和访问属性。所以我们既可以将列族视为独立的数据源,也可以将它视为Bigtable数据源整体的一部分。

Furthermore, the underlying storage infrastructure for a Bigtable is provided by a distributed file system, and so we can also view the correspond- ing files as datasets. In the case of Bigtable, the decision to hide these underlying files in favor of the Bigtable dataset seems rea- sonable. However, a similar decision is less clear in other cases. For instance, we include database tables (specifically, Dremel ta- bles [19]) in our catalog. These tables were created from other files (also in our catalog), and in this case it is meaningful to have both the files and the database tables as separate (but connected) datasets in the catalog given that their access patterns are su ciently di↵er- ent. Note that this last example illustrates a type of dataset aliasing. Aliases can arise in several ways in our catalog, and we have dealt with each alias type separately depending on the corresponding us- age patterns.

3 谷歌目录

(1)爬各个存储系统的数据

(2)在数据急多版本的情况下,考虑到用户希望将多个版本视为一个逻辑上的数据集,并且这些多版本数据集通常具有一些相同的元数据,分开收集每个版本是一种资源浪费,因此将相关联的多版本数据集组织为cluster(逻辑数据集)。

3.1 元数据

两个主要作用:一是发现存在什么样的数据集,二是获取基础元数据信息。

除了爬数据还需要推理元数据。因为有些存储系统不追踪一些重要的元数据信息,这些信息要么在日志中、要么编码在数据集中、要么需要从数据集内容中分析。

(1)基础元数据:时间戳、文件格式、owner、访问权限。从存储系统中爬出来。

其他的GOODS模块依赖这些基础信息,比如绕过访问受限或者近期没有变更过的目录条目。

(2)来源(provenance):如何产生、如何消费、依赖的其他数据集、被什么数据集依赖。(*这个概念更像是血缘

通过分析包含读写Job信息的生产日志获取。创建链接数据集和任务的传输闭环,例如:Job J读取数据集D1并生产处数据集D2,那么D1的元数据包含下游D2,反之亦然。

通过时间信息决定上下游依赖出现的最早和最新时间点。考虑到数据访问事件量、传递闭环的节点量可能非常大。GOODS权衡“来源”的完整性以及效率问题,只对数据访问事件Log进行采样,并只考虑上下游几跳的传输闭环。

(3)schema

google大部分数据格式是自定义的,因此需要推导schema。

google几乎所有的机构化数据集都被编码为protobuf(*类似用户自定义的流数据格式),决定数据集使用了哪个protobuf也是一个难点。先从google*代码库获取可用的protobuf列表,然后与爬到的数据集进行匹配。采样存储文件中的几行记录,遍历每个protocal消息定义并判断其是否可以产生这几行记录中的字节。protobuf将多种逻辑类型编码为同一种物理类型,特别是string和嵌套消息都会被编码为可变长字节字符串。所以匹配的过程是投机的,可能产生多种候选protobuf,所有这些候选以及其匹配权重都会作为元数据的一部分。

(4)内容摘要

对于能够打开和扫描的数据集,采样其内容。

通过分析寻找潜在的键(独立键或组合键),使用HyperLogLog算法评估独立键和组合键的基数并比较该技术记录的条数。

 

收集fingerprints,包括独立属性的校验和,以及内容的LSH值。通过fingerprints判断列或数据集的相似性或一致性。

(5)用户提供的注解

允许数据集的owner提供文本描述,

(6)语义

GOODS结合一些噪音信号来推导数据集语义数据。

对于schema遵循protobuf的数据集,GOODS检出源代码并提取注释,这些注释通常是高质量的,并且通过词汇分析可以得出代表schema语义的短词。例如一些数据集包含很含糊的属性mpn,然而它的源代码包含注释“//Model Product Number”。

GOODS也检出数据集的内容,在Google's Knowledge Graph中进行匹配,识别不同属性的业务实体(位置、业务等)。

(7)其他

数据集的团队owner、隶属的项目描述、变更历史。

 

3.2 组织数据集为cluster(*数据多版本的一种解决方案

 

数据集会定期产生不同的版本,具体场景包括不同数据中心的复制、sharding为更小的数据集便于快速Load等。将这些数据组织为cluster,既可以为用户提供有用的逻辑层面的抽象,又可以节省成本。只收集cluster种一小部分数据集的元数据,并且cluster中的数据集共享这些元数据。

但是,聚合数据集也会带来开销。如果这个开销很大,比如通过分析数据集的内容来集合,就会得不偿失。好在数据集的路径(path)信息可以用来聚合,路径通常包括时间戳、版本等标识符。

例如下图,数据集可以通过日期和版本两个维度,抽象成一个网状图。如果按月份维度进行抽象,那么数据集/dataset/2015-10-10/daily_scan会被抽象为/dataset/2015-10-<day>/daily_scan。以此类推。

论文翻译:《Goods Organizing Google’s Datasets》

如何决定抽象的维度?可以通过一个目标函数来实现,该函数根据每天的目录状态推导出抽象维度。但是这种方法会导致抽象维度不固定,对用户会造成困惑。GOODS直接采用网状图最顶层的节点作为抽象,做最大程度的抽象,从而极大减少了集群的数量。每个数据集只属于一个集群。下图是GOODS的抽象维度:

论文翻译:《Goods Organizing Google’s Datasets》

更少的数据集抽象,可以让用户查看目录更方便,也更加节省计算开销。

4 后台实现

4.1 目录存储

GOODS使用Bigtable存储,可扩展的、临时的KV存储。每个数据集或集群的路径(path)作为key。Bigtable提供每行的事务一致性。大部分处理都是在每一个数据集上,比如推导schema、分析数据集内容。也有一些处理不是在单独的数据集上,比如将多行的信息聚合成抽象网格上的逻辑数据集,或者在同一个集群上传播元数据。但是这些处理只是尽力而为,并不需要强一致性。

 

在物理层,Bigtable由很多列族组成。这些数据只被批处理访问。例如GOODS最大的列族包含用来计算来源图的原始来源数据,但是这些数据不直接服务于前端,而是服务于抽象数据集集群层面的来源信息。

Bigtable后台目录中,每行保存两类元数据:(1)第三节中的元数据;(2)状态元数据,也就是各模块处理数据集的结果相关的元数据。状态元数据会列出每个模块处理特定实体的时间戳、成功状态、错误信息。使用状态元数据来协调模块的执行,并进行系统检查(比如哪些数据集被模块X处理成功了?常见错误是什么?)。结合Bigtable的临时数据模型,状态元数据对调试来说也是非常有用的。通过配置Bigtable保存多版本的状态元数据,可以看到各模块随着时间都做了些什么。

4.2 批处理性能和调度

GOODS有两类JOB任务,一类是大量处理不同批任务的,一类是服务于前端和API的。另外,该系统是可扩展的,并可兼容新的数据来源、其他元数据、新的分析模型。一些批任务是非常快的,通常完场一个完整的目录需要几个小时。其他的,比如分析一个新的爬虫收集来的数据集内容会很慢,需要花费很多天。这类任务会跑在距离它分析的数据集比较近的地方。

各任务独立工作。模块之间可能会相互依赖,比如分析血缘的模块A依赖分析schema的模块B。当模块B扫描一行的时候,会检查该行的状态,如果执行过A了则处理,否则跳过,等下次调度继续检查。状态元数据还可以用来避免该行的重复处理。

大部分任务配置为每日执行,并在24小时之内执行完毕。如果有超过24小时的,就优化或者增加并发度。

当大量新数据集流入(结合一个新的数据集路径来源),大部分重量级任务、schema分析、会花费几天甚至几周来完成追赶。使用一个简单的优化机制来保证在追赶期间,最重要的数据集仍然会被分析schema。指定有用户注释或高来源中心度的数据集为“重要的”,并调度两个任务:一个只处理重要的数据集,另一个处理所有的数据集。

大量爬虫任务执行盲写到目录。每天从数据源读取所有数据并写到Bigtable中。不区分插入还是更新。这种方法比反向关联存量数据和增量数据更有效率。

4.3 容错

对于处理独立数据集的模型,在数据集的状态元数据中记录错误信息,可以触发重试(有限次数)。对于不独立处理数据集的模型,通过任务宽度状态元数据记录任务的开始时间以及是否成功。通过这种方式,可以重做部分失败的bigtable写任务,虽然相对保守一些,但是可以保证幂等,并且利于垃圾回收。

一些模型使用各种库来指定不同的文件格式。有时,这些库会崩溃或者无限循环。对于运行时间较长的分析任务来说,这是不能接受的。所以在单独的进程沙箱中处理有这种潜在风险的任务,然后使用监控线程将长期停顿转换为崩溃,从而让剩下的pipeline继续。

GOODS目录在多个地理位置拷贝多份。写到master,并异步复制到其他位置。

4.4 垃圾回收

有大量临时的数据。在构建来源图之后,需要把已删除的数据的条目也删掉,还有已经被模型消费过的元数据。起初的做法是删除一周没有更新过的行。但是引发了一些问题,在这种场景下,需要停止所有的爬虫和非垃圾回收相关的分析模型。

所以需要更好的垃圾回收机制:

1 可以从元数据和其他模型访问之后的状态得到删除行的条件。如果数据集已经在存储系统中删除了,并且血缘模型已经计算完成它的来源信息计算。

2 从目录中删除条目,要确保不产生“dangling rows”悬挂数据。因此,当删除一行的时候,必须确保没有运行中的并发模型会将该行重新插入(带有模型计算后的部分信息)。

3 所有其他模型必须能够独立于垃圾回收运行,并且能够与垃圾回收同时运行。

Bigtable支持条件,在事物执行中,根据断言true来判断执行更新还是删除。但是,根据所有相关的模型来更新行,存在大量的读日志开销。

GOODS的最终方案是允许非垃圾回收模型在非事务中更新行,垃圾回收分成两个阶段:(a)给满足删除条件的行打标。(b)24小时之后如果该行仍然满足删除条件,执行删除;否则删除标记。

同时,其他模型遵守以下约束:(a)执行非事务更新,(b)忽略打标删除的行,(c)模型的一个迭代不能超过24小时(通过模型调度机制来保证,见4.2)。

这个机制既可以保证满足上述三个条件又可以保证性能。

5 前端:目录服务

5.1 数据集展示页

可根据数据集或集群的路径展示特定数据集的元数据,并可以补充或修改元数据。

页面要在展示全面的信息和保持页面信息量可控之间做权衡。不能压给用户,并且不要传输大量的信息。例如血缘信息,热点数据集可能被几万个任务读取,从而产生几万个下游;同时,GOODS模型扫描全公司的每个数据集,所以会有几十亿个上游。为了避免传输并展示压倒性的数量的信息给用户,需要在线下根据3.2的抽象机制压缩血缘元数据。如果仍然很多,那就只保留最新的一些。

页面交叉链接到其他工具,比如血缘元数据链接到产生数据集的任务明细信息(在任务中心工具),schema元数据链接到代码管理工具。

页面提供访问数据集内容的代码片段。有了schema级别的信息,就可以通过代码片段读数据或分析数据。

总之,为用户提供一站式的服务来读取数据,并了解数据的上下文以便使用数据。

5.2 数据搜索

通过文档检索中常规的倒排索引实现。每个数据集就是一个文档,从元数据的子集中推导索引。

5.3 团队大盘

提供可配置的一站式大盘,便于展示一个团队产生的数据以及感兴趣的元数据、健康指标等。同时提供监控、报警的能力。