<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://maybe.news/issues</id>
    <title>Maybe News - Issues</title>
    <updated>2023-11-24T20:50:00.000Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://maybe.news/issues"/>
    <subtitle>可能是新闻的知识</subtitle>
    <icon>https://maybe.news/img/favicon.ico</icon>
    <rights>Copyright © 2025 Maybe News</rights>
    <entry>
        <title type="html"><![CDATA[SP #1]]></title>
        <id>https://maybe.news/issues/sp-1</id>
        <link href="https://maybe.news/issues/sp-1"/>
        <updated>2023-11-24T20:50:00.000Z</updated>
        <summary type="html"><![CDATA[这是 Maybe News 特别篇的第二期，灵感来自我最近看的一系列演讲。如果你想回顾第一期特别篇，请点击这里查看。]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>这是 Maybe News 特别篇的第二期，灵感来自我最近看的一系列演讲。如果你想回顾第一期特别篇，请点击<a href="https://maybe.news/issues/sp-0">这里</a>查看。</p>
</blockquote>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="paul-graham-before-the-startup">Paul Graham: Before the Startup<a href="https://maybe.news/issues/sp-1#paul-graham-before-the-startup" class="hash-link" aria-label="Direct link to Paul Graham: Before the Startup" title="Direct link to Paul Graham: Before the Startup">​</a></h2>
<p><a href="https://startupclass.samaltman.com/courses/lec03" target="_blank" rel="noopener noreferrer">「Before the Startup」</a>是硅谷著名投资机构 Y Combinator（简称 YC）创始人、<a href="https://book.douban.com/subject/6021440" target="_blank" rel="noopener noreferrer">《黑客与画家》</a>作者 Paul Graham 2014 年在斯坦福大学的一个演讲，事实上这是<a href="https://startupclass.samaltman.com/" target="_blank" rel="noopener noreferrer">「How to Start a Startup」</a>系列课程中的其中一节课，而这个系列课程的发起人正是近期备受瞩目的 OpenAI 创始人 Sam Altman。除了视频以外，你也能在 Paul Graham 的个人网站上看到<a href="http://paulgraham.com/before.html" target="_blank" rel="noopener noreferrer">文字版</a>，但是我建议你两个都看看（演讲时长只有 30 分钟）。视频里包含的是最原始的内容，除了演讲主题以外还穿插了不少 Graham 和现场听众的互动、一些不适合放到文字版中的「玩笑」，以及最后长达 18 分钟的 Q&amp;A。而文字版相比视频多了一些 Graham 后期加上的脚注，以及链接到 Graham 以前写的一些文章的引用（可以作为延伸阅读）。</p>
<p>相比「How to Start a Startup」的其它课程，Graham 的这个演讲没有任何 slide，30 分钟的演讲全程都靠他口述（也基本没有看演讲稿）。对于之前只看过 Graham 写的文字的我来说，观看这个视频进一步加深了 Graham 在我心目中的个人魅力。Graham 的这个演讲主题在整个系列中其实算是相对抽象的内容，并不涉及太多可以直接拿来用的创业「最佳实践」或是「奇技淫巧」，有的只是 Graham 多年从事 VC 总结的一些「经验」，中心思想可以总结为一句话——<strong>初创公司是非常违反直觉的（Startups are very counterintuitive）</strong>。</p>
<p>围绕这个中心思想 Graham 列举了 6 个反直觉的观点：</p>
<ol>
<li>如果你跟着直觉走，它们就会把你引入歧途；</li>
<li>了解很多关于初创公司的信息并不那么重要；</li>
<li>通过玩弄系统（gaming the system）来获得成功在初创公司中并不奏效；</li>
<li>初创公司需要全力以赴；</li>
<li>创业是否成功很难预测；</li>
<li>获得创业想法的方法不是尝试思考创业想法。</li>
</ol>
<p>以上每个观点 Graham 都做了详尽解释和举例说明，这里我就不再一一转述。</p>
<p>在 30 分钟的演讲结束以后，有一个长达 18 分钟的 Q&amp;A 环节。由于 Graham 个人网站的文字版中并不包含 Q&amp;A 的内容，于是我整理了几个我觉得有意思的问题：</p>
<ul>
<li><strong>如果我对创业感兴趣，商学院还有价值吗？</strong>
<ul>
<li>**基本上没有。**商学院的目的是教人如何管理，而管理是一个只有在初创公司取得足够成功时才会遇到的问题。<strong>要想让初创公司取得成功，你需要尽早了解的是如何开发一个好的产品。</strong></li>
<li>我早期做错的一件事是，我建议那些有意创办初创公司的人先去其它公司工作几年，然后再创办自己的公司。但老实说，<strong>学习如何创办初创公司的最好方法就是尝试创办一家初创公司</strong>。</li>
</ul>
</li>
<li><strong>你的演讲中提到只有成功了以后管理才是个问题，那对于最初加入的两三个人是否会遇到管理问题呢？</strong>
<ul>
<li>**初创公司的第一批员工几乎就像创始人一样。**他们应该受到同样的激励，他们不能是你必须管理的人，你不应该过多地管理他们。</li>
<li>一般来说，你需要那些在早期就能自我激励的人。<strong>他们应该像创始人一样。</strong></li>
</ul>
</li>
<li><strong>对于正在寻求资金的女性联合创始人，你有什么建议？</strong>
<ul>
<li>**让你的初创公司做得足够好。**如果从风险投资人的角度来看，你在任何方面都没有达到理想目标，那么解决问题的方法就是让初创公司真正做好。</li>
<li>事实上一两年前，我曾在 Twitter 上发布过一家公司的增长图，但我没有说她们是谁。这其实是一家由女性创办的初创公司，她们在融资方面遇到了困难，但她们的增长却非常惊人。于是我在 Twitter 上发布了这条消息，因为我知道所有的风险投资人都会开始问我这是谁。<strong>增长图表没有性别之分。</strong></li>
</ul>
</li>
<li><strong>在你的工作和个人生活中，有哪些方式或者技巧能让你变得高效？</strong>
<ul>
<li><del>生孩子是提高效率的好办法。因为你没有时间了，所以如果你想完成任何事情，每次完成的量就会很大。这会让你集中精力，因为你别无选择。</del>（这只是个玩笑）</li>
<li>我不认为我是一个效率很高的人，我有两种完成工作的方法：<!-- -->
<ul>
<li>方法 1，被迫工作。我在 YC 的工作方式是被迫的，我不得不设定申请截止日期。然后人们就会提出申请，我必须在某个时间之前做出回复，所以我必须阅读这些申请。我知道如果我读得不好，就会投资到不好的初创公司，所以我必须非常努力地读好它们。</li>
<li>方法 2，做你感兴趣的事情，对我来说就是写文章。我会不由自主地想要写文章，就像走在大街上时，文章就开始在我脑子里诞生。</li>
</ul>
</li>
<li>我要么强迫自己去做不那么令人兴奋的事情，要么就情不自禁地去做令人兴奋的事情，我没有任何有用的技巧来让自己变得高效。<strong>如果你从事自己喜欢的工作，就不必强迫自己提高效率。</strong></li>
</ul>
</li>
<li><strong>该如何确定什么是重要的或者真正有趣的事情？</strong>
<ul>
<li>**我想出了一种检测你是否喜欢真正有趣问题的方法，也就是你是否觉得做无聊的事情难以忍受。**有一些众所周知的枯燥乏味的事情，比如文学理论和在某个大公司的中层管理部门工作。因此，如果你能忍受这些事情，那么你一定是有超强的自律能力，或者你不喜欢真正有趣的问题，反之亦然。</li>
</ul>
</li>
<li><strong>如果你总是雇用自己喜欢的人，你可能会得到一个单一文化（monoculture）的公司，该如何处理可能带来的盲点？</strong>
<ul>
<li>创办一家初创公司很多事情都会出错，你不能指望它十全十美，而<strong>雇用你熟悉和喜欢的人的优势远远大于所谓单一文化的小缺点</strong>。从经验上看，所有最成功的初创公司都会雇用大学毕业的伙伴。</li>
</ul>
</li>
</ul>
<p>在「How to Start a Startup」网站的<a href="https://startupclass.samaltman.com/lists/readings" target="_blank" rel="noopener noreferrer">推荐阅读</a>里，Graham 的这节课有两个推荐项，一个是 Graham 2012 年写的文章<a href="http://www.paulgraham.com/startupideas.html" target="_blank" rel="noopener noreferrer">「How to Get Startup Ideas」</a>，另一个是 Steve Jobs 1995 年的一段采访视频。整个采访接近 1 个半小时，但是从 <a href="https://www.youtube.com/watch?v=M6Oxl5dAnR0&amp;t=1h10m53s" target="_blank" rel="noopener noreferrer">1 小时 10 分</a>开始，记者问了 Steve Jobs 一些关于如何看待初创公司和大公司之间关系的问题，本期 Maybe News SP 将以 Steve Jobs 的这段回答作为结束：</p>
<blockquote>
<p>Technology keeps on advancing so there are opportunities.</p>
<p>…</p>
<p>Human minds settle into fixed ways of looking at the world and that’s always been true and it’s probably always going to be true.</p>
<p>…</p>
<p>One of the things that happens in organizations as well as in people is they settle into ways of looking at the world and become satisfied with those, and the world changes and keeps evolving and new potential arises, but these people that are settled in don’t see it. That’s what gives startup companies their greatest advantage is the sedentary point of view of large companies.</p>
</blockquote>]]></content>
        <category label="sp" term="sp"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Issue #13]]></title>
        <id>https://maybe.news/issues/13</id>
        <link href="https://maybe.news/issues/13"/>
        <updated>2022-06-07T19:00:00.000Z</updated>
        <summary type="html"><![CDATA[本期关键词：InfiniFS、Decentralized Social Networks、杨海崧]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>本期关键词：InfiniFS、Decentralized Social Networks、杨海崧</p>
</blockquote>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="infinifs-an-efficient-metadata-service-for-large-scale-distributed-filesystems">InfiniFS: An Efficient Metadata Service for Large-Scale Distributed Filesystems<a href="https://maybe.news/issues/13#infinifs-an-efficient-metadata-service-for-large-scale-distributed-filesystems" class="hash-link" aria-label="Direct link to InfiniFS: An Efficient Metadata Service for Large-Scale Distributed Filesystems" title="Direct link to InfiniFS: An Efficient Metadata Service for Large-Scale Distributed Filesystems">​</a></h2>
<p><a href="https://www.usenix.org/conference/fast22/presentation/lv" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>本期是关于分布式文件系统论文的又一期解读，再之前已经介绍过如<a href="https://maybe.news/issues/1">第 1 期</a>的 CFS（也就是 ChubaoFS，后来又改名叫 <a href="https://github.com/cubeFS/cubefs" target="_blank" rel="noopener noreferrer">CubeFS</a>）、<a href="https://maybe.news/issues/8">第 8 期</a>的 GFS 以及<a href="https://maybe.news/issues/10">第 10 期</a>的 Tectonic。</p>
<p>本期介绍的这篇论文发表在 2022 年的 FAST 会议（和 Tectonic 一样），论文作者大部分来自清华大学的<a href="https://storage.cs.tsinghua.edu.cn/" target="_blank" rel="noopener noreferrer">存储研究组</a>（Storage Research Group），这个研究组过往发表的论文都集中在存储领域，仅分布式文件系统就有至少三四篇相关的论文，本篇基本算是这个研究系列的最新成果，感兴趣的同学可以去他们的网站<a href="https://storage.cs.tsinghua.edu.cn/pub" target="_blank" rel="noopener noreferrer">查看</a>历史发表的论文。本篇论文还有一个作者来自阿里云分布式存储团队，因此也能在论文中看到很多数据是基于阿里云的<a href="https://www.alibabacloud.com/blog/pangu-the-high-performance-distributed-file-system-by-alibaba-cloud_594059" target="_blank" rel="noopener noreferrer">盘古</a>来分析的，不过文中并没有透露这个文件系统目前是否有在阿里云中正式使用。另外这个来自阿里云的同学的<a href="http://duanple.com/" target="_blank" rel="noopener noreferrer">个人博客</a>也有挺多不错的内容，也都是围绕在分布式系统相关的话题。</p>
<p>铺垫了这么多我们来实际看看这个被称为 InfiniFS（名字挺有野心）的分布式文件系统到底有什么独特之处吧，和前几期介绍的系统不太一样的地方是，本文核心的关注点是在文件系统的元数据管理，而关于数据存储的部分则不是本文的重心（论文中也压根没提）。论文摘要中已经简要概括了 InfiniFS 的几个独特的设计点：</p>
<ul>
<li><strong>把目录元数据中的 Access 和 Content 进行分离</strong>：这样分割（partition）目录树以后既能满足元数据的数据本地性，也能实现负载均衡。关于这里的 Access 和 Content 具体是指什么元数据后面会详细介绍；</li>
<li><strong>设计了一个可推测的路径解析（speculative path resolution）方法</strong>：这样可以把路径解析并行化，熟悉文件系统的同学应该能理解这个优化能带来什么显著变化；</li>
<li><strong>在客户端引入一个乐观访问的元数据缓存（optimistic access metadata cache）</strong>：再强大的服务端也架不住茫茫多的客户端，一定程度的客户端缓存还是需要的。</li>
</ul>
<p>经过这些优化设计以后 InfiniFS 号称可以支撑<strong>千亿级</strong>的文件存储并提供可观的性能，在论文最后的实验验证环节他们也的确模拟了这么大量级的测试数据。</p>
<p>在正式介绍这三个优化的详细设计之前，先来了解一下背景，也就是最初设计 InfiniFS 的动机是什么（当然不只是为了发论文）。文中首先提出了当下分布式文件系统元数据设计的几个特点：</p>
<ul>
<li>首先文件的元数据都是按照<strong>目录树</strong>（directory tree）的组织形式来管理的，这是个非常自然的设计，因为我们已经习惯于按照目录树来管理文件（计算机里之所以采用这种管理形式应该也是将现实生活中的方式直接映射过来，具体原因可能要考据一下计算机交互设计的历史）；</li>
<li>在以目录树为基础设计的元数据访问中有两个关键步骤，<strong>一个是路径解析，一个是处理元数据</strong>。所谓「路径解析」可以举个栗子，当我们想要访问一个文件时首先需要提供一个文件路径，比如 <code>/home/alice/abc.txt</code>，可以看到这是一个多级路径，分别是 <code>home/</code>、<code>alice/</code> 和 <code>abc.txt</code>，这里面的每一部分都有对应的元数据，路径解析就是获取每部分的元数据，通常路径解析是串行的，也就是先解析 <code>home/</code>，再解析 <code>alice/</code>，最后解析 <code>abc.txt</code>，<strong>也就是说路径越长花费在路径解析上的时间也会越长</strong>。「处理元数据」对应的就是实际的元数据操作，比如重命名、修改权限、移动文件等。</li>
</ul>
<p>由于元数据服务在整个文件系统中的角色至关重要，因此很容易就成为瓶颈。随着数据量的增长，数据存储服务可以相对容易地横向扩展，但是元数据服务往往比较困难。于是在分布式文件系统的运维实践中，人们通常倾向于管理很多个小的集群，而不是管理一个非常大的集群（况且很多文件系统往往也支撑不了特别大的规模）。文中举了一个实际的栗子，阿里云上目前已经运维了<strong>近千个盘古文件系统</strong>集群来支撑数据中心百亿级的文件存储需求。Facebook 也同样如此，因为单个 HDFS 集群最多能支撑 <a href="https://www.usenix.org/publications/login/april-2010-volume-35-number-2/hdfs-scalability-limits-growth" target="_blank" rel="noopener noreferrer">1 亿左右</a>的文件，所以在 Facebook 内部维护了很多大大小小的 HDFS 集群（这一点在<a href="https://maybe.news/issues/8">第 8 期</a>介绍的 Tectonic 里也有说明）。</p>
<p>运维数个分布式集群的成本显然不小，而仅用一个集群就能支撑整个数据中心存储需求的「诱惑」又是如此之大，人们才会想要不断探索和突破。关于运维单一文件系统的好处这里就不再赘述，同样可以参考第 8 期里 Tectonic 的内容。</p>
<p>以上就是 InfiniFS 设计的背景和动机，简单讲就是希望通过优化元数据服务来支撑更大规模的数据存储需求。具体优化的几个点前面已经简单介绍过了，下面会详细说明。</p>
<p>首先来看看 InfiniFS 中具体有哪些组件（注意这个系统只包含元数据的部分，不包含数据存储）：</p>
<ul>
<li><strong>客户端</strong>：与大多数分布式文件系统一样，InfiniFS 有一个专用的客户端，这个客户端以用户态库或者 FUSE 的形式存在，也就意味着 InfiniFS 支持 POSIX 接口（是否完全兼容 POSIX 论文中并没有说）。前面提到的 3 个设计亮点中的 2 个都与客户端有关，包括「可推测的路径解析」和「乐观元数据缓存」。</li>
<li><strong>元数据服务器</strong>：文件系统的目录树通过前面提到的「access-content 解耦分割」的方法分布到集群中不同的元数据服务器上。每个元数据服务器将这些元数据存储到本地的 KV 存储中，这里的 KV 存储通常会将元数据缓存在内存中，以及通过追加日志（append log）的形式将数据持久化到 NVMe SSD 盘上（虽然论文里没有明说用的是什么 KV 存储，但估计是类似 LevelDB、RocksDB 的组件）。同时元数据服务器上还会维护一个「缓存失效列表（invalidation list，简写为 Inv. List）」，顾名思义这个列表是和客户端的元数据缓存有关的，后面在介绍「乐观元数据缓存」时会详细解释工作机制。</li>
<li><strong>重命名协调器（Rename Coordinator）</strong>：InfiniFS 的最后一个组件是重命名协调器，这个组件主要用来处理目录 <code>rename</code> 和目录 <code>set_permission</code> 请求。重命名协调器会通过「重命名图（renaming graph）」来检查并发的目录重命名以防止局部死循环（orphaned loop），同时还会广播修改信息给元数据服务器以更新缓存失效列表。</li>
</ul>
<p>接下来依次介绍 InfiniFS 的 3 个设计亮点，即：Access-Content 解耦分割、可推测的路径解析、乐观元数据缓存。</p>
<p>在理解「Access-Content 解耦分割」的好处之前得先了解常见分布式文件系统中分割目录树元数据的挑战是什么。<strong>第一个挑战是保证元数据的本地性（locality）</strong>，简单讲就是把相互关联的元数据尽量放到一个元数据服务器上。这背后的缘由是因为一个文件系统操作通常会同时涉及多个元数据的修改，比如「创建文件」这个操作需要先锁住父目录（以保证目录列表的一致性），然后<strong>原子更新</strong> 3 个元数据（包括文件的元数据、父目录的文件列表以及目录的时间戳）。想象一下如果这些元数据分布在不同的元数据服务器上，势必需要引入分布式锁和分布式事务来保证一致性，但是分布式锁和分布式事务的开销是很大的，如果能分布在同一个元数据服务器上可以大大简化设计以及大幅提升性能。<strong>第二个挑战是负载均衡</strong>，这个挑战和第一个看起来似乎是悖论，理想情况下把所有元数据放在同一个服务器上的话肯定能最大化本地性，但这个服务器很自然也就变成了单点，没法负载均衡。同一个目录树下的所有文件很可能在短时间内被频繁访问，从而导致局部热点。</p>
<p>现有分布式文件系统分割元数据的方法可以大体上总结为两类：<strong>细粒度分割（Fine-grained Partitioning）<strong>和</strong>粗粒度分割（Coarse-grained Partitioning）</strong>，这两类方法都只能满足「本地性」或「负载均衡」中的任意一点，无法同时满足。比如细粒度分割通常的做法是无脑哈希元数据（论文中引用的例子是 <a href="https://www.usenix.org/conference/fast17/technical-sessions/presentation/niazi" target="_blank" rel="noopener noreferrer">HopsFS</a> 和 <a href="https://www.usenix.org/publications/login/summer2017/shvachko" target="_blank" rel="noopener noreferrer">Giraffa</a>），这种方式最大限度地提升了负载均衡的能力，但同时也牺牲了元数据本地性。反之粗粒度分割通常的做法是将文件系统的元数据按照子树为单元进行拆分，并分布到不同的服务器上，这样元数据本地性能获得一定的保障，但显然会产生负载不均衡的问题。</p>
<p>InfiniFS 的作者认为以上两种方式没法同时满足本地性和负载均衡的根因在于<strong>把目录的元数据看作了一个整体</strong>，在他们看来目录的元数据由两个独立的部分组成：Access 和 Content。Access 元数据指的是目录名、目录 ID（应该是指 inode）、权限等，顾名思义这些是与访问目录有关的信息。Content 元数据包含条目列表（entry list）、时间戳等，这些是与当前目录的子目录或子文件相关的信息。通过分析所有元数据操作可以将它们分为 3 类：仅处理目标文件/目录、处理目标文件/目录以及父目录、重命名（这是个特例后面会单说）。因此可以通过把「目录的 Content 元数据」与「子目录的 Access 元数据」及「文件的元数据」组合在一起的方式满足元数据本地性的需求，同时「目录的 Content 元数据」按照当前目录的 ID 哈希分割、「目录的 Access 元数据」和「文件的元数据」都按照父目录的 ID 哈希分割。还是继续拿前面举的「创建文件」操作为栗子，因为文件的元数据和父目录的 Content 元数据是组合在一起的，并且它们都是基于同一个 ID（父目录的 ID）哈希分割，因此创建文件必定是在一个元数据服务器上完成的，同时哈希分割也可以很自然地分散请求负载。</p>
<p>然后来看看「可推测的路径解析」。前面已经讲过随着文件在文件系统目录树中的深度越深，路径解析的成本也越高。这其中的根本原因就是每一级目录的 ID（或者说 inode）都是未知的，只有通过逐级解析才能最终获取到想要访问的目录或文件的元数据。这也就是为什么 InfiniFS 要提出可推测的路径解析的原因，当每一级目录的 ID 是可推测时，路径解析流程就可以由串行执行变成并行执行。</p>
<p>要实现「可推测的路径解析」第一步就是生成可推测的目录 ID，而不能单纯用一个递增数字作为 ID。当创建一个新目录时，InfiniFS 的做法是哈希这样一个三元组 <code>&lt;第一个父目录的 ID，新目录的名称，名称版本号&gt;</code>，哈希得到的结果即为这个新目录的 ID。新目录的第一个父目录被形象地称作「生父（birth parent）」，之所以强调第一个是因为目录在创建以后可能被移动到其它父目录下，但生父只会有一个。生父会记录一个「重命名列表（rename-list，简称 RL）」，这个列表中的元素是 <code>&lt;子目录的名称，名称版本号&gt;</code> 这样的数据结构。同时子目录会记录一个「回溯指针（back-pointer，简称 BP）」，这个指针的数据结构是 <code>&lt;生父的 ID，名称版本号&gt;</code>。RL 和「目录的 Content 元数据」存储在一起，BP 和「目录的 Access 元数据」存储在一起。RL 中记录的是所有「出生」在这个目录中并且已经被移动到其它地方的子目录，一个目录被移动以后它的 BP 依然指向的是生父。「名称版本号」的初始值都是 0，它存在的目的主要是为了解决目录移动（或者说重命名）后可能导致的名称冲突。</p>
<p>这里举一个实际的栗子，假设当前的目录结构是 <code>/A/B</code>，根目录的 ID 是 1， <code>A</code> 目录的 ID 是 2，<code>B</code> 目录的 ID 是 <code>hash(2,B,0) = 3</code>。然后把 <code>/A/B</code> 移动到 <code>/B</code>，此时需要做几件事情：</p>
<ul>
<li>移动 <code>B</code> 目录的 Access 元数据到新的元数据服务器，因为 <code>B</code> 目录的父目录从 <code>A</code> 变成了根目录，所以 Access 元数据哈希分割的 key 也变成了根目录；</li>
<li>在 <code>A</code> 目录的 Content 元数据中新增 RL 条目，值为 <code>&lt;B,0&gt;</code>；</li>
<li>在 <code>B</code> 目录的 Access 元数据中记录 BP，值为 <code>&lt;2,0&gt;</code>；</li>
<li>注意 <code>B</code> 目录的 ID 并没有变（因为生父没变），依然是 3。<strong>但是这里留了一个疑问就是如果 <code>B</code> 目录的名称也变了那它的 ID 是否会变呢</strong>（通常来说文件系统中的 inode 应该是不变的）？</li>
</ul>
<p>此时如果在 <code>A</code> 目录下又创建一个 <code>B</code> 目录（假设叫 <code>B'</code>），因为 <code>A</code> 目录的 RL 中已经记录了一个同名的目录，所以「名称版本号」要加 1，因此这个 <code>B'</code> 目录的 ID 是 <code>hash(2,B,1) = 13</code>。如果没有「名称版本号」，就会生成重复的目录 ID，这是绝对不允许的。如果先把 <code>/B</code> 目录删除，除了 <code>B</code> 目录自身的元数据以外还会删除它的生父（即 <code>A</code> 目录）的 RL 条目。删除 RL 条目也就意味着「名称版本号」减 1，下一次创建 <code>/A/B</code> 时「名称版本号」就已经回到 0 了。引入「名称版本号」从理论上保证了目录 ID 的唯一性，但是哈希算法是有一定概率碰撞的，即便 InfiniFS 已经用了密码学哈希算法（比如 SHA-256）来尽量减少碰撞概率。如果发生碰撞，InfiniFS 的处理方法和刚才举的移动目录的栗子一样，即通过名称版本号、RL 和 BP 来保证生成的 ID 是唯一的，同时也可以通过 RL 中条目的格式来区分它是因为哈希碰撞还是重命名导致的。</p>
<p>知道了怎么生成可推测的目录 ID，路径解析的流程就比较清晰了，大概分为以下几步（这里以解析 <code>/A/B/C/foo.txt</code> 路径为例）：</p>
<ol>
<li>客户端使用某种哈希算法计算每一级目录的 ID，即 <code>A</code>、<code>B</code>、<code>C</code> 这 3 个目录的 ID，这一步是串行的。计算时假设「名称版本号」都为 0，根据前面的介绍应该能发现计算出的 ID 有可能是错误的，后面会讲如何处理这种情形；</li>
<li>客户端发送 lookup 请求到元数据服务器，因为步骤 1 已经计算出了每一级目录的 ID，所以这一步是并行的。Lookup 请求获取的是每一级目录的 Access 元数据，元数据服务器会返回这个目录的「真实 ID」和对应的权限信息。比如 lookup <code>B</code> 目录，请求的参数是父目录（即 <code>A</code> 目录）的 ID 和 <code>B</code> 目录的名称，假设这个 <code>B</code> 目录的生父不是 <code>A</code> 目录，此时元数据服务器返回的 <code>B</code> 目录的「真实 ID」一定不等于步骤 1 中计算的 ID（「名称版本号」不为 0）。当遇到这种情况时客户端会以 <code>B</code> 目录作为起点回到步骤 1 重新开始；</li>
<li>不断重复步骤 1 和步骤 2 直到得到所有目录的真实 ID，其实最终目的是为了获取最后一级目录（即 <code>C</code> 目录）的真实 ID。</li>
</ol>
<p>可以看到上述算法的最坏情况是所有中间目录的「名称版本号」都不为 0，这种情况的时间复杂度肯定比传统的路径解析算法高不少，但这属于小概率情况，大部分时候应该可以在 1 次迭代中完成。</p>
<p>最后介绍 InfiniFS 的乐观元数据缓存设计。InfiniFS 只会在客户端缓存目录的 Access 元数据，即目录名称、目录 ID 和目录权限。这些缓存是以文件系统目录树的结构组织，这棵树的所有叶子节点会连接在一起组成一个列表，当需要淘汰缓存时，会根据 LRU 算法从叶子节点列表中驱逐条目，这个算法能最大程度保证根目录元数据的缓存命中率（因为根目录更容易成为访问热点）。目录的 Access 元数据会因为 <code>rename</code> 和 <code>set_permission</code> 操作而失效，主动失效所有客户端的元数据缓存显然成本比较高，InfiniFS 的选择是把与缓存失效相关的信息主动推送到元数据服务器上，毕竟相比客户端的数量，元数据服务器的数量会小不少。所谓「乐观元数据缓存」就是客户端任何时候都乐观地认为自己的缓存是最新的，并以这个缓存为基础来请求元数据服务器，因为元数据服务器拥有最新的缓存失效信息，所以元数据服务器可以判断当前请求的客户端上的元数据缓存是否已经过期，如果过期就会告知客户端更新或者失效缓存。在具体实现上元数据服务器会维护一个「缓存失效列表」，这个列表中的每个条目包含两个信息：版本号以及对应的操作（如 <code>rename</code>、<code>set_permission</code>），版本号是递增的，当产生了需要失效缓存的操作时客户端会推送到所有元数据服务器，元数据服务器会在「缓存失效列表」中记录一个新的版本号。同时每个客户端本地也维护了一个版本号，这个版本号有可能小于元数据服务器上的「最新」版本号。当客户端请求元数据服务器时会带上操作的「路径名」和「自己本地的版本号」，元数据服务器可以将它们与「缓存失效列表」中的版本号进行对比，一旦发现客户端操作的路径名已经有变化就会告知客户端最新的版本号和对应的信息。</p>
<p>至此基本将 InfiniFS 的 3 个设计亮点介绍完毕，当然还有很多细节没有涉及，比如分布式事务如何实现（InfiniFS 用的是 2PC）、重命名协调器的工作机制是什么，有兴趣的同学可以继续阅读论文了解。论文最后的验证章节的数据也挺有意思，例如在比较元数据集群的横向扩展能力时，通过与 LocoFS（这是 InfiniFS 团队的上一篇文件系统论文）、HopsFS 和 CephFS 的比较中发现只有 InfiniFS 的吞吐（每秒处理的操作数）可以随着元数据服务器的数量增长而线性增长，HopsFS 和 CephFS 甚至在集群规模已经扩大了 32 倍的情况下吞吐也只有一点点增长。另一个验证是评估存储不同规模文件时的元数据集群吞吐变化，这个测试一直测到了 1 千亿文件，不敢说这是目前分布式文件系统的存储上限（理论上肯定还可以继续横向扩展），但是从现实世界的数据来看已经能满足单个数据中心的存储需求了（比如 Tectonic 论文中披露的数据是总共存储了约 100 亿文件，阿里云的一个数据中心里也存储了约 100 亿文件）。InfiniFS 在不同存储规模下的吞吐表现基本保持在一个恒定的水平，也就是说不管存储多少数据 InfiniFS 都能提供稳定的性能保障。</p>
<p>InfiniFS 的编程语言未知，目前也没有开源（大概率应该不会开源），是否已经应用在阿里云上也未知。另外「华中大高性能体系结构与系统实验室」完整翻译了本篇论文，可以点击<a href="https://haslab.org/2022/04/06/InfiniFS.html" target="_blank" rel="noopener noreferrer">这里</a>查看。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="decentralized-social-networks">Decentralized Social Networks<a href="https://maybe.news/issues/13#decentralized-social-networks" class="hash-link" aria-label="Direct link to Decentralized Social Networks" title="Direct link to Decentralized Social Networks">​</a></h2>
<p><a href="https://medium.com/decentralized-web/decentralized-social-networks-e5a7a2603f53" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>这篇文章来自 <a href="https://www.jaygraber.com/" target="_blank" rel="noopener noreferrer">Jay Graber</a>，你可能对于这个名字比较陌生，但你有可能听过最初由 Twitter 公司发起的去中心化社交媒体项目 <a href="https://blueskyweb.org/" target="_blank" rel="noopener noreferrer">Bluesky</a>，而 Jay Graber 目前是 Bluesky 项目的总负责人。不过本期并不是要介绍 Bluesky，关于这个项目以后可以慢慢聊。这篇文章也并不是要聊区块链，当下有一个不好的风气，就是一旦提到「去中心化」似乎就必须和区块链扯上点什么关系，但是如果你纵观计算机的发展历史，去中心化是一个非常古老的概念，区块链只能说是去中心化的一种形态，并不是唯一的实现方式。</p>
<p>本文发表于 2020 年年初，聚焦在介绍有别于中心化社交网络的两种新形态：联邦（federated）网络和点对点（P2P）网络，以及基于这两种网络的社交产品。所谓「联邦网络」就是用户自由选择接入一个服务器，他可以通过这个服务器以一种「标准开放的协议」阅读和发布内容，而接入服务器同样以这个协议与这个网络中的其它服务器通信，进而传播不同用户产生的内容。与中心化网络的区别是，联邦网络的服务器并不是由某一个公司掌控，所有处于这个网络中的节点都可能分布在大大小小不同的组织中，这里的「组织」可能是某个人也可能是某个公司。联邦网络的一个实际例子就是 Email，Email 的收发协议是标准且公开的（IMAP、POP3、SMTP 等），任何邮件服务提供商和邮件客户端都需要依照这些协议来实现，因此用户可以选择任何客户端与邮件服务商通信。但是联邦网络的弊端是用户很难甚至无法在不同服务商之间迁移，想象一下把你的 Gmail 帐号迁移到其它地方的成本会有多大。</p>
<p>另一个更加松散的网络就是「P2P 网络」，在这个网络中没有服务端和客户端的区别，任何一个节点都可能同时具有这两种角色，这给予用户最大程度的控制权，不论是数据还是帐号，因此用户切换「服务商」（准确说在 P2P 网络中用户自己就是服务商）的成本是很低的。P2P 网络的弊端也很明显，换取松散自由以及完全的掌控权的代价是每一个用户都需要成为联邦网络或者中心化网络中的那个服务商，这意味着用户不仅要付出资源（CPU、内存、磁盘等），还需要自己来维护网络，保证这个网络的稳定性和一致性，甚至要以牺牲效率为前提。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="鹈鹕-hits-043--回顾兵马司杨海崧觉得最可惜的是什么">鹈鹕 Hits 043 | 回顾兵马司，杨海崧觉得最可惜的是什么？<a href="https://maybe.news/issues/13#%E9%B9%88%E9%B9%95-hits-043--%E5%9B%9E%E9%A1%BE%E5%85%B5%E9%A9%AC%E5%8F%B8%E6%9D%A8%E6%B5%B7%E5%B4%A7%E8%A7%89%E5%BE%97%E6%9C%80%E5%8F%AF%E6%83%9C%E7%9A%84%E6%98%AF%E4%BB%80%E4%B9%88" class="hash-link" aria-label="Direct link to 鹈鹕 Hits 043 | 回顾兵马司，杨海崧觉得最可惜的是什么？" title="Direct link to 鹈鹕 Hits 043 | 回顾兵马司，杨海崧觉得最可惜的是什么？">​</a></h2>
<p><a href="https://music.163.com/#/program?id=2495620910" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>在 Maybe News 的<a href="https://maybe.news/about" target="_blank" rel="noopener noreferrer">「关于」</a>中已经介绍过这个项目的名字由来，而标题中的「杨海崧」则是现在兵马司厂牌的主理人。如果你关注中国近代的摇滚乐，即便没有听过杨海崧也可能听过 <a href="https://zh.wikipedia.org/wiki/PK14" target="_blank" rel="noopener noreferrer">P.K. 14</a> 这个乐队（喜不喜欢另说）。P.K. 14 的乐队成员（包括历史成员）对于中国摇滚乐（特别是独立摇滚）的发展有着无法忽视的影响，比如曾经的鼓手华东目前是乐队「重塑雕像的权利」的核心，现任吉他手许波是豆瓣的 5 号员工（豆瓣音乐人、豆瓣 FM、阿比鹿音乐奖都与他有关）、曾经的大福唱片主理人、现在的<a href="https://merrierecord.bandcamp.com/" target="_blank" rel="noopener noreferrer">美丽唱片</a>主理人。</p>
<p>稍微扯远了，本期介绍的是由鹈鹕 Hits 采访杨海菘的一期播客，除了 P.K. 14 乐队成员这个身份，杨海菘日常还会作为制作人给很多乐队（以兵马司厂牌的为主）制作专辑。因此这期播客首先聊了聊杨海菘最近为这些乐队制作专辑的感想和背后的故事，在聊天的过程中穿插着介绍了几个有意思的新乐队，比如<a href="https://music.163.com/#/artist?id=50522347" target="_blank" rel="noopener noreferrer">今日出品</a>、<a href="https://music.163.com/#/artist?id=12258239" target="_blank" rel="noopener noreferrer">暴力香槟</a>。也聊到了杨海菘自己的最新分支项目<a href="https://music.163.com/#/artist?id=49486157" target="_blank" rel="noopener noreferrer">弗拉基米尔</a>，你在这个神奇乐队的介绍里根本看不出乐队成员具体有谁，但是听了这期播客就能知道（其实都是「老朋友」）。最后推荐一张专辑<a href="https://music.163.com/#/album?id=36406" target="_blank" rel="noopener noreferrer">《Mind Shop》</a>，来自已经解散多年的上海乐队 Muscle Snog。</p>]]></content>
        <category label="issue" term="issue"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[SP #0]]></title>
        <id>https://maybe.news/issues/sp-0</id>
        <link href="https://maybe.news/issues/sp-0"/>
        <updated>2022-05-19T13:50:00.000Z</updated>
        <summary type="html"><![CDATA[这是一期特别的 Maybe News，本来第 13 期已经准备了大半，后来临时决定推迟发布，用一整期特别篇来记录发生在当下的一系列特别的事件。以后是否还会有类似的特别篇我也不知道，但希望届时发生的事情能给予人们力量与希望。]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>这是一期特别的 Maybe News，本来第 13 期已经准备了大半，后来临时决定推迟发布，用一整期特别篇来记录发生在当下的一系列特别的事件。以后是否还会有类似的特别篇我也不知道，但希望届时发生的事情能给予人们力量与希望。</p>
</blockquote>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="我们向封控区的人们收集了解封后的-100-个愿望">我们向封控区的人们收集了解封后的 100 个愿望<a href="https://maybe.news/issues/sp-0#%E6%88%91%E4%BB%AC%E5%90%91%E5%B0%81%E6%8E%A7%E5%8C%BA%E7%9A%84%E4%BA%BA%E4%BB%AC%E6%94%B6%E9%9B%86%E4%BA%86%E8%A7%A3%E5%B0%81%E5%90%8E%E7%9A%84-100-%E4%B8%AA%E6%84%BF%E6%9C%9B" class="hash-link" aria-label="Direct link to 我们向封控区的人们收集了解封后的 100 个愿望" title="Direct link to 我们向封控区的人们收集了解封后的 100 个愿望">​</a></h2>
<p><a href="https://mp.weixin.qq.com/s/ksFU-js4r2-VErQbCSSe6Q" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>这篇文章来自<a href="https://www.latepost.com/" target="_blank" rel="noopener noreferrer">「晚点」</a>，在正式介绍文章内容之前我想要把时间线先拉回到 2020 年 1 月的春节。</p>
<p>2020 年 1 月 23 日 10 时起，武汉市政府正式宣布全市城市公交、地铁、轮渡、长途客运暂停运营，机场、火车站离汉通道也暂时关闭，也就是俗称的「封城」。1 月 23 日这天是除夕的前一天，大部分人可能还在准备回老家或者正在回老家的路上，并且可能还没有意识到武汉封城这件事对所有人的影响是什么。但很快大家就通过各种渠道觉察到这个所谓的「新型冠状病毒」不同于普通的流行病，从湖北开始，全国每一个省每一个市逐渐「沦陷」。2020 年的春节让所有人人心惶惶，不敢出门，买不到口罩，也不敢去亲戚家串门。每天从各种社交媒体上看着一个又一个新闻，人们迫切地想要知道当下正在发生什么，但在知道了以后焦虑感反而愈加严重。当一个人在短时间内输入了大量信息，且都是偏负面的信息时，这种疲惫感和焦虑感让人不再想去思考任何其它事情。于是 2020 年的春节假期变得异常沉重，匆忙过完了假期回到上海，发现家附近的盒马已经买不到新鲜的蔬菜和肉，方便食品也已经售罄，只有各种冷冻肉制品还能买到。怀着必须得囤点什么的想法，采购了不少东西，这可能是我那段时间在盒马买过最多东西的一次经历。幸运的是后来借助于各种买菜应用还是买到了新鲜的蔬菜和肉，现在想来其实在 2020 年疫情最严重的时期，大部分地区的基本生活都还能得到保障。</p>
<p>时间线再回到 2022 年，两年以后的今天我们已经有了各种各样的<a href="https://en.wikipedia.org/wiki/COVID-19_vaccine" target="_blank" rel="noopener noreferrer">新冠病毒疫苗</a>。根据<a href="http://www.nhc.gov.cn/xcs/s3574/202205/3e9cae4a4a6b4c03b74fb58b50871e47.shtml" target="_blank" rel="noopener noreferrer">中国卫健委的数据</a>，截至 2022 年 5 月 12 日，全国累计报告接种新冠疫苗 33 亿 5857.6 万剂次，接种总人数达到 12 亿 8719.5 万，已完成全程接种 12 亿 5259.2 万人，覆盖人数和全程接种人数分别占全国总人口的 91.3%、88.85%。而根据 <a href="https://ourworldindata.org/coronavirus" target="_blank" rel="noopener noreferrer">Our World in Data</a> 整理的数据，截至 2022 年 5 月 18 日，全球已经有超过 51 亿人接种了新冠疫苗，约占全球总人口的 65.65%。核酸检测已经变得稀松平常，除了官方提供的集中检测点，居民在家自测也非常简单，同时也能很方便地查询核酸检测的历史记录。从最开始的健康码，到现在的场所码、行程卡，全国以一种统一标准的方式实现了每个人健康状态的查询和行动轨迹追踪，当发现新的确诊病例时，政府也会第一时间发布这些病例的完整行动轨迹。</p>
<p>种种数据都似乎在表明人类离完全战胜新冠病毒已经不远了，或许在有生之年我们还有继续出国看看的机会。但是自今年 3 月开始的上海封城（现在的官方表述是「全域静态管理」）事件似乎又让人们的心情回到了 2020 年的春节，每个人（包括居住在上海的人）的心里都有非常多的疑问和不解，为什么在有了这么多经验和基础设施以后，上海这样的一线城市还会出现如此多的「混乱」？作为一个曾经在上海生活过的人，我试图以一种客观的态度想要去探寻整个事件背后的缘由和所谓真相的还原，但最终发现作为一个个体，不论身处围城中还是围城外，很多事情的「真相」我们可能永远也不会知道，永远也无法还原。所有人就像在玩<a href="https://en.wikipedia.org/wiki/Souls_(series)" target="_blank" rel="noopener noreferrer">魂系游戏</a>，获取到的只是信息的碎片，当你不断收集这些碎片（还得过滤虚假和无效的信息）并试图去拼凑出一个完整的故事时，你会发现故事依然是不完整的。游戏创造者刻意的「留白」是为了让每个玩家都能够讲述一个属于自己的故事，而现实世界的「留白」又是为了什么呢？或许在很多年以后我们可以通过某种形式回顾今天发生的一切，就像 1969 年夏天那个被刻意遗忘的 Harlem Cultural Festival（哈莱姆音乐节）在 52 年后的 2021 年依然可以通过<a href="https://movie.douban.com/subject/35288813" target="_blank" rel="noopener noreferrer">一个纪录片</a>让所有人铭记。我相信真正的历史永远不会被遗忘，终有一天它会得到应有的关注。</p>
<p>晚点的这篇文章是我近期看过的众多文章之一，之所以选择它作为标题，一方面我也很好奇解封的那一天人们都想做些什么，更重要的是在这个强调集体主义的国度，单个个体真实的情感表达在此时显得弥足珍贵。这里面有社区工作人员，有大学生，有社区志愿者，有生活在中国的老外，有团购团长，有出租车司机，有宝宝刚出生一个月的新手妈妈，有互联网从业者，有保洁主管，有货车司机，有 84 岁的老人，有叮咚买菜配送员，有服装店老板，有刚从方舱回家的人，有即将参加高考的高三学生，有残障人士，有律师，有投资人，有三甲医院的医生。每个人的故事这里不会一一列举，仅摘录几个令我触动的片段：</p>
<p><strong>宝宝刚出生一个月的新手妈妈</strong>：</p>
<blockquote>
<p>原本她是一个不爱囤货的人，封控前想着“不过闭关 5 天而已，家里库存肯定够用”。谁知道计划赶不上变化。宝贝喝的是纯进口奶粉，封控前就是紧俏产品。眼看奶粉就要见底，她急得焦头烂额，到处寻找同城拥有同款奶粉、愿意匀出一两罐的宝妈救急。虽然说纸尿裤和药品可以跑腿采购，但高额的跑腿费也令她咂舌。</p>
<p>在目前上海人民币兑人民币 2.5:1 的时间段、在靠蔬菜盲盒及不可挑选的团购套餐过活的日子里，网络货品价格之合理、品类之丰富无疑是非常诱人的。</p>
<p>解封那一天，她想赶紧备好宝贝的刚需产品：奶粉，纸尿裤，常用药品。</p>
</blockquote>
<p><strong>团购团长</strong>：</p>
<blockquote>
<p>杨女士决定当团长不是为了别的，是她自己没有面粉和大米了，怎么也买不到，只好承担了这个角色。28 岁的杨女士是上海某培训机构的职员，她想说：当团长比上班还累。</p>
<p>做团长流程繁琐。需要甄别并联系供货商、联系货车并确认报价、做团购链接、回答群里的问题、做表格、跟团购人同步信息、货抵达后再找人分拣、核对数量、跟踪派送（经常会有人偷东西）。</p>
<p>封控让她感悟到“比疫情更可怕的是人心”。但也看到人性的温暖。很多邻居会帮不会团购的老人购买物资。解封后，她最想做的三件事是：去外面的餐厅吃个饭；找个街边的小酒吧，吹吹风喝点酒，看看路人和街道；找个阳光好的日子，去草坪上晒晒太阳，静静地什么也不做地感受下大自然。</p>
</blockquote>
<p><strong>出租车司机</strong>：</p>
<blockquote>
<p>因为跑出租，他每天早出晚归，和邻居没有接触。现在上海物价很贵，他舍不得买高价菜，20 多天几乎没吃过菜，每天只吃一顿煮面条，吃不饱就睡觉，有时睡到中午、下午，“睡着了不会感觉很饿。”</p>
<p>网络发达，黄林华总能在抖音上看到很多人领到了油、大米、肉，但他什么都没有，更感受到上海对他的冷漠。封控大约半个月后，街道第一次给他发了物资：十个鸡蛋、两个西红柿、两根黄瓜、两颗娃娃菜。</p>
<p>解封后，他最想做的三件事是：开出租车挣点钱，还上前段时间母亲住院，自己问亲戚借的钱；吃一次大米和红烧肉，但是买回来自己做，外面太贵了；等手里有差不多一万块钱就回老家，这辈子再也不来上海。</p>
</blockquote>
<p><strong>84 岁的老人</strong>：</p>
<blockquote>
<p>陈老师今年 84 岁。十年前，他和妻子从上海浦西的老弄堂搬到浦东的一栋动迁安置房，住上 24 层的高楼，如今已被挂上了蓝色的“封控楼”牌子。陈老师在家里被封控了 38 天。</p>
<p>社区发放的蔬菜包不是专门为他这样的高龄老人准备的。他收到过“人脚那么长的莴笋”、几包餐巾纸、两斤面粉和小苏打饼干。陈老师和妻子的牙齿都不好，烧饭前，他要先挑出能咬动的，再拣出没变质的。</p>
<p>陈老师有点现在年轻人常说的丧气，此前的人生“是随大流惯了的”。封控期间食物不合口味，他就想，红军万里长征也吃野菜。4 月 23 日，浦东全部封控楼开始“硬隔离”。赶在小区装栅栏的此前一天，孙子托朋友给他送去了 60 个鸡蛋、4 箱牛奶、一些麦片和包子，陈老师分了一些给楼下同样高龄的邻居。对于眼下的情况，他很平静。就算是自然灾害，也总会过去——那是他二十岁出头时发生过的事。</p>
<p>解封之后想去哪里？陈老师不响。再问，陈老师说，没有什么地方要去。</p>
</blockquote>
<p><strong>叮咚买菜配送员</strong>：</p>
<blockquote>
<p>3 月 27 日，浦东宣布全域静态管理后，站点订单量翻了两三倍，他从早上六点一直工作到晚上十一点后，最多一天送了 216 单。回到仓里还要开总结会，再帮同事一起理货，经常忙到两三点，睡三四个小时，又该工作了。</p>
<p>最难的一次是一个阿姨找到他，抢到的菜希望转送给在徐汇的姑姑，老人快八十岁，不会用智能手机，断粮两天。那天他前后跑了三趟，想着趁志愿者换班送进去，都被社区和小区拦下来，那个小区有病例，不能接收政府发放以外的物资。他拿出核酸结果，证明自己是健康的，跟工作人员讲到流泪，“老人家饿两天要出事的啊。”还是没能送进去。</p>
<p>解封后，他想给孩子买以前舍不得买的 1500 块的乐高火箭，“寄顺丰最贵的那种空运。” 给父母买个按摩椅，父亲做了大半辈子的煤矿工人，患风湿，有个按摩应该会舒服点。再给媳妇买新衣服，在家带孩子照顾父母辛苦了。</p>
<p>他自己倒是没什么想买的，也没什么想吃的，只想睡个饱觉，不用设闹钟睡到自然醒。他想早点挣够钱回老家，这场疫情不知道什么时候能结束，他觉得自己最该做的事是陪在家人身边。</p>
</blockquote>
<p>在一个群体性事件中，人性的善与恶被展现得淋漓尽致。几个月以后人们的生活会被新的信息充斥，逐渐遗忘曾经发生的一切，直到某一天某个人或者某些人拂去历史的尘埃，试图去提醒这个世界曾经有这样一件事情存在过。</p>
<p>P.S. 2022 年 5 月 17 日上海市政府<a href="https://mp.weixin.qq.com/s/9gRo_PWkDKwHJyOkhxj2jg" target="_blank" rel="noopener noreferrer">宣布</a>全市 16 个区都已实现社会面清零</p>
<p>P.P.S. 2022 年 5 月 29 日上海市政府公布了<a href="https://mp.weixin.qq.com/s/dxBB9jvk_yckTs29PVB1Tw" target="_blank" rel="noopener noreferrer">《上海市加快经济恢复和重振行动方案》</a></p>
<p>P.P.P.S. 2022 年 5 月 31 日上海市政府<a href="https://mp.weixin.qq.com/s/4dZErfcWAFp6V5bdAfajbg" target="_blank" rel="noopener noreferrer">宣布</a>自 6 月 1 日起，进入全面恢复全市正常生产生活秩序阶段，全面实施疫情防控常态化管理。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="延伸阅读聆听观看">延伸阅读、聆听、观看<a href="https://maybe.news/issues/sp-0#%E5%BB%B6%E4%BC%B8%E9%98%85%E8%AF%BB%E8%81%86%E5%90%AC%E8%A7%82%E7%9C%8B" class="hash-link" aria-label="Direct link to 延伸阅读、聆听、观看" title="Direct link to 延伸阅读、聆听、观看">​</a></h2>
<ul>
<li><a href="https://www.reddit.com/r/China_irl/comments/tz4bct/%E4%B8%8A%E6%B5%B7%E7%96%AB%E6%83%85%E4%B8%8B%E7%9A%84%E4%BF%A1%E6%81%AF%E5%92%8C%E4%BA%92%E5%8A%A9%E6%95%B4%E7%90%86/" target="_blank" rel="noopener noreferrer">Reddit：上海疫情下的信息和互助整理</a></li>
<li><a href="https://mp.weixin.qq.com/s/myNgBMgideMQiaJGOpSyBA" target="_blank" rel="noopener noreferrer">Leona Cheng：我在方舱医院的真实经历</a></li>
<li><a href="https://www.nytimes.com/zh/interactive/2022/05/04/world/asia/shanghai-lockdown-chinese.html" target="_blank" rel="noopener noreferrer">纽约时报中文网：封城下的上海</a></li>
<li><a href="https://music.163.com/#/program?id=2502966569" target="_blank" rel="noopener noreferrer">西海之声：如果能理解它，我就不能理解人性</a></li>
<li><a href="https://music.163.com/#/program?id=2499103534" target="_blank" rel="noopener noreferrer">西海之声：如何装作一切如常</a></li>
<li><a href="https://music.163.com/#/program?id=2502027686" target="_blank" rel="noopener noreferrer">别的电波：此时此刻，只是想和上海的朋友聊聊天</a></li>
<li><a href="https://music.163.com/#/program?id=2502996453" target="_blank" rel="noopener noreferrer">大内密谈：特殊时期的中年男人隔空悲歌</a></li>
<li><a href="https://music.163.com/#/song?id=28577827" target="_blank" rel="noopener noreferrer">顶楼的马戏团：海风</a></li>
<li><a href="https://music.163.com/#/song?id=21598238" target="_blank" rel="noopener noreferrer">Simon &amp; Garfunkel：The Sound of Silence</a></li>
<li><a href="https://www.youtube.com/watch?v=mBdOXwdBn5s&amp;ab_channel=404%E8%B5%84%E6%96%99%E9%A6%86" target="_blank" rel="noopener noreferrer">YouTube：四月之声</a></li>
</ul>]]></content>
        <category label="sp" term="sp"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Issue #12]]></title>
        <id>https://maybe.news/issues/12</id>
        <link href="https://maybe.news/issues/12"/>
        <updated>2021-10-15T13:00:00.000Z</updated>
        <summary type="html"><![CDATA[本期关键词：Paxos、InfluxDB IOx、16bit 音乐]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>本期关键词：Paxos、InfluxDB IOx、16bit 音乐</p>
</blockquote>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="paxos-made-live---an-engineering-perspective">Paxos Made Live - An Engineering Perspective<a href="https://maybe.news/issues/12#paxos-made-live---an-engineering-perspective" class="hash-link" aria-label="Direct link to Paxos Made Live - An Engineering Perspective" title="Direct link to Paxos Made Live - An Engineering Perspective">​</a></h2>
<p><a href="https://research.google/pubs/pub33002" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>在<a href="https://maybe.news/issues/2">第 2 期</a>曾经介绍过 Raft 算法，作为目前可能最成功、影响最深远的共识算法，Paxos 是一个不可能绕开的话题。Paxos 的作者是大名鼎鼎的 <a href="https://en.wikipedia.org/wiki/Leslie_Lamport" target="_blank" rel="noopener noreferrer">Leslie Lamport</a>，大神目前在微软研究院工作。Paxos 的诞生起源于 <a href="https://lamport.azurewebsites.net/pubs/lamport-paxos.pdf" target="_blank" rel="noopener noreferrer">The Part-Time Parliament</a> 这篇论文，虽然论文是 1998 年发表，但是早在 1990 年就已经提交过，关于这段<a href="https://lamport.azurewebsites.net/pubs/pubs.html#lamport-paxos" target="_blank" rel="noopener noreferrer">有趣的历史</a>可以从 Leslie 的个人网站上了解。</p>
<p>因为 The Part-Time Parliament 过于晦涩，2001 年 Leslie 又发表了 <a href="https://lamport.azurewebsites.net/pubs/paxos-simple.pdf" target="_blank" rel="noopener noreferrer">Paxos Made Simple</a>，尝试用更直白的叙述来解释什么是 Paxos。但如果你是第一次了解 Paxos，这篇论文其实也不是很好理解。本期并不是零基础 Paxos 入门指南（还没有这个资格），但会尝试让你尽量多地了解 Paxos 以及它的众多衍生者。如果你只是想在工程中实际使用一个共识算法，建议直接了解 Raft。</p>
<p>这篇 Paxos Made Live 来自 Google，2007 年发表，正如副标题所说，是一篇从工程实现角度来介绍 Paxos 的论文。Leslie 的论文更专注在从数学角度进行理论推理和论证，而如何在工程上实现 Paxos 并没有细讲（关于这一点 Raft 的论文中也有所提及）。因此本篇论文就显得格外珍贵，可以帮助有兴趣实现 Paxos 算法的人了解背后的「艰辛」。</p>
<p>为什么 Google 要自己实现 Paxos 算法还得追溯到 GFS（<a href="https://maybe.news/issues/8">第 8 期</a>曾经介绍过）和 Bigtable 的设计，这些分布式系统的实现都依赖分布式锁这个特性，因此 Google 的工程师们设计了 <a href="https://www.usenix.org/legacy/events/osdi06/tech/burrows.html" target="_blank" rel="noopener noreferrer">Chubby</a>。Chubby 也是一个分布式系统，主要作用就是提供分布式锁功能以及存储少量的元数据。正如 Google 的很多系统，Chubby 的设计也影响了后续很多开源项目（如 ZooKeeper、etcd），以后可以再详细介绍。</p>
<p>第一版 Chubby 是基于某个第三方的商用容错数据库实现的，论文中称之为 3DB（感觉是在影射 Oracle 的 DB2）。这个数据库有很多与数据复制有关的历史 bug，它的复制机制也不是基于一个经过严格论证的复制算法实现。因此 Google 的工程师们才决定基于 Paxos 来重构 Chubby。</p>
<p>Chubby 的架构很简单，所有数据复制都会通过最底层的容错日志复制来实现，而如何复制这些容错日志即是 Paxos 算法要完成的。本篇论文简单介绍了完成一次 Paxos 算法（Paxos 的术语是一个「instance」）的流程：</p>
<ol>
<li>从所有节点中选举一个 coordinator（在 Paxos 中这个过程中涉及的请求被称作「Prepare/Propose」及「Promise」）；</li>
<li>Coordinator 选择一个值并通过一个消息广播给所有节点，这个消息（或者请求）被称作「Accept」，其它节点可以选择确认（acknowledge）或者拒绝（reject）；</li>
<li>一旦大多数节点确认了这个消息，那么就可以认为一致已经达成，coordinator 会再广播一个「Commit」消息给所有节点。</li>
</ol>
<p>严格意义上来说上面的这段描述并不完全匹配 Paxos 原始论文的步骤和术语，但大体上是一致的。是不是看起来很像 Raft？Paxos 和 Raft 最大的不同其实在于并不存在一个中心的 leader（注意 coordinator 并不等价于 leader），上面这个流程是任意一个节点在任何时候都可以发起的（发起者被称作「Proposer」），但是每一轮流程都会有一个唯一的「编号」，Paxos 通过限定这个编号是有序的来保证数据的一致性。去中心化的一个好处是没有单点问题，缺点也很明显，每次数据更新都要完整重复整个流程，写性能是很差的。</p>
<p>因此有了「Multi-Paxos」这个优化方案，注意它和<a href="https://maybe.news/issues/2">第 2 期</a>介绍过的 Multi-Raft 没有任何关联。各种资料显示 Multi-Paxos 这个概念似乎最早出自「Paxos Made Simple」这篇论文，但你在其中并搜不到 Multi-Paxos 这个词汇。Multi-Paxos 的核心思想是如果一段时间内 coordinator 是不变的，那么其实可以省略第一阶段的「Prepare/Propose」消息，直接进入第二阶段的「Accept」，大大减少需要通信的请求量以及处理时延。那要如何保证一段时间内 coordinator 不变呢？熟悉 Raft 的同学可能要说了那就先选举一个 leader，只要 leader 存活就代表 coordinator 不变。Paxos 的做法是并不需要真的「选举」一个 leader 出来，前面也介绍过了，Paxos 并不存在一个真正意义上的中心化的 leader，coordinator 是可以随时被改变的。因此要做的就是除 coordinator 以外的其它节点在一段时间内「拒绝」新的 proposer 发起的「Prepare/Propose」请求，这个「固定」的 coordinator 就这样无形地被产生出来了，并不需要刻意去「选举」。</p>
<p>Multi-Paxos 只是实践 Paxos 算法中的一个挑战，本篇论文还列举了其它一些。</p>
<p>例如<strong>该如何检测和应对磁盘损坏</strong>。因为 Paxos 的状态需要持久化到磁盘上，当磁盘损坏时有可能出现两种现象：文件内容产生了变动，或者文件无法访问。为了应对第一种现象，Chubby 保存了每个文件的 checksum，因此当文件内容有变动时可以及时发现。对于后者，因为无法区分文件无法访问是因为磁盘损坏还是因为这是一个新的副本节点（此时磁盘是空的），Chubby 选择当新的副本节点正常启动以后在 GFS 上保存一个标记符（marker），因此当出现文件无法访问且标记符已经存在，就表示一定是因为磁盘损坏导致。为了自动修复破损的文件，节点会作为一个「非投票成员（non-voting member）」加入 Paxos（Raft 也有这个概念），在追赶上完整数据之前不会处理「Prepare/Propose」以及「Accept」请求。</p>
<p>再比如<strong>该如何优化读请求的性能</strong>。为了保证读的强一致性，每次读请求也需要完整走一遍 Paxos 的算法流程，对于读请求占比高的场景开销很大。一种变通的解决方案是「主节点租约（master lease）」。在一段时间内有且仅有一个节点能获得主节点租约（这个节点可以称作 master），其它节点将不能处理请求，因此获得主节点租约的节点将持有最新的数据，它可以直接在本地完成读请求的处理而不用发起一次完整的 Paxos 算法。既然叫做租约那一定有时间限制（timeout），master 会定期续约以保证租约可以长期有效，在实践中 Chubby 系统的一个租约可以最长保持数天。Master 的超时时间比其它节点稍短一些，这是为了防止时钟漂移（clock drift）。以上描述看起来都很像 Raft，只不过在 Raft 中主节点被叫做 leader，前面讲到的 Paxos 没有中心化 leader 的「优点」似乎也不复存在。</p>
<p>讲完了实践 Paxos 的挑战，接下来把关注点放在如何实现一个健壮（robust）的算法。Paxos 的核心算法看起来很「简单」，可能用很少的代码就能实现，但是如何确保实现的算法是可靠的呢？Google 的一些经验值得借鉴和学习。</p>
<p>因为容错算法很难准确表达，即使是用伪代码也是如此。因此 Google 的工程师们设计了一种状态机描述语言（specification language），通过这个描述语言构建了两个状态机，最终用于描述 Paxos 的核心算法。同时有一个特定的编译器来将这个描述语言翻译成 C++ 代码，翻译的过程中还能自动生成一些日志以及辅助调试和测试的逻辑。有了这个描述语言，1 个屏幕就能完整展示 Paxos 的核心算法，可见语言足够精简。为什么要如此折腾搞这么一套东西呢？Google 的工程师们相信当需要对核心算法做调整时这种方式能更有效率，并举了一个实际的例子，他们曾经需要将成员关系（group membership）的状态机从 3 个状态改成 2 个，仅仅用了 1 个小时就完成了逻辑变更，以及用了 3 天来完成测试调整。如果没有这套东西，整个核心算法实现是跟其它逻辑混杂在一起的，可能就没有这么快完成了。</p>
<p>验证一个系统是否可靠的另一个好方法就是「测试」。Chubby 从设计之初就确保整个系统是可测试的（testable），所有测试都有两种运行模式：安全模式（safety mode）和在线模式（liveness mode），当运行在安全模式时允许系统不可用，而在线模式不允许。每个测试都会先运行在安全模式并随机注入一段时间的故障，然后等待系统自动恢复，再切换到在线模式去验证系统是否已经处于正常状态。注入的故障类似于：网络中断、消息延迟、超时、进程崩溃、文件损坏、让某个节点宕机、让某个节点与其它节点断开一段时间的连接、让一个节点假装它不再是主节点等等。这个测试方法跟现在很火的<a href="https://en.wikipedia.org/wiki/Chaos_engineering" target="_blank" rel="noopener noreferrer">「混沌工程」</a>非常类似，但是比 Netflix <a href="https://netflixtechblog.com/the-netflix-simian-army-16e57fbab116" target="_blank" rel="noopener noreferrer">提出</a>整整早了 4 年。</p>
<p>论文的最后指出了目前容错算法领域的一些「问题」，比如 Paxos 理论知识和工程实践中的巨大鸿沟、没有合适的工具来帮助实现一个容错算法、对测试没有足够的重视。相比之下编译器领域就好很多，虽然编译器的理论知识也很复杂，但是已经积累了众多工具来帮助工程师进行日常开发（例如 Yacc、ANTLR、Coco/R）。众人拾柴火焰高，前人的经验不断通过各种工具沉淀下来，后来者的开发效率自然会提高不少，也能避免很多已知的错误。</p>
<p>时至今日，不管是 Paxos 还是 Raft 在工程上都有了各种各样的实践，实现一个容错算法已经不再像以前一样困难重重或者遥不可及，CS 专业的分布式系统课程也开始教授各种容错算法，变成了一种专业基础知识。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="paxos-理论介绍">Paxos 理论介绍<a href="https://maybe.news/issues/12#paxos-%E7%90%86%E8%AE%BA%E4%BB%8B%E7%BB%8D" class="hash-link" aria-label="Direct link to Paxos 理论介绍" title="Direct link to Paxos 理论介绍">​</a></h2>
<p><a href="https://mp.weixin.qq.com/s/eeJXS5rBA9mXpSJaTNjF-Q" target="_blank" rel="noopener noreferrer">[一]</a> <a href="https://mp.weixin.qq.com/s/UO-4ycfleNkE-flsDpGdIA" target="_blank" rel="noopener noreferrer">[二]</a> <a href="https://mp.weixin.qq.com/s/DGFWZPlnE6r_bV2ALgts-A" target="_blank" rel="noopener noreferrer">[三]</a> <a href="https://mp.weixin.qq.com/s/JdkOAHxOvHiWgusfe5ISBg" target="_blank" rel="noopener noreferrer">[四]</a></p>
<p>这是一个介绍 Paxos 的系列文章，发表在「微信后台团队」的公众号，文章发布时间是 2016 年，如果你回看那段时间这个公众号的文章，会发现微信刚刚开源了 <a href="https://github.com/Tencent/phxpaxos" target="_blank" rel="noopener noreferrer">PhxPaxos</a> 库。这是一个由微信后台团队自研的 Paxos C++ 库，之后开源的 MySQL 集群 <a href="https://github.com/Tencent/phxsql" target="_blank" rel="noopener noreferrer">PhxSQL</a> 即是基于这个库开发（微信开源了一系列以 Phx 命名的项目）。这一系列的文章即包含了 Paxos 的基础理论介绍（可以作为 Paxos Made Simple 的补充），也类似前面介绍的 Paxos Made Live 一样包含微信团队在 Paxos 工程实践中的宝贵经验。作为中文领域介绍 Paxos 且来自一线工程师的内容，值得一读。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="paxosmon-gotta-consensus-them-all">Paxosmon: Gotta Consensus Them All<a href="https://maybe.news/issues/12#paxosmon-gotta-consensus-them-all" class="hash-link" aria-label="Direct link to Paxosmon: Gotta Consensus Them All" title="Direct link to Paxosmon: Gotta Consensus Them All">​</a></h2>
<p><a href="https://vadosware.io/post/paxosmon-gotta-concensus-them-all" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>Paxos 起源于 The Part-Time Parliament，但并没有止于此。在 Paxos 诞生后的近 30 年间，众多学术界的研究人员围绕着 Paxos 进行了各种理论和实践上的探索，这篇文章按照时间线梳理了各种 Paxos 算法的「变种」，并总结了每种算法的优缺点。这其中包含如 Multi-Paxos、FastPaxos、Multicoordinated Paxos、SPaxos、Ring Paxos、Flexible Paxos（<a href="https://maybe.news/issues/10">第 10 期</a>也介绍过）等等各种你可能从来没听过的 Paxos 算法，虽然这其中的每个算法不一定都能直接应用在工程中，但对于了解 Paxos 算法家族的发展历史来说很有意义。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="announcing-influxdb-iox-the-future-of-influxdb-built-with-rust--arrow">Announcing InfluxDB IOx: The Future of InfluxDB Built with Rust &amp; Arrow<a href="https://maybe.news/issues/12#announcing-influxdb-iox-the-future-of-influxdb-built-with-rust--arrow" class="hash-link" aria-label="Direct link to Announcing InfluxDB IOx: The Future of InfluxDB Built with Rust &amp; Arrow" title="Direct link to Announcing InfluxDB IOx: The Future of InfluxDB Built with Rust &amp; Arrow">​</a></h2>
<p><a href="https://www.influxdata.com/blog/announcing-influxdb-iox" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>每个服务端开发者对于 InfluxDB 应该都不会陌生，作为可能是最流行的时序数据库（TSDB）之一，是每个公司都会拥有的基础设施。这篇文章来自 InfluxDB 创始人 Paul Dix，发表时间是 2020 年 11 月。同样是 11 月，倒回到 7 年前，Paul Dix 首次对外公开了 InfluxDB 项目。InfluxDB 的核心数据模型可以理解为 tag 和 field。Tag 是可索引的字段，即能够用于过滤查询，常见的 tag 如主机名、请求类型等，tag 的限制是不能将某个有大量取值的字段作为一个 tag（比如用户 ID），否则对于 InfluxDB 来说会造成负面影响。Field 是具体的数值，不可索引，也没有类似 tag 的取值限制。随着 InfluxDB 的应用越来越广泛，这个最初的数据模型设计也遇到了挑战，比如分布式 tracing 场景，每一条 trace 信息都会包含一个唯一的 ID，这个 trace ID 也会频繁用于数据查询，但因为 trace ID 的数量级过于庞大，当前的 InfluxDB 设计没法承载这种类型的数据。InfluxDB 诞生至今也没有提供开源版本的分布式实现（在早期版本中曾经有过，但是后来就没有了），分布式存储作为了 InfluxDB 商业版的一个重要特性，之所以这样选择也是为了维持 InfluxDB 开源版本持续开发的「无奈之举」。以上种种因素使得 Paul Dix 开始思考未来的 InfluxDB 会是什么样的呢，这篇文章即是他的思考结果。从文章标题里也能看到，核心关键词是 Rust 和 Apache Arrow（<a href="https://maybe.news/issues/11">上一期</a>介绍过），但 InfluxDB IOx 并不仅限于此。文章中关于开源软件协议的见解也很有意思，InfluxDB IOx 目前已经<a href="https://github.com/influxdata/influxdb_iox" target="_blank" rel="noopener noreferrer">开源</a>并持续迭代中。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="mega-drive-黑色的-16bit-传奇-vol3-16bit-音乐探秘">MEGA DRIVE-黑色的 16bit 传奇 Vol.3 16bit 音乐探秘<a href="https://maybe.news/issues/12#mega-drive-%E9%BB%91%E8%89%B2%E7%9A%84-16bit-%E4%BC%A0%E5%A5%87-vol3-16bit-%E9%9F%B3%E4%B9%90%E6%8E%A2%E7%A7%98" class="hash-link" aria-label="Direct link to MEGA DRIVE-黑色的 16bit 传奇 Vol.3 16bit 音乐探秘" title="Direct link to MEGA DRIVE-黑色的 16bit 传奇 Vol.3 16bit 音乐探秘">​</a></h2>
<p><a href="https://www.gcores.com/radios/140121" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>这是一期播客，来自「竟然还能聊游戏」的机核，嘉宾是我非常喜欢的重轻老师，有兴趣的同学也可以听听他以前在机核的<a href="https://www.gcores.com/users/31418/content?tab=radios" target="_blank" rel="noopener noreferrer">播客节目</a>，推荐聊《西部世界》、吹哥（Jonathan Blow）、乐理的这几期。作为<a href="https://www.gcores.com/radios/26610" target="_blank" rel="noopener noreferrer">上一期</a>聊 8bit 音乐的非正式续集，这一期也是从音乐的角度聊聊我们那些曾经的游戏时光（并没有聊具体的游戏）。如果你和我一样是从红白机时代经历过来的，那一定对于其中列举的各种场景深有感触，每一段作为示例的音乐也是听得热血沸腾。即使你没有经历过早期的游戏机时代，也会惊叹于那个年代音乐创作者（亦或音乐工程师？）的艰辛与才华，特别是 16bit 这一期的音乐，就算放到今天将之列为顶尖音乐的行列也绝不为过。</p>]]></content>
        <category label="issue" term="issue"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Issue #11]]></title>
        <id>https://maybe.news/issues/11</id>
        <link href="https://maybe.news/issues/11"/>
        <updated>2021-07-08T13:10:00.000Z</updated>
        <summary type="html"><![CDATA[本期关键词：Procella、PrestoDB@Facebook、Apache Arrow、S3 Consistency、Feast、Uptown Records]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>本期关键词：Procella、PrestoDB@Facebook、Apache Arrow、S3 Consistency、Feast、Uptown Records</p>
</blockquote>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="procella-unifying-serving-and-analytical-data-at-youtube">Procella: Unifying serving and analytical data at YouTube<a href="https://maybe.news/issues/11#procella-unifying-serving-and-analytical-data-at-youtube" class="hash-link" aria-label="Direct link to Procella: Unifying serving and analytical data at YouTube" title="Direct link to Procella: Unifying serving and analytical data at YouTube">​</a></h2>
<p><a href="https://research.google/pubs/pub48388" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>Procella 是 YouTube 自研的 SQL 查询引擎，旨在用一个引擎满足多种使用场景的需求（有点像<a href="https://maybe.news/issues/10">上一期</a>介绍的 Tectonic），目前已经被广泛应用在 YouTube、Google Play、Firebase 等多个产品。论文发表在 2019 年的 VLDB 会议，<a href="https://maybe.news/issues/4">第 4 期</a>曾经介绍过同一年发表的 AliGraph。</p>
<p>虽说 Procella 自称 SQL 查询引擎，但其实它也包含一些与存储格式有关的技术，并且这个存储格式对于整体系统来说是不可或缺的一部分，关于这部分后面会单独介绍。先来了解一下 Procella 想要满足的几个场景：</p>
<ul>
<li><strong>分析报告和 dashboard</strong>：YouTube 的创作者（现在应该流行叫 YouTuber）需要通过一个实时更新的 dashboard 来了解所有视频的统计信息，评估内容的影响力。这个场景对响应时间有较高要求（数十毫秒），处理的数据量也非常大（每天有千亿行的新增数据）。</li>
<li><strong>实时统计数据</strong>：这里的统计数据和上一个的区别是上一个还只是面向小部分用户，而这里的统计数据是所有用户在访问 YouTube 时都能看到的（比如点赞数、观看次数），因此请求量会更大，对响应时间的要求也更高。通常 OLAP 系统都是偏离线的，为了应对这种在线查询的场景 Procella 做了很多针对性的优化，后面会详细介绍。</li>
<li><strong>监控系统</strong>：这个场景面向的是内部工程团队，虽然数据量相对较小，但是对数据更新的实时性要求很高。常见的监控系统存储都是用 TSDB 来实现，Procella 因此也需要支持一些 TSDB 独有的特性，如自动降采样（downsampling）和过期旧数据。</li>
<li><strong>Ad-hoc 分析</strong>：这也是面向内部团队的场景，ad-hoc 场景的挑战是查询不可预测，可大可小，可快可慢，系统怎么自适应这些千奇百怪的查询是一个难题。</li>
</ul>
<p>从上面的场景介绍也能看出 Procella 不打算支持或者目前暂时不支持的场景，比如 OLTP、ETL、图计算。</p>
<p>Procella 的主要组件有：Root Server（RS）、Data Server（DS）、Metadata Server（MDS）、Ingestion Server（IgS）、Registration Server（RgS）、Compaction Server（CS）。除此之外还依赖一些外部组件：Metadata Store（Bigtable 和 Spanner）、分布式文件系统（Colossus，<a href="https://maybe.news/issues/8">第 8 期</a>曾经介绍过）、容器调度和编排（<a href="https://research.google/pubs/pub43438" target="_blank" rel="noopener noreferrer">Borg</a>）。</p>
<p>RS 是用户提交 SQL 的入口，负责分析、重写、计划和优化执行计划（execution plan）。RS 会和 MDS、Metadata Store 和 DS 直接通信，其中 Metadata Store 保存着表的 schema、表到文件的映射、统计信息、zone map 等元数据，MDS 保存着 zone map、bitmap、bloom filter、分区和排序键等索引信息（这些索引信息一部分在首次注册文件时生成，一部分在查询时生成），DS 负责运行 RS 或者其它 DS 发送的执行计划。不同 DS 之间通信使用 <a href="https://grpc.io/blog/principles" target="_blank" rel="noopener noreferrer">Stubby</a> 这个 RPC 框架（也就是后来开源的 gRPC），同时通过 RDMA 来执行 shuffle 操作（复用了 <a href="https://cloud.google.com/blog/products/bigquery/in-memory-query-execution-in-google-bigquery" target="_blank" rel="noopener noreferrer">BigQuery 的 shuffle 库</a>）。</p>
<p>RgS 负责执行 DDL 命令（如 <code>CREATE</code>、<code>ALTER</code>、 <code>DROP</code>）来管理表，执行结果会保存到 Metadata Store。用户可以在 schema 中指定如列名、数据类型、如何分区和排序、数据接入（data ingestion）方式等信息。Procella 支持两种数据接入方式：批量接入（batch ingestion）和实时接入（realtime ingestion），也就是 <a href="https://en.wikipedia.org/wiki/Lambda_architecture" target="_blank" rel="noopener noreferrer">Lambda 架构</a>，这一点和 <a href="https://druid.apache.org/" target="_blank" rel="noopener noreferrer">Druid</a>、<a href="https://pinot.apache.org/" target="_blank" rel="noopener noreferrer">Pinot</a> 类似。「批量接入」时由外部批处理任务（如 MapReduce）将数据导入并注册到 RgS，RgS 会从文件头（header）中提取相关信息并建立索引，某些没法直接通过文件头创建的索引（如 bloom filter）会在查询时通过 DS 来创建。「实时接入」时用户通过 RPC 或者 PubSub 的方式把数据导入 IgS，当 IgS 接收到数据以后可能会根据表结构转换数据格式，并写入 WAL 到 Colossus（后台会定期压缩）。IgS 同时还会把数据写入 DS，DS 会在内存中临时缓冲这些数据供用户查询，并定期 checkpoint 到 Colossus 以便在故障时恢复数据。IgS 可能也会将数据写入多个 DS，查询执行时会访问所有副本并使用最完整的集合。</p>
<p>CS 会定期压缩（compact）和重新分区（repartition）IgS 写入的 WAL 日志，转换成对于 DS 来说更高效的更大的分区。在 CS 压缩的过程中，可以执行一些由用户定义的逻辑以减少数据大小，这些逻辑包括过滤、聚合、淘汰旧数据、仅保留最新数据等，通过 SQL 来表示。当处理完以后 CS 会与 RgS 通信更新元数据，从元数据中删除旧文件然后插入新生成的文件。</p>
<p>**为了满足不同场景对于性能的要求，Procella 在多个方面进行了优化，包括：缓存、数据格式、评估引擎（evaluation engine）、分区与索引、分布式操作、查询优化器（optimizer）等。**本期不会对所有优化进行介绍，有兴趣的同学请查看原文。</p>
<p>对于存算分离的引擎，缓存是至关重要的，Procella 也不例外。Procella 包含以下几种类型的缓存：</p>
<ul>
<li><strong>Colossus 元数据缓存</strong>：DS 会缓存 Colossus 的文件句柄，这个句柄包含数据块到 Colossus 服务器的映射关系，可以优化打开文件的性能（减少 1 个或多个 RPC 请求）。</li>
<li><strong>文件头（header）缓存</strong>：Procella 的数据格式（后面会详细介绍）是类似 Parquet 这样的列存格式，因此文件头或者尾会包含很多重要的元信息，DS 会在一个单独的 LRU 缓存中保存这些信息，减少与 Colossus 的交互。</li>
<li><strong>数据缓存</strong>：Procella 的数据格式在内存和磁盘上是同样的结构（可能是能直接通过 mmap 映射到内存），因此数据缓存的管理很轻量。同时 DS 还会缓存一些衍生信息，如某些复杂操作的输出、bloom filter。因为 Colossus 的文件一旦关闭就是不可变的（immutable），所以不存在缓存不一致的问题。</li>
<li><strong>元数据缓存</strong>：Procella 的元数据依赖外部的分布式存储（Bigtable 和 Spanner），虽然元数据很容易进行横向扩展，但外部存储也可能成为系统瓶颈，因此 MDS 通过一个 LRU 缓存保存这些信息。</li>
<li><strong>亲和性（affinity）调度</strong>：当执行查询时，会把操作尽量调度到已经缓存了要处理的数据或者元数据的节点，提高缓存的命中率。这个调度策略不是强制的，有可能会调度到其它节点。</li>
</ul>
<p>综合以上所有类型的缓存，Procella 大体上已经变成了一个全内存数据库。在其中一个分析报告的业务场景，虽然内存只能存放 2% 的数据，但是实际表现是文件句柄的缓存命中率达到了 99%+，数据缓存的命中率达到了 90%，基本不依赖外部系统。</p>
<p>第一版 Procella 的数据格式用的是和 Dremel 一样的 <a href="https://cloud.google.com/blog/products/bigquery/inside-capacitor-bigquerys-next-generation-columnar-storage-format" target="_blank" rel="noopener noreferrer">Capacitor</a>，但由于 Capacitor 的设计主要面向大规模扫描场景，没法支持高效查找（lookup），因此 Procella 设计了一种新的数据格式「Artus」。Artus 主要有以下几个创新点：</p>
<ul>
<li><strong>定制的编码方式</strong>：区别于传统列存格式使用的通用压缩方式（如 LZW），Artus 的编码方式可以在不解压数据块的前提下直接查找单行，进而更加适合小规模的点查和范围扫描场景。</li>
<li><strong>多轮自适应编码</strong>：第一轮先扫描数据收集轻量级的统计信息（如去重以后的数量、最小和最大值），第二轮使用这些统计信息选择最优的编码方式。这里「最优」的判断逻辑是基于各种编码方式提供的预估方法（estimation method），这个预估方法能给出处理对应数据的时间和空间，然后根据用户设定的目标函数（objective function）来自动选择。</li>
<li><strong>选择支持二分查找的编码方式</strong>：对于有序的列，查找所需的时间复杂度是 O(logN)。同时在列数据中查找某一行的时间复杂度可以做到 O(1)，因此查找 K 列的时间复杂度是 O(logN + K)。O(1) 是最优的时间复杂度，对于如 RLE（Run-Length Encoding）这样的编码需要每 B 行额外维护一些「跳块」（skip block）信息，实际的时间复杂度其实是 O(B)，但 B 的取值通常很小（如 32、128）。</li>
<li><strong>用一种创新性的方式表示嵌套和重复数据类型</strong>：对于查找嵌套（nested）和重复（repeated）数据类型的数据可以做到 O(1) 的时间复杂度。</li>
<li><strong>把字典索引、RLE 以及其它编码信息直接暴露给评估引擎</strong>：可以把过滤逻辑下推到数据格式，多数场景下都能有不错的性能提升。</li>
<li><strong>在文件头和列头记录丰富的元数据</strong>：这些元数据包括排序信息、最小和最大值、编码信息、bloom filter 等，可以在不读取实际数据的情况下实现剪枝（pruning）操作。</li>
<li><strong>支持存储倒排索引</strong>：Procella 主要在 A/B 实验分析场景用到倒排索引，这些倒排索引存储为 <a href="https://www.roaringbitmap.org/" target="_blank" rel="noopener noreferrer">Roaring bitmaps</a> 格式。Roaring bitmaps 已经被广泛应用于各种大数据引擎，包括 Spark、Druid、Kylin、Doris、ClickHouse 等。</li>
</ul>
<p>在 YouTube Analytics 数据集的实际测试中，Artus 相比 Capacitor 最多能有 140 倍的性能提升，平均也有 50 倍的性能差距。</p>
<p>高性能的评估引擎（evaluation engine）对于低延时请求来说是至关重要的，很多现代分析系统会在查询时通过 LLVM 将执行计划编译为原生代码，以此来加速查询。但是 Procella 因为还要处理高 QPS 的在线服务场景，编译的耗时会成为这个场景的性能瓶颈。因此 Procella 研发了自己的评估引擎「Superluminal（超光速）」，它主要有以下几个特点：</p>
<ul>
<li>广泛使用 C++ 的模板元编程（template metaprogramming）特性在编译时生成代码</li>
<li>以块（block）为单位处理数据，更好地利用向量化（vectorized）计算以及缓存感知（cache-aware）算法。每个块的大小会根据 L1 缓存的大小进行预估。</li>
<li>直接原生操作底层数据编码，并在函数应用期间尽可能保留它。编码信息或者是从文件格式中获取，或者是在执行时生成。</li>
<li>以列式存储的方式处理结构化数据，不会物化（materialized）中间结果。</li>
<li>动态合并过滤器以及下推执行计划，使得每个节点仅仅扫描确切需要的数据。</li>
</ul>
<p>前面提到 Procella 一个重要使用场景就是「实时统计数据」，通常意义上的 OLAP 系统都不太能应对这类实时场景的要求（不管是延时还是 QPS），所以一般会选用 OLTP 或者缓存系统（如 Redis）来解决。这类场景的查询非常简单：<code>SELECT SUM(views) FROM Table WHERE video id = N</code>，数据规模也不大（最多 10 亿级行），但是每个 Procella 实例需要在承载百万级 QPS 的同时实现毫秒级响应，数据也需要能够做到近实时更新，因此 Procella 专门设计了一个「stats serving」模式，这个模式包含以下一些优化：</p>
<ul>
<li>当新数据注册进来时，RgS 会通知 DS（主从 DS 都包括）新数据已经准备好了，然后 DS 就会立即将数据加载到内存中。这个优化是为了保证执行查询时不会访问远端存储，虽然会耗费一些内存但是按照业务的数据量这是可接受的。</li>
<li>MDS 被作为一个模块编译进 RS，尽可能减少 RPC 通信。</li>
<li>所有元数据都会异步加载进内存，和数据一样确保不会产生远端访问。</li>
<li>激进地缓存查询计划，降低分析和计划查询的开销。</li>
<li>RS 会将相同 key 的请求批量发送给 DS，减少 RPC 通信。</li>
<li>RS 和 DS 会监控每个任务的错误率和响应时间，如果发现异常高的任务会自动转移到其它机器。做这个优化一部分也是因为 Borg 的资源隔离做得不够好。</li>
<li>关闭复杂的优化和操作（如自适应 join 和 shuffle），避免不必要的开销以及生产环境的风险。</li>
</ul>
<p>最后是一些生产环境的实际数据。在 YouTube Analytics（YTA）业务中，一共有 5 组 Procella 集群，其中至少有 3 个在服务线上请求，每组集群有大约 6000 个核以及 20TB 的内存。YTA 业务每天有超过数十亿的请求，涉及数十 PB 的数据。YTA E2E 的响应时间 P50、P99、P99.9 分别是 25、412、2859 毫秒。「实时统计数据」业务每天有上千亿的请求，响应时间 P50、P99、P99.99 分别是 1.6、3.3、21.7 毫秒。</p>
<p>以上就是 Procella 的全部介绍，熟悉 Google 的同学可能关心为什么 YouTube 团队不直接用 Google 现有的一些基础设施呢。论文中也对相关系统做了简单介绍和比较，其中：</p>
<ul>
<li><a href="https://research.google/pubs/pub36632" target="_blank" rel="noopener noreferrer">Dremel</a>（VLDB 2010）是一个为 ad-hoc 场景优化的列式 SQL 查询引擎，支持多种后端（如 ColumnIO、Capacitor、Bigtable），同时 Dremel 也是 Google Cloud BigQuery 的底层引擎。Procella 与 Dremel 有诸多相似之处，如 RPC 协议、SQL 分析器、底层存储（Colossus）、容器编排（Borg）。但也有很多不一样的地方，如大量使用缓存（Dremel 基本是无状态的）、为不同业务场景部署不同的集群（Dremel 是一个多租系统）、可索引的数据格式（Capacitor 不可索引）。Dremel 影响了后续很多大数据开源项目，如 Parquet（Parquet 最初叫做 <a href="https://github.com/julienledem/redelm" target="_blank" rel="noopener noreferrer">RedElm</a>，是由 Dremel 的字母重新排列组成）。</li>
<li><a href="https://research.google/pubs/pub40465" target="_blank" rel="noopener noreferrer">PowerDrill</a>（VLDB 2012）是一个全内存列式分布式 SQL 查询引擎。PowerDrill 适合小 QPS 以及简单查询的场景，主要应用在日志数据分析以及内部 dashboard。</li>
<li><a href="https://research.google/pubs/pub41344" target="_blank" rel="noopener noreferrer">F1</a>（VLDB 2013）是一个分布式查询引擎，支持多种后端（如 ColumnIO、Capacitor、Bigtable、Spanner、Mesa）。F1 和 Procella 最大的不同是，因为 F1 本身只是一个查询引擎，用户需要根据场景选用合适的后端存储，如 OLTP 场景选 Spanner、ad-hoc 分析场景选 Capacitor 等。但是 Procella 用一套系统同时满足了多种场景的需求，计算和数据格式也是强绑定的。</li>
<li><a href="https://research.google/pubs/pub42851" target="_blank" rel="noopener noreferrer">Mesa</a>（VLDB 2014）是一个集存储和查询一体的数据系统。Mesa 最初是为了满足 Google 广告业务的需求而设计，因此在设计上有很多独特的地方，如预聚合数据、基于 delta 的数据接入（更新时效在分钟级）。Mesa 并不支持 SQL 查询，而是通过 F1 来实现。百度开源的 <a href="http://doris.incubator.apache.org/" target="_blank" rel="noopener noreferrer">Doris</a> 即是基于 Mesa 来设计（一个题外话，Doris 的 SQL 查询是基于 Impala 实现）。</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="presto-at-facebook-state-of-the-union">Presto at Facebook: State of the Union<a href="https://maybe.news/issues/11#presto-at-facebook-state-of-the-union" class="hash-link" aria-label="Direct link to Presto at Facebook: State of the Union" title="Direct link to Presto at Facebook: State of the Union">​</a></h2>
<p><a href="https://www.youtube.com/watch?v=JuWiWmUtn3M" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>今年 3 月 24 日 PrestoDB 举办了 PrestoCon Day（<a href="https://maybe.news/issues/6">第 6 期</a>曾经介绍过「Presto: SQL on Everything」这篇论文），这个视频来自前面 Procella 论文的第一作者 Biswapesh Chattopadhyay，从他的 <a href="https://www.linkedin.com/in/biswapesh" target="_blank" rel="noopener noreferrer">LinkedIn</a> 上看他于 2019 年从 Google 离职并加入了 Facebook，领导整个大数据基础设施团队。Biswapesh 首先总结了 PrestoDB 目前为止的一些进展，如 Disaggregated coordinator、CBO、RaptorX、Velox（原生代码加速器，有点类似 Procella 的 Superluminal）、CoreSQL。这些特性有些已经在 Facebook 生产环境稳定运行，有些还在灰度测试，有些还处于早期设计阶段。未来规划中列举了一些 PrestoDB 的目标，如支持更多类型的 SQL（HQL 等）、更多运行模式（流式、批处理、交互式查询、图计算）、更多元数据存储（Hive Metastore、Iceberg、Delta Lake）、更多格式（ORC、Parquet）。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="origin-and-history-of-apache-arrow">Origin and History of Apache Arrow<a href="https://maybe.news/issues/11#origin-and-history-of-apache-arrow" class="hash-link" aria-label="Direct link to Origin and History of Apache Arrow" title="Direct link to Origin and History of Apache Arrow">​</a></h2>
<p><a href="https://www.dremio.com/origin-history-of-apache-arrow" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>2016 年 2 月 Cloudera 公开<a href="https://blog.cloudera.com/introducing-apache-arrow-a-fast-interoperable-in-memory-columnar-data-structure-standard" target="_blank" rel="noopener noreferrer">宣布</a>了 Apache Arrow 项目（以下简称 Arrow），距今这个项目已经发展了 5 年时间。Arrow 最初由 <a href="https://pandas.pydata.org/" target="_blank" rel="noopener noreferrer">Pandas</a> 和 <a href="https://ibis-project.org/" target="_blank" rel="noopener noreferrer">Ibis</a> 项目的作者 Wes McKinney 在 2014 年发起，当时他还在 Cloudera 工作，项目名也还不叫做 Arrow。2015 年 Wes 和 Jacques Nadeau 及 <a href="https://drill.apache.org/" target="_blank" rel="noopener noreferrer">Apache Drill</a> 团队有了一些接触，Jacques 等人对于这个项目表示了浓厚的兴趣。这里稍微扯远一些，Apache Drill 是由 Tomer Shiran 在 MapR 工作时发起，很大程度上是受 Google Dremel 启发和影响，2015 年那会儿 Tomer 和 Jacques 正在准备创立 <a href="https://www.dremio.com/" target="_blank" rel="noopener noreferrer">Dremio</a>（Tomer 是 CEO，Jacques 是 CTO）。后来经过早期参与者<a href="https://docs.google.com/spreadsheets/d/1q6UqluW6SLuMKRwW2TBGBzHfYLlXYm37eKJlIxWQGQM" target="_blank" rel="noopener noreferrer">投票</a>，正式确定了使用 Arrow 作为项目名（因为向量的数学符号是箭头）。这里提到向量，是因为 Arrow 是受现在很火的向量化（vectorization）影响。提到向量化往往还会讲到列式存储，我们熟知的列式存储格式如 Parquet 已经定义了数据如何在磁盘上组织，但是当把这些数据从磁盘中读取出来处理时，它们在内存以及 CPU 中应该以何种格式存在呢？是继续以列式格式处理还是转成行式呢？目前大部分计算引擎其实还是以行式进行处理，但是向量化证明了以列式进行处理在 OLAP 场景是更加高效的。**因此 Arrow 的目标是制定一种语言无关的内存中的列式存储格式，并以库的形式集成到各种计算引擎中，避免重复造轮子。**理想情况下，数据从磁盘 → 内存 → CPU 不需要经过任何格式转换和拷贝，最大化处理效率。Arrow 官方目前已经支持 12 种编程语言，如果想拓展到新语言，可以很方便地在 C++ 实现之上扩展。现在 Arrow 已经不仅仅是一种存储格式，还包括 RPC 框架（<a href="https://arrow.apache.org/blog/2019/10/13/introducing-arrow-flight" target="_blank" rel="noopener noreferrer">Flight</a>）、高性能执行内核（<a href="https://www.dremio.com/announcing-gandiva-initiative-for-apache-arrow" target="_blank" rel="noopener noreferrer">Gandiva</a>）、查询引擎（<a href="https://arrow.apache.org/blog/2019/02/04/datafusion-donation" target="_blank" rel="noopener noreferrer">DataFusion</a>）等组件，逐渐丰富 Arrow 的生态。有了这些基础组件，使得实现一个新的 OLAP 系统的门槛大大降低，也难怪 InfluxDB 创始人 Paul Dix 要<a href="https://www.influxdata.com/blog/apache-arrow-parquet-flight-and-their-ecosystem-are-a-game-changer-for-olap" target="_blank" rel="noopener noreferrer">大肆鼓吹</a>。Arrow 是一个非常有野心的项目，值得长期关注。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="diving-deep-on-s3-consistency">Diving Deep on S3 Consistency<a href="https://maybe.news/issues/11#diving-deep-on-s3-consistency" class="hash-link" aria-label="Direct link to Diving Deep on S3 Consistency" title="Direct link to Diving Deep on S3 Consistency">​</a></h2>
<p><a href="https://www.allthingsdistributed.com/2021/04/s3-strong-consistency.html" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>去年 12 月 1 日，AWS <a href="https://aws.amazon.com/blogs/aws/amazon-s3-update-strong-read-after-write-consistency" target="_blank" rel="noopener noreferrer">宣布</a>在 S3 发布 14 年后终于支持强一致性。作为对象存储的创造者，S3 一直是这个领域的标杆，基本就是对象存储的代名词（一个不太一样的地方是，国内通常用 OSS 来指代对象存储）。但对象存储的「最终一致性」一直被人所<a href="https://databricks.com/session_na20/from-hdfs-to-s3-migrate-pinterest-apache-spark-clusters" target="_blank" rel="noopener noreferrer">诟病</a>，特别是随着越来越多业务场景依赖对象存储（比如大数据），这是 S3 诞生时完全预料不到的。而 AWS 的竞争者们虽然是后来者，但已经纷纷支持强一致性，比如 <a href="https://cloud.google.com/blog/products/gcp/how-google-cloud-storage-offers-strongly-consistent-object-listing-thanks-to-spanner" target="_blank" rel="noopener noreferrer">Google Cloud Storage</a>、<a href="https://sigops.org/sosp/sosp11/current/2011-Cascais/printable/11-calder.pdf" target="_blank" rel="noopener noreferrer">Azure Storage</a>。Amazon 的 CTO Werner Vogels 近期发布的这篇博客披露了更多有关 S3 如何实现强一致性的细节，以 S3 目前的体量增加新特性必须做到小心翼翼，不能对现有系统造成任何影响。具体的，S3 在元数据子系统中新增了一个叫做 <a href="http://www2.cs.uh.edu/~paris/MYPAPERS/Icdcs86.pdf" target="_blank" rel="noopener noreferrer">Witness</a> 的组件通过事件通知的机制来确保缓存的一致性，这样既实现了强一致性的目标，也确保了 S3 整体的稳定性没有受到影响。有趣的是这篇博客的评论中有人也对这个设计提出了一些质疑，怀疑 S3 是否真的能做到强一致性，毕竟这次改进并没有改变 S3 系统的核心设计。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="a-state-of-feast">A State of Feast<a href="https://maybe.news/issues/11#a-state-of-feast" class="hash-link" aria-label="Direct link to A State of Feast" title="Direct link to A State of Feast">​</a></h2>
<p><a href="https://feast.dev/blog/a-state-of-feast" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>Feast 项目由 Willem Pienaar 在 Gojek（一家面向东南亚市场的「啥都做」公司）工作时发起，并和 Google Cloud 合作，在 2019 年联合发表了一篇<a href="https://cloud.google.com/blog/products/ai-machine-learning/introducing-feast-an-open-source-feature-store-for-machine-learning" target="_blank" rel="noopener noreferrer">介绍文章</a>。Feast 的定位很好理解，就是做开源的特征存储框架，叫存储系统可能不太合适，毕竟实际的数据存储还是要依靠外部系统，Feast 专注在如何统一管理和使用特征。说到特征管理，有过相关经验的同学一定知道「训练/服务偏斜」（Training-Serving Skew），这一点在著名的「Rules of Machine Learning」中也有<a href="https://developers.google.com/machine-learning/guides/rules-of-ml#training-serving_skew" target="_blank" rel="noopener noreferrer">提到</a>。数据科学家可能有 <a href="https://www.forbes.com/sites/gilpress/2016/03/23/data-preparation-most-time-consuming-least-enjoyable-data-science-task-survey-says" target="_blank" rel="noopener noreferrer">80% 的时间</a>都在清洗和准备数据，特征工程（feature engineering）对于模型质量也至关重要。大公司可能都会自己造各种轮子来管理特征，比如 Uber 的 <a href="https://eng.uber.com/michelangelo-machine-learning-platform" target="_blank" rel="noopener noreferrer">Michelangelo</a>，现在 Uber 的这几位也出来创业，成立了一家叫做 <a href="https://www.tecton.ai/" target="_blank" rel="noopener noreferrer">Tecton</a> 的公司。Feast 的作者 Willem Pienaar 也<a href="https://www.tecton.ai/blog/feast-announcement" target="_blank" rel="noopener noreferrer">加入</a>了 Tecton，不过他还会继续负责 Feast 开源社区。未来 Feast 会朝着更易用、更模块化、更云厂商中立的定位去发展，同时补足与 Tecton 闭源版本之间的一些功能差异。如果你对什么是特征存储感兴趣，也推荐阅读<a href="https://www.tecton.ai/blog/what-is-a-feature-store" target="_blank" rel="noopener noreferrer">这篇文章</a>。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="uptown-records-十年不只是一家唱片店">Uptown Records 十年，不只是一家唱片店<a href="https://maybe.news/issues/11#uptown-records-%E5%8D%81%E5%B9%B4%E4%B8%8D%E5%8F%AA%E6%98%AF%E4%B8%80%E5%AE%B6%E5%94%B1%E7%89%87%E5%BA%97" class="hash-link" aria-label="Direct link to Uptown Records 十年，不只是一家唱片店" title="Direct link to Uptown Records 十年，不只是一家唱片店">​</a></h2>
<p><a href="https://mp.weixin.qq.com/s/0A9WqPBGl462QOSn-e30Xw" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>Uptown 是一家位于上海以黑胶为主的独立唱片店，如果你曾经在上海生活过并且喜爱音乐一定听说过它。Uptown 的诞生得追溯到 2011 年，在平武路和幸福路交叉口的一间地下室，原本想要做成一间酒吧，后来阴差阳错地变成了一家独立唱片店，老板 Sacco 和老板娘 Sophia 就这样一直经营到了现在。大概 5、6 年前我初到上海时，就和叶子一起慕名前往了这间没有任何招牌的地下室，走下阴暗的楼梯，然后穿过长长的狭窄的昏暗过道，尽头的一间小房子就是 Uptown。店里当时只有一名员工，屋里放着音乐，顾客只有我们两个，基本把店里陈列的唱片都翻了一遍，印象比较深的是看到了几张 Carsick Cars 的唱片，大部分还是国外的我不认识的艺术家。后来听说 Uptown 开了分店，在永福路，也终于不是在地下，但一直没有造访。2019 年因为要给一个<a href="https://maybe.news/issues/5">好朋友</a>送礼物特地去了一次永福路的 Uptown，虽说在地上但招牌依然不明显，黑色招牌上只有 RnB（Records &amp; Beer）这几个字，不仔细留意的话很容易错过。这次买了两张唱片，一张是 New Order 的《Movement》，一张是 JAMC 的《Automatic》，成色都还不错。这篇文章来自「BIE 别的」公众号，介绍了很多 Uptown 早期不为人知的故事，比如上海胶圈另一个著名的组织 Daily Vinyl 的联合创始人曾经也是 Uptown 的员工。现在平武路的这间地下室租约到期，因为无法承担涨价以后的房租，Uptown 不得不发起了「Save Uptown」的筹款活动，试图保住这个对于城市音乐场景有着重大意义的地方。从<a href="http://www.dianping.com/shop/H1OY8mlbOWjK5hJ7" target="_blank" rel="noopener noreferrer">大众点评</a>的信息看，平武路的 Uptown 最终还是没能延续，就像几年前的另一家上海音乐地标 Shelter 一样（类似的故事还有很多）。每个城市都有属于这个城市的「角落」，正是这些角落的存在慰藉着形形色色的人们。</p>]]></content>
        <category label="issue" term="issue"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Issue #10]]></title>
        <id>https://maybe.news/issues/10</id>
        <link href="https://maybe.news/issues/10"/>
        <updated>2021-04-29T16:50:00.000Z</updated>
        <summary type="html"><![CDATA[本期关键词：Tectonic、Redlock、Flexible Paxos、明天的盐]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>本期关键词：Tectonic、Redlock、Flexible Paxos、明天的盐</p>
</blockquote>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="facebooks-tectonic-filesystem-efficiency-from-exascale">Facebook’s Tectonic Filesystem: Efficiency from Exascale<a href="https://maybe.news/issues/10#facebooks-tectonic-filesystem-efficiency-from-exascale" class="hash-link" aria-label="Direct link to Facebook’s Tectonic Filesystem: Efficiency from Exascale" title="Direct link to Facebook’s Tectonic Filesystem: Efficiency from Exascale">​</a></h2>
<p><a href="https://www.usenix.org/conference/fast21/presentation/pan" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>这篇论文发表在 2021 年的 FAST 会议，介绍了 Facebook 自研的分布式文件系统 Tectonic，这个系统早在 2016 年就已经<a href="https://atscaleconference.com/videos/facebooks-disaggregated-storage-and-compute-for-mapreduce" target="_blank" rel="noopener noreferrer">公开分享</a>过，在内部又被叫做 Warm Storage。Tectonic 的目标是用一个系统同时满足对象存储（blob storage）和数据仓库（data warehouse）这两种截然不同的存储需求，目前已经有数十个租户（关于租户的概念后面会讲）以及 EB 级的数据量。</p>
<p>在介绍 Tectonic 之前需要先介绍 Facebook 在用的另外几个存储系统：<a href="https://www.usenix.org/conference/osdi10/finding-needle-haystack-facebooks-photo-storage" target="_blank" rel="noopener noreferrer">Haystack</a>、<a href="https://www.usenix.org/conference/osdi14/technical-sessions/presentation/muralidhar" target="_blank" rel="noopener noreferrer">f4</a> 以及 HDFS。Haystack 和 f4 都是 Facebook 自研的对象存储，HDFS 则是大数据数仓最主要的存储系统。</p>
<p>Haystack 的论文发表在 2010 年的 OSDI 会议，当时的 Facebook 已经存储了超过 2600 亿张照片以及 20PB 的数据，传统的存储方案并不适合管理这么大规模的图片，因此才有了 Haystack 这个系统。f4 的论文发表在 2014 年的 OSDI 会议，距离 2010 年 Facebook 存储的照片数量已经增长到超过 4000 亿张，Haystack 的存储变得越来越昂贵，急需一个更高效的存储系统来管理那些不常被访问的温数据，f4 的核心便是通过 Reed-Solomon（RS）编码来降低存储成本，在 f4 上线 19 个月以后总共存储了超过 65PB 的数据，并节省了 53PB 的存储空间。Haystack 的理论副本数（replication factor）是 3.6x，但是由于每块磁盘的 IOPS 不会随着磁盘容量的增加而有显著提升，因此 Haystack 集群需要额外配置大量的「空闲」磁盘才能满足系统对于 IOPS 的要求，导致实际的副本数增加到了 5.3x，相比之下 f4 的副本数是 2.8x。Haystack 对于 IOPS 的需求使得集群的存储容量明显过剩，而 f4 的数据因为不经常被访问磁盘的 IOPS 又有很多空闲，可以看到这两种系统的资源都有很大的浪费，也有明显可以互补的空间。</p>
<p>HDFS 已经是大数据领域存储系统的事实标准，它的优点不用过多介绍（有兴趣的同学可以看看<a href="https://maybe.news/issues/8">第 8 期</a>对 GFS 的介绍），缺点其实也很明显，因为 NameNode 是单点，不管是在可用性还是集群容量上都有很大限制。因此 Facebook 管理了数十个大大小小的 HDFS 集群，这不仅带来了很大的运维成本，对于上层服务来说也需要感知到不同集群的数据。按照 Facebook 目前的数据量，单个 HDFS 集群已经无法承载某些数仓的数据集，因此不得不把数据拆分到不同的集群，进一步将计算引擎的逻辑复杂化。</p>
<p>因此回到 Tectonic 面临的 3 个挑战（同时也是目标）：<strong>单集群支撑 EB 级数据存储，不同租户之间的性能隔离，以及支持一定程度的租户个性化定制</strong>。Tectonic 的架构由几部分组成：chunk 集群、元数据集群、无状态的后台服务以及客户端库。Chunk 集群负责存储数据；元数据集群负责管理文件系统目录结构、block 到 chunk 的映射关系等；无状态的后台服务负责如垃圾回收、数据重新均衡（rebalance）等工作；客户端库与 chunk 和元数据集群交互，并提供类似 HDFS 的 API。每个 Tectonic 集群支持十个左右的租户，租户之间不会共享数据，比如对象存储和数仓分别是不同的租户，每个租户同时又服务上百个应用，比如 Newsfeed、搜索、广告等。</p>
<p>在 Tectonic 中一个文件会被分割为数个 block，每个 block 又会由多个 chunk 组成，chunk 集群即是存储这些 chunk 的服务。这里的 block 是一个逻辑概念，chunk 集群并不感知，而是由元数据集群来维护文件到 block、block 到 chunk 的映射关系。论文中并没有说明每个 block 和 chunk 的具体大小，但是从后面列举的生产环境数据中可以大概估算出单个 block 的大小在 90MB 左右。Chunk 节点使用了 <a href="https://en.wikipedia.org/wiki/XFS" target="_blank" rel="noopener noreferrer">XFS</a> 文件系统，每个 chunk 即为一个文件，对外暴露的接口包括 get、put、append、delete、list 以及 scan。每个 chunk 节点配置了 36 块硬盘（同样的根据生产环境数据可以算出单块硬盘的容量在 10TB 左右），并同时有一块 1TB 的 SSD 用于存储 XFS 的元数据以及缓存热的 chunk。数据复制的最小单元是 block，block 的持久性可以通过 Reed-Solomon 编码或者复制来完成。Tectonic 支持单独对每个 block 使用不同的持久化方法，而不是像其它系统一样是一个全局配置直接影响所有数据，也就是说可以在一个系统中实现多种复制策略。</p>
<p>在任何存储系统中，元数据都是整个系统的核心，<strong>Tectonic 的元数据设计有这么几个特点：存算分离，多层元数据，基于哈希分片，元数据缓存，read-after-write 一致性</strong>。下面分别讲解。</p>
<p>存算分离是指元数据集群分为逻辑节点和存储节点，逻辑节点是无状态的后面会提到，存储节点目前使用的是 Facebook 自研的分布式 K/V 存储 <a href="https://www.youtube.com/watch?v=ZRP7z0HnClc" target="_blank" rel="noopener noreferrer">ZippyDB</a>。ZippyDB 是基于 RocksDB 和 Paxos 来实现的分布式存储系统，广泛用于 Facebook 内部各种业务，如 Newsfeed、Instagram、WhatsApp 等。ZippyDB 根据 shard 进行复制，支持单 shard 内的事务操作，但不支持跨多个 shard 的事务。ZippyDB 也会自动地在不同节点之间进行 shard 迁移，实现负载均衡。Tectonic 的元数据会根据某个 ID 来分片进而分布到不同的 shard，例如目录 ID、文件 ID、block ID。</p>
<p>Tectonic 将元数据分为了 Name、File、Block 三层，Name 层保存的是文件系统的目录结构，File 和 Block 层分别保存文件到 block、block 到 chunk 的映射关系。之所以要把元数据拆分成多层结构，是为了避免请求热点，热点问题在大数据场景尤为常见，例如访问一个目录中的所有文件。这个设计与 <a href="https://dl.acm.org/doi/10.1145/3035918.3056100" target="_blank" rel="noopener noreferrer">Azure Data Lake Store</a>（ADLS）非常类似，不过 ADLS 的元数据是范围分割（range partitioning）的，而 Tectonic 是哈希分割，在之前介绍如何设计一个分布式索引框架的<a href="https://xiaogaozi.me/blog/2020/05/25/how-to-design-a-distributed-index-framework-part-5" target="_blank" rel="noopener noreferrer">文章</a>中已经介绍过这两种数据分割方法各自的优缺点。简单讲范围分割的优点是可以快速扫描（比如递归遍历一个目录），缺点是容易产生热点，因此采用范围分割的系统通常都需要搭配一个好的自动负载均衡系统，动态分割、合并、迁移数据。哈希分割的优点是数据天然就是均匀分布的，也就没有了范围分割的热点问题，但是缺点是没法快速扫描。Tectonic 将近 2/3 的请求都是在 Block 层，因此哈希分割可以很好地实现负载均衡（想象如果采用范围分割那么一个目录中所有文件的 block 元信息可能都会集中在 1 个节点上）。有兴趣了解 ADLS 为什么采用范围分割的同学可以衍生阅读 <a href="https://sigops.org/s/conferences/sosp/2011/current/2011-Cascais/printable/11-calder.pdf" target="_blank" rel="noopener noreferrer">Windows Azure Storage</a> 的这篇论文。</p>
<p>Tectonic 允许 block、文件、目录被密封（seal），当一个目录被密封以后将不能在这个目录中创建新的对象（但是子目录不受限制）。因为文件系统中的对象一旦密封将不会被修改，所以他们的元数据可以缓存在元数据服务节点和客户端上，大大降低读负载，也不用担心产生一致性问题。一个例外是 block 到 chunk 的映射关系有可能被更新，例如 chunk 从一个磁盘迁移到另一个磁盘，此时需要失效 Block 层的缓存。当读取的时候会主动检测缓存是否过期，如果过期便会触发缓存更新。</p>
<p>为了保证同一个目录内的元数据操作的强一致性，Tectonic 依赖 K/V 存储本身提供强一致性的操作以及单分片内（in-shard）的 <a href="https://en.wikipedia.org/wiki/Read%E2%80%93modify%E2%80%93write" target="_blank" rel="noopener noreferrer">read-modify-write</a> 级别的原子事务。具体来说，Tectonic 保证包括数据操作（如 append 和 read）、涉及单个对象的文件和目录操作（如 create 和 list）、同一个父目录内的移动（move）操作在内的这些操作的 read-after-write 一致性。ZippyDB 目前还不支持跨多分区（cross-shard）的事务，因此 Tectonic 也没法提供原子的跨目录移动操作，现在的实现是一个两阶段的过程。比如要把一个子目录从当前的父目录移动到另一个父目录，首先在新的父目录创建一个链接，然后删除旧父目录中的链接；跨目录的文件移动也是类似的，先把文件拷贝到新目录，然后从原目录中删除，这里的拷贝不会涉及到底层数据，仅仅是元数据维度的操作。</p>
<p>Tectonic 的客户端库通过编排元数据和 chunk 服务的信息来暴露一个文件系统抽象给上层应用，使得每个应用对每一次读写操作都可以非常灵活地控制。读写操作的粒度是 chunk，当写入数据时客户端库会负责复制或者 RS 编码这些 chunk，并且在读取的时候重新组织。Tectonic 限制每个文件同一时间只能有一个写入方（single-writer），当应用写入数据时需要先生成一个 token 并在打开文件时把这个 token 添加到文件的元数据中，此时如果有另一个应用也尝试写入，会用新的 token 覆盖当前写入方的 token，并且密封所有当前写入方已经打开的 block。如果应用需要多写入方的语义，可以在 Tectonic 之上实现。</p>
<p>以上是 Tectonic 核心的系统实现，不过 Tectonic 是面向多租户设计的，因此还需要解决多租户场景的很多问题，例如如何保障资源能公平分配给每个租户、如何在维持高资源利用率的情况下进行性能隔离、如何让每个租户有一定的定制空间去优化它自己的请求。</p>
<p>Tectonic 把资源分为两种类型：长期（non-ephemeral）和短期（ephemeral）。存储容量即属于长期资源，一旦分配给某个租户就不会再分给其它租户。每个租户会有一个预先定义好的容量配额，这个配额不会动态伸缩，需要手动调整。短期资源是那些随着时间需求不断变化的资源，比如存储的 IOPS 容量、元数据请求容量。因为短期资源需求变化得很快，所以需要一个细粒度的实时自动化管理来确保资源共享的公平性、资源隔离性以及高的资源利用率。</p>
<p>短期资源共享是 Tectonic 面临的一个比较大的挑战，原因在于不仅租户的需求是多种多样的，而且每个租户服务的应用也有着不同的请求模式和性能要求，比如对象存储类型的租户会同时包含来自 Facebook 生产环境的请求以及后台的垃圾回收请求。如果按照租户的粒度来管理短期资源将没法应对同一租户内部的不同需求，但如果按照应用的粒度来管理也不合适，因为 Tectonic 服务着上百个应用，应用这个粒度过于细也增加了管理的复杂度。</p>
<p>因此 Tectonic 把短期资源按照应用组的粒度来管理，这些应用组被叫做 TrafficGroup。同一个 TrafficGroup 中的应用有着相似的资源和响应时间要求，比如一个 TrafficGroup 是来自生产环境请求的应用组，另一个 TrafficGroup 是来自后台服务请求的应用组。每个 Tectonic 集群支持大约 50 个 TrafficGroup，每个租户都有不同数量的 TrafficGroup，租户需要负责为他们的应用选择适合的 TrafficGroup。每个 TrafficGroup 会对应一个 TrafficClass，TrafficClass 用来描述对于响应时间的需求以及决定哪些请求可以获得空闲资源。目前有 3 种 TrafficClass：黄金、白银、青铜，分别对应延迟敏感、普通和后台应用。空闲资源会根据不同 TrafficClass 的优先级来分配到同一租户内的不同应用，如果某个租户的资源有剩余，会优先给它自己的 TrafficGroup，之后才是给其它租户。</p>
<p>客户端库通过一个速率限制器（rate limiter）来控制请求速率，这个速率限制器基于 <a href="https://en.wikipedia.org/wiki/Leaky_bucket" target="_blank" rel="noopener noreferrer">leaky bucket 算法</a>实现，通过一个高性能、近实时的分布式计数器来统计近期每个租户、每个 TrafficGroup 的请求次数。当客户端发起请求时会增加计数，同时会按照自己的 TrafficGroup、同租户内的其它 TrafficGroup 以及其它租户的顺序检查是否有空闲容量，如果有，请求会继续发送给后端，如果没有则请求会被延迟发送或者拒绝，具体如何处理视请求的超时时间而定。</p>
<p>为了保障在每个存储节点上黄金 TrafficClass（延迟敏感）应用的请求不会被低优先级请求阻塞，Tectonic 实现了一个权重轮询（weighted round-robin，WRR）调度器。具体的，WRR 有三个策略。首先当调度器认为低优先级请求可以延迟执行时就会优先执行更高优先级的请求，如何界定是否可以延迟执行是通过一个贪心优化来实现，简单理解是确保低优先级请求延迟执行以后也有足够的时间来完成。其次，调度器会限制每个磁盘即将执行（in flight）的非黄金等级的 IO 请求数量，如果超过设定的阈值这些低优先级请求就会被阻塞。最后，如果某个磁盘的黄金等级的请求已经等待了足够长时间还没有被执行，将会停止调度非黄金等级的请求到这个磁盘。</p>
<p>每个 Tectonic 集群支持大约 10 个租户，每个租户可以根据自己的需求非常灵活地控制请求。这个灵活性主要得益于两点：客户端库能够直接在 chunk 粒度操作数据，以及为每次请求设置不同的配置。其它一些系统（如 HDFS）只支持目录级别的持久性配置，然而 Tectonic 能支持到 block 级别。论文接下来分别介绍了 Tectonic 在数仓和对象存储这两种类型场景如何进行针对性优化，简单总结几个点：数仓场景写入时会等数据缓冲到一整个 block 大小以后再进行 RS 编码；长期数据用 RS(9,6) 编码，短期数据（如 shuffle 数据）用 RS(3,3) 编码；写入数据前会先发送预留请求，且请求会超过实际所需的节点数（如 RS(9,6) 会发送给 19 台存储节点）；对象存储场景写入时只要大多数节点返回就代表请求成功；当 block 被密封以后会按照 RS(10,4) 进行编码。从评测结果来看，Tectonic 在读写性能上基本和 Haystack 相当，但整体还是要更慢一些，特别是长尾读请求（如 P95、P99）。</p>
<p>论文中分享了一些 Facebook 生产环境的真实数据，某个 Tectonic 集群一共有 4208 台存储节点，总容量是 1590PB，目前已经使用了 1250PB，总共 100.7 亿文件和 150 亿 block。这个集群有两个租户，也就是对象存储和数仓，对象存储大约使用了 49% 的空间，剩下 51% 是数仓。从 IOPS 和吞吐看，对象存储请求一直比较稳定，而数仓则有明显的波峰波谷。三层元数据服务按 QPS 从大到小排列分别是：File、Name、Block，每个元数据分片最高能支撑 1 万 QPS，只有 1% 的 Name 层请求超过了这个最大 QPS 限制。</p>
<p>最后是一些在 Tectonic 设计过程中的权衡、妥协和总结。之所以选择通过客户端库而不是代理与底层交互是为了减少网络、硬件开销（Tectonic 每秒的网络吞吐能达到 TB 级），当然客户端库这个方案也有缺点，比如库的稳定性会直接影响应用。在跨数据中心访问的场景还是需要通过代理，客户端库直接请求不适合。Tectonic 的元数据性能相比 HDFS 还是差不少，毕竟 NameNode 是全内存存储也不涉及多分区操作，因此在使用 Tectonic 时应用可能也需要做一些优化，例如重命名一批文件在 HDFS 中可以一个一个串行操作，但是在 Tectonic 中计算引擎需要并行操作。因为 Tectonic 是哈希分割数据，所以并不支持递归遍历目录，也就不支持类似 HDFS 中的 <code>du</code> 操作，一个变通的实现是 Tectonic 会周期性地聚合每个目录的统计信息。第一版 chunk 存储会将多个 block 组合并进行 RS 编码，目的是减少元数据，但是后来发现这样会大幅降低集群的可用性。最初 Name 和 File 层元数据也没有分开，直到后来发现容易产生热点才分开。在 Tectonic 这个量级内存数据损坏变得非常常见，因此在各个环节都需要强制检查 checksum 来保证数据的完整性。</p>
<p>总的来说 Tectonic 的设计比较简洁，没有太多外部依赖。在 EB 级这个数据规模元数据管理是一个很大的挑战，用一套存储系统来满足不同类型租户的需求也是一个很有意思的点，虽然单纯从性能上比较肯定不如专有系统，不过很多时候性能并不是唯一的衡量因素。尽管叫做文件系统，但也牺牲了一些传统文件系统的特性（如 POSIX 兼容），这也大大简化了很多方面的系统设计。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="how-to-do-distributed-locking">How to do distributed locking<a href="https://maybe.news/issues/10#how-to-do-distributed-locking" class="hash-link" aria-label="Direct link to How to do distributed locking" title="Direct link to How to do distributed locking">​</a></h2>
<p><a href="https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>这篇文章来自 Martin Kleppmann，他同时也是著名技术书籍<a href="http://dataintensive.net/" target="_blank" rel="noopener noreferrer">《Designing Data-Intensive Applications》</a>的作者，曾在 LinkedIn 工作。文章源自他写书过程中做的各种研究，主要论证了 Redis 官方的分布式锁 <a href="https://redis.io/topics/distlock" target="_blank" rel="noopener noreferrer">Redlock</a> 的设计是否足够安全。具体论证过程可以查看文章，大体的结论就是 Redlock 用一个复杂的设计实现了一个并不安全的锁，特别是 Redlock 的算法依赖了一些危险的假设（比如时间、系统时钟），一旦这些假设不成立，锁的安全性将很容易被打破。这篇文章的草稿其实也被 Redis 的作者 Salvatore Sanfilippo 审核过，Salvatore 后来还在自己的博客上写了一篇叫做<a href="http://antirez.com/news/101" target="_blank" rel="noopener noreferrer">「Is Redlock safe?」</a>的文章来逐一反驳 Martin 的观点，不过 Martin 表示自己依然坚持之前的观点。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="a-more-flexible-paxos">A More Flexible Paxos<a href="https://maybe.news/issues/10#a-more-flexible-paxos" class="hash-link" aria-label="Direct link to A More Flexible Paxos" title="Direct link to A More Flexible Paxos">​</a></h2>
<p><a href="https://www.sougou.io/a-more-flexible-paxos" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>这篇文章来自 Sugu Sougoumarane，这个名字你也许陌生，不过他是著名开源项目 <a href="https://vitess.io/" target="_blank" rel="noopener noreferrer">Vitess</a> 的创始成员之一，曾在 YouTube 和 PayPal 工作。文章的主题是介绍一种更灵活的 Paxos 算法实现，通常我们对于共识算法的理解都认为当进行 leader 选举或者复制数据时需要至少多数票（quorum）才能保证分布式系统的强一致性，但是 Sugu 提出一个假设，在包含 N 个节点的集群中，如果参与选举的节点数是 L，参与数据复制的节点数是 P，那么只要 L + P &gt; N，这个算法依然是可靠的。例如集群有 11 个节点，传统上需要 11 / 2 + 1 = 6 个节点参与选举和数据复制，按照新的算法可以用 9 个节点参与选举，然后用 11 - 9 + 1 = 3 个节点参与数据复制。这个新算法带来的好处是随着 N 的增大不会影响写性能（更小的 P）。无独有偶，Heidi Howard、Dahlia Malkhi 和 Alexander Spiegelman 也有类似的观察（<a href="https://maybe.news/issues/9">上一期</a>曾经提到过 Dahlia），并写了一篇论文称之为 <a href="https://fpaxos.github.io/" target="_blank" rel="noopener noreferrer">Flexible Paxos</a>。目前 Flexible Paxos 最著名的实现恐怕就是 Facebook 开源的 <a href="https://logdevice.io/docs/Consensus.html" target="_blank" rel="noopener noreferrer">LogDevice</a>，已经被<a href="https://engineering.fb.com/2017/08/31/core-data/logdevice-a-distributed-data-store-for-logs" target="_blank" rel="noopener noreferrer">用</a>在了包括 Scribe、TAO、机器学习 pipeline 等场景。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="昨天的黎忘年和明天的盐">昨天的黎忘年和明天的盐<a href="https://maybe.news/issues/10#%E6%98%A8%E5%A4%A9%E7%9A%84%E9%BB%8E%E5%BF%98%E5%B9%B4%E5%92%8C%E6%98%8E%E5%A4%A9%E7%9A%84%E7%9B%90" class="hash-link" aria-label="Direct link to 昨天的黎忘年和明天的盐" title="Direct link to 昨天的黎忘年和明天的盐">​</a></h2>
<p><a href="https://mp.weixin.qq.com/s/V_gjwcIqnn6k350L_LK2wQ" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>最早听说黎忘年是来自<a href="https://maybe.news/issues/5#boiled-hippo">一个朋友</a>介绍，这个朋友和黎忘年是浙大校友，虽然不同级也不同专业，但因为有着音乐这个共同的爱好而结识。初次见面觉得这个男生瘦瘦高高，讲话声音也不大，没想到后来竟然组建了一个后朋乐队，也就是「智齿」。从智齿的音乐能听出很多乐队的影子，比如 Joy Division、P.K. 14，看过唯一一次智齿的现场是在上海的老育音堂，那天他们翻唱了 P.K. 14 的《快》，现在想来也是比较幸运的，因为后来智齿就解散了，留下了一张由杨海菘制作的<a href="https://downloads.maybemars.org/album/dance-on-the-street" target="_blank" rel="noopener noreferrer">专辑</a>。智齿虽然解散了，但是黎忘年的乐队生涯还在，最新的乐队叫做「明天的盐」，这篇采访中也透露了今年将会发布首张专辑，制作人是我非常喜欢的音乐人杨帆。</p>]]></content>
        <category label="issue" term="issue"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Issue #9]]></title>
        <id>https://maybe.news/issues/9</id>
        <link href="https://maybe.news/issues/9"/>
        <updated>2021-03-23T12:30:00.000Z</updated>
        <summary type="html"><![CDATA[本期关键词：Delos、Virtual Consensus、Elastic Horovod、OpenAI K8s、Late Troubles]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>本期关键词：Delos、Virtual Consensus、Elastic Horovod、OpenAI K8s、Late Troubles</p>
</blockquote>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="virtual-consensus-in-delos">Virtual Consensus in Delos<a href="https://maybe.news/issues/9#virtual-consensus-in-delos" class="hash-link" aria-label="Direct link to Virtual Consensus in Delos" title="Direct link to Virtual Consensus in Delos">​</a></h2>
<p><a href="https://www.usenix.org/conference/osdi20/presentation/balakrishnan" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>一致性协议是分布式系统领域的核心概念，在之前介绍如何设计一个分布式索引框架的<a href="https://xiaogaozi.me/blog/2020/05/25/how-to-design-a-distributed-index-framework-part-5" target="_blank" rel="noopener noreferrer">一篇文章</a>中已经简单梳理了一致性协议的不同类别以及一些经典的共识算法。今天要介绍的这篇论文并不是发明了一种新的一致性协议，而是对一致性协议进行了拆解和抽象（即论文标题中 Virtual Consensus 的含义），这个抽象可以简化分布式系统的开发和加快迭代速度。Delos 目前已经作为 Facebook 集群调度管理系统 <a href="https://www.usenix.org/conference/osdi20/presentation/tang" target="_blank" rel="noopener noreferrer">Twine</a> 的控制平面（control plane）在生产环境中稳定运行数年（Delos 是希腊 Cyclades 群岛中一个岛屿的名称，离 Paxos 岛不远）。本篇论文发表在 2020 年的 OSDI 会议，并获得了当年的最佳论文。</p>
<p>在介绍 Delos 系统之前先讲讲为什么要有 Virtual Consensus 这个概念。现有的分布式系统通常都是基于某一种一致性协议来开发的，比如 ZooKeeper 是基于 Zab、etcd 是基于 Raft、Chubby 是基于 Paxos。同时学术界对于一致性协议的研究并没有中止，不断会有新的协议出现（<a href="https://vadosware.io/post/paxosmon-gotta-concensus-them-all" target="_blank" rel="noopener noreferrer">大部分</a>可能都是基于 Paxos 进行改良）。因为这些系统的分布式协议层和状态复制层是紧耦合的，如果想要从一种一致性协议切换到另一种，基本上是需要重写系统的（当然很多人也喜欢重复造轮子）。时间回溯到 2017 年，当时的 Facebook 需要一个保证一致性、可用性、持久性的分布式系统作为他们的核心控制平面来存储元数据，这个系统要在 6-9 个月之内上线，并且支持持续的迭代。虽然当时已经有一些现成的系统可以选择，比如 ZooKeeper、<a href="https://www.usenix.org/conference/osdi18/presentation/annamalai" target="_blank" rel="noopener noreferrer">ZippyDB</a>、<a href="https://www.usenix.org/conference/fast20/presentation/cao-zhichao" target="_blank" rel="noopener noreferrer">UDB</a>（分布式 MySQL）、<a href="https://logdevice.io/" target="_blank" rel="noopener noreferrer">LogDevice</a>，但这些系统要么不支持复杂的 API（比如 get、put、multi-get、multi-put、scan 等），要么可用性达不到要求。更重要的是每个系统都和某种一致性协议绑定，没法平滑过渡到新的协议。</p>
<p>为了解决上述问题， Delos 提出了 Virtual Consensus 这个概念（以下简称 VC），VC 的目标是可以平滑切换一致性协议（甚至同时使用多种协议），并把一致性协议中很多可以复用的逻辑抽出来，从而降低实现新协议的成本。 VC 把一致性协议分解为了控制平面和数据平面两部分，控制平面负责 leader 选举、配置参数、成员管理等，数据平面负责保证数据的顺序（ordering）以及持久性。在 VC 中控制平面称作「VirtualLog」，这是可以复用的部分；数据平面称作「Loglet」，Loglet 可以用不同的一致性协议（比如 Raft、Paxos）实现，也可以是对其它分布式系统（比如 ZooKeeper、HDFS）的封装。如同虚拟内存一样，VirtualLog 把多个物理的 Logets 映射到一个连续的虚拟的 VirtualLog 地址空间中。对 VirtualLog 的用户来说，这些 Loglets 的细节被 VirtualLog 隐藏了起来 ，看起来就像是一个连续的 log。</p>
<p>VirtualLog 由两部分组成：一个客户端层暴露访问 log 的 API，以及一个管理元数据的 MetaStore。这里的元数据是指从 VirtualLog 地址到 Loglet 的映射关系，多个 Loglets 组成了一个链（chain），类似一个单向链表。随着时间推移，链可能会发生变动（比如从 1 个 Loglet 变成 2 个），MetaStore 在保存每个链时也会有一个对应的版本号，每当需要更新链时需要提供一个更大的版本号，这个更新操作是一个类似 CAS（compare-and-swap）的过程，会和当前版本号进行比较，保证原子性，避免并发冲突。当需要修改链时就会触发「重新配置（reconfiguration）」流程，这个流程分为 3 步：密封（sealing）当前链、把 MetaStore 中的链修改为新的链、从 MetaStore 获取新的链。MetaStore 需要保证容错一致性（fault-tolerant consensus），也就是说得通过类似 Paxos 这样的一致性协议去实现，后面会讲到具体如何实现。</p>
<p>因为 VirtualLog 已经负责了控制平面的工作，Loglet 的职责就比较简单了，只需要保证数据的全局顺序性（total order）以及持久性，不需要承担 leader 选举、成员变更管理等工作，也不需要实现容错一致性，因此实现一个 Loglet 的成本非常低（相比实现一个完整的一致性协议）。但有一个要求必须满足，前面提到在重新配置时需要密封链，这个密封操作是通过发送 <code>seal</code> 命令给 Loglet 实现的，Loglet 负责更新本地的 seal 状态，seal 状态是一个布尔值，也就是说只会有两种取值。为了保证后续不会错误地把新数据添加到已经密封的链，Loglet 需要保证 <code>seal</code> 命令是高可用的（highly available），也就是说确保大多数（quorum）的 Loglet 都更新完成。</p>
<p>在了解完 VC 的概念以后来看看 Delos 是如何实现的。Delos 是一个基于 VC 思想实现的分布式存储系统，可以简单把它理解成类似 etcd、ZooKeeper 的系统。Delos 分为 3 层：API、Core 和 Loglet。API 层提供多种类型的接口，例如 table API、ZooKeeper API；Core 层除了包含 VirtualLog 以外，还有 Delos 自己的 runtime，以及基于 RocksDB 的本地存储；Loglet 层就是各种不同的 Loglet 实现。当 Delos 接收到读写事务（read-write transaction）时会先将数据追加（append）到 VirtualLog，VirtualLog 负责把数据发送给底层的 Loglet（具体 Loglet 怎么处理 append 请求后面会讲），然后 Delos 开始从 VirtualLog 同步（synchronize）日志，每当读到一个事务（这个事务既可能是当前 Delos 节点也可能是其它节点写的）就会执行这个操作并保存到本地的 RocksDB 中，最后当把当前 Delos 节点自己写入的最后一个事务执行完毕后就返回请求。对应的，Delos 接收到只读事务（read-only transaction）时，首先通过 VirtualLog 检查全局日志的末尾（tail）位置是多少，然后与 VirtualLog 同步日志直到达到上一步得到的位置（同步过程中如果遇到写事务就需要更新 RocksDB），最后在本地的 RocksDB 中执行这个读请求并返回结果。可以看到，当处理只读事务时 Delos 节点可能需要与底层 Loglet 同步日志，这显然会对读性能造成影响，为了优化这一步 Delos 会在与 VirtualLog 的一次同步请求中包含多个只读事务，而不是一次同步请求只处理一个事务，极端情况下如果需要同步的日志中不包含任何写事务那其实可以跳过这一步。</p>
<p>前面提到 VirtualLog 需要一个 MetaStore 来保存当前的链状态，Delos 最初是通过外部的 ZooKeeper 服务来作为 MetaStore。后来为了去掉对外部系统的依赖改为了在 Delos 服务中内嵌一个 MetaStore，多个 Delos 服务便构成了一个 MetaStore 集群，并通过 Paxos 来保证容错一致性。</p>
<p>Delos 目前已经实现了 5 种 Loglet：ZKLoglet 基于 ZooKeeper 实现，这是 Delos 最初上线时使用的 Loglet；LogDeviceLoglet 基于 LogDevice 实现；BackupLoglet 基于类似 HDFS 的系统实现，目的是作为日志的冷存；NativeLoglet 顾名思义是最原生的 Loglet，只包含最必要的功能，不依赖任何外部系统，也是目前 Delos 在使用的 Loglet（替代 ZKLoglet）；最后是 StripedLoglet，这个 Loglet 其实是多个其它 Loglet 的组合，可以有多种组合方式。论文中详细介绍了后两种 Loglet。</p>
<p>NativeLoglet 由两个组件组成：LogServer 和 Sequencer，这些组件都是独立的服务，并且和 Delos 服务部署在一起（当然也可以分开）。LogServer 负责处理有关日志的各种请求，并将日志持久化到本地磁盘，LogServer 可以有多个，且需要为奇数个（因为涉及到类似投多数票的场景）；Sequencer 只有 1 个，职责也只有一个，就是处理 <code>append</code> 请求，后面会详细介绍处理流程。</p>
<p>这里有几个概念需要先介绍一下。当说到一个命令被「本地提交（locally committed）」时表示的是一个 LogServer 已经将这个命令同步到了它的本地日志中。当说到一个命令被「全局提交（globally committed）」时表示这个命令已经被大多数的 LogServer 本地提交，并且这个命令之前的所有命令也都已经全局提交。「全局末尾（global tail）」表示最近一个还没有被全局提交的日志位置，NativeLoglet 的日志是没有空隙的，也就是说从位置 0 开始到全局末尾结束中间的每一个位置都有日志数据。同时每个组件（LogServer、Sequencer、Delos 服务等）都维护了一个 <code>knownTail</code> 变量，这个变量保存的是当前这个组件获取到的全局末尾的值，这些组件之间在交互时都会带上各自的 <code>knownTail</code>，如果发现自己本地的值更小就更新到最新的值。</p>
<p>接下来讲一下 NativeLoglet 支持的几种请求。首先是 <code>append</code> 请求，Delos 服务会将请求发送给 Sequencer，Sequencer 会给每个命令分配一个位置（类似一个计数器），然后 Sequencer 负责转发命令给所有 LogServer，当大多数 LogServer 都正常返回时即表示请求成功。如果大多数 LogServer 都返回说已经密封了，请求会失败。除此之外的其它情况（比如超时、LogServer 请求失败等）Sequencer 都会不断重试直到请求成功或者 NativeLoglet 被密封，重试是幂等的，也就是说同样的命令一定会写入同样的位置。Sequencer 维护了一个请求队列，当收到大多数 LogServer 的返回时即认为这个命令已经全局提交，那些发送给还没有返回结果的 LogServer 的请求就可以忽略了，这样处理可以防止某些特别慢的 LogServer 影响整体性能，同时从这里也能看到每个 LogServer 保存的命令可能是不一样的，对于那些缺失的命令也不需要补上。</p>
<p>然后是 <code>seal</code> 请求。正如前面所介绍的，这个请求是用于密封 Loglet。任何客户端都可以发送这个请求给 LogServer，当大多数 LogServer 都正常返回时即表示请求成功，后续的 <code>append</code> 请求也会失败。需要注意的是当 LogServer 被密封以后，每个 LogServer 本地日志的末尾（tail）可能是不同的。</p>
<p>接着是 <code>checkTail</code> 请求。这个请求会返回全局末尾以及当前 NativeLoglet 的密封状态。任何客户端都可以发送这个请求给 LogServer，并等待大多数 LogServer 返回。一旦大多数 LogServer 返回，<code>checkTail</code> 请求会根据返回结果进行不同的后续处理，这些返回结果会有 3 种可能：全部密封、部分密封、全部未密封。「全部密封」即所有 LogServer 都已经处于密封状态，返回结果中同时也会包含每个 LogServer 的末尾位置，前面介绍 <code>seal</code> 请求已经提到不同 LogServer 密封后的末尾可能是不同的，当 <code>checkTail</code> 发现这种情况时会触发一个修复（repair）操作，对那些有漏掉命令的 LogServer 进行补全（通过从其它 LogServer 拷贝数据过来）。「部分密封」即返回结果中同时存在密封和未密封两种状态，针对这种情况客户端会先发送 <code>seal</code> 请求给 LogServer，然后再次发送 <code>checkTail</code>，一般情况下都会得到全部密封的结果。「全部未密封」即所有 LogServer 都处于未密封状态，此时客户端会从所有 LogServer 返回的末尾位置中挑选最大的那个值，然后等待它自己的 <code>knownTail</code> 达到这个值（论文中并没有具体介绍客户端的 <code>knownTail</code> 是如何更新），如果等待过程中有 LogServer 被密封了就会变成「部分密封」的状态，此时就会按照刚才描述的流程进行处理。</p>
<p>最后是 <code>readNext</code> 请求。客户端已经可以通过 <code>checkTail</code> 获取到全局末尾，因此它会根据这个末尾位置首先请求本地的 LogServer（前面提到过 LogServer 和 Delos 服务是部署在一起的），如果本地的 LogServer 没有这个位置的数据，就会再发送请求给其它 LogServer。</p>
<p>以上就是 NativeLoglet 的设计，因为它不需要具备类似容错一致性这样的特性，Facebook 仅仅用了 4 个月就实现完成并部署到生产环境。当然 LogServer、Sequencer 都有可能出问题，这些错误的检测既有一些内部机制来保证，也有一些外部监控系统来触发。一旦发现问题就会进入重新配置流程，即密封现有 Loglet，创建新的 Loglet 并更新链。</p>
<p>StripedLoglet 是多个 Loglet 的组合，实际上只用了 300 行左右的代码就实现完成。Loglet 的组合可以有多种方式，比如为了避免单个 Sequencer 成为瓶颈，可以把相同的一组 LogServer 看作不同的 NativeLoglet 集群，区别在于不同集群的 Sequencer 不一样；再比如为了对 Loglet 进行分片，达到横向扩展的目的，可以用多组 LogServer，每组只存储部分日志。StripedLoglet 目前并没有在实际生产中使用，更多是作为评估阶段的一个方案。</p>
<p>到这里本篇论文中关于 VC 和 Delos 的介绍就差不多讲完了，但其实还有很多细节没有提到，例如 leader 如何选举、如果处理网络分区、集群成员如何变更、为什么要有密封操作、具体什么时候触发重新配置、如何实现一个 RaftLoglet。Delos 其实是建立在论文第一作者 Mahesh Balakrishnan 多年的研究之上，在来到 Facebook 之前他曾经在微软硅谷研究院以及 VMware 研究院工作，Delos 的很多思想来源于 2012 年的「From Paxos to CORFU: A Flash-Speed Shared Log」这篇论文（一个小八卦，这篇论文的第一作者 Dahlia Malkhi 也是学术界大牛，目前是 Diem Association 的 CTO），以及<a href="https://github.com/CorfuDB/CorfuDB/wiki/White-papers" target="_blank" rel="noopener noreferrer">后续多篇论文</a>。有兴趣的同学可以继续阅读这些论文，另外 OSDI 每篇论文都会有一个来自论文作者的演讲，可以点击<a href="https://www.youtube.com/watch?v=wd-GC_XhA2g" target="_blank" rel="noopener noreferrer">这里</a>查看视频。</p>
<p>最后特别感谢 Delos 作者之一 Chen Shen 对本文提出的细致及宝贵的修改意见。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="virtualizing-consensus-in-delos-for-rapid-upgrades-and-happy-engineers">Virtualizing Consensus in Delos for Rapid Upgrades and Happy Engineers<a href="https://maybe.news/issues/9#virtualizing-consensus-in-delos-for-rapid-upgrades-and-happy-engineers" class="hash-link" aria-label="Direct link to Virtualizing Consensus in Delos for Rapid Upgrades and Happy Engineers" title="Direct link to Virtualizing Consensus in Delos for Rapid Upgrades and Happy Engineers">​</a></h2>
<p><a href="https://atscaleconference.com/2021/03/15/virtualizing-consensus" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>在支持 table API 并成功在 Twine 上应用之后，Delos 的下一个目标是提供 ZooKeeper API，并替代 Facebook 内部使用 ZooKeeper 的场景。Delos 目前已经成为了一个构建分布式数据库的「平台」，基于这个平台实现不同的 API 即可满足不同的场景需求，大部分逻辑都可以复用，比如支持 table API 的数据库 DelosTable，支持 ZooKeeper API 的数据库 Zelos。把 Delos 平台化以后也能让不同团队更加专注于各自的目标，提高开发效率（软件工程第一准则「高内聚低耦合」）。不过理想归理想，现实归现实，在 Zelos 的开发过程中逐渐发现组件的边界并没有那么清晰，很多时候 Zelos 需要依赖对 Delos 平台核心进行修改，使得开发流程受到了很大阻碍。为了解决这个问题，Delos 团队发明了一个新的抽象叫做「log-structured protocol」（LSP？），一层叠加在 VirtualLog 之上的协议，每个协议都会包含一个引擎（engine），且可以像网络栈一样把不同协议进行组合。当应用在请求时，会自上而下经过不同的协议，每一层协议也能带上自己的协议头（header），最终到达 VirtualLog。这个设计有点类似于把 Delos runtime 插件化，不同团队可以根据自己的需求开发不同的插件，这些插件也可以同时被不同团队共享。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="rfc-elastic-horovod">RFC: Elastic Horovod<a href="https://maybe.news/issues/9#rfc-elastic-horovod" class="hash-link" aria-label="Direct link to RFC: Elastic Horovod" title="Direct link to RFC: Elastic Horovod">​</a></h2>
<p><a href="https://docs.google.com/document/d/15ZoHA5AeSI_boeyIBapg9WPXKrYXMRvPytPzQWTCTn4/edit?usp=sharing" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>在<a href="https://maybe.news/issues/7">第 7 期</a>介绍过 Horovod 背后的 Ring Allreduce 算法，从 <a href="https://github.com/horovod/horovod/releases/tag/v0.20.0" target="_blank" rel="noopener noreferrer">v0.20.0</a> 开始 Horovod 从框架上原生支持了模型训练弹性伸缩。不管是基于 PS 还是 Ring Allreduce 架构，弹性伸缩一直是分布式模型训练领域的热门问题，特别是对于一个 AI 平台来说，弹性伸缩能最大程度提高集群的资源利用率。当然为了实现弹性并不是说能够动态扩缩容就好了，框架也要做到足够好的容错性，才能不对训练效果造成影响。这篇 RFC 详细介绍了 Elastic Horovod 的设计背景及架构。目前阿里云容器团队开源的 <a href="https://github.com/AliyunContainerService/et-operator" target="_blank" rel="noopener noreferrer">et-operator</a> 已经支持在 K8s 中使用 Elastic Horovod，通过 <code>TrainingJob</code>、<code>ScaleIn</code> 和 <code>ScaleOut</code> 这 3 个 CRD 来动态控制训练集群的规模，不过 et-operator 暂时还不支持自动伸缩，社区有<a href="https://github.com/AliyunContainerService/et-operator/pull/6" target="_blank" rel="noopener noreferrer">一个 PR</a> 正在解决这个问题。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="scaling-kubernetes-to-7500-nodes">Scaling Kubernetes to 7,500 Nodes<a href="https://maybe.news/issues/9#scaling-kubernetes-to-7500-nodes" class="hash-link" aria-label="Direct link to Scaling Kubernetes to 7,500 Nodes" title="Direct link to Scaling Kubernetes to 7,500 Nodes">​</a></h2>
<p><a href="https://openai.com/blog/scaling-kubernetes-to-7500-nodes" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>OpenAI 团队在这篇文章中分享了他们是如何将 K8s 扩展到 7500 个节点，在 2018 年其实他们已经<a href="https://openai.com/blog/scaling-kubernetes-to-2500-nodes" target="_blank" rel="noopener noreferrer">分享</a>过扩展到 2500 个节点的经验。需要注意的是，OpenAI 的 K8s 集群主要服务于 AI 任务，每个 pod 会独占一个 node 的所有资源，类似资源碎片、bin-packing 这样的问题都不存在，调度器并不会成为瓶颈，因此他们的经验不一定适用于所有场景。几个有趣的地方：当集群规模到达 7500 节点时，API server 占用大约 70GB 的内存；尽量避免任何 DaemonSet 与 API server 进行交互，如果有需要可以借助一个中间的缓存服务；<a href="https://github.com/prometheus-operator/kube-prometheus" target="_blank" rel="noopener noreferrer">kube-prometheus</a> 收集了很多有用的数据，但是其中有很多对于 OpenAI 来说都是没用的，因此他们选择通过 <a href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#relabel_config" target="_blank" rel="noopener noreferrer">P8s rules</a> 来丢弃一些指标；P8s 经常性出现 OOM，最终定位到问题出在 Grafana 和 P8s 之间的交互，解决方法是 <a href="https://github.com/openai/prometheus/pull/1" target="_blank" rel="noopener noreferrer">patch P8s</a>；Gang scheduling 用到了 K8s 1.15 以后支持的 scheduling framework 以及其中由阿里云和腾讯贡献的 <a href="https://github.com/kubernetes-sigs/scheduler-plugins/tree/master/pkg/coscheduling" target="_blank" rel="noopener noreferrer">coscheduling 插件</a>（<a href="https://maybe.news/issues/2">第 2 期</a>也有做介绍）。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="播客文字回顾--late-troubles-是陈曦的中年朋克辛酸吗">播客文字回顾 | Late Troubles 是陈曦的“中年朋克辛酸”吗？<a href="https://maybe.news/issues/9#%E6%92%AD%E5%AE%A2%E6%96%87%E5%AD%97%E5%9B%9E%E9%A1%BE--late-troubles-%E6%98%AF%E9%99%88%E6%9B%A6%E7%9A%84%E4%B8%AD%E5%B9%B4%E6%9C%8B%E5%85%8B%E8%BE%9B%E9%85%B8%E5%90%97" class="hash-link" aria-label="Direct link to 播客文字回顾 | Late Troubles 是陈曦的“中年朋克辛酸”吗？" title="Direct link to 播客文字回顾 | Late Troubles 是陈曦的“中年朋克辛酸”吗？">​</a></h2>
<p><a href="https://mp.weixin.qq.com/s/ZS5GjlskTBH1DS52iss9yQ" target="_blank" rel="noopener noreferrer">[上集]</a> <a href="https://mp.weixin.qq.com/s/KiuoI6NfCHbTeuceHBggyQ" target="_blank" rel="noopener noreferrer">[下集]</a></p>
<p>本篇又名「微软产品经理教你如何上班摸鱼做音乐」，Late Troubles 是 Snapline 乐队主唱陈曦的个人计划，相比 Snapline 的音乐风格，Late Troubles 更加温和。目前陈曦已经移居西雅图，作为一个音乐人、一个父亲、一个上班族，他在这个访谈中表达了自己作为异乡异客的思考、关于家庭的思考、关于音乐制作的思考、疫情对生活的影响等等内容，可能就像 Late Troubles 的音乐一样，这篇访谈让我觉得平实及坦诚，任何光鲜的外表下都会有不尽相同的故事，希望有一天能在国内看到 Late Troubles 的现场演出。</p>]]></content>
        <category label="issue" term="issue"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Issue #8]]></title>
        <id>https://maybe.news/issues/8</id>
        <link href="https://maybe.news/issues/8"/>
        <updated>2021-02-04T10:07:06.000Z</updated>
        <summary type="html"><![CDATA[本期关键词：GFS、Colossus、Streaming Storage、lakeFS、Magnet、Go+]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>本期关键词：GFS、Colossus、Streaming Storage、lakeFS、Magnet、Go+</p>
</blockquote>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-google-file-system">The Google File System<a href="https://maybe.news/issues/8#the-google-file-system" class="hash-link" aria-label="Direct link to The Google File System" title="Direct link to The Google File System">​</a></h2>
<p><a href="https://research.google/pubs/pub51" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>2003 年的 SOSP 会议上作为一家刚成立 5 年的创业公司，Google 发表了这篇影响深远的论文。论文的第一作者 Sanjay Ghemawat 相比他的同事 Jeff Dean 可能不太为外界所知，但看过他的履历以后就会发现早在 DEC 工作期间他就已经与 Jeff Dean 共事，当 Jeff Dean 在 1999 年加入 Google 后不久 Sanjay Ghemawat 也随即加入，并一起研发了 Google File System（以下简称 GFS）、MapReduce、Bigtable、Spanner、TensorFlow 这些每一个都鼎鼎大名的系统，是当之无愧的 Google 元老。</p>
<p>18 年后的今天再来回顾这篇论文依然能发现很多值得借鉴的地方，作为 GFS 最著名的开源实现，HDFS 近年来虽然已经有了很多自己的改进，但核心架构依然沿用的是这篇论文的思想。让我们回到十几年前，去探求为什么 Google 当时要研发这样一个分布式文件系统。</p>
<blockquote>
<p>GFS shares many of the same goals as previous distributed file systems such as performance, scalability, reliability, and availability. However, its design has been driven by key observations of our application workloads and technological environment, both current and anticipated, that reflect a marked departure from some earlier file system design assumptions.</p>
</blockquote>
<p>论文开篇的第一段话已经很好地概括了 GFS 设计的初衷，这是一个完全基于 Google 业务特点设计的系统。回想一下 Google 的业务是什么？搜索引擎。搜索引擎依靠的是爬虫抓取大量数据，通过用户输入的关键词在这个庞大的数据库中检索，最后通过 Google 独有的<a href="https://en.wikipedia.org/wiki/PageRank" target="_blank" rel="noopener noreferrer">排序算法</a>把搜索结果展示给用户。GFS 面对的业务场景有下面几个特点：</p>
<ul>
<li><strong>组件故障随处可见</strong>：存储集群由成百上千台普通商用机器组成（与之对应的是昂贵的超级计算机），再加上应用程序和操作系统的 bug、人为错误、各种硬件故障，系统随时都面临着很多不稳定的因素。因此持续监控、错误检测、容错以及自动恢复就显得尤为重要。</li>
<li><strong>大文件为主</strong>：GB 级文件非常常见，每个文件通常包含很多应用对象（application objects），比如 web 文档。对于数十亿对象的 TB 级数据集来说，把文件切分成 KB 级大小会使得管理变得非常复杂，即使系统能够支撑这样的量级。因此系统设计的假设、文件块的大小都需要重新衡量。</li>
<li><strong>大多数文件都只是追加写而不是覆盖</strong>：随机写的场景完全不存在，文件一旦写入，只会涉及读操作，且通常是顺序读。多种类型的数据都具有这样的特征，例如某些数据是被数据分析程序批量扫描、某些数据是由数据流持续生成、某些数据是归档数据、某些数据属于中间结果（由某一台机器生成然后被另一台机器处理）。</li>
</ul>
<p>Google 当时已经部署了多个 GFS 集群，最大的一个集群有超过 1000 个存储节点以及超过 300TB 的磁盘，同时被数百个客户端访问。</p>
<p>论文的第二章节详细介绍了 GFS 的设计假设，除了前面提到的 3 个以外还包括：</p>
<ul>
<li>**业务场景主要包含两种读取模式：大批量的流式读取和小量的随机读取。**对于前一种模式，每次请求一般读取数百 KB 或者 MB 级的数据，同一个客户端的连续请求一般也是读取某个文件的连续区域。而后一种模式通常从文件任意偏移位置读取几 KB 数据，对于那些性能敏感的应用会把多个随机读请求排序后批量发送，避免在单个文件中来来回回。</li>
<li><strong>系统需要针对并发追加写同一个文件的场景设计好的语义</strong>：典型的应用场景是把 GFS 作为消息队列，数百个生产者并发追加数据到同一个文件；或者多路合并文件，想象一下 MapReduce 的 reduce 阶段。这个文件有可能是边写边读，也有可能是写完以后再读。因此用最小的同步开销保证原子性是非常有必要的。</li>
<li><strong>高吞吐比低延时更重要</strong>：GFS 面对的大多数应用追求的是高速率批量处理数据，只有少部分应用对于点查有严格的延时要求。</li>
</ul>
<p>像传统的文件系统一样，GFS 提供包括创建、删除、打开、关闭、读取、写入这样的接口，但是 GFS 并不提供 POSIX 这样的标准 API。文件通过目录结构组织，可以通过路径名来标识某一个文件。除此之外，GFS 还提供快照（snapshot）和原子追加写（record append）功能。</p>
<p>在介绍完 GFS 的设计背景以及假设以后，接下来是详细的 GFS 架构讲解。**GFS 服务端由一个 master 和多个 chunkserver 组成，通过特定的 client 库（实现了 GFS 的文件系统 API）与应用集成。**GFS 是非常经典的分布式系统架构，影响了后来很多系统的设计。</p>
<p>Master 负责维护整个文件系统的元数据（metadata），包括命名空间（namespace）、访问控制（access control）信息、文件到 chunk 的映射以及每一个 chunk 的具体位置（location）。命名空间可以理解为目录结构、文件名等信息。除此之外，master 还承担一些系统级的活动，例如 chunk 的租约（lease）管理、垃圾回收无效 chunk、在不同 chunkserver 之间迁移 chunk。Master 会周期性地与每一个 chunkserver 进行心跳通信，心跳信息中同时还会包含 master 下发的指令以及 chunkserver 上报的状态。元数据都是保存在 master 的内存中，因此 master 的操作都非常快。每个 chunk 的元数据大约会占用 64 字节内存空间，每个文件的命名空间信息也是占用 64 字节左右（因为 master 针对文件名进行了前缀压缩），相对来说内存的开销是很小的，随着文件数的增加对 master 节点进行纵向扩展即可。比较重要的元数据信息（比如命名空间、文件到 chunk 的映射）还会同步持久化操作日志（operation log）到 master 的本地磁盘以及复制到远端机器，保证系统的可靠性，避免元数据丢失。当操作日志增长到一定大小，master 会生成一个检查点（checkpoint）用于加快状态恢复，检查点文件是一个类似 B 树的结构，可以不经过解析映射到内存直接查询。每个 chunk 的具体位置不会被持久化，master 每次启动时会通过请求所有 chunkserver 来获取这些信息。最初设计时其实考虑过持久化 chunk 的位置信息，但是后来发现在 chunkserver 拓扑经常变化（比如宕机、扩缩容）的情况下如何保持 master 和 chunkserver 之间的数据同步是一个难题。此外 GFS 还提供仅用于只读场景的影子（shadow）master，影子 master 的数据不是实时同步，因此不保证是最新的数据。</p>
<p>采用单 master 的架构极大地简化了 GFS 的设计（也成为了之后被人诟病的因素），master 作为掌握全局信息的唯一入口，必须确保最小程度影响读写操作，否则就会变成整个系统的瓶颈。**因此 GFS 的设计是 client 读写数据永远不会经过 master。**实现方式很简单，client 请求 master 获取到具体需要通信（不管是读还是写）的 chunkserver 列表，把这个列表缓存在本地，之后就直接请求 chunkserver。</p>
<p>Chunkserver 这个名字的来历其实是因为 GFS 把文件分割成了多个固定大小的 chunk。每个 chunk 的大小是 64MiB，相比传统文件系统的块（block）大小大了很多（比如 ext4 默认的块大小是 4KiB），同时 master 会为每一个 chunk 分配一个全局唯一的 64 位 ID。Chunkserver 除了将 chunk 存储到本地磁盘上，还会复制到其它 chunkserver，GFS 默认会存储 3 个副本，当然用户也可以为不同的目录指定不同的复制等级。为什么 GFS 会选择 64MiB 这么大的 chunk 大小呢？论文中列举了几个原因：</p>
<ul>
<li><strong>减少 client 与 master 的交互</strong>：前面提到 client 不管是读还是写数据都需要首先与 master 通信，GFS 的业务场景通常都是顺序读写大文件，chunk 大小越大 client 就能在 1 次请求中获取到更多的信息。即使是随机读的场景，client 也能更多地缓存 chunk 位置信息。</li>
<li><strong>降低 chunkserver 的网络开销</strong>：更大的 chunk，client 越可能执行更多的操作，因此可以降低 client 与 chunkserver 之间的 TCP 长连接的网络开销。</li>
<li><strong>减少 master 维护的元数据大小</strong>：chunk 越大，master 就可以在内存中保存更多元数据。</li>
</ul>
<p>每个 chunk 以及它的副本有两种角色：主副本（primary replica）和从副本（secondary replica），主副本只有 1 个，其它的都是从副本，至于具体哪个是主副本是由 master 决定的。master 会授权一个租约（lease）给主副本，租约的初始超时时间是 60 秒，但是只要 chunk 还在被修改，主副本可以无限续租，master 也可以随时废除租约。基于主从副本和租约的概念，数据写入 GFS 的流程是：</p>
<ol>
<li>Client 请求 master 获取当前 chunk 所有副本所在的 chunkserver 列表，如果目前还没有租约，master 会授权给其中一个副本（也就是说这个副本升级为主副本）。</li>
<li>Master 将主副本的 ID 以及从副本的位置回复给 client，client 会将这些信息缓存在本地，只有当主副本无法通信或者租约失效时才会再次请求 master。</li>
<li>Client 发送<strong>数据</strong>给主从副本所在的全部 chunkserver。发送顺序无所谓，一般是发送给离 client 最近的一个 chunkserver，然后这个 chunkserver 会传递给离它最近的下一个 chunkserver，依此类推。Chunkserver 之间的距离是通过 IP 地址估算出来的，之所以采用这种线性传递数据的方式，目的是最大化网络吞吐。Chunkserver 不会等到一个 chunk 全部接收完毕才发送出去，而是采用管道（pipeline）的方式，只要接收到一定的数据就立即发送。<strong>值得一提的是，当时 Google 的网络带宽是 100Mbps，而现在（2021 年）AWS 上的机器网络带宽能达到 25Gbps，是当年的 250 倍。</strong></li>
<li>一旦所有副本都回复收到了数据，client 就发送<strong>写请求</strong>给主副本。这个请求包含了上一步发送的所有数据的标识符，主副本会分配连续的序列号给写请求，并按照序列号的顺序修改它的状态。</li>
<li>主副本转发写请求给其它从副本，从副本也会按照相同的序列号顺序修改状态。</li>
<li>当所有从副本都回复给主副本，即表示这次写请求已经完成。</li>
<li>主副本回复请求给 client。如果任何副本发生了错误也会一并回复，GFS 的客户端会尝试重试。步骤 3~步骤 7 执行时也会有一定的重试机制，避免每次都从头开始。</li>
</ol>
<p>原子追加写（record append）的流程大体上和上面介绍的一样，区别在于第 4 步时主副本会检查写入以后是否会超过最后一个 chunk 的大小（64MiB），如果没超过就追加到后面，如果超过了会把最后一个 chunk 填充（pad）满，并回复 client 重试。</p>
<p>快照（snapshot）功能基于 copy-on-write 实现，master 通过仅仅复制元数据的方式能够在短时间内完成快照的创建。当 client 需要修改快照数据时，master 会通知所有 chunkserver 本地复制对应的 chunk，新的修改会在复制后的 chunk 上进行。</p>
<p>限于本期的篇幅，还有很多 GFS 的特性没有介绍，例如命名空间管理与锁、副本放置策略（placement policy）、chunk 重新复制（re-replication）、数据均衡（rebalancing）、垃圾回收、高可用等。最后是一个彩蛋，如果你仔细看论文最后的感谢名单，会发现一个熟悉的名字（当然不是 Jeff Dean）。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="colossus-successor-to-the-google-file-system">Colossus: Successor to the Google File System<a href="https://maybe.news/issues/8#colossus-successor-to-the-google-file-system" class="hash-link" aria-label="Direct link to Colossus: Successor to the Google File System" title="Direct link to Colossus: Successor to the Google File System">​</a></h2>
<p><a href="https://www.systutorials.com/colossus-successor-to-google-file-system-gfs" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>自从 GFS 的论文发布以来，Google 的数据已经增长了好几个数量级，很显然 GFS 的架构已经无法支撑如此大规模的数据存储。那 Google 下一代的文件存储是什么呢？答案就是 Colossus。这个神秘的项目直到目前为止都没有在公开场合被全面正式地介绍过，我们只能通过很多碎片的信息来拼凑出它的模样，上面链接中的内容即是通过这些信息整理出来的。一些有趣的信息是：元数据服务（Curators）基于 Bigtable；相比 GFS 至少可以横向扩展 100 倍；GFS 依然存在，只不过是用来存储文件系统元数据的元数据（metametadata）；Colossus 可以基于另一个 Colossus 构建，就像俄罗斯套娃一样无限嵌套（让我想到了分形）；存储数据的服务叫做 D server；默认使用 Reed-Solomon 编码存储数据，也就是通常所说的纠删码（erasure code）。建议配合这个 2017 年的 <a href="http://www.pdsw.org/pdsw-discs17/slides/PDSW-DISCS-Google-Keynote.pdf" target="_blank" rel="noopener noreferrer">slide</a> 以及这篇<a href="https://levy.at/blog/22" target="_blank" rel="noopener noreferrer">中文博客</a>一起阅读。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="storage-reimagined-for-a-streaming-world">Storage Reimagined for a Streaming World<a href="https://maybe.news/issues/8#storage-reimagined-for-a-streaming-world" class="hash-link" aria-label="Direct link to Storage Reimagined for a Streaming World" title="Direct link to Storage Reimagined for a Streaming World">​</a></h2>
<p><a href="https://blog.pravega.io/2017/04/09/storage-reimagined-for-a-streaming-world" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>流式计算这几年应该算是红到发紫？看看 Flink 社区的发展便可知晓。不过本期要介绍的不是流式计算，而是流式存储。说到与流式计算有关的存储，首先想到的可能是 Kafka，作为实时数据流的消息总线，Kafka 承担着非常重要的角色。但是 Kafka 也不是完美的，它的诞生其实比流式计算更早。Kafka 2011 年<a href="https://blog.linkedin.com/2011/01/11/open-source-linkedin-kafka" target="_blank" rel="noopener noreferrer">开源</a>，Spark <a href="https://spark.apache.org/news/spark-0-7-0-released.html" target="_blank" rel="noopener noreferrer">v0.7.0</a> 2013 年发布开始支持 streaming，Flink <a href="https://flink.apache.org/news/2014/11/04/release-0.7.0.html" target="_blank" rel="noopener noreferrer">v0.7.0</a> 2014 年发布开始支持 streaming（跟 Spark 是同一个版本号不知是否是巧合）。因此 Kafka 的很多设计并不是针对流式计算场景优化。比如 topic partition 这个概念，本质上是为了提高读或者写的并发，但是 partition 本身是一个静态配置，并不能做到动态伸缩。再比如 Kafka 的数据存储，目前只支持内存和本地磁盘两种，消费新数据都是从内存，如果是旧数据就可能读磁盘，但是 Kafka 集群的存储容量上限毕竟还是受限于磁盘空间，在流式计算越来越重以及云计算大行其道的今天集群运维是一个难题（某些公司已经自研了 Kafka on HDFS 的方案，比如<a href="https://cloud.tencent.com/developer/news/599446" target="_blank" rel="noopener noreferrer">快手</a>）。Pravega 便这样应运而生，这是一个来自戴尔的<a href="https://github.com/pravega/pravega" target="_blank" rel="noopener noreferrer">开源项目</a>，一些设计亮点是动态 partition 以及自动数据分层（Apache BookKeeper + HDFS）。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-we-built-lakefs-atomic-and-versioned-data-lake-operations">Why We Built lakeFS: Atomic and versioned Data Lake Operations<a href="https://maybe.news/issues/8#why-we-built-lakefs-atomic-and-versioned-data-lake-operations" class="hash-link" aria-label="Direct link to Why We Built lakeFS: Atomic and versioned Data Lake Operations" title="Direct link to Why We Built lakeFS: Atomic and versioned Data Lake Operations">​</a></h2>
<p><a href="https://lakefs.io/why-we-built-lakefs-atomic-and-versioned-data-lake-operations" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>在数据库领域 ACID 和 MVCC 已经不是什么新鲜的概念，但是文件系统领域似乎还是一个属于比较「早期」的阶段，虽然过去已经有类似 <a href="https://en.wikipedia.org/wiki/ZFS" target="_blank" rel="noopener noreferrer">ZFS</a>、<a href="https://en.wikipedia.org/wiki/Btrfs" target="_blank" rel="noopener noreferrer">Btrfs</a> 这样创新的设计，但它们并不是广泛被大众了解以及使用的技术。特别是当云计算以及 S3 这样的「傻瓜」方案出现后，人们似乎已经习惯了开箱即用的产品。数据湖（data lake）这个词汇不知道从什么时候开始流行，对象存储的角色变得越来越重（至少云厂商是这样希望的？）。人们对这个「万能」的存储有着越来越多的期望，但是对象存储并不是万能的。为了解决对象存储的各种问题（这里不赘述具体问题）或者说填补它的一些缺失，越来越多基于对象存储的项目诞生。<a href="https://lakefs.io/" target="_blank" rel="noopener noreferrer">lakeFS</a> 即是其中一个，lakeFS 希望通过提供类似 Git 的体验来管理对象存储中的数据，并且保证 ACID。比如创建一个数据的「分支」即可实现多版本管理。lakeFS 的开发团队来自以色列（<a href="https://www.treeverse.io/" target="_blank" rel="noopener noreferrer">公司官网</a>挺有意思），项目使用 Go 语言实现。一些类似的项目还有 <a href="https://dvc.org/" target="_blank" rel="noopener noreferrer">DVC</a>、<a href="https://github.com/quiltdata/quilt" target="_blank" rel="noopener noreferrer">Quilt</a> 以及 <a href="https://github.com/tensorwerk/hangar-py" target="_blank" rel="noopener noreferrer">Hanger</a>。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="magnet-a-scalable-and-performant-shuffle-architecture-for-apache-spark">Magnet: A scalable and performant shuffle architecture for Apache Spark<a href="https://maybe.news/issues/8#magnet-a-scalable-and-performant-shuffle-architecture-for-apache-spark" class="hash-link" aria-label="Direct link to Magnet: A scalable and performant shuffle architecture for Apache Spark" title="Direct link to Magnet: A scalable and performant shuffle architecture for Apache Spark">​</a></h2>
<p><a href="https://engineering.linkedin.com/blog/2020/introducing-magnet" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>在<a href="https://maybe.news/issues/6">第 6 期</a> Maybe News 曾经介绍过 Facebook 的 Cosco，一个给 Hive/Spark 使用的 remote shuffle service 实现。本期介绍的 Magnet 来自 LinkedIn，也是一个 shuffle service。跟 Cosco 的区别在于 Magnet 不是存算分离架构，不依赖外部存储，核心思想是 mapper 把 shuffle 数据先写到本地的 shuffle 服务，然后这些 shuffle 数据会根据某种负载均衡算法推到远端的 shuffle 服务上，远端 shuffle 服务会定期合并（merge）数据，最后 reducer 从远端 shuffle 服务读取数据。这里的「远端」其实是一个相对的概念，有可能 reducer 跟 shuffle 服务在同一个节点上，那就不需要发送 RPC 请求而是直接读取本地磁盘的数据。更多技术细节可以参考 VLDB 2020 的<a href="http://www.vldb.org/pvldb/vol13/p3382-shen.pdf" target="_blank" rel="noopener noreferrer">论文</a>，另外 LinkedIn 的工程师也在积极将 Magnet 贡献给 Spark 社区，目前已经合入了几个 PR，具体请参考 <a href="https://issues.apache.org/jira/browse/SPARK-30602" target="_blank" rel="noopener noreferrer">SPARK-30602</a>。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="支付宝研究员王益go-可有效补全-python-的不足">支付宝研究员王益：Go+ 可有效补全 Python 的不足<a href="https://maybe.news/issues/8#%E6%94%AF%E4%BB%98%E5%AE%9D%E7%A0%94%E7%A9%B6%E5%91%98%E7%8E%8B%E7%9B%8Ago-%E5%8F%AF%E6%9C%89%E6%95%88%E8%A1%A5%E5%85%A8-python-%E7%9A%84%E4%B8%8D%E8%B6%B3" class="hash-link" aria-label="Direct link to 支付宝研究员王益：Go+ 可有效补全 Python 的不足" title="Direct link to 支付宝研究员王益：Go+ 可有效补全 Python 的不足">​</a></h2>
<p><a href="https://tech.antfin.com/community/articles/993" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p><a href="https://github.com/wangkuiyi" target="_blank" rel="noopener noreferrer">王益</a>目前是蚂蚁集团研究员，同时也是开源项目 <a href="https://github.com/sql-machine-learning/sqlflow" target="_blank" rel="noopener noreferrer">SQLFlow</a> 和 <a href="https://github.com/sql-machine-learning/elasticdl" target="_blank" rel="noopener noreferrer">ElasticDL</a> 的负责人（这两个项目也很有意思，有兴趣的同学可以去了解了解）。这里介绍的 <a href="https://github.com/goplus/gop" target="_blank" rel="noopener noreferrer">Go+</a> 是七牛创始人许式伟发起的开源项目，从 Go+ 的 slogan「the language for data science」就能看出项目的设计初衷。如果说目前什么编程语言在数据科学和机器学习领域最受欢迎，那可能就是 Python 了。但是 Python 的语言特性决定了它可能并不是最适合的，Go+ 依托 Go 语言作为基础，很好地弥补了 Python 的缺失。推荐对机器学习感兴趣的同学看看这篇文章，其中提到的一些八卦历史也很有趣。</p>]]></content>
        <category label="issue" term="issue"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Issue #7]]></title>
        <id>https://maybe.news/issues/7</id>
        <link href="https://maybe.news/issues/7"/>
        <updated>2020-11-15T16:37:20.000Z</updated>
        <summary type="html"><![CDATA[本期关键词：Delta Lake、Delta Engine、Iceberg、Hudi、Ring Allreduce、TensorFlow Recommenders]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>本期关键词：Delta Lake、Delta Engine、Iceberg、Hudi、Ring Allreduce、TensorFlow Recommenders</p>
</blockquote>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="delta-lake-high-performance-acid-table-storage-over-cloud-object-stores">Delta Lake: High-Performance ACID Table Storage over Cloud Object Stores<a href="https://maybe.news/issues/7#delta-lake-high-performance-acid-table-storage-over-cloud-object-stores" class="hash-link" aria-label="Direct link to Delta Lake: High-Performance ACID Table Storage over Cloud Object Stores" title="Direct link to Delta Lake: High-Performance ACID Table Storage over Cloud Object Stores">​</a></h2>
<p><a href="https://databricks.com/research/delta-lake-high-performance-acid-table-storage-overcloud-object-stores" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>14 年前，Amazon 发布了 EC2（Elastic Compute Cloud）和 S3（Simple Storage Service）这两个划时代的产品，从此「云计算」这个词开始进入大众的视野，经过十几年的发展已经逐渐被大众所认知与接受。「云」意味着近乎无限的资源，EC2 为用户提供了计算资源，S3 为用户提供了存储资源。传统基于 Hadoop 的大数据平台是将这两种资源绑定在一起的，而迁移到云端以后非常自然地会想到将存储资源转到类似 S3 的对象存储中，从而真正实现存储计算分离的架构，能够更加弹性地管理计算和存储这两种天生异构的资源，既大幅节约了成本还省去了运维 HDFS 集群的各种烦恼。</p>
<p>作为 Spark 的发明者，Databricks 这家商业公司的很多客户同时也是 AWS 的客户，因此有着非常丰富的在大数据场景使用 S3 的经验。这些经验暴露了 S3（或者类似的对象存储）作为 HDFS 替代者的种种缺陷。</p>
<p>对象存储（object store）的用户可以创建很多 bucket，每个 bucket 中存储了很多对象（object），每个对象都会有一个唯一的 key 作为标识。因此对象存储本质上是一个 K/V 存储，这一点非常重要，因为通常的认知都会将对象存储等同于文件系统（file system）。对象存储中的「目录」其实是通过 key 的前缀模拟出来的，虽然对象存储提供类似 LIST 目录这样的 API，底层实现却是遍历相同前缀的对象，这个操作在文件系统中是 O(1) 的时间复杂度，但在对象存储中是 O(n)。更加严重的情况是，S3 的 LIST API 每次请求最多返回 1000 个 key，单次请求延时通常为几十到几百毫秒，因此当处理超大规模的数据集时单单花在遍历上的时间就可能是分钟级。重命名对象或者目录也是一样，文件系统是一个原子操作，对象存储是先拷贝到新路径，再删除原路径的对象，代价非常高。</p>
<p>另一个对象存储严重的问题是一致性模型，S3 的一致性模型是<a href="https://docs.aws.amazon.com/AmazonS3/latest/dev/Introduction.html#ConsistencyModel" target="_blank" rel="noopener noreferrer">最终一致性</a>。当某个客户端上传了一个新的对象以后，其它客户端并不一定保证能立即 LIST 或者读取这个对象。当一个对象被更新或者删除以后也会发生同样的现象，即使是负责写入的这个客户端自己也有可能遇到。S3 能确保的一致性是 read-after-write，也就是说 PUT 请求产生以后的 GET 请求是保证一定能返回正确数据的。</p>
<p>论文概述了目前大数据存储的 3 种方案：分区目录、自定义存储引擎、元数据在对象存储中，下面分别介绍。</p>
<p><strong>分区目录</strong>顾名思义就是将数据按照某些属性进行分区，比如日期。这是大数据领域非常普遍的做法，好处是可以根据分区过滤不需要的数据，也就能减少 LIST 请求的数量。这个方案并没有解决前面提到的对象存储的问题，因此缺点也很明显：不支持跨多个对象的原子操作、最终一致性、低性能、不支持多版本和审计日志。</p>
<p><strong>自定义存储引擎</strong>的意思是在云上实现一个独立的元数据服务，类似 Snowflake、<a href="https://juicefs.com/" target="_blank" rel="noopener noreferrer">JuiceFS</a> 的做法。对象存储只是被当作一个无限容量的块存储，一切元数据操作都依赖这个单独的元数据服务。这个方案的挑战是：</p>
<ul>
<li>所有 I/O 操作都需要经过元数据服务，这会带来额外的请求开销，降低性能和可用性。</li>
<li>实现一个与现有计算引擎互通的连接器（connector）需要更高的工程成本</li>
<li>用户会因为元数据服务而绑定在某一个特定的服务供应商上，没法直接访问对象存储中的数据。</li>
</ul>
<p><strong>元数据在对象存储中</strong>是 Databricks 提倡的方案，即今天介绍的 Delta Lake。这个方案和前一个的本质区别是不存在一个中心化的元数据服务，元数据是通过「日志」的形式直接存放在对象存储中。从目录结构上来看，Delta Lake 定义了一种特殊的存储格式，例如对于更新或者删除的数据会产生很多小的 delta 文件。这一点上其实跟 <a href="https://issues.apache.org/jira/browse/HIVE-5317" target="_blank" rel="noopener noreferrer">Hive 实现 ACID</a> 的设计很像，后者在 2013 年就已经开始开发，而 Delta Lake 项目是 2016 年启动，很难说有没有借鉴的成分。更加类似 Delta Lake 的是另外两个项目：<a href="https://hudi.apache.org/" target="_blank" rel="noopener noreferrer">Apache Hudi</a> 和 <a href="https://iceberg.apache.org/" target="_blank" rel="noopener noreferrer">Apache Iceberg</a>，关于这几个项目的异同后面会有一个更详细的介绍，Databricks 目前宣传的一些 Delta Lake 独有的特性（比如 Z-order clustering）其实并没有开源。</p>
<p>Delta Lake 的思想其实很好理解，本质上是把所有操作都通过日志的形式记录下来，当读取时需要重放这些日志来得到最新的数据状态，最终实现 ACID 的语义。优化的点在于怎么加速整个流程，比如定期合并日志为一个 checkpoint、索引最新的 checkpoint 等。这种把日志作为元数据的设计解决了前面提到的对象存储最终一致性的问题，即只依赖日志来确定具体读取的文件，而不是简单通过 LIST 一个目录。但是从论文描述的场景来看还是有可能因为最终一致性踩坑（因为依然会用到 LIST API），至于这个概率有多大就不知道了，因此我对于是否能根本性解决一致性问题存疑。</p>
<p>写数据的时候有一个地方需要特别注意，日志文件的文件名是递增且全局唯一的 ID，因为写入存在并发，所以需要在这一步保证操作的原子性。根据不同的对象存储有不同的解决方案：</p>
<ul>
<li>Google Cloud Storage 和 Azure Blob Store 因为支持原子 put-if-absent 操作，因此可以通过这个 API 实现。</li>
<li>对于支持原子 rename 的文件系统（比如 HDFS、Azure Data Lake Storage），可以通过这种方式实现。</li>
<li>如果以上功能都不支持（比如 S3），在 Databricks 的企业版本里是通过一个独立的轻量级协调服务（coordination service）来确保 ID 递增的原子性。在开源版本的 Spark 连接器里是通过 Spark driver 来统一分配 ID，这样也能保证在 1 个 Spark 任务里可以并发写。你也可以通过 <code>LogStore</code> 这个接口实现一个类似 Databricks 提供的协调服务。因为依赖了一个中心化的服务（虽然只是在写数据时），也一定程度上破坏了 Delta Lake 宣扬的去中心化思想。</li>
</ul>
<p>由于日志中记录了所有的历史操作，并且数据和日志都是不可变的（immutable），因此 Delta Lake 可以很轻松实现时间旅行（Time Travel）功能，也就是重现某个历史时刻的数据状态。Delta Lake 通过类似 <code>TIMESTAMP AS OF</code> 这种语法的 SQL 可以让用户指定读取某个时间的数据，不过这个 SQL 语法目前在开源版本中还<a href="https://github.com/delta-io/delta/issues/128" target="_blank" rel="noopener noreferrer">不支持</a>。</p>
<p>Delta Lake 也可以很好地跟流式计算进行结合，不管是生产者还是消费者都可以利用 Delta Lake 的 API 来实现流式写和读数据。当然毕竟因为是 Databricks 开发的产品，目前结合得最好的肯定是 Spark Structured Streaming。你问支持 Flink 吗？至少 Databricks 员工的<a href="https://github.com/delta-io/delta/issues/156#issuecomment-552503730" target="_blank" rel="noopener noreferrer">回答</a>是还在计划中，短期内估计没戏。</p>
<p>最后是性能评测部分。首先评测的是 LIST 大量文件的场景，通过对同一张表进行不同程度地分区来模拟不同量级的文件，评测的引擎是 Hive、Presto、Databricks Runtime（企业版 Spark，以下简称 DR），其中 Hive 和 Presto 读取的数据格式是 Parquet，DR 读取的格式是 Parquet 和 Delta Lake。Hive 在有 1 万个分区时总时间已经超过 1 小时；Presto 稍好一些在 10 万个分区时才超过 1 小时；DR + Parquet 在 10 万个分区时的耗时是 450 秒（得益于并发执行 LIST 请求）；DR + Delta Lake 在 1 百万分区时的耗时才 108 秒，如果启用了本地缓存可以进一步缩短到 17 秒，可以看出来优化效果非常明显。这个结果也基本符合预期，毕竟 Delta Lake 主要目标之一就是优化 list 的性能（以及一致性），对象存储在元数据性能上肯定没有优势。</p>
<p>接下来是更接近真实场景的 TPC-DS 测试，数据集大小是 1 TB，测试结果取的是 3 次运行时间的平均值。最后的数据是 Presto + Parquet 耗时 3.76 小时，社区版 Spark + Parquet 耗时 1.44 小时，DR + Parquet 耗时 0.99 小时，DR + Delta Lake 耗时 0.93 小时。DR + Parquet 相比社区版 Spark 快的主要原因是 DR 做了很多运行时和执行计划的优化，相比之下 DR + Delta Lake 并没有比直接读取 Parquet 提升太多，论文中的解释是 TPC-DS 的表分区都不大，不能完全体现 Delta Lake 的优势。</p>
<p>总结一下，Delta Lake 的思想其实并不复杂，也是工业界为了解决对象存储诸多问题的一种尝试，虽然并不能完全解决（比如原子重命名和删除）。在大数据存储上实现 ACID 这一点对于构建实时数仓至关重要，Delta Lake 通过一种简单统一的方式实现了这个需求，而不用像传统的 <a href="https://en.wikipedia.org/wiki/Lambda_architecture" target="_blank" rel="noopener noreferrer">Lambda 架构</a>一样再单独部署一套存储系统（比如 HBase、Kudu）。但现在流式计算领域的风头已经从 Spark 逐渐转向了 Flink，像 Delta Lake 这种只对 Spark 支持的技术在某种程度上也会限制它的普及，相比之下 Iceberg 和 Hudi 似乎更有竞争力。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="delta-engine-high-performance-query-engine-for-delta-lake">Delta Engine: High Performance Query Engine for Delta Lake<a href="https://maybe.news/issues/7#delta-engine-high-performance-query-engine-for-delta-lake" class="hash-link" aria-label="Direct link to Delta Engine: High Performance Query Engine for Delta Lake" title="Direct link to Delta Engine: High Performance Query Engine for Delta Lake">​</a></h2>
<p><a href="https://www.youtube.com/watch?v=o54YMz8zvCY" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>前面介绍了 Delta Lake，算是 Databricks 今年一个重量级的开源产品，但其实真正的杀手锏并没有开放出来，也就是这里要介绍的 Delta Engine。简单介绍这是一个在 Delta Lake 之上，基于 Spark 3.0 的计算引擎。Delta Engine 主要包含 3 部分：原生执行引擎（Native Execution Engine），查询优化器（Query Optimizer）以及缓存（Caching）。这个视频重点介绍了原生执行引擎，这个引擎的代号是 Photon，它使用 C++ 编写，并且实现了目前在 OLAP 领域很火的向量化（vectorization）功能，感兴趣的同学强烈建议阅读 <a href="http://cidrdb.org/cidr2005/papers/P19.pdf" target="_blank" rel="noopener noreferrer">MonetDB/X100: Hyper-Pipelining Query Execution</a> 这篇论文，Databricks 厉害的地方在于是跟论文作者 Peter Boncz 一起合作设计。在 30 TB 的 TPC-DS 测试中，Photon 带来了 3.3 倍的性能提升。关于查询优化器以及缓存功能的介绍可以参考 Delta Engine 的<a href="https://docs.databricks.com/delta/optimizations/index.html" target="_blank" rel="noopener noreferrer">文档</a>。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="a-thorough-comparison-of-delta-lake-iceberg-and-hudi">A Thorough Comparison of Delta Lake, Iceberg and Hudi<a href="https://maybe.news/issues/7#a-thorough-comparison-of-delta-lake-iceberg-and-hudi" class="hash-link" aria-label="Direct link to A Thorough Comparison of Delta Lake, Iceberg and Hudi" title="Direct link to A Thorough Comparison of Delta Lake, Iceberg and Hudi">​</a></h2>
<p><a href="https://databricks.com/session_na20/a-thorough-comparison-of-delta-lake-iceberg-and-hudi" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>Iceberg 和 Hudi 是另外两个会经常拿来跟 Delta Lake 做比较的对象，Iceberg 是 Netflix 开源，而 Hudi 是 Uber 开源。它们之间有着诸多相似之处，又有着很多截然不同的设计思想。这个视频来自腾讯云数据湖团队的陈俊杰，比较系统地对比了这 3 种技术。相对来说 Iceberg 的设计是这 3 个里面最中立的，不跟某种特定的格式和引擎绑定，这也是腾讯选择 Iceberg 的原因之一，具体可以看<a href="https://www.infoq.cn/article/59lbbuvcrzlusmdowjbb" target="_blank" rel="noopener noreferrer">「为什么腾讯看好 Apache Iceberg？」</a>这篇文章。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="bringing-hpc-techniques-to-deep-learning">Bringing HPC Techniques to Deep Learning<a href="https://maybe.news/issues/7#bringing-hpc-techniques-to-deep-learning" class="hash-link" aria-label="Direct link to Bringing HPC Techniques to Deep Learning" title="Direct link to Bringing HPC Techniques to Deep Learning">​</a></h2>
<p><a href="https://andrew.gibiansky.com/blog/machine-learning/baidu-allreduce" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>深度学习的核心之一是 <a href="https://en.wikipedia.org/wiki/Stochastic_gradient_descent" target="_blank" rel="noopener noreferrer">SGD</a>（Stochastic Gradient Descent），通过把数据集拆分成若干小的集合（mini-batch），再基于这些小集合反复进行前向传播（forward propagation）和反向传播（backpropagation）计算，不断获取新的梯度（gradient）和权重（weight）。分布式训练本质上要解决的问题就是怎么让多机计算的效率线性提升，即所谓的「线性加速比」，理论值当然是 100%，但是实际情况往往差了很多。传统的同步 SGD 在每一轮计算完以后需要把所有梯度汇总，再重新计算新的权重，类似一个 MapReduce 的过程，此时 reducer 需要等待所有 mapper 计算完成，计算性能会随着 mapper 数量的增加而线性下降。怎么解决这个问题呢？这篇 2017 年的旧文介绍的便是影响至今的 Ring Allreduce 算法，作者 Andrew Gibiansky 之前在百度硅谷 AI 实验室工作，后来联合创办了语音合成公司 <a href="https://www.voicery.com/" target="_blank" rel="noopener noreferrer">Voicery</a>（不过悲剧地发现这家公司今年 10 月份已经关了）。基于 Andrew Gibiansky 的成果，Uber 开源了目前公认的 Ring Allreduce 标准框架 <a href="https://github.com/uber/horovod" target="_blank" rel="noopener noreferrer">Horovod</a>。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="introducing-tensorflow-recommenders">Introducing TensorFlow Recommenders<a href="https://maybe.news/issues/7#introducing-tensorflow-recommenders" class="hash-link" aria-label="Direct link to Introducing TensorFlow Recommenders" title="Direct link to Introducing TensorFlow Recommenders">​</a></h2>
<p><a href="https://blog.tensorflow.org/2020/09/introducing-tensorflow-recommenders.html" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>推荐系统是一直都是机器学习一个重要的应用领域，如果你不了解什么是推荐系统可以看我之前写的<a href="https://xiaogaozi.me/blog/2020/04/21/how-to-design-a-distributed-index-framework-part-1" target="_blank" rel="noopener noreferrer">一篇简介</a>。使用 TensorFlow 可以很方便地训练一个推荐系统模型，不管是召回模型还是排序模型。现在 TensorFlow 官方将这个流程进一步简化，推出了 TensorFlow Recommenders（TFRS）库，旨在让训练、评估、serving 推荐系统模型更加容易，并且融合一些 Google 自己的经验，对于初学者来说会是一个好的入门指南。</p>]]></content>
        <category label="issue" term="issue"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Issue #6]]></title>
        <id>https://maybe.news/issues/6</id>
        <link href="https://maybe.news/issues/6"/>
        <updated>2020-09-15T18:54:00.000Z</updated>
        <summary type="html"><![CDATA[本期关键词：Presto、Spark shuffle、Facebook Cosco、Federated Learning、Distributed file system]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>本期关键词：Presto、Spark shuffle、Facebook Cosco、Federated Learning、Distributed file system</p>
</blockquote>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="presto-sql-on-everything">Presto: SQL on Everything<a href="https://maybe.news/issues/6#presto-sql-on-everything" class="hash-link" aria-label="Direct link to Presto: SQL on Everything" title="Direct link to Presto: SQL on Everything">​</a></h2>
<p><a href="https://prestosql.io/Presto_SQL_on_Everything.pdf" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>Presto 是 Facebook 2012 年开始开发并于 2013 年开源的分布式查询引擎。和<a href="https://maybe.news/issues/3">第 3 期</a>介绍的 Kudu 一样，主要应用在 OLAP 场景，但跟 Kudu 不一样的地方是，Presto 仅仅是一个查询引擎，并不负责数据存储。这也是论文标题「SQL on Everything」的含义，这里的「Everything」指代的即是任意类型的存储，比如 HDFS、MySQL 等。</p>
<p>论文开篇先总结了 Presto 几个值得关注的特点：</p>
<ul>
<li>Presto 是一个自适应的多租户系统（adaptive multi-tenant system），可以很容易扩展到上千节点的同时，还能有效利用集群资源。这里的「自适应」很重要，是将 Presto 和其它系统进行比较的要点之一。</li>
<li>Presto 可以很方便地和多种数据源进行集成，甚至在 1 条查询语句里就可以同时查询多个数据源。Presto 通过连接器（connector）的概念统一底层存储的访问。</li>
<li>通过不同的配置可以让 Presto 同时适配不同的场景。关于这一点在后续介绍 Facebook 的查询场景时也有体现。</li>
<li>Presto 通过很多关键特性实现了一个高性能的查询引擎。多个并行的查询在同一个 JVM 中运行，虽然可以降低响应时间，但同时也需要在调度、资源管理、隔离这些方面特别注意。</li>
</ul>
<p>接下来介绍 Facebook 目前使用 Presto 的几个主要场景：</p>
<ul>
<li><strong>交互式分析（Interactive Analytics）</strong>：这是将 Facebook 数据仓库作为数据源的查询场景。这个场景通常查询的数据量较小，压缩后大概 50GB-3TB 左右。单集群需要支持的并发查询量在 50-100 左右，秒级或者分钟级返回结果。用户对于查询时间非常敏感，但同时对于查询所需的资源量没有特别精确的判断。在进行探索式分析时，用户通常不需要返回所有结果集，只要有初步的结果或者满足 <code>LIMIT</code> 的限制整个查询就可以提前终止。</li>
<li><strong>批量 ETL（Batch ETL）</strong>：这个场景的用户一般是数据工程师，目前已经是 Facebook 内部一个很大的 Presto 应用场景。相比交互式查询，ETL 需要的资源也更多，不管是 CPU 还是内存，特别是当涉及到聚合或者 join 很多大表的时候。在这个场景查询时间反而没有那么重要，更加重要的是资源利用率和整体集群的吞吐。</li>
<li><strong>A/B 测试（A/B Testing）</strong>：为了满足用户对产品验证越来越快的需求，A/B 测试的结果需要在小时级（而不是天级）内得到，并保证数据完整且精确。当用户需要进行更深层次的分析时，查询结果需要在 5-30 秒左右返回。很难通过预聚合（pre-aggregating）的方式满足这些查询需求，因此必须通过在线计算来解决。查询会涉及到 join 多个大的数据集，同时查询语句的特征是相对固定的。</li>
<li><strong>开发者／广告主分析（Developer/Advertiser Analytics）</strong>：这是面向外部开发者或者广告主的分析场景，比如 <a href="https://analytics.facebook.com/" target="_blank" rel="noopener noreferrer">Facebook Analytics</a>。同 A/B 测试场景一样，这个场景的查询语句特征也是相对固定的。虽然总的数据规模很大，但是用户查询时因为会限制在他自己的数据里相对来说查询量会小很多。数据接入（data ingestion）的时延大概是分钟级，查询时延需要严格限定在 50 毫秒到 5 秒左右。因为应用在外部商业产品，Presto 集群的可用性需要保证在 99.999%，并且支持上百并发查询。</li>
</ul>
<p>以上这些场景可能除了 ETL 以外，也是目前很多公司使用 Presto 的主要场景，总的来说主要还是应用在交互式查询上。</p>
<p>然后是 Presto 整体的架构介绍，集群分为两种类型的节点：coordinator 和 worker。Coordinator 节点负责解析、规划以及优化查询，通常只会有 1 个。Worker 节点负责处理查询请求，根据集群规模可以横向扩展。</p>
<p>当客户端通过 HTTP 请求将 SQL 发送给 coordinator 时，经过解析和分析，coordinator 会生成一个分布式执行计划（distributed execution plan）。这个执行计划由多个 stage 连接而成，类似一个 DAG 的形式。因为这是一个分布式执行计划，stage 会被分发到不同的 worker，因此 stage 之间需要通过 shuffle 来交换数据。每个 stage 内部由多个 task 组成，一个 task 可以被看作一个处理单元（processing unit）。Task 内又由多个 pipeline 构成，一个 pipeline 内包含一系列的 operator。到这里，operator 已经是最小的处理单位，通常只负责某一类单一计算任务。</p>
<p>Coordinator 很大一部分工作是负责调度，调度分为三个维度：stage、task 和 split。Stage 调度决定 stage 的执行顺序；task 调度决定多少任务需要被调度以及应该分配给哪些 worker；split 调度决定 split 会被分配给哪些任务（关于 split 这个概念后面会详细介绍）。</p>
<p>调度 stage 分为两种策略：all-at-once 和 phased。All-at-once 很好理解就是所有 stage 并行执行，这个策略可以最大化执行效率，适合时延敏感的场景（如交互式分析）。而 phased 策略就是只并行执行那些强关联的组件，整体任务分阶段执行，这个策略可以有效降低内存占用，适合 ETL 场景。</p>
<p>当 stage 调度成功，coordinator 即会开始分配 task。任务调度器将 stage 分为两类：leaf 和 intermediate。Leaf stage 负责从连接器中读取数据，intermediate stage 负责处理来自其它 stage 的中间结果。对于 leaf stage，任务调度器会根据如网络拓扑、数据本地性这些因素来决定应该把 task 分配给哪些 worker 节点，这个过程依赖连接器实现的 Data Layout API。如果没有任何限制，Presto 倾向于把 leaf stage 的任务分散到整个集群，以加快数据读取效率。Intermediate stage 的任务可以被分配到任意节点上，但是调度器仍然需要决定当前每个 stage 有多少任务需要被调度，且这个任务数是可以在运行时动态调整的。</p>
<p>当 leaf stage 的任务分配好以后，这个 worker 节点便可以开始接收来自 coordinator 分配的 split。Split 是对底层数据的逻辑封装，例如底层存储是 HDFS，那一个 split 通常包含的信息有文件路径、文件偏移等。Leaf stage 的任务必须至少分配一个 split 才能开始运行，而 intermediate stage 的任务是一直可运行的。Split 的创建由连接器负责，并且懒分配给 leaf stage 的任务，也就是说并不会等到所有 split 都创建完毕。这样做有几个好处：</p>
<ul>
<li>将连接器创建 split 的时间从查询中解耦。某些连接器（如 Hive）可能需要花费很长时间去遍历分区和 list 文件。</li>
<li>查询可以尽快开始执行而不用等到所有数据处理完毕。在交互式分析场景很有可能查询会被提前中断。</li>
<li>每个 worker 维护了一个 split 的队列，coordinator 分配 split 时会优先选择队列长度最短的节点。</li>
<li>不用一次保存所有 split 的元信息。对于 Hive 连接器来说很有可能会产生上百万个 split，这会直接导致 coordinator 内存不足。</li>
</ul>
<p>介绍完了 coordinator 的工作接下来就是 worker。前面已经提到最小的执行单位是 operator，operator 负责处理输入数据，同时输出处理完的数据。Operator 输入输出的数据单元叫做 page，一个 page 是连接器将 split 中的多行数据转为列式存储以后产生的数据结构。Shuffle 也是 worker 的主要工作之一，区别于传统的 Hadoop 组件，Presto 是基于全内存的 shuffle 实现，这也是 Presto 性能更优的原因之一。Shuffle 的数据会暂存在内存缓冲区（buffer）中，简单理解 map 端的缓冲区为输出缓冲区，reduce 端的为输入缓冲区。这两个缓冲区都是有容量限制的，会根据数据消费的速率动态调整生产速率，确保整体任务的稳定性以及多租户之间的公平性。当输出缓冲区容量持续偏高时，Presto 会减少可消费的 split 数量。输入缓冲区这端会有一个类似 TCP 滑动窗口的策略动态控制上游的生产速率。</p>
<p>回顾开篇总结的 Presto 特点，其中很重要的一个是<strong>自适应的多租户场景</strong>，上一段落介绍 shuffle 缓冲区的时候其实已经涉及到部分针对性的优化。本质上资源管理需要考虑的就是 CPU 和内存这两种资源，Presto 分别都有不同的解决方案。</p>
<p>CPU 调度场景每个 split 都会有一个允许在一个线程上一秒内执行的最大 quanta，当 quanta 超出时这个 split 将会被放回队列释放线程给其它 split。当输出缓冲区满（下游消费慢）、输入缓冲区空（上游生产慢）或者集群内存紧张时，即使 quanta 没使用完调度器也会强行切换任务。这个基于 quanta 的调度策略使得 Presto 能够最大化 CPU 资源的利用率。当线程被释放应该如何挑选下一个运行的任务呢？Presto 建立了一个 5 级的反馈队列（feedback queue），每个等级都分配了一个可配置的 CPU 时间比例。随着一个任务使用的 CPU 时间不断累积，这个任务会移动到更高等级的队列。也就是说 Presto 倾向于优先执行那些「快」的任务，因为用户期望轻的查询尽快完成，而对于那些重的查询所需的时间不太敏感。</p>
<p>内存管理是一个比 CPU 更复杂的场景。Presto 将内存分为两种类别：用户（user）和系统（system），并分别维护不同的内存池。引擎对用户内存和总内存（用户 + 系统）都有不同的限制，超过全局（所有 worker 聚合以后）或者单节点内存限制的任务将会被强行杀掉。虽然有全局的内存限制，但是为了满足并行执行多个任务的需求通常还是会超卖（overcommit）内存，即使真的出现部分节点内存耗尽的情况，Presto 也提供了两种机制去确保整体集群的稳定性。这两种机制分别是：spilling 和预留内存池（reserved pool）。Spilling 其实就是在节点内存耗尽时按照任务的执行时间升序排列，依次把内存中的状态写到本地磁盘。不过 Facebook 内部并没有开启这个特性，因为集群资源（TB 级的内存）足够支撑用户的使用场景，全内存计算也更加能保证查询的执行时间。如果没有开启 spilling 特性，那 Presto 将会采用预留内存池的策略。这个策略的大意是把内存池分为通用（general）和预留（reserved）两种，当一个 worker 节点的通用内存池耗尽时将会把这个节点上占用最多内存的查询「晋升」到预留内存池，整个集群同一时间只允许一个查询晋升。后续的内存申请会优先满足这个晋升的查询，直到它执行完毕。这个策略当然会影响整体集群的效率，因此用户也可以选择直接杀掉查询。</p>
<p>最后是容错（fault tolerance）。作为一个多租户的分布式系统，优良的容错性是一个必不可少的需求。但是遗憾的是在这一点上 Presto 做得并不好，coordinator 依然是单点（一个题外话，Starburst Data 这家提供商业 Presto 版本的公司<a href="https://docs.starburstdata.com/latest/aws/high-availability.html" target="_blank" rel="noopener noreferrer">支持 coordinator HA</a>），worker 宕机将会导致所有运行在这个节点上的查询失败（社区有<a href="https://github.com/prestodb/presto/issues/9855" target="_blank" rel="noopener noreferrer">一</a><a href="https://github.com/prestosql/presto/issues/455" target="_blank" rel="noopener noreferrer">些</a> issue 但是目前没有进展），Presto 非常依赖客户端自己去重试。Facebook 内部是通过外部的编排系统来确保集群的可用性，对于交互式分析和 ETL 场景有 standby 的 coordinator，A/B 测试和开发者／广告主分析场景部署了多活（multiple active）集群。监控系统将会识别不可用的节点自动从集群中移除，并在之后再重新加入集群。</p>
<p>在开发 Presto 的过程中，作者也总结了一些工程上的经验：</p>
<ul>
<li><strong>自适应胜过手动调优（Adaptiveness over configurability）</strong>：前面已经介绍了很多 Presto 自适应的特性，作者认为当面对一个多租户场景，且查询的特征千变万化的时候，自适应显得尤为重要。否则就需要人工去针对性地手动调优，这种方式在面对大规模的查询场景时是没法扩展的。</li>
<li><strong>非常轻松地监控（Effortless instrumentation）</strong>：Presto 作者相信可观察（observable）的系统设计是非常重要的，要允许工程师去了解和优化自己代码的性能。Presto 每个 worker 平均导出了约 10000 个监控指标，粒度细到了 operator 级别，并会聚合到 task 和 stage 级别。</li>
<li><strong>静态配置（Static configuration）</strong>：错误的配置可能会对系统性能造成非常大的影响，为了保证时刻对系统整体状态有一个清晰的了解，Presto 作者选择使用静态配置的方案而不是动态配置。</li>
<li><strong>垂直集成（Vertical integration）</strong>：这其实是一个要不要重复造轮子的问题，对于一个大型项目来说肯定会依赖很多基础库，那什么时候选择用开源实现，什么时候选择自研是一个需要认真思考的问题（当然类似 Google 这种只考虑自研的公司就没有这个困扰了）。Presto 作者倾向于在那些对性能和效率要求比较高的场景选择自研。</li>
</ul>
<p>最后是一个八卦。Presto 最早是由一批 Facebook 的员工开发，2018 年这批员工中的部分核心离职，全职建设 Presto 开源社区。2019 年 1 月 31 日<a href="http://www.prweb.com/releases/presto_software_foundation_launches_to_advance_presto_open_source_community/prweb16070792.htm" target="_blank" rel="noopener noreferrer">成立</a>「Presto Software Foundation」，并在 GitHub 上创建了新的组织 <a href="https://github.com/prestosql" target="_blank" rel="noopener noreferrer">PrestoSQL</a>。有趣的是在 2019 年 9 月 23 日 Facebook 联合多家公司<a href="https://www.linuxfoundation.org/press-release/2019/09/facebook-uber-twitter-and-alibaba-form-presto-foundation-to-tackle-distributed-data-processing-at-scale" target="_blank" rel="noopener noreferrer">成立</a>了一个新的基金会叫「Presto Foundation」，在 GitHub 上的组织叫 <a href="https://github.com/prestodb" target="_blank" rel="noopener noreferrer">PrestoDB</a>。按照 Presto 作者的<a href="https://github.com/prestosql/presto/issues/380" target="_blank" rel="noopener noreferrer">说法</a>，他们在成立 Presto Software Foundation 之后其实是有邀请过 Facebook 加入的，但是显然对方拒绝了这个邀请。于是你会发现目前在开源社区有两个版本的 Presto，并且项目名是一样的，不过为了便于区分一般还是分别叫做 PrestoSQL 和 PrestoDB。前者背后的商业公司主要是 Starburst Data，这家公司的 3 个 CTO 同时也是 Presto 的原始作者（是的，这家公司有 3 个 CTO）；后者背后的商业公司有 Facebook、Uber、Twitter、阿里巴巴、Alluxio 和 Ahana。为了不至于让用户混淆，Starburst Data 还在官网<a href="https://www.starburstdata.com/prestosql-and-prestodb" target="_blank" rel="noopener noreferrer">比较</a>了这两个版本的 Presto。目前公有云厂商提供的产品中，<a href="https://docs.aws.amazon.com/athena/latest/ug/presto-functions.html" target="_blank" rel="noopener noreferrer">AWS Athena</a>、<a href="https://help.aliyun.com/document_detail/169871.html" target="_blank" rel="noopener noreferrer">阿里云 DLA</a> 都是基于 PrestoDB 开发的 serverless 产品，<a href="https://docs.aws.amazon.com/emr/latest/ReleaseGuide/emr-release-components.html" target="_blank" rel="noopener noreferrer">AWS EMR</a> 两种 Presto 都支持，<a href="https://cloud.google.com/dataproc/docs/concepts/versioning/dataproc-release-1.5" target="_blank" rel="noopener noreferrer">Google Dataproc 1.5</a>、<a href="https://help.aliyun.com/document_detail/132036.html?#title-fm0-jq8-sog" target="_blank" rel="noopener noreferrer">阿里云 EMR 3.25.0</a> 以后默认集成的是 PrestoSQL，<a href="https://cloud.tencent.com/document/product/589/20279" target="_blank" rel="noopener noreferrer">腾讯云 EMR</a> 默认集成的是 PrestoDB。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="spark-architecture-shuffle">Spark Architecture: Shuffle<a href="https://maybe.news/issues/6#spark-architecture-shuffle" class="hash-link" aria-label="Direct link to Spark Architecture: Shuffle" title="Direct link to Spark Architecture: Shuffle">​</a></h2>
<p><a href="https://0x0fff.com/spark-architecture-shuffle" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>要理解什么是 shuffle 就得先了解什么是 MapReduce，自从 2004 年 Google 那篇惊世骇俗的介绍 MapReduce 的<a href="https://research.google/pubs/pub62" target="_blank" rel="noopener noreferrer">论文</a>发表以来，大数据的生态就被彻底改变了（并沿用至今）。基于这样一个简单的编程模型实现了各种复杂的计算逻辑，但也存在一些「问题」，shuffle 就是其中一个。当 map 任务完成以后，数据需要根据 partition 策略重新分配到不同的 reduce 任务中，这个过程即称为 shuffle。这篇文章详细介绍了 Spark 历史上各种 shuffle 方案是怎么实现的。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="cosco-an-efficient-facebook-scale-shuffle-service">Cosco: An Efficient Facebook-Scale Shuffle Service<a href="https://maybe.news/issues/6#cosco-an-efficient-facebook-scale-shuffle-service" class="hash-link" aria-label="Direct link to Cosco: An Efficient Facebook-Scale Shuffle Service" title="Direct link to Cosco: An Efficient Facebook-Scale Shuffle Service">​</a></h2>
<p><a href="https://databricks.com/session/cosco-an-efficient-facebook-scale-shuffle-service" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>接上一篇文章，这是 Facebook 在 2018 年的 Spark+AI Summit 上的一个分享，介绍了他们实现的一个外部 shuffle 服务 Cosco，可以同时用于 Hive 和 Spark 任务。当时已经在 90%+ 的 Hive 任务上使用，并在生产环境运行 1 年以上，Spark 任务也在逐渐推广中。为什么要开发一个外部 shuffle 服务呢？Facebook 列举了一些他们当时面临的问题，比如单次 shuffle 需要交换的数据量级是 PiB 级，总共有 10 万个 mapper、1 万个 reducer，3 倍的写放大（shuffle 1 PiB 的数据实际要写 3 PiB 到磁盘），平均 IO 大小只有 200 KiB。这些都是促使他们开发 Cosco 的原因（当然不是所有公司都会遇到），另一个好处是 executor 变成了无状态，对于动态伸缩更加友好。如果对 Cosco 有兴趣还可以继续看一看他们在 2019 年的 Spark+AI Summit 上做的<a href="https://databricks.com/session_na20/flash-for-apache-spark-shuffle-with-cosco" target="_blank" rel="noopener noreferrer">后续分享</a>。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="federated-learning-collaborative-machine-learning-without-centralized-training-data">Federated Learning: Collaborative Machine Learning without Centralized Training Data<a href="https://maybe.news/issues/6#federated-learning-collaborative-machine-learning-without-centralized-training-data" class="hash-link" aria-label="Direct link to Federated Learning: Collaborative Machine Learning without Centralized Training Data" title="Direct link to Federated Learning: Collaborative Machine Learning without Centralized Training Data">​</a></h2>
<p><a href="https://ai.googleblog.com/2017/04/federated-learning-collaborative.html" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>传统机器学习中的优化算法（例如 <a href="https://en.wikipedia.org/wiki/Stochastic_gradient_descent" target="_blank" rel="noopener noreferrer">SGD</a>）是将大规模数据集分布式运行在多个节点上，这需要低延时、高吞吐地读取训练数据，因此数据一般都是提前收集到一个中心化存储里。但是在某些场景并不适合这样做，不管是因为数据量太大不易收集，还是出于数据隐私的考虑。因此 Google 提出了联邦学习（Federated Learning）的概念，这个词源于发表在 2017 年 AISTATS 会议上的一篇论文 <a href="https://arxiv.org/abs/1602.05629" target="_blank" rel="noopener noreferrer">Communication-Efficient Learning of Deep Networks from Decentralized Data</a>。联邦学习的大体思想就是在数据的生产端（例如你的手机）直接进行模型训练，经过汇总以后把对模型的更新数据发送到服务端，服务端再把其它客户端上传的更新数据一起汇总生成一个新的模型，最后下发这个新模型到所有客户端。可以看到整个过程中训练数据依然保留在客户端，并不需要上传。如果你在 Android 系统中使用 Gboard 这个 app，那其实你已经参与到联邦学习的过程中了，当然只会在当你的手机空闲并且连接电源和 Wi-Fi 的时候才会进行。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="分布式文件系统架构对比">分布式文件系统架构对比<a href="https://maybe.news/issues/6#%E5%88%86%E5%B8%83%E5%BC%8F%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F%E6%9E%B6%E6%9E%84%E5%AF%B9%E6%AF%94" class="hash-link" aria-label="Direct link to 分布式文件系统架构对比" title="Direct link to 分布式文件系统架构对比">​</a></h2>
<p><a href="https://juicefs.com/blog/cn/posts/distributed-filesystem-comparison" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>2003 年 Google 发表了 <a href="https://research.google/pubs/pub51" target="_blank" rel="noopener noreferrer">The Google File System</a> 论文，就像前面提到的 MapReduce 一样，从此对业界产生了非常深远的影响。这篇博客梳理了 GlusterFS、CephFS、GFS、HDFS、MooseFS 和 <a href="https://juicefs.com/" target="_blank" rel="noopener noreferrer">JuiceFS</a> 这几个分布式文件系统的架构设计。随着网络带宽的发展，在云计算和云原生的大趋势下，总的来说正逐步朝着存储计算分离的方向演进，这对于基础设施的架构也有着一定的要求。</p>]]></content>
        <category label="issue" term="issue"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Issue #5]]></title>
        <id>https://maybe.news/issues/5</id>
        <link href="https://maybe.news/issues/5"/>
        <updated>2020-07-21T14:08:28.000Z</updated>
        <summary type="html"><![CDATA[本期关键词：DynamicEmbedding、Generics again、Fiber、NFS、Kale、Spark dynamic allocation、Boiled Hippo]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>本期关键词：DynamicEmbedding、Generics again、Fiber、NFS、Kale、Spark dynamic allocation、Boiled Hippo</p>
</blockquote>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="dynamicembedding-extending-tensorflow-for-colossal-scale-applications">DynamicEmbedding: Extending TensorFlow for Colossal-Scale Applications<a href="https://maybe.news/issues/5#dynamicembedding-extending-tensorflow-for-colossal-scale-applications" class="hash-link" aria-label="Direct link to DynamicEmbedding: Extending TensorFlow for Colossal-Scale Applications" title="Direct link to DynamicEmbedding: Extending TensorFlow for Colossal-Scale Applications">​</a></h2>
<p><a href="https://arxiv.org/abs/2004.08366" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>在<a href="https://maybe.news/issues/1">第一期</a> Maybe News 中介绍了腾讯提出的解决 TensorFlow 中大规模稀疏特征模型训练的方案，本期的这篇论文来自 Google（准确说是 Google Smart Campaigns 团队）。作为发明 TensorFlow 的公司，Google 内部团队的设计思想值得借鉴。</p>
<p>这个系统被称之为 DynamicEmbedding（DE），名字简单直观，要解决的场景也是很多公司都遇到的如何动态维护 embedding。系统内部分为两个组件：DynamicEmbedding Master（DEM）和 DynamicEmbedding Worker（DEW），合起来叫做 DynamicEmbedding Service（DES）。DEM 负责处理所有客户端请求，包括 embedding 查找（lookup）、更新（update）等。DEW 负责 embedding 存储、梯度更新等，所有请求都来自 DEM。同时新增了几个 TensorFlow API，如 <code>dynamic_embedding_lookup()</code>、<code>compute_sampled_logits()</code>，这些 API 是整个系统的关键入口，任何模型在接入 DES 的时候都需要在特定的地方调用这些 API。以上设计看起来跟大部分公司的方案没有太大差别。</p>
<p>通过实现一个叫做 EmbeddingStore 的通用接口，DEW 后端支持对接多种类型的存储，例如 Protocol Buffers、GFS、Bigtable，比较巧妙地将大规模 embedding 存储时面临的扩展性和稳定性问题转移到了外部存储系统。当然因为多了一次网络请求是否会影响整体的训练效率这点有待商榷，论文中介绍 BigtableEmbedding 时提到会将数据同时存储到本地缓存和远端，猜测这里本地缓存的目的便是为了加速存储操作。</p>
<p>Embedding 更新这一步涉及到一些常用的梯度下降（gradient descent）算法，为了保持一致，DEW 内部实现了跟 TensorFlow 原生提供的优化器（optimizer）同样的逻辑，并且大部分代码是可以复用的。当训练数据时间跨度很大时（如数月或者数年），可能存在很多无效的特征或者一些需要特殊处理的特征。因此 DEW 在每次 embedding 更新的时候会同时统计这个 embedding 的更新频率，通过设定一个恰当的阈值来保证只有部分 embedding 会持久化到存储系统里，那些低频的数据便不会继续保存。除了统计频率这种方法，通过 bloom filter 也可以实现类似的效果。</p>
<p>Serving 的时候因为 embedding 都已经存储到了外部系统，所以 DEW 就没有必要存在了，只需要在本地部署 DEM 负责处理读请求。为了提升推理的性能，本地缓存肯定是少不了的，同时批量处理查询请求也是非常重要的。</p>
<p>实验评估阶段首先比较了和原生 TensorFlow 训练同样的模型、同样的超参是否会有指标上的差异，模型选择的是 Word2Vec，梯度下降算法选择的是 SGD、Adagrad 和 Momentum。从最终训练的 loss 上看几乎没有差别，说明 DE 系统不会对模型质量有影响。</p>
<p>接着测试了字典（dictionary）大小对模型精度（accuracy）的影响，理论上 DE 系统其实是不限定字典大小的，从实验的两个模型 Word2Vec 和 Sparse2Seq 上来看也的确是字典大小越大模型精度越高。</p>
<p>然后是评测模型训练时两个重要的系统指标：集群总的内存占用和每秒训练的 global steps（GSS）。分别测试了三个模型：Word2Vec，Image2Lable 和 Seq2Seq。在使用原生的 TensorFlow 时集群内存占用会随着 worker 数量的增大而显著增长（在 Word2Vec 模型中尤为明显），相比之下 DE 系统的内存占用只跟 embedding 的总大小有关，与 DEW 的数量无关。之所以有这样的差异也是因为原生的 TensorFlow 会在不同 worker 间重复存储 embedding 数据。GSS 的对比上两者的加速比都差不多，但是总体上 DE 还是会更优。</p>
<p>最后论文中详细介绍了 DE 在 Google Smart Campaigns 产品中的一个重要应用：给广告主自动推荐投放的关键词。这是一个叫做 Sparse2Label 的模型，输出即是推荐的关键词（label）。这个模型带来的最大变化是以前需要针对每一种语言训练一个单独的模型，而现在只需要一个模型即可。通过对比一些核心指标（如 CTR），DE 推荐的关键词都明显更好。整个模型也是随着时间不断增长的，截止 2020 年 2 月这个模型的参数量已经达到了 1249 亿个，如果每个参数按 4 字节算的话模型大小差不多为 465 GiB（其实比想象中小）。</p>
<p>另一个更难评估的指标是用户搜索的关键词（query）与广告投放的关键词之间的关联度，很多时候两者之间并不是完全匹配的。作者是通过人工评估 38 万个样本的方式来解决的，每个样本都会有 5 个人类进行打分，分数区间从 -100 到 100，越高越匹配，然后计算这 5 个分数的平均值作为这个样本的最终分数。大于等于 50 分的样本认为是好（good）的样本，小于等于 0 分的则认为是不好（bad）的样本，前者除以后者被称作 GB ratio，这个比率越大越好。每个推荐的关键词都会同时有一个置信值（也就是网页和关键词 embedding 之间的 cosine 距离），从评测结果上来看当这个置信值大于 0.7 时，不好的样本量将会显著减少。实际生产环境收集的数据也印证了 DE 系统推荐的关键词是 GB ratio 最高的。</p>
<p>总结一下 DE 系统解决了原生 TensorFlow 在大规模 embedding 模型训练时效率低下（甚至不可用）的问题，短期内这个系统估计也不会开源或者合并到上游。目前可以期待的还是腾讯的方案，他们已经提交了<a href="https://github.com/tensorflow/tensorflow/pull/41371" target="_blank" rel="noopener noreferrer">代码</a>到 TensorFlow 社区。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-next-step-for-generics">The Next Step for Generics<a href="https://maybe.news/issues/5#the-next-step-for-generics" class="hash-link" aria-label="Direct link to The Next Step for Generics" title="Direct link to The Next Step for Generics">​</a></h2>
<p><a href="https://blog.golang.org/generics-next-step" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>在<a href="https://maybe.news/issues/2">第二期</a> Maybe News 中曾经介绍过 Go 语言开发者关于泛型设计的一些思考，近期 Ian Lance Taylor 又和社区同步了一下最新进展。最大的变化就是去掉了 contract 这个新增的概念，改为复用 interface。同时创建了一个新的 <a href="https://go2goplay.golang.org/" target="_blank" rel="noopener noreferrer">playground</a>，可以方便大家试验泛型代码。如果最新的这一版设计社区没有太大异议的话，乐观估计将会在 Go 1.17 加入泛型特性，也就是在 2021 年 8 月左右。当然最终实现这个目标还是有很多的不确定性，特别是当前疫情对于全球影响的情况下。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="fiber-distributed-computing-for-ai-made-simple">Fiber: Distributed Computing for AI Made Simple<a href="https://maybe.news/issues/5#fiber-distributed-computing-for-ai-made-simple" class="hash-link" aria-label="Direct link to Fiber: Distributed Computing for AI Made Simple" title="Direct link to Fiber: Distributed Computing for AI Made Simple">​</a></h2>
<p><a href="https://eng.uber.com/fiberdistributed" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>分布式计算在 AI 领域的需求一直都很强烈，但分布式计算不仅仅是将单机迁移到多机这样就足够了，还需要考虑如：易用性（降低用户从单机迁移的成本）、稳定性（自动容错）、弹性伸缩（和底层资源调度层配合）、线性加速（横向扩展多少机器就能带来多少性能提升）。Uber 和 OpenAI 共同开发的 Fiber 框架便是尝试解决以上问题的一个例子，从 Fiber 的<a href="https://arxiv.org/abs/2003.11164" target="_blank" rel="noopener noreferrer">论文</a>能看到这个框架最初设计面向的是强化学习（Reinforcement Learning）场景，在这个领域有很多类似的框架，比如 Google 的 <a href="https://github.com/google/dopamine" target="_blank" rel="noopener noreferrer">Dopamine</a>、Facebook 的 <a href="https://github.com/facebookresearch/ReAgent" target="_blank" rel="noopener noreferrer">ReAgent</a>、UC Berkeley 的 <a href="https://github.com/ray-project/ray" target="_blank" rel="noopener noreferrer">Ray</a>。自动容错和弹性伸缩这两个特性又让我联想到蚂蚁金服的 <a href="https://github.com/sql-machine-learning/elasticdl" target="_blank" rel="noopener noreferrer">ElasticDL</a> 和才云的 <a href="https://github.com/caicloud/ftlib" target="_blank" rel="noopener noreferrer">FTLib</a>。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-impact-of-slow-nfs-on-data-systems">The impact of slow NFS on data systems<a href="https://maybe.news/issues/5#the-impact-of-slow-nfs-on-data-systems" class="hash-link" aria-label="Direct link to The impact of slow NFS on data systems" title="Direct link to The impact of slow NFS on data systems">​</a></h2>
<p><a href="https://engineering.linkedin.com/blog/2020/the-impact-of-slow-nfs-on-data-systems" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>LinkedIn 分享了他们使用 NFS 进行数据库备份时遇到的性能问题，因为备份进程和数据库进程是一起部署的，因此这个问题还间接影响到了在线业务的稳定性。整个问题分析过程清晰易懂，还能顺便复习一下大学里学习的计算机网络和操作系统的一些知识。但问题的根源 NFS 服务的性能为什么这么差还是没有特别好的解决方案，可能在这个场景里 NFS 就不是特别好的选择吧。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="kubeflow--kale-simplify-building-better-ml-pipelines-with-automatic-hyperparameter-tuning">Kubeflow &amp; Kale simplify building better ML Pipelines with automatic hyperparameter tuning<a href="https://maybe.news/issues/5#kubeflow--kale-simplify-building-better-ml-pipelines-with-automatic-hyperparameter-tuning" class="hash-link" aria-label="Direct link to Kubeflow &amp; Kale simplify building better ML Pipelines with automatic hyperparameter tuning" title="Direct link to Kubeflow &amp; Kale simplify building better ML Pipelines with automatic hyperparameter tuning">​</a></h2>
<p><a href="https://medium.com/kubeflow/kubeflow-kale-simplify-building-better-ml-pipelines-with-automatic-hyperparameter-tuning-5821747f4fcb" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>Jupyter Notebooks 是当下数据科学家或者算法工程师日常工作非常重要的一个组件，交互式的界面加上即时的代码运行反馈极大地提升了开发效率。但是如果要将机器学习任务提交到集群中运行往往还得依靠类似 Kubeflow Pipelines 这种 DAG 管理及调度组件，Kubeflow Pipelines 有一套基于 Python API 的语法，因此用户需要再重新定义一个独立的 pipeline。有没有办法直接将 notebook 中已经验证过的代码自动转换成 pipeline 并提交到集群呢？<a href="https://kubeflow-kale.github.io/" target="_blank" rel="noopener noreferrer">Kale</a>（<strong>K</strong>ubeflow <strong>A</strong>utomated Pipe<strong>L</strong>ines <strong>E</strong>ngine）即是为了解决这个问题而诞生，它是一个能够将 Jupyter Notebooks 自动转换为 Kubeflow Pipelines 的工具。在最新的 0.5 版本中 Kale 新增了对 <a href="https://github.com/kubeflow/katib" target="_blank" rel="noopener noreferrer">Katib</a> 的集成，后者是进行自动超参调优（Hyperparameter Tuning）和神经网络架构搜索（Neural Architecture Search）的组件。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="googlecloudplatformspark-on-k8s-operator-976-add-support-for-dynamic-allocation-via-shuffle-tracking">GoogleCloudPlatform/spark-on-k8s-operator #976: Add support for dynamic allocation via shuffle tracking<a href="https://maybe.news/issues/5#googlecloudplatformspark-on-k8s-operator-976-add-support-for-dynamic-allocation-via-shuffle-tracking" class="hash-link" aria-label="Direct link to GoogleCloudPlatform/spark-on-k8s-operator #976: Add support for dynamic allocation via shuffle tracking" title="Direct link to GoogleCloudPlatform/spark-on-k8s-operator #976: Add support for dynamic allocation via shuffle tracking">​</a></h2>
<p><a href="https://github.com/GoogleCloudPlatform/spark-on-k8s-operator/pull/976" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>Spark 3.0 为<a href="http://spark.apache.org/docs/latest/job-scheduling.html#dynamic-resource-allocation" target="_blank" rel="noopener noreferrer">动态资源分配</a>（dynamic resource allocation）新增了 shuffle tracking 特性（默认关闭），具体实现可以查看 <a href="https://issues.apache.org/jira/browse/SPARK-27963" target="_blank" rel="noopener noreferrer">SPARK-27963</a>。当使用动态资源分配时用户需要预先设定诸如初始、最小和最大 executor 数量这样的参数，之后 Spark 运行时会根据当前任务排队时间和 executor 空闲时间这些指标去创建或者销毁 executor。对于有状态的 executor（如 shuffle 时存储到磁盘的数据、cache 到内存和磁盘的数据）会有一些特殊的策略防止错误回收资源，过去的做法是使用外部 shuffle 服务。开启 shuffle tracking 以后就不再依赖外部 shuffle 服务，而是设置一个 executor 持有 shuffle 数据的超时时间。过去 Spark 的 K8s 模式不支持外部 shuffle 服务，有了这个新的特性以后使得动态资源分配在 K8s 模式上成为可能。spark-on-k8s-operator 项目近期也支持了这个特性，可以直接通过 <a href="https://github.com/GoogleCloudPlatform/spark-on-k8s-operator/blob/master/docs/user-guide.md#dynamic-allocation" target="_blank" rel="noopener noreferrer">YAML 配置</a>来开启。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="boiled-hippo">Boiled Hippo<a href="https://maybe.news/issues/5#boiled-hippo" class="hash-link" aria-label="Direct link to Boiled Hippo" title="Direct link to Boiled Hippo">​</a></h2>
<p><a href="https://spacefruityrecords.bandcamp.com/album/boiled-hippo-2" target="_blank" rel="noopener noreferrer">[Bandcamp]</a> <a href="https://music.163.com/#/album?id=91278378" target="_blank" rel="noopener noreferrer">[网易云音乐]</a> <a href="https://www.xiami.com/album/1ttwrEdcce1" target="_blank" rel="noopener noreferrer">[虾米]</a></p>
<p>本期最后推荐一张来自我的一个好朋友的唱片，Boiled Hippo 是一支北京的迷幻摇滚乐队，经过多年的演出积累终于在今年发行了乐队的第一张同名专辑。虽说是迷幻摇滚，但如果从旋律上讲绝对是非常「好听」的。如果你有兴趣购买实体唱片（黑胶、磁带、CD 都有），目前可以在北京的 fRUITYSPACE、fRUITYSHOP、独音唱片，上海的 Daily Vinyl，金华的 Wave 这几个地方购买。</p>]]></content>
        <category label="issue" term="issue"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Issue #4]]></title>
        <id>https://maybe.news/issues/4</id>
        <link href="https://maybe.news/issues/4"/>
        <updated>2020-06-17T14:07:52.000Z</updated>
        <summary type="html"><![CDATA[本期关键词：AliGraph、Monorepo、Docker image、Go interface、Guitar tab]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>本期关键词：AliGraph、Monorepo、Docker image、Go interface、Guitar tab</p>
</blockquote>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="aligraph-a-comprehensive-graph-neural-network-platform">AliGraph: A Comprehensive Graph Neural Network Platform<a href="https://maybe.news/issues/4#aligraph-a-comprehensive-graph-neural-network-platform" class="hash-link" aria-label="Direct link to AliGraph: A Comprehensive Graph Neural Network Platform" title="Direct link to AliGraph: A Comprehensive Graph Neural Network Platform">​</a></h2>
<p><a href="https://dl.acm.org/doi/10.1145/3292500.3340404" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>AliGraph 是阿里巴巴团队研发的 GNN（Graph Neural Network）分布式训练框架（虽然标题里是「平台」但感觉还算不上），论文发表在 KDD 2019 和 PVLDB 2019。</p>
<p>论文开篇便提出了当下 GNN 模型训练的 4 个挑战：</p>
<ol>
<li>如何提高大规模图模型的训练效率及优化空间占用？</li>
<li>怎样优雅地将异构（heterogeneous）信息组合到一个统一的 embedding 结果中？</li>
<li>如何将结构化的拓扑（topological）信息与非结构化的属性（attribute）信息统一来共同定义那些需要保留的信息？</li>
<li>如何设计一个高效的增量更新动态图的 GNN 方法？</li>
</ol>
<p>后面的篇章便是详细介绍 AliGraph 如何解决以上这 4 个问题。框架从上至下整体分为 3 层：算子（operator）、采样（sampling）、存储（storage）。算子层包含常见的 GNN 运算操作，采样层包含几种预设的采样算法，存储层主要关注如何高效对大规模图进行分布式存储。在这 3 层基础之上可以实现任意的 GNN 算法以及应用。</p>
<p>存储层因为是要解决一个图的分布式存储问题，因此首先要将图进行分割（partition）。AliGraph 内置了 4 种图分割算法：<a href="https://dm.kaist.ac.kr/kse625/resources/metis.pdf" target="_blank" rel="noopener noreferrer">METIS</a>、<a href="https://www.usenix.org/conference/osdi12/technical-sessions/presentation/gonzalez" target="_blank" rel="noopener noreferrer">顶点切割和边切割</a>、<a href="https://dl.acm.org/doi/10.1145/2503210.2503293" target="_blank" rel="noopener noreferrer">2D 分割</a>、<a href="https://dl.acm.org/doi/10.1145/2339530.2339722" target="_blank" rel="noopener noreferrer">流式分割</a>。这 4 种算法分别适用于不同的场景，METIS 适合处理稀疏（sparse）的图，顶点切割和边切割适合密集（dense）的图，2D 分割适合 worker 数量固定的场景，流式分割通常应用在边（edge）频繁更新的图。用户需要根据自己的需求选择恰当的分割算法，当然也可以通过插件的形式自己实现。</p>
<p>另一个存储层关心的问题是如何将图结构和属性（attribute）共同存储。这里讲的图结构即顶点和边的信息，这是最主要的图数据。同时每个顶点也会附加一些独特的属性，例如某个顶点表示一个用户，那附加在这个用户上面的属性就是类似性别、年龄、地理位置这样的信息。如果直接将属性信息和图结构一起存储会造成非常大的空间浪费，因为从全局角度看同一种类型的顶点的属性是高度重合的。并且属性与图结构的大小差异也非常明显，一个顶点 ID 通常占用 8 字节，但是属性信息的大小从 0.1KB 到 1KB 都有可能 。因此 AliGraph 选择将属性信息单独存储，通过两个单独的索引分别存储顶点和边的属性，而图结构中只存储属性索引的 ID。这样设计的好处自然是显著降低了存储所需的空间，但代价就是降低了查询性能，因为需要频繁访问索引来获取属性信息。AliGraph 选择增加一层 LRU 缓存的方式对查询性能进行优化。</p>
<p>存储层关心的最后一个问题也是跟查询性能有关。在图算法中一个顶点的邻居（neighbor）是非常重要的信息，邻居可以是直接（1 跳）的也可以是间接（多跳）的，由于图被分割以后本地只会存储直接的邻居，当需要访问间接邻居的时候就必须通过网络通信与其它存储节点进行交互，这里的网络通信代价在大规模图计算中是不容忽视的。解决思路也很直接，即在每个节点本地缓存顶点的间接邻居，但要缓存哪些顶点的邻居，要缓存几个邻居是需要仔细考量的问题。AliGraph 没有使用目前常见的一些缓存算法（如 LRU），而是提出了一种新的基于顶点重要性（importance）的算法来对间接邻居进行缓存。在有向图中计算一个顶点重要性的公式是 <code>入邻居的个数 / 出邻居的个数</code>，注意这里的邻居个数同样可以是直接的或者间接的。当这个公式的计算结果大于某个用户自定义的阈值时即认为这是一个「重要」的顶点。从实际测试中得出的经验值是通常只需要计算两跳（hop）的邻居个数就够了，而阈值本身不是一个特别敏感的数值，设置在 0.2 左右是对于缓存成本和效果一个比较好的平衡。选出所有重要的顶点以后，最终会在所有包含这些顶点的节点上缓存 <em>k</em> 跳的出邻居（out-neighbor）。</p>
<p>GNN 算法通常可以总结为 3 个步骤：采样（sample）某个顶点的邻居，聚合（aggregate）这些采样后的顶点的 embedding，将聚合后的 embedding 与顶点自己的进行合并（combine）得到新的 embedding。这里可以看到采样是整个流程中的第一步，采样的效果也会直接影响后续计算的 embedding 结果。AliGraph 抽象了 3 类采样方法：遍历采样（traverse）、近邻采样（neighborhood）和负采样（negative）。遍历采样是从本地子图中获取数据；近邻采样对于 1 跳的邻居可以从本地存储中获取，多跳的邻居如果在缓存中就从缓存中获取否则就请求其它节点；负采样通常也是从本地挑选顶点，在某些特殊情况下有可能需要从其它节点挑选。</p>
<p>在采样完邻居顶点以后就是聚合这些顶点的 embedding，常用的聚合方法有：element-wise mean、max-pooling 和 LSTM。最后是将聚合后的 embedding 与顶点自己的进行合并，通常就是将这两个 embedding 进行求和。为了加速聚合和合并这两个算子的计算，AliGraph 应用了一个物化（materialization）中间向量的策略，即每个 mini-batch 中的所有顶点共享采样的顶点，同样的聚合和合并操作的中间结果也共享，这个策略会大幅降低计算成本。</p>
<p>在最后的评估环节用了两个来自淘宝的数据集，两个数据集之间只有大小的区别，大数据集是小数据集的 6 倍左右。大数据集的基础数据是：4.8 亿个用户顶点，968 万个商品顶点，65.8 亿条用户到商品的边，2.3 亿条商品到商品的边，用户平均有 27 个属性，商品平均有 32 个属性。当使用 200 个 worker（节点配置论文中没有说明）时大数据集只需要 5 分钟即可将整个图构建完毕，相比之下以往的一些方案可能需要耗费数小时。基于顶点重要性的缓存算法相比 LRU 这些传统算法也是明显更优。3 类采样方法的性能评估结果从几毫秒到几十毫秒不等，但最长也不超过 60 毫秒，并且采样性能与数据集大小不太相关。聚合和合并算子相比传统的实现也有一个数量级的性能提升，这主要得益于前面提到的物化策略。</p>
<p>AliGraph 目前已经开源（一部分？）但是换了一个名字叫做 <a href="https://github.com/alibaba/graph-learn" target="_blank" rel="noopener noreferrer">graph-learn</a>，跟大多数深度学习框架一样，底层使用 C++ 语言实现并提供 Python 语言的 API，目前支持 TensorFlow，未来会支持 PyTorch。有意思的是刚刚开源不久就有人提了一个 <a href="https://github.com/alibaba/graph-learn/issues/16" target="_blank" rel="noopener noreferrer">issue</a> 希望能够跟另外几个流行的 GNN 框架进行比较，但是项目成员的回答比较含糊。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="building-ubers-go-monorepo-with-bazel">Building Uber’s Go Monorepo with Bazel<a href="https://maybe.news/issues/4#building-ubers-go-monorepo-with-bazel" class="hash-link" aria-label="Direct link to Building Uber’s Go Monorepo with Bazel" title="Direct link to Building Uber’s Go Monorepo with Bazel">​</a></h2>
<p><a href="https://eng.uber.com/go-monorepo-bazel" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>Uber 应该是除了 Google 以外很早选择在后端服务中大规模使用 Go 语言的公司之一，并贡献了很多著名的 Go 语言项目（如 <a href="https://github.com/uber-go/zap" target="_blank" rel="noopener noreferrer">zap</a>、<a href="https://github.com/jaegertracing/jaeger" target="_blank" rel="noopener noreferrer">Jaeger</a>）。早在 2017 年，Uber 的 Android 和 iOS 团队就已经只使用一个代码仓库进行开发，俗称 monorepo。实践 monorepo 最著名的公司应该还是 Google，有兴趣可以看看 <a href="https://research.google/pubs/pub45424" target="_blank" rel="noopener noreferrer">Why Google Stores Billions of Lines of Code in a Single Repository</a> 这篇文章。现在后端团队也开始采用 monorepo 来管理 Go 语言项目，但是和客户端团队的不同之处在于没有用 <a href="https://buck.build/" target="_blank" rel="noopener noreferrer">Buck</a> 而是用 <a href="https://bazel.build/" target="_blank" rel="noopener noreferrer">Bazel</a>（前者是 Facebook 开源，后者是 Google 开源）。这篇文章介绍了在 monorepo 中将 Go 语言和 Bazel 结合遇到的一些问题。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="optimising-docker-layers-for-better-caching-with-nix">Optimising Docker Layers for Better Caching with Nix<a href="https://maybe.news/issues/4#optimising-docker-layers-for-better-caching-with-nix" class="hash-link" aria-label="Direct link to Optimising Docker Layers for Better Caching with Nix" title="Direct link to Optimising Docker Layers for Better Caching with Nix">​</a></h2>
<p><a href="https://grahamc.com/blog/nix-and-layered-docker-images" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>恐怕大多数时候接触容器是从构建一个 Docker 镜像开始的，这一步往往也是最容易被忽视的。为什么我的镜像这么大？为什么每次拉取镜像都要从头开始？这些问题可能会随着使用时间越来越长逐渐浮现出来，要回答它们需要了解 Docker 镜像的一个核心概念「layer」，本质上你在 <code>Dockerfile</code> 里写的每一行命令都会生成一个 layer，一个镜像便是由很多 layer 构成。Layer 之间是有层级关系的，当拉取镜像时如果本地已经存在某个 layer 就不会重复拉取。在传统的 Linux 发行版中安装依赖时 Docker 是不知道具体有哪些文件被修改的，而 <a href="https://github.com/NixOS/nix" target="_blank" rel="noopener noreferrer">Nix</a> 这个特殊的包管理器采用了不一样的设计思路使得安装依赖这件事情对于 Docker layer 缓存非常友好。衍生阅读推荐 Jérôme Petazzoni 写的关于如何减少镜像大小的<a href="https://www.ardanlabs.com/blog/2020/02/docker-images-part1-reducing-image-size.html" target="_blank" rel="noopener noreferrer">系列文章</a>。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="proposal-permit-embedding-of-interfaces-with-overlapping-method-sets">Proposal: Permit embedding of interfaces with overlapping method sets<a href="https://maybe.news/issues/4#proposal-permit-embedding-of-interfaces-with-overlapping-method-sets" class="hash-link" aria-label="Direct link to Proposal: Permit embedding of interfaces with overlapping method sets" title="Direct link to Proposal: Permit embedding of interfaces with overlapping method sets">​</a></h2>
<p><a href="https://github.com/golang/proposal/blob/master/design/6977-overlapping-interfaces.md" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>Interface 是 Go 语言一个重要的特性，类似很多其它语言中的概念，接口定义好以后是需要通过 struct 来实现的。但不同之处又在于 struct 不需要显式声明实现了什么 interface，只要满足 interface 中定义的接口就行，这个关键设计使得 Go 语言的 interface 使用场景可以非常灵活。跟 struct 一样 interface 也允许嵌套，也就是可以在一个 interface 定义中嵌套另一个 interface。如果同时嵌套了多个 interface，并且这些 interface 之间有重复的接口在编译时是会报错的。实际开发过程中为了规避这个限制可能需要修改 interface 的定义，这对于开发者来说不太友好。上面这个提案允许开发者在不修改代码的情况下避开这个限制，目前这个功能已经在 <a href="https://golang.org/doc/go1.14#language" target="_blank" rel="noopener noreferrer">Go 1.14</a> 中发布。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="vextab">VexTab<a href="https://maybe.news/issues/4#vextab" class="hash-link" aria-label="Direct link to VexTab" title="Direct link to VexTab">​</a></h2>
<p><a href="https://github.com/0xfe/vextab" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>不管是音乐创作还是音乐演奏，乐谱都是一个必不可少的东西。还记得刚学吉他那会儿非常热衷的一件事情就是去网上搜集各种歌曲的六线谱，这些乐谱的格式从最朴素的纯文本到高级的 <a href="https://www.guitar-pro.com/" target="_blank" rel="noopener noreferrer">Guitar Pro</a> 格式都有。再后来开始学习扒歌，也面临把扒下来的谱子纪录下来的需求。虽然 Guitar Pro 很好但毕竟是一个收费软件，文件格式也是私有的。就像我更喜欢 Markdown 而不是直接用 Word 一样，一直希望能有一个类似的标记语言用于编写乐谱。VexTab 即是这样一个专门用于编写五线谱和六线谱的语言，也提供一个 JavaScript 库方便嵌入到网页中。有意思的是 VexTab 的作者同时也是 Google 的一名员工。</p>]]></content>
        <category label="issue" term="issue"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Issue #3]]></title>
        <id>https://maybe.news/issues/3</id>
        <link href="https://maybe.news/issues/3"/>
        <updated>2020-06-10T17:37:27.000Z</updated>
        <summary type="html"><![CDATA[本期关键词：OLAP、ByteKV、GPU share、DL paper、TensorFlow dev]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>本期关键词：OLAP、ByteKV、GPU share、DL paper、TensorFlow dev</p>
</blockquote>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="kudu-storage-for-fast-analytics-on-fast-data">Kudu: Storage for Fast Analytics on Fast Data<a href="https://maybe.news/issues/3#kudu-storage-for-fast-analytics-on-fast-data" class="hash-link" aria-label="Direct link to Kudu: Storage for Fast Analytics on Fast Data" title="Direct link to Kudu: Storage for Fast Analytics on Fast Data">​</a></h2>
<p><a href="https://kudu.apache.org/kudu.pdf" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p><a href="https://en.wikipedia.org/wiki/Online_analytical_processing" target="_blank" rel="noopener noreferrer">OLAP</a>（Online Analytical Processing）一直是大数据领域非常重要的应用场景，光有数据也不行，你得「分析」啊。自从有了 Hadoop，OLAP 的工具就一直在演变，从最早的裸写 MapReduce 任务，到 <a href="https://pig.apache.org/" target="_blank" rel="noopener noreferrer">Pig</a>、<a href="https://hive.apache.org/" target="_blank" rel="noopener noreferrer">Hive</a>、<a href="https://prestosql.io/" target="_blank" rel="noopener noreferrer">Presto</a>、<a href="https://impala.apache.org/" target="_blank" rel="noopener noreferrer">Impala</a>、<a href="https://druid.apache.org/" target="_blank" rel="noopener noreferrer">Druid</a>、<a href="https://clickhouse.tech/" target="_blank" rel="noopener noreferrer">ClickHouse</a>，以及今天要介绍的 <a href="https://kudu.apache.org/" target="_blank" rel="noopener noreferrer">Kudu</a>。一个明显的趋势是 OLAP 引擎在逐步朝着「去 Hadoop 化」和「实时化」发展，当然这些项目里最新的也已经是 2016 年发布的了，接下来会怎么变化还是个未知数。</p>
<p>先讲讲为什么会有类似 Kudu 这样的项目诞生。传统的 OLAP 引擎因为是构建在 HDFS 上的，要想分析数据首先得将数据存储到 HDFS 上，而这个过程（通常叫做 ETL）往往是比较耗时以及复杂的。同时由于 HDFS 天生不支持随机读写，为了弥补这个「缺陷」，有了 HBase 这样的项目。但 HBase 对于 OLAP 场景是不够友好的，因此往往需要把数据从 HBase 再导入到 HDFS 中，这个过程也可能比较耗时，维护成本也比较高。因此 Kudu 的目标是实现一个即支持随机读写（主要是写），又针对大批量查询进行优化的存储引擎。这种时候 HDFS 就显得很累赘了，这也是为什么越来越多引擎选择不依赖 HDFS 的缘故（Kudu 官网也在 FAQ 中专门<a href="https://kudu.apache.org/faq.html#why-doesnt-kudu-store-its-data-in-hdfs" target="_blank" rel="noopener noreferrer">解释</a>了为什么不用 HDFS）。当然并不是说 HDFS 就没用了，有很多数据还是非常静态的，对于实时性要求也不高，此时用 HDFS 是一种简单经济的选择。</p>
<p>这篇论文虽然介绍的是 Kudu 早期的一些设计思想，但基本上属于最核心的功能。跟很多分布式数据库一样，Kudu 也是受 <a href="https://research.google/pubs/pub39966" target="_blank" rel="noopener noreferrer">Spanner</a> 启发。系统架构上分为一个 Master 服务和若干 Tablet 服务。Master 负责维护元信息，包括 Tablet 节点和数据的。Tablet 服务则负责数据存储，每台节点上会有几十至数百个 tablet，每个 tablet 中包含了若干数据，最大可以达到几十 GB 的规模（这里你可以把 tablet 类比为很多别的系统中的 region 概念）。</p>
<p>跟很多关系数据库一样，Kudu 是有 table 的概念的。但跟很多 NoSQL 数据库不一样的地方是，强制用户必须显式定义 schema。Kudu 一个有意思的设计在于同时支持了 hash 和 range 这两种数据 partition 方法，而不像别的系统只支持其中一种（有关这两种 partition 的介绍可以看我之前的<a href="https://xiaogaozi.me/blog/2020/05/25/how-to-design-a-distributed-index-framework-part-5" target="_blank" rel="noopener noreferrer">一篇文章</a>）。这样设计的好处是即保留了 hash 的数据均匀分配特点，可以在一定程度上防止读写热点，又保留了 range 对于范围扫描的友好性。</p>
<p>Tablet 服务之间是通过 Raft 来进行数据复制，因此可以认为 Kudu 是一个保证强一致性的存储系统。值得注意的是 Kudu 的默认设置是 500 毫秒的心跳间隔以及 1.5 秒的选举超时，这个跟 Raft 论文推荐的时间相比长了不少（推荐的选举超时是 150~300 毫秒）。当集群扩容时，新节点将会首先进入 <code>PRE_VOTER</code> 状态，等到 log 追上以后再变成 <code>VOTER</code> 状态，这个设计也是 Raft 论文中建议的，不过论文中叫做 learner 或者 non-voting member。Master 服务虽然是单点设计（即状态不是分布式存储），但为了保障高可用也可以通过 Raft 实现多节点状态复制，只不过任意时间只能有一个节点工作。</p>
<p>Kudu 的数据存储引擎是完全自己设计的，没有直接用任何现有的引擎，虽然也能多少看出一些别的引擎的影子。关于这一点可以理解，OLAP 系统区别于 <a href="https://en.wikipedia.org/wiki/Online_transaction_processing" target="_blank" rel="noopener noreferrer">OLTP</a> （Online Transactional Processing）系统的最大不同即在于数据存储的形式，简单理解后者是行式（row-oriented）存储，而前者是列式（column-oriented）存储。著名的 <a href="https://parquet.apache.org/" target="_blank" rel="noopener noreferrer">Parquet</a> 就是广泛被用于 OLAP 场景的列式存储格式，Kudu 在实现上也复用了很多 Parquet 的代码。</p>
<p>每个 table 在存储级别会被分割为多个 RowSets，顾名思义每个 RowSets 是由很多行（row）组成，RowSets 之间不会有重复的数据，但主键的范围可能会交叉。</p>
<p>当有新的数据时会首先存储到内存中的 MemRowSets，底层实现是一个使用乐观锁（optimistic locking）的并发（concurrent）B 树。比较特别的一点是数据并不是一开始就按照列式进行存储，MemRowSets 中还是用的行式存储。当数据累积到一定程度 MemRowSets 就会持久化到磁盘上，称之为 DiskRowSets，每个 DiskRowSet 大小上限是 32MB。DiskRowSet 由两部分组成：基础数据（base data）和增量数据（delta stores）。</p>
<p>基础数据是列式格式，即每一列都单独连续存储，每一列内部又划分成了多个小的页（page），有一个 B 树根据行号索引了这些页。每一列可以由用户指定不同的编码（encoding）方法（如 dictionary encoding、bitshuffle、front coding），同时也可以使用通用的压缩算法对数据进行压缩（如 LZ4、gzip、bzip2），基于列的编码及数据压缩是列式存储非常大的一个特点。</p>
<p>增量数据也分为内存和磁盘两种形式。内存中的叫做 DeltaMemStores，这个跟 MemRowSets 的实现一样。磁盘中的叫做 DeltaFiles，是一个二进制类型的列块（column block）。不管是内存还是磁盘上的数据都会有一个额外的从 <code>(row_offset, timestamp)</code> 到 RowChangeList 的映射，<code>row_offset</code> 是某一行在一个 RowSet 中的偏移，RowChangList 是二进制编码以后的增量操作（如更新某一列、删除某一行）列表。同样的，DeltaMemStores 也会持久化到磁盘上变成 DeltaFiles。</p>
<p>这些增量数据会定期跟基础数据进行合并（compation），以防止过多的增量文件。同时 DiskRowSets 之间也会进行合并，目的是清理已经被删除的行以及减少 DiskRowSets 之间的主键交叉范围。</p>
<p>前面提到的将内存中的数据持久化到磁盘及对数据进行合并操作，都是由一组单独的后台任务来完成，但是什么时候执行什么操作是由一个调度器来控制的。有趣的是 Kudu 将调度器的逻辑抽象成了一个<a href="https://en.wikipedia.org/wiki/Knapsack_problem" target="_blank" rel="noopener noreferrer">背包问题</a>，只不过需要权衡的不是背包容量，而是 I/O 带宽。</p>
<p>Kudu 本身只提供编程语言级别的 API（如 Java、C++），而 OLAP 系统中常用的 SQL 需要配合其它项目来实现。Kudu 原生已经跟 Spark 和 Impala 集成，也就是说你可以在这两个系统中通过 SQL 来查询。</p>
<p>最后是性能评测。在 <a href="http://www.tpc.org/tpch" target="_blank" rel="noopener noreferrer">TPC-H</a> 数据集上与 Parquet 进行对比测试，Kudu 平均有 31% 的性能提升，尽管如此 Kudu 团队认为随着 Parquet 的迭代这个差距可能会逐渐缩小。在与 <a href="http://phoenix.apache.org/" target="_blank" rel="noopener noreferrer">Phoenix</a> 的对比测试中也有 16~187 倍的性能提升。最后与 HBase 进行的 <a href="https://github.com/brianfrankcooper/YCSB" target="_blank" rel="noopener noreferrer">YCSB</a> 测试是为了看看 Kudu 在 OLTP 场景的性能，虽然它本身并不是为 OLTP 场景而设计，结果上的确也是 HBase 表现更好，但 Kudu P99 6 毫秒的响应时间在某些时候也许可以代替 OLTP 系统。</p>
<p>Kudu 由 Cloudera 公司开发并于 2016 年正式发布，现已捐献给 Apache 基金会，整个系统使用 C++ 语言编写。</p>
<p>顺带说个题外话这两年炒得比较火的 <a href="https://en.wikipedia.org/wiki/Hybrid_transactional/analytical_processing" target="_blank" rel="noopener noreferrer">HTAP</a>（Hybrid Transactional/Analytical Processing），本质上是希望在一个引擎中同时适配 OLTP 和 OLAP 这两个场景。但在我看来就目前的技术现状这个愿景实现起来还是比较困难，软件工程界的名言<a href="https://en.wikipedia.org/wiki/No_Silver_Bullet" target="_blank" rel="noopener noreferrer">「没有银弹」</a>告诉我们不存在一个可以通吃的、完美的方案，因此 HTAP 目前更多还只是一个营销概念吧。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="字节跳动自研强一致在线-kv--表格存储实践">字节跳动自研强一致在线 KV &amp; 表格存储实践<a href="https://maybe.news/issues/3#%E5%AD%97%E8%8A%82%E8%B7%B3%E5%8A%A8%E8%87%AA%E7%A0%94%E5%BC%BA%E4%B8%80%E8%87%B4%E5%9C%A8%E7%BA%BF-kv--%E8%A1%A8%E6%A0%BC%E5%AD%98%E5%82%A8%E5%AE%9E%E8%B7%B5" class="hash-link" aria-label="Direct link to 字节跳动自研强一致在线 KV &amp; 表格存储实践" title="Direct link to 字节跳动自研强一致在线 KV &amp; 表格存储实践">​</a></h2>
<p><a href="https://mp.weixin.qq.com/s/jdPE9WClBuimIHVxJnwwUw" target="_blank" rel="noopener noreferrer">[上篇]</a> <a href="https://mp.weixin.qq.com/s/DvUBnWBqb0XGnicKUb-iqg" target="_blank" rel="noopener noreferrer">[下篇]</a></p>
<p><a href="https://github.com/cockroachdb/cockroach" target="_blank" rel="noopener noreferrer">又</a><a href="https://github.com/pingcap/tidb" target="_blank" rel="noopener noreferrer">又</a><a href="https://github.com/dgraph-io/dgraph" target="_blank" rel="noopener noreferrer">又</a><a href="https://kudu.apache.org/" target="_blank" rel="noopener noreferrer">又</a><a href="https://github.com/vesoft-inc/nebula" target="_blank" rel="noopener noreferrer">又</a>一个受 Spanner 启发的分布式存储（Google 功德无量！Jeff Dean 万寿无疆！），这次的项目来自字节跳动。关键词：range 分割、Raft、RocksDB、MVCC、分布式事务、SQL 层，看看这些也基本能对整体设计猜个八九不离十了，比较有价值的信息是学习学习字节跳动在他们的实践中的一些经验。项目使用 C++ 语言编写，目前没有开源。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="challenges-supporting-mig-in-kubernetes">Challenges Supporting MIG in Kubernetes<a href="https://maybe.news/issues/3#challenges-supporting-mig-in-kubernetes" class="hash-link" aria-label="Direct link to Challenges Supporting MIG in Kubernetes" title="Direct link to Challenges Supporting MIG in Kubernetes">​</a></h2>
<p><a href="https://docs.google.com/document/d/1Dxx5MwG_GiBeKOuMNwv4QbO8OqA7XFdzn7fzzI7AQDg" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>随着深度学习的蓬勃发展，GPU 共享逐渐成为了 K8s 社区的一个<a href="https://github.com/kubernetes/kubernetes/issues/52757" target="_blank" rel="noopener noreferrer">热门话题</a>。目前 NVIDIA 官方提供的<a href="https://github.com/NVIDIA/k8s-device-plugin" target="_blank" rel="noopener noreferrer">设备插件</a>可以申请的最小资源粒度还是 1 个 GPU，但很多时候资源是浪费的。为了提升 GPU 的资源利用率，社区已经出现了多种解决方案，例如分别来自<a href="https://github.com/AliyunContainerService/gpushare-scheduler-extender" target="_blank" rel="noopener noreferrer">阿里云</a>、<a href="https://github.com/tkestack/gpu-manager" target="_blank" rel="noopener noreferrer">腾讯云</a>以及 <a href="https://github.com/awslabs/aws-virtual-gpu-device-plugin" target="_blank" rel="noopener noreferrer">AWS</a> 的实现。现在 NVIDIA 官方终于在新一代的 Ampere 架构硬件上原生支持了共享，也就是标题中的 MIG（Multi-Instance GPUs）。这篇文档来自 NVIDIA 团队，首先介绍了当前是如何在 K8s 中管理 GPU 资源的，然后介绍了 MIG 的一些概念，最后提议了 4 个支持 MIG 的可能的解决方案。整体感觉 GPU 共享还是没有 CPU 灵活，不少地方设置了限制，但毕竟这是 NVIDIA 官方迈出的第一步。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="how-to-read-deep-learning-papers">How to read deep learning papers?<a href="https://maybe.news/issues/3#how-to-read-deep-learning-papers" class="hash-link" aria-label="Direct link to How to read deep learning papers?" title="Direct link to How to read deep learning papers?">​</a></h2>
<p><a href="https://www.reddit.com/r/MachineLearning/comments/gi3ihe/d_how_to_read_deep_learning_papers" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>Reddit 上一个有趣的讨论：如何阅读深度学习的论文？我们常常调侃机器学习就是在「炼丹」，没有人能解释为什么结果就是有效的，反正<a href="https://www.youtube.com/watch?v=YPN0qhSyWy8" target="_blank" rel="noopener noreferrer">「it just works」</a>。最高票的评论说你不需要接受论文中的每一个观点，只要把注意力集中在作者提供的证据并有选择性地调整你的想法就好了。正好前段时间前微软执行副总裁沈向洋博士做了一个主题名为<a href="https://www.bilibili.com/video/BV1df4y1m74k" target="_blank" rel="noopener noreferrer">「You are how you read」</a>的演讲，主要内容就是一些阅读论文的经验（有趣的是沈博士在几年前还写过一篇叫做<a href="https://www.linkedin.com/pulse/you-what-write-harry-shum" target="_blank" rel="noopener noreferrer">「You are what you write」</a>的博客）。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="farewell-tensorflow">Farewell, TensorFlow<a href="https://maybe.news/issues/3#farewell-tensorflow" class="hash-link" aria-label="Direct link to Farewell, TensorFlow" title="Direct link to Farewell, TensorFlow">​</a></h2>
<p><a href="https://mrry.github.io/2020/05/10/farewell-tensorflow.html" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>TensorFlow 核心开发者、Google Brain 团队的 Derek Murray 宣布离开，这位大哥在 GitHub 和 Stack Overflow 上都很活跃，如果你经常浏览社区应该对他的头像不陌生。在这篇告别文中 Murray 提到了很多 Google 内部帮助工程师解决问题的工具，并详细介绍了近期对 TensorFlow 底层运行时进行的一项性能优化的过程，在部分评测中可以提升 10% 的推理性能。这个优化目前已经合入 master，并将在 TensorFlow 2.3 发布。</p>]]></content>
        <category label="issue" term="issue"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Issue #2]]></title>
        <id>https://maybe.news/issues/2</id>
        <link href="https://maybe.news/issues/2"/>
        <updated>2020-06-02T09:25:45.000Z</updated>
        <summary type="html"><![CDATA[本期关键词：Raft、Generics、OAM、K8s Scheduling]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>本期关键词：Raft、Generics、OAM、K8s Scheduling</p>
</blockquote>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="in-search-of-an-understandable-consensus-algorithm-extended-version">In Search of an Understandable Consensus Algorithm (Extended Version)<a href="https://maybe.news/issues/2#in-search-of-an-understandable-consensus-algorithm-extended-version" class="hash-link" aria-label="Direct link to In Search of an Understandable Consensus Algorithm (Extended Version)" title="Direct link to In Search of an Understandable Consensus Algorithm (Extended Version)">​</a></h2>
<p><a href="https://raft.github.io/raft.pdf" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>终于有机会仔细阅读一遍 Raft 的论文，如果你还不了解 Raft 是什么可以看看我过去的一篇介绍分布式系统基础概念的<a href="https://xiaogaozi.me/blog/2020/05/25/how-to-design-a-distributed-index-framework-part-5" target="_blank" rel="noopener noreferrer">文章</a>。</p>
<p>Raft 为节点定义了三种状态：leader、follower 和 candidate（以及一个非正式状态 learner 或者叫做 non-voting member）。一个集群只会有 1 个 leader，其余节点都是 follower。Leader 负责处理所有的读写请求，如果请求 follower 会失败并告知客户端 leader 的地址。</p>
<p>每个节点都有一个自己的 log，log 中每个条目都有一个下标（index）。这个 log 基本算是 append-only 的，通常也需要持久化到可靠的存储上（例如磁盘）。当处理写请求时 leader 会首先更新自己的 log，然后通过 RPC 复制到其它节点，只要大多数（majority）节点更新成功 leader 就会认为这个请求已经 committed，此时会更新自己的状态机（state machine）并返回给客户端。如果 RPC 请求失败 leader 会不断重试直到成功。</p>
<p>如果出现异常，如 leader 宕机、网络故障等，就可能触发 leader 重新选举。选举过程是所有 follower 为 candidate 投票，只要获得多数票 candidate 就会升级为 leader。如果投票失败会继续新一轮选举，选举过程通常是毫秒级的。每一轮新的选举都会产生一个对应的 term（任期），Raft 在协议上保证了重新选举后的新 leader 一定是包含之前所有 term 已经 committed 的 log，这样就避免了新 leader 选举成功以后需要首先补上缺失的数据。</p>
<p>当集群需要伸缩时，leader 会首先将旧集群配置（configuration）和新集群配置合并到一起并通过 log 的形式复制到 follower。成功收到这个合并后配置的节点会用这个配置替代老的配置。一旦这个合并后的配置 committed，leader 就会创建一个只包含新配置的 log 继续复制到 follower。等到新的配置 committed，旧配置将不再生效，需要下线的节点也可以被安全关闭。</p>
<p>随着时间增长，log 的容量会越来越大，Raft 引入了快照（snapshot）机制，定期将 log 压缩到快照文件。这个快照文件同时也可以帮助新加入的节点快速补上缺失的数据。</p>
<p>总结一下 Raft 算法保证了以下几个属性始终成立：</p>
<ul>
<li><strong>Election Safety</strong>：在一个特定的任期最多只能有一个 leader 被选举出来</li>
<li><strong>Leader Append-Only</strong>：leader 永远不会覆盖或者删除 log 中的条目，只会追加新的条目。</li>
<li><strong>Log Matching</strong>：如果两份 log 同时包含一个具有相同任期数和下标的条目，那么这两份 log 中这个下标之前的所有条目都应该是一致的。</li>
<li><strong>Leader Completeness</strong>：如果某个任期中的一个 log 条目已经 committed，那么在之后任期中选举出的新 leader 一定包含这个条目。</li>
<li><strong>State Machine Safety</strong>：如果一个节点已经将一个给定下标的 log 条目更新到自己的状态机，那么其它节点上同样的下标一定不会是不同的条目，也就是说不会更新一个不同的条目到自己的状态机。</li>
</ul>
<p>更多有关 Raft 的信息可以查看它的<a href="https://raft.github.io/" target="_blank" rel="noopener noreferrer">官网</a>，强烈建议初次接触一致性协议的朋友看看网站上的动画演示，非常有助于建立一个形象直观的认知。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="scaling-raft">Scaling Raft<a href="https://maybe.news/issues/2#scaling-raft" class="hash-link" aria-label="Direct link to Scaling Raft" title="Direct link to Scaling Raft">​</a></h2>
<p><a href="https://www.cockroachlabs.com/blog/scaling-raft" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>作为前面介绍 Raft 的一篇衍生阅读，原始的 Raft 实现是将所有节点看作一个 group，这种设计在某些场景（例如集群规模很小）是可行的。但是当集群规模大到一定程度，或者类似 <a href="https://github.com/cockroachdb/cockroach" target="_blank" rel="noopener noreferrer">CockroachDB</a> 和 <a href="https://github.com/tikv/tikv" target="_blank" rel="noopener noreferrer">TiKV</a> 这种将数据划分为非常多的 range，多个 range 组成一个 Raft group 的场景（通常叫做 Multi-Raft），就会发现 Raft 的基础网络通信已经足以影响单节点的性能（比如过多的心跳请求）。因此社区已经针对这样的问题有了一些优化方案，比如 <a href="https://github.com/cockroachdb/cockroach/issues/357" target="_blank" rel="noopener noreferrer">CockroachDB 的方案</a>和 <a href="https://github.com/tikv/tikv/pull/4591" target="_blank" rel="noopener noreferrer">TiKV 的方案</a>。这两个方案都很类似，基本思想是暂停那些不活跃的 Raft group 的网络通信，等到需要的时候再唤醒。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-generics">Why Generics?<a href="https://maybe.news/issues/2#why-generics" class="hash-link" aria-label="Direct link to Why Generics?" title="Direct link to Why Generics?">​</a></h2>
<p><a href="https://blog.golang.org/why-generics" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>这篇文章是 Ian Lance Taylor 在 GopherCon 2019 演讲的文字版（文章中也附带了视频），主要介绍了目前 Go 的核心开发者关于泛型（generics）的一些思考。总的来说 Go 核心团队的设计思想还是保持 Go 语言一贯的简洁，不希望引入过多的概念和复杂性。大部分新增的语法特性都由提供泛型接口的开发者来学习，对于使用者来说和调用普通接口几乎没有区别。早在 2016 年社区就已经有了 <a href="https://github.com/golang/go/issues/15292" target="_blank" rel="noopener noreferrer">#15292</a> 这个关于泛型的讨论，并且还在持续更新中，目前已经有了 710 条评论，Ian Lance Taylor 也在其中积极回复。虽然这个 issue 打上了 Go2 的标签，但泛型特性是否能在 Go 语言的 2.0 版本中出现现在还是个未知数。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="the-open-application-model-from-alibabas-perspective">The Open Application Model from Alibaba’s Perspective<a href="https://maybe.news/issues/2#the-open-application-model-from-alibabas-perspective" class="hash-link" aria-label="Direct link to The Open Application Model from Alibaba’s Perspective" title="Direct link to The Open Application Model from Alibaba’s Perspective">​</a></h2>
<p><a href="https://www.infoq.com/articles/oam-alibaba" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>阿里云和微软在去年<a href="https://cloudblogs.microsoft.com/opensource/2019/10/16/announcing-open-application-model" target="_blank" rel="noopener noreferrer">共同宣布</a>了 Open Application Model（OAM），OAM 组织的<a href="https://github.com/orgs/oam-dev/people" target="_blank" rel="noopener noreferrer">核心成员</a>同时也是前 CoreOS 团队成员以及 etcd、K8s Operator 的创造者。简单理解 OAM 就是希望将传统的 K8s YAML 配置抽象成两部分：开发者和运维，开发者的配置中只包含与业务最相关的内容，而运维的配置中则包含与运行环境相关的内容。本质上是希望将开发者和运维的界线分得更清楚，让不同的角色更专注于自己的领域。在我看来 OAM 的好处当然是降低了普通开发者接入 K8s 的门槛，所谓大道至简，但这种表面上的「简」背后隐藏的复杂性也是不能忽略的。理想情况是某个云服务商能够完全包办所有跟运维有关的事情，用户只需要负责业务开发就好了。但现状还是不管多小的公司都肯定会有专人在负责运维工作。很多年前 Google App Engine 刚诞生时让所有人都眼前一亮，都认为这才是软件开发的未来啊，但即使是 Google 也没能让这个趋势持续下去。最近几年这个趋势又开始回潮，只不过换了一个名字叫做「Serverless」，希望这一次能够持续下去，虽然还有很长的路要走。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="lightweight-coscheduling-based-on-back-to-back-queue-sorting">Lightweight coscheduling based on back-to-back queue sorting<a href="https://maybe.news/issues/2#lightweight-coscheduling-based-on-back-to-back-queue-sorting" class="hash-link" aria-label="Direct link to Lightweight coscheduling based on back-to-back queue sorting" title="Direct link to Lightweight coscheduling based on back-to-back queue sorting">​</a></h2>
<p><a href="https://github.com/kubernetes-sigs/scheduler-plugins/blob/master/kep/20200116-lightweight-coscheduling-based-on-back-to-back-queue-sorting.md" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>自从 K8s 1.15 新增了 <a href="https://kubernetes.io/docs/concepts/scheduling-eviction/scheduling-framework" target="_blank" rel="noopener noreferrer">Scheduling Framework</a> 以后，原生调度器的扩展性有了很大程度的增强。这个 KEP 来自阿里云团队，提出了基于 Scheduling Framework 来实现 coscheduling（或者叫做 gang scheduling）。Coscheduling 这个特性对于机器学习任务来说是非常重要的，一个任务通常包含多个 pod，只有当多个 pod 能够同时运行时这个任务才算是正常运行，如果只有部分 pod 可以运行其实是一种资源的浪费。因此 coscheduling 保证的就是一个任务必须满足一定数量的 pod 都能够被调度时才会实际分配资源。这个特性在 K8s 社区早有讨论，也诞生了一些相关联的项目，如 <a href="https://volcano.sh/" target="_blank" rel="noopener noreferrer">Volcano</a>（前身是 <a href="https://github.com/kubernetes-sigs/kube-batch" target="_blank" rel="noopener noreferrer">kube-batch</a>）。5 月初这个插件的第一版已经被 <a href="https://github.com/kubernetes-sigs/scheduler-plugins/pull/4" target="_blank" rel="noopener noreferrer">merge</a> 到 scheduler-plugins 项目。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="scheduler-support-for-elastic-quota-management">Scheduler Support for Elastic Quota Management<a href="https://maybe.news/issues/2#scheduler-support-for-elastic-quota-management" class="hash-link" aria-label="Direct link to Scheduler Support for Elastic Quota Management" title="Direct link to Scheduler Support for Elastic Quota Management">​</a></h2>
<p><a href="https://docs.google.com/document/d/1ViujTXLP1XX3WKYUTk6u5LTdJ1sX-tVIw9_t9_mLpIc/edit?usp=sharing" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>同样是与 K8s 相关的一个讨论，也同样来自阿里云团队。<code>ResourceQuota</code> 是 K8s 目前提供的一种限制某个 namespace 最大资源使用量的方式，但是在实际的多租户场景中，<code>ResourceQuota</code> 往往显得不够灵活。很多时候我们是希望给每个租户一个可以保证（guarantee）的最小资源量，以及一个超卖的最大资源量。当某个租户的资源比较空闲时，就允许其它租户临时租用。但是调度器也要保障这个租户有能力在必要的时候可以拿回这些被租用的资源，这通常是通过抢占（preemption）的方式来实现。这个提案就提出了扩展 <code>ResourceQuota</code> 来实现类似功能的想法。</p>]]></content>
        <category label="issue" term="issue"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Issue #1]]></title>
        <id>https://maybe.news/issues/1</id>
        <link href="https://maybe.news/issues/1"/>
        <updated>2020-05-21T17:34:22.000Z</updated>
        <summary type="html"><![CDATA[本期关键词：CFS、TensorFlow、Alluxio、Rob Pike、Shoegaze]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>本期关键词：CFS、TensorFlow、Alluxio、Rob Pike、Shoegaze</p>
</blockquote>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="cfs-a-distributed-file-system-for-large-scale-container-platforms">CFS: A Distributed File System for Large Scale Container Platforms<a href="https://maybe.news/issues/1#cfs-a-distributed-file-system-for-large-scale-container-platforms" class="hash-link" aria-label="Direct link to CFS: A Distributed File System for Large Scale Container Platforms" title="Direct link to CFS: A Distributed File System for Large Scale Container Platforms">​</a></h2>
<p><a href="https://dl.acm.org/doi/10.1145/3299869.3314046" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>跟<a href="https://xiaogaozi.me/blog/2020/04/26/weekly-reading-list-issue-1" target="_blank" rel="noopener noreferrer">上次介绍</a>的 FoundationDB Record Layer 一样，这篇来自京东团队的论文也是发表在 SIGMOD 2019，介绍了一个为大规模容器平台设计的分布式文件系统。</p>
<p>系统整体由 3 部分组成：元数据子系统（metadata subsystem）、数据子系统（data subsystem）、资源管理器（resource manager）。元数据子系统负责维护 inode 和 dentry（directory entry），数据子系统负责存储数据块，资源管理器负责处理客户端的各种文件操作请求以及维护前面两个子系统的状态。元数据子系统和数据子系统都是多 partition 的分布式系统，多个元数据和数据的 partition 逻辑上共同组成一个卷（volume），这个卷即是对客户端（容器）可见的存储单元并且可以被挂载，通过传统的 POSIX 接口访问。</p>
<p>因为上述 3 部分组件内部其实都是一个分布式系统，因此都用到了 Raft 作为一致性协议，资源管理器还用到了 RocksDB 作为本地持久化存储。稍微特殊的是数据子系统根据不同类型的写操作选择了不同的复制方案，论文里把这个叫做 Scenario-Aware Replication，具体讲就是顺序写操作（比如 append）用的是 primary-backup，而覆盖（overwrite）操作用的是 Raft。</p>
<p>系统的另一个亮点是基于资源利用率的 partition 分配策略，论文中叫做 Utilization-Based Placement。传统的 partition 分配策略通常是哈希，这种策略的优点是简单但是当扩缩容时必须进行 rebalance。CFS 的做法是元数据和数据子系统定期上报内存、磁盘使用率到资源管理器，当需要创建新的 partition 时根据资源利用率选择最低的那个节点，这样设计的好处是不再需要 rebalance。但是对于这种设计方案是否会造成数据不均衡表示存疑，论文中也没有做过多论述。</p>
<p>为了尽量减少客户端的网络交互，不让某个系统组件成为瓶颈，客户端会缓存元数据子系统、数据子系统和资源管理器的信息到本地，当执行文件操作时会优先读取本地缓存。当然某些组件（比如资源管理器）还是有可能在某一天成为瓶颈，但是基于京东的经验这件事情基本上不会发生。</p>
<p>在与 Ceph 的评测中，CFS 平均有 3 倍的 IOPS 提升，特别是多客户端和随机读写的场景。这很大程度上得益于元数据和数据节点分离的设计，且 CFS 的元数据是全内存存储，而 Ceph 并不是。</p>
<p>分布式文件系统一直都是比较重要的基础组件，在分布式数据库、大数据、机器学习领域有广泛应用。常见的分布式文件系统如 HDFS、Ceph，在如今这个全面推行容器化的时代越来越显得捉襟见肘。容器化一个很大的特点是快速扩缩容，传统的存储系统在这一点上是非常不友好的，因此才会有越来越多针对容器化场景的基础组件诞生（具体可以访问 <a href="https://www.cncf.io/" target="_blank" rel="noopener noreferrer">CNCF</a> 查看），这里介绍的 CFS 是一个例子，另一个类似的是 <a href="https://juicefs.com/" target="_blank" rel="noopener noreferrer">JuiceFS</a>。</p>
<p>CFS 目前属于 CNCF 下的 <a href="https://www.cncf.io/sandbox-projects" target="_blank" rel="noopener noreferrer">sandbox 项目</a>，且已经<a href="https://github.com/chubaofs/chubaofs" target="_blank" rel="noopener noreferrer">开源</a>，使用 Go 语言编写。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="tensorflowcommunity-237-rfc-sparse-domain-isolation-for-supporting-large-scale-sparse-weights-training">tensorflow/community #237: RFC: Sparse Domain Isolation for Supporting large-scale Sparse Weights Training<a href="https://maybe.news/issues/1#tensorflowcommunity-237-rfc-sparse-domain-isolation-for-supporting-large-scale-sparse-weights-training" class="hash-link" aria-label="Direct link to tensorflow/community #237: RFC: Sparse Domain Isolation for Supporting large-scale Sparse Weights Training" title="Direct link to tensorflow/community #237: RFC: Sparse Domain Isolation for Supporting large-scale Sparse Weights Training">​</a></h2>
<p><a href="https://github.com/tensorflow/community/pull/237" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>推荐系统大规模稀疏特征分布式训练一直是工业界一件有挑战的事情，大公司内部自研的训练框架大多已经解决了这个问题，但是在开源社区问题仍然存在。TensorFlow 作为也许目前最流行的深度学习训练框架，社区里也早有相关的讨论（比如 <a href="https://github.com/tensorflow/tensorflow/issues/19324" target="_blank" rel="noopener noreferrer">#19324</a>、<a href="https://github.com/tensorflow/tensorflow/issues/24539" target="_blank" rel="noopener noreferrer">#24539</a>、<a href="https://github.com/tensorflow/tensorflow/pull/24915" target="_blank" rel="noopener noreferrer">#24915</a>），但基本都以烂尾告终。最新的 RFC #237 来自腾讯，区别于现有的一些开源实现（比如阿里巴巴的 <a href="https://github.com/alibaba/x-deeplearning" target="_blank" rel="noopener noreferrer">XDL</a>、字节跳动的 <a href="https://github.com/bytedance/byteps" target="_blank" rel="noopener noreferrer">BytePS</a>、蚂蚁金服的 <a href="https://github.com/sql-machine-learning/elasticdl" target="_blank" rel="noopener noreferrer">ElasticDL</a>）完全自己重新造了一个 parameter server，腾讯的方案最大限度复用了 TensorFlow 现有的组件，对用户的代码侵入也最小。目前这个 RFC 还在讨论中，有兴趣可以订阅 PR。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="深入云原生-ai基于-alluxio-数据缓存的大规模深度学习训练性能优化">深入云原生 AI：基于 Alluxio 数据缓存的大规模深度学习训练性能优化<a href="https://maybe.news/issues/1#%E6%B7%B1%E5%85%A5%E4%BA%91%E5%8E%9F%E7%94%9F-ai%E5%9F%BA%E4%BA%8E-alluxio-%E6%95%B0%E6%8D%AE%E7%BC%93%E5%AD%98%E7%9A%84%E5%A4%A7%E8%A7%84%E6%A8%A1%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E8%AE%AD%E7%BB%83%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96" class="hash-link" aria-label="Direct link to 深入云原生 AI：基于 Alluxio 数据缓存的大规模深度学习训练性能优化" title="Direct link to 深入云原生 AI：基于 Alluxio 数据缓存的大规模深度学习训练性能优化">​</a></h2>
<p><a href="https://mp.weixin.qq.com/s/2Pj8erPbYuMo7mBJvweJgQ" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>机器学习模型训练由于依赖大量的数据作为输入，因此数据 I/O 的性能会直接影响模型训练的效率。有时间会发现计算设备的算力升级了，但是数据 I/O 跟不上了，反而拖慢了整个训练流程。阿里云团队分享的这篇文章便是他们在使用 Alluxio（试图）加速数据 I/O 的过程中的经验，虽然最后的优化结果性能指标其实也只是基本跟本地读取持平。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="rob-pike-interview-go-has-indeed-become-the-language-of-cloud-infrastructure">Rob Pike interview: “Go has indeed become the language of cloud infrastructure”<a href="https://maybe.news/issues/1#rob-pike-interview-go-has-indeed-become-the-language-of-cloud-infrastructure" class="hash-link" aria-label="Direct link to Rob Pike interview: “Go has indeed become the language of cloud infrastructure”" title="Direct link to Rob Pike interview: “Go has indeed become the language of cloud infrastructure”">​</a></h2>
<p><a href="https://evrone.com/rob-pike-interview" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>没啥好介绍的了，值得一读的一篇采访。文中有两个有趣的问题：</p>
<ul>
<li><strong>对于 Rust 宣称的「没有 GC」的设计有什么看法</strong>：Rob Pike 只是表示了他对 Rust 很感兴趣，其它意见不便发表。</li>
<li><strong>如果可以时间旅行到最初设计 Go 的时候想给自己一个什么忠告</strong>：无视那些仇恨者（haters），只需要聆听那些理解以及和你有共同目标的人的声音。不可能每一个人都认同你正在做的事情，但是那些鼓励你前进的人会是提供给你非常棒（fantastic）的想法、能量和灵感的源泉。</li>
</ul>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="孤芳自赏盯鞋音乐的前世与今生">孤芳「自赏」：盯鞋音乐的前世与今生<a href="https://maybe.news/issues/1#%E5%AD%A4%E8%8A%B3%E8%87%AA%E8%B5%8F%E7%9B%AF%E9%9E%8B%E9%9F%B3%E4%B9%90%E7%9A%84%E5%89%8D%E4%B8%96%E4%B8%8E%E4%BB%8A%E7%94%9F" class="hash-link" aria-label="Direct link to 孤芳「自赏」：盯鞋音乐的前世与今生" title="Direct link to 孤芳「自赏」：盯鞋音乐的前世与今生">​</a></h2>
<p><a href="https://www.gcores.com/articles/121368" target="_blank" rel="noopener noreferrer">[上]</a> <a href="https://www.gcores.com/articles/123770" target="_blank" rel="noopener noreferrer">[下]</a></p>
<p>这两篇文章来自「竟然还能聊游戏」的机核，相对系统地介绍了「盯鞋（shoegaze）」这种音乐风格，作为目前可能是除了后朋克以外我最喜欢的音乐风格非常高兴能够有人科普，稍微欠缺的是文中没有提到任何中国的乐队。</p>]]></content>
        <category label="issue" term="issue"/>
    </entry>
    <entry>
        <title type="html"><![CDATA[Issue #0]]></title>
        <id>https://maybe.news/issues/0</id>
        <link href="https://maybe.news/issues/0"/>
        <updated>2020-05-11T14:44:52.000Z</updated>
        <summary type="html"><![CDATA[本期关键词：LightRec、TFRT、MLOps、Mid-stack inlining、Uber’s Microservice]]></summary>
        <content type="html"><![CDATA[<blockquote>
<p>本期关键词：LightRec、TFRT、MLOps、Mid-stack inlining、Uber’s Microservice</p>
</blockquote>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="lightrec-a-memory-and-search-efficient-recommender-system">LightRec: a Memory and Search-Efficient Recommender System<a href="https://maybe.news/issues/0#lightrec-a-memory-and-search-efficient-recommender-system" class="hash-link" aria-label="Direct link to LightRec: a Memory and Search-Efficient Recommender System" title="Direct link to LightRec: a Memory and Search-Efficient Recommender System">​</a></h2>
<p><a href="http://staff.ustc.edu.cn/~liandefu/paper/lightrec.pdf" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>这篇论文由微软亚洲研究院与中科大共同发表在 <a href="https://www2020.thewebconf.org/" target="_blank" rel="noopener noreferrer">WWW 2020</a> 会议上，提出了一种新的表示物品向量的方法，大幅降低存储向量所需空间的同时还显著提升了召回效果。一个直观的数据：LightRec 将 1 千亿 256 维双精度向量的内存占用从 9.5 GB 降到了 337 MB，这是非常惊人的！现在工业界常用的 <a href="https://github.com/nmslib/nmslib" target="_blank" rel="noopener noreferrer">nmslib</a> 和 <a href="https://github.com/facebookresearch/faiss" target="_blank" rel="noopener noreferrer">Faiss</a> 都无法实现如此高的压缩比，因此很多时候都需要借助分布式存储来满足业务场景，如果真的如论文中所描述的一样那单机存储在未来很长一段时间来说都是完全足够的。</p>
<p>这里简单解释一下为什么向量召回对于当下的推荐系统如此重要，传统的召回是基于倒排索引的方式，正如我在<a href="https://xiaogaozi.me/blog/2020/04/21/how-to-design-a-distributed-index-framework-part-1" target="_blank" rel="noopener noreferrer">之前的一篇文章</a>中介绍的那样，召回与模型优化目标之间的差异较大导致召回效果始终较差。自从 <a href="https://www.microsoft.com/en-us/research/publication/learning-deep-structured-semantic-models-for-web-search-using-clickthrough-data" target="_blank" rel="noopener noreferrer">Learning Deep Structured Semantic Models for Web Search using Clickthrough Data</a> 这篇论文（同样也是由微软研究院发表）提出 DSSM（Deep Structured Semantic Models）以后，将召回与 DNN 进行结合，显著提升了召回的效果，在很多公司的实践中也的确论证了 DSSM 是一个非常有效的召回方式。DSSM 的核心是分别为物品和用户生成向量，再通过 ANN（Approximate Nearest Neighbors）查询相似向量从而实现召回。因此向量的存储和查询效率决定了在线请求的效果和性能，如何平衡向量索引的空间占用和召回效果是非常重要的。</p>
<p>微软研究院的微信公众号有一篇简短的针对这篇论文的<a href="https://mp.weixin.qq.com/s/E43gc16A3OVWgxyfdUxr7g" target="_blank" rel="noopener noreferrer">中文版介绍</a>，有兴趣也可以先看这篇文章。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="tfrt-a-new-tensorflow-runtime">TFRT: A new TensorFlow runtime<a href="https://maybe.news/issues/0#tfrt-a-new-tensorflow-runtime" class="hash-link" aria-label="Direct link to TFRT: A new TensorFlow runtime" title="Direct link to TFRT: A new TensorFlow runtime">​</a></h2>
<p><a href="https://blog.tensorflow.org/2020/04/tfrt-new-tensorflow-runtime.html" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>Google 近期开源了新的 TensorFlow 运行时 TFRT（TensorFlow Runtime），这是一个介于上层用户代码和底层设备之间的执行环境。项目的愿景是实现一个统一的、可扩展的、性能首屈一指（best-in-class）的，同时可跨越多种领域硬件（domain specific hardware）的运行时。未来 TFRT 会成为 TensorFlow 默认的运行时，目前还在集成中。从 ResNet-50 的 inference 测试结果上看平均提升了 28% 的性能。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-we-need-devops-for-ml-data">Why We Need DevOps for ML Data<a href="https://maybe.news/issues/0#why-we-need-devops-for-ml-data" class="hash-link" aria-label="Direct link to Why We Need DevOps for ML Data" title="Direct link to Why We Need DevOps for ML Data">​</a></h2>
<p><a href="https://tecton.ai/blog/devops-ml-data" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>虽然这是一篇产品推广软文（在文章最后一节），但是文章中普及的关于 DevOps 与机器学习之间的关系还是非常有价值的。很多人可能以为机器学习就只是模型算法而已，诚然这是学术研究的基石，但是要真正把机器学习应用到工业界光有算法是远远不够的。Google 著名的 <a href="https://papers.nips.cc/paper/5656-hidden-technical-debt-in-machine-learning-systems.pdf." target="_blank" rel="noopener noreferrer">Hidden Technical Debt in Machine Learning Systems</a> 论文已经论述了那些隐藏在模型背后的往往被人忽略的技术，模型规模越大需要付出的工程努力也是越大的（所以很多时候大公司才需要自己造轮子）。作为衍生阅读也可以同时看看 <a href="https://towardsdatascience.com/how-linkedin-uber-lyft-airbnb-and-netflix-are-solving-data-management-and-discovery-for-machine-9b79ee9184bb" target="_blank" rel="noopener noreferrer">How LinkedIn, Uber, Lyft, Airbnb and Netflix are Solving Data Management and Discovery for Machine Learning Solutions</a> 这篇文章。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="mid-stack-inlining-in-go">Mid-stack inlining in Go<a href="https://maybe.news/issues/0#mid-stack-inlining-in-go" class="hash-link" aria-label="Direct link to Mid-stack inlining in Go" title="Direct link to Mid-stack inlining in Go">​</a></h2>
<p><a href="https://dave.cheney.net/2020/05/02/mid-stack-inlining-in-go" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>Dave Cheney 继续科普 Go 的一些实现细节，这次的主题是编译器如何实现 mid-stack inlining。所谓 mid-stack inlining 就是将那些调用了其它函数的函数变成 inline，相对的还有 leaf inlining，即不调用任何其它函数。有兴趣了解 leaf inlining 的可以看 Dave Cheney 的<a href="https://dave.cheney.net/2020/04/25/inlining-optimisations-in-go" target="_blank" rel="noopener noreferrer">上一篇文章</a>。</p>
<h2 class="anchor anchorWithStickyNavbar_LWe7" id="why-we-leverage-multi-tenancy-in-ubers-microservice-architecture">Why We Leverage Multi-tenancy in Uber’s Microservice Architecture<a href="https://maybe.news/issues/0#why-we-leverage-multi-tenancy-in-ubers-microservice-architecture" class="hash-link" aria-label="Direct link to Why We Leverage Multi-tenancy in Uber’s Microservice Architecture" title="Direct link to Why We Leverage Multi-tenancy in Uber’s Microservice Architecture">​</a></h2>
<p><a href="https://eng.uber.com/multitenancy-microservice-architecture" target="_blank" rel="noopener noreferrer">[链接]</a></p>
<p>Uber 介绍了他们在微服务领域实践的一个经验「多租户」，简单讲就是让请求链路上的所有组件和系统都能够感知「租户」这个概念，比如租户可以分为生产环境和测试环境。Uber 列举了两个应用场景：集成测试和 Canary 部署，这两个场景都依赖生产环境的请求，有了租户的概念就可以自动进行请求路由和数据隔离。愿景其实挺美好，但「代价」也是不容忽视，前面讲了要让所有组件和系统都感知就非常依赖基础组件的统一，要解决这个问题很多时候并不单纯是一个技术问题。如何做好不同环境的数据隔离也是一个难题，关于这一点文章并没有做特别详细的介绍。</p>]]></content>
        <category label="issue" term="issue"/>
    </entry>
</feed>