<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>站在巨人的肩膀上</title>
  
  <subtitle>技术文章精选</subtitle>
  <link href="http://posts.hufeifei.cn/atom.xml" rel="self"/>
  
  <link href="http://posts.hufeifei.cn/"/>
  <updated>2026-01-01T06:47:29.852Z</updated>
  <id>http://posts.hufeifei.cn/</id>
  
  <author>
    <name>Holmofy</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>K8S 网络之深入理解 CNI</title>
    <link href="http://posts.hufeifei.cn/backend/k8s-cni/"/>
    <id>http://posts.hufeifei.cn/backend/k8s-cni/</id>
    <published>2025-11-22T00:00:00.000Z</published>
    <updated>2026-01-01T06:47:29.852Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><h2 id="引子"><a href="#引子" class="headerlink" title="引子"></a>引子</h2><p>谷歌：“我说小刀儿(docker)啊，你看我们已经在 kubelet 的代码里内嵌了 docker-shim 好久了，最近 rkt 也想让我们接进来，你看是不是咱整个规范，你也按照规范改改 docker 呀，这样以后 k8s 就海纳百川了~”</p><p>docker：“滚！”</p><p>谷歌：“……”</p><p>………………</p><p>谷歌：“我说，那个 CoreOS，还有那边那个 linux 基金会，还有那边那几个大厂，你们都过来一下。”</p><p>谷歌：“我要给我的 k8s 对接容器这块儿制定个规范，你们就说支不支持吧！”</p><p>其他大厂：“好的大哥！”</p><p>………………</p><p>新闻：“k8s 宣布于 1.20 正式弃用 docker(docker shim)！”</p><p>docker：“卧槽！爸爸我错了！”</p><h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><p><img src="https://velog.velcdn.com/images/chan9708/post/9ba8e1e7-4b60-4eb1-9163-1bfa4988fcb9/image.png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="Containerd变化历史"></p><h3 id="一、CNI-简介-1-先导小知识"><a href="#一、CNI-简介-1-先导小知识" class="headerlink" title="一、CNI 简介 1 - 先导小知识"></a>一、CNI 简介 1 - 先导小知识</h3><p>K8S 已经在很早前的 1.20 版发布之前就宣布要移除内嵌在 kubelet 中的 docker shim 的代码，大概原因就是谷歌一直在 k8s 中使用一套叫做 CRI（container runtime interface）的规范，该规范旨在定义 k8s 如何更好地操作容器技术，该规范大概分为三部分：CRI Client，CRI Server，OCI Runtime。</p><p>简单来讲，就是在 kubelet 中放一个 grpc 的客户端，这个客户端要和一个 grpc 的服务端进行通信，这个 grpc 的服务端里头进行容器的“拉起”，“销毁” 等动作的调用，而真正执行 “拉起”，“销毁” 等动作的代码由 OCI Runtime 实现。</p><p><img src="https://img2020.cnblogs.com/blog/794174/202201/794174-20220114161636291-2100977762.png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="cri shim"></p><p>或者再简单点，对应到实现来说：CRI Client 端有个实现就是 kubelet，CRI Server 端有个实现叫 Containerd，OCI Runtime 有好多实现其中有个叫 runc。然后把他们串起来就是：kubelet 在做完了一些准入校验，CSI 的存储卷挂载等操作之后，要去拉起一个 pod 了，在拉起 pod 的时候，就先启动一个 grpc 的客户端，然后与 Containerd 中的 grpc 的服务端通信，告诉它说要拉起一个 pod 了。然后 containerd 收到后会按照一定的流程去“拉镜像”，“创建 sandbox”，“创建 netns”，“启动容器”，“创建容器网络”，“把容器加入到 sandbox” 中等。containerd 基本上只负责调用（高级运行时），真正实现这些功能的地方在 OCI 的 runc（或其他低级运行时）中，有点像是通常服务端的 controller 和 service。</p><p>不同的低级运行时因为实现逻辑以及调用逻辑可能都不太一样，就比如 runc 是利用 <code>namespace</code>，kata 是直接起的 <code>qemu</code> 虚拟机，因此 containerd 到 OCI 之间还会有一层 shim，containerd 到 runc 有 runc-shim，到 kata 有 kata-shim。</p><p>所谓的废弃 docker，其实不是真的直接干掉 docker，而是上面我们说 CRI 规范要有个 grpc 的 client 和 server 端嘛，然后 server 端通知 oci 里头去创建 ns 或者创建网络等，但是对于 docker 来讲，docker 内置了创建网络，创建存储卷等功能，在 k8s 里，这些功能比如挂载存储功能由 CSI 实现，挂载网络由 CNI 实现等，所以 docker 很多功能 k8s 并不需要，因此 kubelet 内嵌了一个 docker-shim 作为 grpc 的 client 端用来屏蔽掉 docker 的一些内置功能后再和 containerd 通信，所以其实对于 k8s 来讲，这个 docker-shim 是个冗余的负担，因此所谓的“废弃”，指的是将 docker-shim 的代码从 kubelet 中移除，在减负的同时也能通过让第三方实现 CRI 规范中的东西而接入到 k8s 里。</p><h2 id="二、CNI-简介-2-正文"><a href="#二、CNI-简介-2-正文" class="headerlink" title="二、CNI 简介 2 - 正文"></a>二、CNI 简介 2 - 正文</h2><p>罗里吧嗦半天，终于说到真正的正文。</p><p>本次我们想介绍的，就是上面的 OCI 规范中有一步叫 “创建网络”。对于 k8s 来讲，网络属于最重要的功能之一，因为没有一个良好的网络，集群不同节点之间甚至同一个节点之间的 pod 就无法良好的运行起来。</p><p>K8S 在设计网络的时候，采用的准则就一点：“灵活”！那怎么才能灵活呢？答案就是“我不管，你自己做”~</p><p>没错，k8s 自己没有实现太多跟网络相关的操作，而是制定了一个规范。该规范贼简单，一共就三点：</p><p>“首先！有一个配置文件！配置文件里写上要使用的网络插件名儿，然后以及一些该插件需要的信息”。</p><p>“其次！让 CRI 调用这个插件，并把容器的运行时信息，包括容器的命名空间，容器 ID 等信息传给插件”。</p><p>“最后！你这插件自己爱干啥干啥去吧，都嚯嚯完了玩够了给我吐一个结果，结果里头能让我知道你给 pod 的 ip 是啥就行了”</p><p>没错一共就这三点，这就是大名鼎鼎的 CNI 规范！</p><p>虽然简单，但足够灵活！因为我啥也不干！</p><p>不过恰恰就是因为 k8s 自己“啥也不干”，所以大家可以自由发挥，自由实现不同的 CNI 插件，反正 pod 的相关你都给我了，我要的配置也都通过配置文件设置好了，那我作为插件的实现方，当然就可以 free style 了，反正最终目的就是能让 pod 有一个健康的网络就好了嘛~</p><p>因此就有了现在如此多的网络插件，比如 <code>flannel</code>，这个是利用的静态路由表配置或者 vxlan 实现的网络通信。再比如 <code>calico</code>，是通过 BGP 协议实现了动态路由。再比如其他还有什么 <code>Weave</code>，以及 OVN 之类的各种各样的网络插件。这些插件虽然实现的方法各式各样，但是最终目的只有一个，就是让集群中的各种 pod 之间能自由通信。</p><p>在 k8s 中，由于不同的 pod 可能遍布在同一个节点上，也可能在不同节点上，因此在 k8s 的网络环境中，需要解决的问题大概有如下三点：</p><ol><li>pod ip 地址的管理</li><li>同一节点上的 pod 之间互相通信</li><li>不同节点上的 pod 之间互相通信</li></ol><p>基本上只要想好了方法解决这三个问题，那我们就可以自己实现一个 CNI 网络插件了。</p><h2 id="三、在？看看-demo？"><a href="#三、在？看看-demo？" class="headerlink" title="三、在？看看 demo？"></a>三、在？看看 demo？</h2><p>我们可以简单看几个网络插件的例子。</p><p>比如我手边集群上的网络插件使用的是 calico，我们就可以在 /etc/cni/net.d 目录下看到其对应的配置文件：</p><p><img src="https://pic1.zhimg.com/v2-a450ea628dce184e9fb02a0b04b5a378_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="CNI配置文件"></p><p>我们可以看到有个 .conflist 结尾的文件，是的，k8s 的 cni 允许同时使用多个插件，并且会把上一个插件的执行结果作为参数传递给下一个插件，以此我们可以串通多个插件，让多个插件做不同的事情，比如我可以第一个插件只负责让同一台主机上的节点通信，然后第二个插件可以负责让不同节点上的 pod 通信，总之还是那句话，配置文件有了，pod 信息也被 k8s 传过来了，爱怎么玩儿是你插件自己的事儿了~</p><p>我看还可以再简单个 flannel 的配置文件：</p><p><img src="https://picx.zhimg.com/v2-328860a4f7c05162f44af91cdb04b8f3_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt=" flannel 的配置文件"></p><p>我手边没有用 flannel 的集群，因此就直接上 gayhub 上截图了。我们也可以看到，和 calico 的配置文件长得还比较像。其中 “name” 和 “cniVersion” 都是必须的字段，对于 .conflist 来说，”plugins” 也是必须的字段，然后还有一个 “type” 字段，也是必须的，因为 kubelet 通过这个字段来查找要使用哪个插件。</p><p>插件的默认存放地址在 /opt/cni/bin 这个目录下，该目录下都是 k8s 可以使用的网络插件，如果谁自己实现了个 cni 插件的话，给它搞成二进制的可执行文件后，往这目录下一扔就 ok 了，我们来简单看一下：</p><p><img src="https://pica.zhimg.com/v2-b6aa305d02c0b4c42df055039ddbd57a_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="cni 插件"></p><p>可以看到，对应的 calico 或者什么 flannel 之类的网络插件都在这个目录下了。</p><p>当然了，集群中的每个节点上在 /opt/cni/bin 和 /etc/cni/net.d 目录下应该都要有相应的配置文件以及对应的二进制可执行文件。我们可以来简单看下 gayhub 上 flannel 的 yaml 文件：</p><p><img src="https://picx.zhimg.com/v2-cf01c1ade9f8e29d393b434804829555_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="flannel 的 yaml 文件"></p><p>可以看到，这就是一个 DaemonSet，意味着它会被 k8s 给调度到集群中的每一个节点上，而它里面主要做的事情也非常简单，就是用 volume 做路径的映射，然后执行两条 cp 命令，分别把配置文件和创建网络相关的可执行二进制文件给 copy 到主机的 /etc/cni/net.d 和 /opt/cni/bin 两个目录下。然后在适当的时机，k8s 就会自动去读取配置文件，然后根据配置文件中的 “type” 字段，去 /opt/cni/bin 目录下找对应名字的二进制文件然后直接给 exec 咯。</p><p>所以综上所述，CNI 规范其实是个非常简单的规范，总结一下就是要求第三方插件需要有一个配置文件，然后有个自己实现创建网络环境的功能二进制，最后 k8s 帮你调用这个插件并把容器的一些运行时环境传过去。就这么简单~</p><h2 id="四、CNI-的调用流程"><a href="#四、CNI-的调用流程" class="headerlink" title="四、CNI 的调用流程"></a>四、CNI 的调用流程</h2><p>首先上面我们提到了 CRI 规范要走个 containerd，然后 containerd 要调用 OCI 是吧，这里我们先简单看下 containerd 的源码的一小小小部分，以此先来佐证一下上面我们所说的：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// github.com/containerd/containerd/pkg/cri/server/sandbox_run.go</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="params">(c *criService)</span></span> RunPodSandbox(ctx context.Context, r *runtime.RunPodSandboxRequest) (_ *runtime.RunPodSandboxResponse, retErr <span class="type">error</span>) {</span><br><span class="line">  <span class="comment">// ......</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// Create initial internal sandbox object.</span></span><br><span class="line">  sandbox := sandboxstore.NewSandbox(</span><br><span class="line">    sandboxstore.Metadata{</span><br><span class="line">      ID:             id,</span><br><span class="line">      Name:           name,</span><br><span class="line">      Config:         config,</span><br><span class="line">      RuntimeHandler: r.GetRuntimeHandler(),</span><br><span class="line">    },</span><br><span class="line">    sandboxstore.Status{</span><br><span class="line">      State: sandboxstore.StateUnknown,</span><br><span class="line">    },</span><br><span class="line">  )</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Ensure sandbox container image snapshot.</span></span><br><span class="line">  image, err := c.ensureImageExists(ctx, c.config.SandboxImage, config)</span><br><span class="line">  <span class="comment">// ......</span></span><br><span class="line"></span><br><span class="line">  ociRuntime, err := c.getSandboxRuntime(config, r.GetRuntimeHandler())</span><br><span class="line">  <span class="comment">// ......</span></span><br><span class="line">  podNetwork := <span class="literal">true</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment">// ......</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> podNetwork {</span><br><span class="line">    <span class="comment">// ......</span></span><br><span class="line">    <span class="keyword">var</span> netnsMountDir = <span class="string">"/var/run/netns"</span></span><br><span class="line">    <span class="comment">// ......</span></span><br><span class="line">    </span><br><span class="line">    sandbox.NetNS, err = netns.NewNetNS(netnsMountDir)</span><br><span class="line">    </span><br><span class="line">    sandbox.NetNSPath = sandbox.NetNS.GetPath()</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// ......</span></span><br><span class="line">    <span class="keyword">if</span> err := c.setupPodNetwork(ctx, &amp;sandbox); err != <span class="literal">nil</span> {</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">nil</span>, errors.Wrapf(err, <span class="string">"failed to setup network for sandbox %q"</span>, id)</span><br><span class="line">    }</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// ......</span></span><br><span class="line">  }</span><br><span class="line">  <span class="comment">// ......</span></span><br><span class="line"> </span><br><span class="line">  opts := []containerd.NewContainerOpts{</span><br><span class="line">    containerd.WithSnapshotter(c.config.ContainerdConfig.Snapshotter),</span><br><span class="line">    customopts.WithNewSnapshot(id, containerdImage, snapshotterOpt),</span><br><span class="line">    containerd.WithSpec(spec, specOpts...),</span><br><span class="line">    containerd.WithContainerLabels(sandboxLabels),</span><br><span class="line">    containerd.WithContainerExtension(sandboxMetadataExtension, &amp;sandbox.Metadata),</span><br><span class="line">    containerd.WithRuntime(ociRuntime.Type, runtimeOpts)}</span><br><span class="line"></span><br><span class="line">  container, err := c.client.NewContainer(ctx, id, opts...)</span><br><span class="line">  <span class="comment">// ......</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> err = c.setupSandboxFiles(id, config); err != <span class="literal">nil</span> {</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span>, errors.Wrapf(err, <span class="string">"failed to setup sandbox files"</span>)</span><br><span class="line">  }</span><br><span class="line"> </span><br><span class="line">  <span class="comment">// ......</span></span><br><span class="line">  <span class="keyword">return</span> &amp;runtime.RunPodSandboxResponse{PodSandboxId: id}, <span class="literal">nil</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>剔除掉了一些部分，只留下有用的(凑合能看懂的)部分。其主要逻辑就是：</p><ul><li>创建一个 sandbox 结构体，里面描述了这个 sandbox 的各种信息</li><li>拉它的镜像</li><li>生成 oci 的 runtime，这里的 oci 大部分情况下就是 runc，当然也可以使用其他 oci</li><li>然后生成 netns，设置网络</li><li>把这些网络塞给 sandbox</li><li>设置 sandbox 的文件系统</li><li>用 OCI 拉起 pod</li><li>逻辑还是蛮清晰的，这里我们解释一下 sandbox 是个啥。</li></ul><p>首先 sandbox 是 CRI 规范中的一环，CRI 规范规定了容器启动之前必须要有个 sandbox。sandbox 我们可以理解为沙箱，这个沙箱的作用是什么呢，其实就是为了整合网络资源和存储资源。</p><p>我们知道容器可以自己拥有自己的网络，因为容器其实也只是利用 linux 的 namespace 功能抽象出来的隔离，namespace 就可以设置单独的 net ns，docker 中就是如此。但是在 k8s 中，我们不感知“容器”，能感知到的最小单位叫做 “Pod”，为什么要这样做呢？</p><p>其实 k8s 作为一个平台，应该是本着包罗万象的愿景而发展的，自然是希望可以在底层对接多种容器技术，docker 也只是其中一种，因此使用 Pod 这个概念来以此屏蔽底层的容器的概念，这样利于平台的发展。</p><p>另外还有一点，假设我们只使用容器这个概念，那么每启动一条容器，就要给它设置网络资源，以及设置存储资源，如果该容器挂了十次，那么重启十次就要重复设置十次网络以及十次存储资源，那有没有更好的方法能让容器挂掉重启之后不那么频繁的创建资源呢？诶，有滴，就是把一个或多个容器抽象成一个个的 Pod，让在同一个 Pod 里的容器去共享这一条 Pod 中的网络资源和存储资源(cgroup 之类的资源不共享)，这样容器挂了十次，但是只要 Pod 不挂，那它里面的网络资源或者存储资源就还在，下次新创建的 pod 只需要简单地加入进去就可以享用资源了。</p><p>那么回到上面说的 sandbox，其实 sandbox 就是刚刚所说的网络资源以及存储资源的承载。我们可以简单地把它理解成，这个 sandbox 就是一个容器，它在 k8s 里被叫做 “pause”，我们可以简单看下 k8s 默认的 pause 容器是啥：</p><p><img src="https://pic3.zhimg.com/v2-8460bfb7fdab0e95ef4607a0d3074eca_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="pause 容器"></p><p>它是一段 c 代码，代码的作用很简单，就是单纯地死循环 pause 方法，该方法是让程序直接陷入睡眠，也不消耗 cpu。</p><p>也就是说，这 pause 是一个极度稳定的一条进程，k8s 把这个 pause 容器作为一个 pod 启动之前的 sandbox，具备启动快切稳定的特点，然后把创建的网络资源存储资源等挂到这个 pause 容器里头，然后等 pod 中的其他容器启动后再和这个 pause 共享网络和存储，这样就避免了重复创建网络和存储资源了。</p><p>所以了解了 CRI 会将创建的网络资源都加入到 sandbox 之后，我们就可以去看一看 OCI 是如何通过调用插件将网络设置给 sandbox 的了。</p><p>这里我们不直接看 OCI 的源码了，在 k8s 官方的 cni repo 中(<a href="https://github.com/containernetworking/cni/tree/master/cnitool">https://github.com/containernetworking/cni/tree/master/cnitool</a>) 有一个 cnitool 目录，该目录下是一个很简单的 go 文件，里面主要就是模拟了一个网络插件是如何被调用的，我们可以通过学习这个文件来了解 CNI 网络插件被调用的过程：</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line">func <span class="title function_">main</span><span class="params">()</span> {</span><br><span class="line">  <span class="comment">// ......</span></span><br><span class="line">  netdir := os.Getenv(EnvNetDir)</span><br><span class="line">  <span class="comment">// ......</span></span><br><span class="line">  netconf, err := libcni.LoadConfList(netdir, os.Args[<span class="number">2</span>])</span><br><span class="line">  <span class="comment">// ......</span></span><br><span class="line">  var cniArgs [][<span class="number">2</span>]<span class="built_in">string</span></span><br><span class="line">  args := os.Getenv(EnvCNIArgs)</span><br><span class="line">  <span class="keyword">if</span> len(args) &gt; <span class="number">0</span> {</span><br><span class="line">    cniArgs, err = parseArgs(args)</span><br><span class="line">    <span class="comment">// ......</span></span><br><span class="line">  }</span><br><span class="line">  ifName, ok := os.LookupEnv(EnvCNIIfname)</span><br><span class="line">  <span class="keyword">if</span> !ok {</span><br><span class="line">    ifName = <span class="string">"eth0"</span></span><br><span class="line">  }</span><br><span class="line">  netns := os.Args[<span class="number">3</span>]</span><br><span class="line">  netns, err = filepath.Abs(netns)</span><br><span class="line">  <span class="comment">// ......</span></span><br><span class="line">  <span class="comment">// Generate the containerid by hashing the netns path</span></span><br><span class="line">  s := sha512.Sum512([]byte(netns))</span><br><span class="line">  containerID := fmt.Sprintf(<span class="string">"cnitool-%x"</span>, s[:<span class="number">10</span>])</span><br><span class="line">  cninet := libcni.NewCNIConfig(filepath.SplitList(os.Getenv(EnvCNIPath)), nil)</span><br><span class="line">  rt := &amp;libcni.RuntimeConf{</span><br><span class="line">    ContainerID:    containerID,</span><br><span class="line">    NetNS:          netns,</span><br><span class="line">    IfName:         ifName,</span><br><span class="line">    Args:           cniArgs,</span><br><span class="line">    CapabilityArgs: capabilityArgs,</span><br><span class="line">  }</span><br><span class="line">  <span class="keyword">switch</span> os.Args[<span class="number">1</span>] {</span><br><span class="line">  <span class="keyword">case</span> CmdAdd:</span><br><span class="line">    result, err := cninet.AddNetworkList(context.TODO(), netconf, rt)</span><br><span class="line">    <span class="keyword">if</span> result != nil {</span><br><span class="line">      _ = result.Print()</span><br><span class="line">    }</span><br><span class="line">    <span class="built_in">exit</span>(err)</span><br><span class="line">  <span class="keyword">case</span> CmdCheck:</span><br><span class="line">    err := cninet.CheckNetworkList(context.TODO(), netconf, rt)</span><br><span class="line">    <span class="built_in">exit</span>(err)</span><br><span class="line">  <span class="keyword">case</span> CmdDel:</span><br><span class="line">    <span class="built_in">exit</span>(cninet.DelNetworkList(context.TODO(), netconf, rt))</span><br><span class="line">  }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>该文件主要流程较为简单：</p><ul><li>获取一些环境变量</li><li>获取网络插件配置(从 /etc/cni/net.d)</li><li>获取网卡名(这个网卡名会交给 CNI 网络插件, 网络插件中会把这个名字作为每个 pod 里的默认网卡, 默认就叫 eth0)</li><li>获取 netns(这个就是在 containerd 中生成的属于这条 pod 的网络命名空间, 不过在 cnitool 中是从命令行参数中获取的, 想通过 cnitool 测试网络插件就得自己先创建一个 netns 然后作为参数传进来)</li><li>生成 containerID</li><li>生成容器运行时的相关信息</li><li>根据命令行参数来判断，调用 Add 还是 Check 还是 Del，这里以 Add 为例，cni 这个 repo 的主要代码的 AddNetworkList 方法(在 CRI 中也会调用这个 repo 的这个方法)</li><li>可以看到这个 AddNetworkList 接收的参数主要就是 netconf 和 rf 俩, 其中 netconf 是从 /etc/cni/net.d 中读取出来的每个插件自己 copy 过去的配置文件, rt 是容器的运行时信息, 这些运行时信息在 cnitool 中是瞎创建的, 在 k8s 中由 CRI 创建</li></ul><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">func (c *CNIConfig) addNetwork(ctx context.Context, name, cniVersion <span class="built_in">string</span>, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) {</span><br><span class="line">  <span class="comment">// ......</span></span><br><span class="line">  <span class="keyword">return</span> invoke.ExecPluginWithResult(ctx, pluginPath, newConf.Bytes, c.args(<span class="string">"ADD"</span>, rt), c.exec)</span><br><span class="line">}</span><br></pre></td></tr></table></figure><ul><li>顺着 AddNetworkList 往下看, 其实最终就是把从 opt/cni/bin 下拿到的二进制文件给 exec 一下</li><li>把容器运行时的信息, 包括 containerId, netns path, 网卡设备名字等作为环境变量, 然后还再把从 /etc/cni/net.d 中读取到的以及上一次执行的插件结果(上面说过插件可以是个 list)作为标准输入</li><li>这样实现 CNI 插件的人在插件代码中就可以通过这两种方式去获取一些必要的信息了</li><li>最后执行完毕后回到 cnitool 中：<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">case</span> CmdAdd:</span><br><span class="line">    result, err := cninet.AddNetworkList(context.TODO(), netconf, rt)</span><br><span class="line">    <span class="keyword">if</span> result != nil {</span><br><span class="line">      _ = result.Print()</span><br><span class="line">    }</span><br><span class="line">    <span class="built_in">exit</span>(err)</span><br></pre></td></tr></table></figure></li><li>可以看到拿到了 result 之后直接 Print 了, 也就是说把插件执行结果作为标准输出了, 之后 CRI Runtime 就可以通过标准输出上的东西拿到插件执行结果从而进行一些后续操作</li></ul><h2 id="五、总结"><a href="#五、总结" class="headerlink" title="五、总结"></a>五、总结</h2><p>最后简单总结一下 CNI 相关。</p><ul><li>k8s 的 kubelet 在启动一个容器之前，会先做一些预先检查以及 csi 的操作</li><li>然后 kubelet 调用 CRI 的接口，通过 grpc 的方式和 CRI runtime 通信，告知 CRI 要创建 pod 了</li><li>随后 CRI 的 Server 端收到通知后调用 OCI 的接口去真正的执行拉起 Pod 的操作</li><li>不过在真的拉起 pod 之前，会先给 pod 创建一个 pause 容器，这个 pause 容器是一个特别小又特别稳定的进程，主要用来挂载网络命名空间和存储资源</li><li>然后 CRI 调用 CNI 提供的接口，先在 /etc/cni/net.d 目录中获取网络插件配置(这个配置由每个插件自己通过 daemonset 的方式拷贝到主机上), 然后把插件的配置作为标准输入, 再把容器的运行时信息作为环境变量, 最后执行插件</li><li>CNI 插件执行完毕后, 把执行结果(结果要包含一些关键信息比如 Pod IP 等)直接干到标准输出上</li><li>CRI 从标准输出上读取插件执行结果再做后续操作, 后续操作就是拉起真正的容器等</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;h2 id=&quot;引子&quot;&gt;&lt;a href=&quot;#引子&quot; class=&quot;headerli</summary>
      
    
    
    
    <category term="后端" scheme="http://posts.hufeifei.cn/categories/%E5%90%8E%E7%AB%AF/"/>
    
    
    <category term="K8S" scheme="http://posts.hufeifei.cn/tags/K8S/"/>
    
    <category term="CNI" scheme="http://posts.hufeifei.cn/tags/CNI/"/>
    
  </entry>
  
  <entry>
    <title>大新闻 | Snowflake 和 Databricks 为夺 PG 再次大打出手!</title>
    <link href="http://posts.hufeifei.cn/db/pg_lake/"/>
    <id>http://posts.hufeifei.cn/db/pg_lake/</id>
    <published>2025-11-10T00:00:00.000Z</published>
    <updated>2026-01-01T06:47:29.860Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><h2 id="为了争-PostgreSQL生态-Snowflake-和-Databricks-大打出手"><a href="#为了争-PostgreSQL生态-Snowflake-和-Databricks-大打出手" class="headerlink" title="为了争 PostgreSQL生态, Snowflake 和 Databricks 大打出手"></a>为了争 PostgreSQL生态, Snowflake 和 Databricks 大打出手</h2><p>PG真的这么香吗? 为了争 PostgreSQL 生态, Snowflake 和 Databricks 又打起来了?</p><p>这不对啊, 为什么国内PG没什么人用? 也不多招聘需求啊!</p><p>我敢拍胸脯说, 国内大多数人第一次听说PostgreSQL, 可能是因为国产化, 因为很多数据库套壳PG!</p><p>近期可能还由于AI的兴起, 一部分传统关系数据库用户可能因为pgvector开始接触到PG.</p><p>国外则不一样, 看DB-Engein就知道, 很多年以前, PG的发展就是一路飙升, 多年以前一直是开发者最喜爱和最想用两个维度的第一.</p><p>如果你不能直接感知到PG在国外有多火, 从两家著名的数据库上市公司Snowflake和Databrick的收购行为即可窥透一切!</p><p>近期, 为了争 PostgreSQL 生态, Snowflake 和 Databricks 又打起来了!</p><p>故事要从收购说起,</p><p>《德说-第333期, Databricks花10亿美元买开源Neon数据库, 值得吗?》 这篇文章说了前后脚的两起收购: Databrick 10亿美金收购PG商业发行版Neon Database, Snowflake 2.5亿美金收购PG商业发行版Crunchy Data!</p><p>Neon和Crunchy都是PostgreSQL的云服务提供商, 也是PostgreSQL的核心贡献者Hikki和Tom Lane所在的2家公司.</p><p>紧随其后, Databrick又干了一票:《大新闻 | Databricks收购Mooncake, 补齐AI Agent实时数据管道短板》</p><ul><li><a href="https://www.databricks.com/blog/mooncake-labs-joins-databricks-accelerate-vision-lakebase">https://www.databricks.com/blog/mooncake-labs-joins-databricks-accelerate-vision-lakebase</a></li></ul><p>Mooncake和CrunchyData的功能有点类似, 都是结合了duckdb的算力, 对象存储和iceberg/parquet等存储文件, 给PG加上数据湖(实时分析)的翅膀. 可参考阅读:《内置列存储 vs 传统 ETL: pg_mooncake 与 CrunchyData 打起来了》</p><p>Mooncake Labs被Databricks收购后, Snowflake也没闲着, 就在前几天, CrunchyData开源了pg_lake等一系列插件, 这系列插件可以让开源PG用户直接获得DuckDB的算力, 实时与Iceberg/对象存储的交互能力, 在对象存储的数据可写可读. ( <a href="https://www.snowflake.com/en/engineering-blog/pg-lake-postgres-lakehouse-integration/">https://www.snowflake.com/en/engineering-blog/pg-lake-postgres-lakehouse-integration/</a> )</p><p>你可以看看moonlink和crunchydata提供的架构, 简直一毛一样, 只是moonlink还多做了一点: pg逻辑订阅+实时管道入湖, 我个人认为moonlink对PG用户的意义更大.</p><p>两者架构对比如下, 你觉得谁家的略胜一筹?</p><p><img src="https://pic3.zhimg.com/v2-e5b1686f9b49fa6108ed4c287f9508a0_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p><img src="https://picx.zhimg.com/v2-b1b5ba12945c2ced1803b82829605195_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p><strong>Snowflake 和 Databricks 为什么这么看重PostgreSQL生态?</strong> 很简单, PG的用户体量大(而且还在向上发展). 作为云数据库厂商, 你觉得他的核心是什么?</p><ul><li><p>第一步, 当然是把数据存进来,</p></li><li><p>第二步, 让用户对数据进行不断的计算挖掘.即可以赚存储的钱, 又可以赚计算的钱.</p></li><li><p>第三步, 再结合AI, 还可赚深度分析的钱( 这个价值无法估量. 打个比方: 如果你问他明天那支股票会涨, 成功概率有90%, 你觉得你会花多少钱买这个消息? ) 这么说起来这两家应该还会投一些做数据挖掘/模型训练的公司?</p><ul><li>当然, 如果要跨用户访问其他用户数据, 这个事就关系到数据资产的归属, 不容易干, 否则就是偷盗用户数据了! 目前仅仅是美好想象而已</li></ul></li></ul><p>Snowflake 和 Databricks 争夺PG生态, 目的是数据!因为 PG 真香, 只是国内绝大多数用户有眼不识好货!</p><p>争夺还没有停止! 看着吧!</p><p>你有什么看法? 欢迎留言!</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;h2 id=&quot;为了争-PostgreSQL生态-Snowflake-和-Data</summary>
      
    
    
    
    <category term="数据库" scheme="http://posts.hufeifei.cn/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    
    <category term="PostgreSQL" scheme="http://posts.hufeifei.cn/tags/PostgreSQL/"/>
    
    <category term="pg_lake" scheme="http://posts.hufeifei.cn/tags/pg-lake/"/>
    
  </entry>
  
  <entry>
    <title>DuckLake 承袭 DuckDB 简洁理念，重构 Lakehouse 本质</title>
    <link href="http://posts.hufeifei.cn/db/DuckLake-lakehouse/"/>
    <id>http://posts.hufeifei.cn/db/DuckLake-lakehouse/</id>
    <published>2025-10-02T00:00:00.000Z</published>
    <updated>2026-01-01T06:47:29.860Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>在大数据领域，“<a href="https://zhida.zhihu.com/search?content_id=258373320&content_type=Article&match_order=1&q=%E6%B9%96%E4%BB%93%E4%B8%80%E4%BD%93&zhida_source=entity">湖仓一体</a>”这个概念早已不再陌生。它试图将<a href="https://zhida.zhihu.com/search?content_id=258373320&content_type=Article&match_order=1&q=%E6%95%B0%E6%8D%AE%E6%B9%96&zhida_source=entity">数据湖</a>（Data Lake）的开放性与<a href="https://zhida.zhihu.com/search?content_id=258373320&content_type=Article&match_order=1&q=%E6%95%B0%E6%8D%AE%E4%BB%93%E5%BA%93&zhida_source=entity">数据仓库</a>（Data Warehouse）的事务一致性结合起来。但在现实中，很多 Lakehouse 系统为了兼顾开放性与一致性，不得不引入复杂的文件元数据系统、目录服务，甚至绕了一个大圈子又回到“数据库”。</p><p><img src="https://pic4.zhimg.com/v2-39118c621f66495996d6564084f43de5_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p><a href="https://zhida.zhihu.com/search?content_id=258373320&content_type=Article&match_order=1&q=DuckLake&zhida_source=entity">DuckLake</a> 提出了一种看似简单却革命性的想法：<strong>既然最终都要引入数据库来管理元数据，那不如从一开始就用数据库来管理所有元数据！</strong></p><blockquote><p>DuckLake 通过将所有元数据存储在标准 SQL 数据库中，而不是复杂的基于文件的系统，从而简化了数据湖仓的实现。同时，它仍然使用如 <a href="https://zhida.zhihu.com/search?content_id=258373320&content_type=Article&match_order=1&q=Parquet&zhida_source=entity">Parquet</a> 这样的开放格式来存储数据。这种方式使系统更加可靠、更快速，也更易于管理。</p></blockquote><h2 id="背景介绍"><a href="#背景介绍" class="headerlink" title="背景介绍"></a>背景介绍</h2><p>像 <a href="https://zhida.zhihu.com/search?content_id=258373320&content_type=Article&match_order=1&q=BigQuery&zhida_source=entity">BigQuery</a> 和 <a href="https://zhida.zhihu.com/search?content_id=258373320&content_type=Article&match_order=1&q=Snowflake&zhida_source=entity">Snowflake</a> 这样的创新型数据系统表明，在存储已成为一种虚拟化商品的时代，将存储与计算解耦是一个非常棒的想法。这样，存储和计算可以独立扩展，我们也不需要为了存储那些永远不会被读取的表而购买昂贵的数据库设备。</p><p>与此同时，市场力量推动人们要求数据系统采用诸如 Parquet 这样的开放格式，以避免数据被某个厂商“绑架”的常见问题。在这个新世界中，许多数据系统快乐地围绕着建立在 Parquet 和 S3 之上的“数据湖”运作，一切似乎都很美好。谁还需要那些老派的数据库呢？</p><p>但很快人们就发现——令人震惊的是——用户其实还想<strong>修改</strong>他们的数据集。简单的追加操作还算顺利，只需将新文件丢进文件夹即可，但除此之外的操作就需要依赖复杂且容易出错的自定义脚本，既没有正确性保障，更别提<strong>事务保障</strong>了。</p><h2 id="Iceberg-and-Delta"><a href="#Iceberg-and-Delta" class="headerlink" title="Iceberg and Delta"></a>Iceberg and Delta</h2><p>为了解决“在数据湖中修改数据”这一基本需求，各种新的开放标准应运而生，其中最突出的是 <strong>Apache Iceberg</strong> 和 <strong>Linux Foundation Delta Lake</strong>。这两种格式的设计初衷，是在不放弃“在对象存储中使用开放格式”这一核心理念的前提下，引入对数据表进行修改的合理方式。例如，Iceberg 通过一系列复杂的 JSON 和 Avro 文件来定义 schema、快照以及某一时刻哪些 Parquet 文件属于该表。这种体系被称为 <strong>“Lakehouse（湖仓一体）”</strong> —— 实质上是在数据湖的基础上叠加了数据库特性，从而支持了许多令人兴奋的新型数据管理用例，比如跨引擎数据共享。</p><p><img src="https://pic3.zhimg.com/v2-0df96da59a030cb86eb3cec1d6878b3c_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>但这两种格式都遇到了同一个难题：<strong>在一致性不稳定的对象存储中定位表的最新版本很困难</strong>。原子性更新（ACID 中的 “A”）变得棘手——很难确保一个“指针”的切换能被所有用户即时看到表的最新状态。此外，Iceberg 和 Delta Lake 都只关注于<strong>单个表的管理</strong>，而现实是用户还希望能同时管理<strong>多个表</strong>。</p><h2 id="目录（Catalogs）"><a href="#目录（Catalogs）" class="headerlink" title="目录（Catalogs）"></a>目录（Catalogs）</h2><p>为了解决上述问题，人们又引入了一层技术方案：<strong>在这些文件之上增加一个目录服务（catalog service）</strong>。这个目录服务会连接一个数据库，用来管理所有表对应的文件夹名称。</p><p><img src="https://pic3.zhimg.com/v2-f32159c479b29edddff8c0ae9fcd95b0_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>同时，它还维护着一张“史上最悲伤的表”：这张表每个表只占一行，记录着该表的<strong>当前版本号</strong>。借助数据库的事务保障（transactional guarantees），我们就可以安全地更新这个版本号。这样一来，所有人都能一致地看到最新版本，大家也就都开心了。</p><h2 id="绕了一圈，还是得用数据库？"><a href="#绕了一圈，还是得用数据库？" class="headerlink" title="绕了一圈，还是得用数据库？"></a>绕了一圈，还是得用数据库？</h2><p>但问题在于：<strong>Iceberg 和 Delta Lake 的初衷就是不依赖数据库</strong>。它们的设计者煞费苦心地将读取和更新表所需的全部信息编码进了对象存储（blob store）上的文件中，力求摆脱数据库的束缚。</p><p>为此，它们做出了很多妥协。例如，Iceberg 中的每个根文件（root file）都包含了所有现存的快照（snapshots），包括完整的 schema 信息等等。每次数据变更，都会生成一个新文件，记录完整的历史。为了避免频繁读写过多小文件（在对象存储中效率很低），其他元数据还必须批量组织，比如采用双层的 manifest 文件结构。</p><p>至于对数据进行<strong>小规模修改</strong>，这仍然是一个悬而未解的难题，通常需要复杂的清理过程（cleanup procedures），而这些过程目前在开源实现中既不成熟也缺乏支持。</p><p>然而，正如上文所提到的：<strong>Iceberg 和 Delta Lake 最终还是不得不妥协，引入数据库作为 catalog 的一部分以确保一致性</strong>。但它们并没有因此重新审视自己原有的设计限制与技术架构来适应这一根本性的改变。</p><h2 id="DuckLake-是什么？"><a href="#DuckLake-是什么？" class="headerlink" title="DuckLake 是什么？"></a>DuckLake 是什么？</h2><p>在 <a href="https://zhida.zhihu.com/search?content_id=258373320&content_type=Article&match_order=1&q=DuckDB&zhida_source=entity">DuckDB</a>，我们其实是喜欢数据库的。它们是用来安全、高效地管理相当大规模数据集的绝佳工具。既然数据库最终已经被引入了 Lakehouse 体系，那么用它来管理其余的表元数据也就变得非常合理！我们仍然可以利用对象存储的“无限”容量和“无限”扩展性，将实际的表数据以 Parquet 等开放格式存储在其中；但用于支持数据变更所需的元数据，放在数据库中管理会更加高效、可靠！</p><p>巧的是，Google BigQuery（用的是 Spanner）和 Snowflake（用的是 FoundationDB）也是这么做的——只是它们在底层并没有采用开放格式罢了。</p><p><img src="https://pic1.zhimg.com/v2-24c719052568992a3b3a04a3f3f721fe_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>为了解决当前 Lakehouse 架构中存在的根本性问题，我们创建了一种新的开放表格式，名为 <strong>DuckLake</strong>。DuckLake 重新构想了 “Lakehouse” 格式应有的样子，基于两个简单的事实：</p><ul><li>  <strong>将数据文件以开放格式存储在对象存储中</strong>，在可扩展性方面非常优秀，同时也能避免厂商锁定。  </li><li>  <strong>元数据管理是一项复杂且高度关联的数据管理任务，最适合由数据库管理系统来完成</strong>。  </li></ul><p>DuckLake 的基本设计理念是：<strong>将所有元数据结构（包括目录信息和表级数据）都移入一个 SQL 数据库中进行管理</strong>。该格式被定义为一组关系型表，以及在这些表上的纯 SQL 事务，用来描述各种数据操作，比如：创建和修改 schema、增删改数据等。</p><p>DuckLake 格式能够管理任意数量的表，并支持跨表事务。它还支持一些“高级”数据库概念，比如视图、嵌套类型、事务性 schema 更改等。这一设计的一个重要优势是，可以借助关系型数据库提供的<strong>参照完整性</strong>（即 ACID 中的 “C”），例如确保不会出现重复的快照 ID。</p><blockquote><p>DuckLake 的架构非常直观：<strong>就是一些 Parquet 文件 + 一个 SQL 数据库</strong>。</p></blockquote><p><img src="https://pic3.zhimg.com/v2-370f703e3fe078bceacd142a87e7d0b4_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>具体使用哪种 SQL 数据库由用户自行决定，唯一的要求是系统必须支持 ACID 操作和主键，并具备标准的 SQL 支持。DuckLake 内部使用的表结构故意设计得非常简洁，以最大化对不同 SQL 数据库的兼容性。以下通过一个示例展示核心表结构。</p><p>让我们来看当在一个全新空表上执行以下查询时，DuckLake 中依次发生的查询操作：</p><p><img src="https://pic4.zhimg.com/v2-3dd97d0d009d8223e636e0db34c0b4b7_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>我们看到一个完整的、连贯的 SQL 事务，它：</p><ul><li>  插入新的 Parquet 文件路径  </li><li>  更新全局表统计信息（现在行数增加了）  </li><li>  更新全局列统计信息（最小值和最大值发生了变化）  </li><li>  更新文件列统计信息（同时记录最小值/最大值等内容）  </li><li>  创建一个新的模式快照（编号为 #2）  </li><li>  记录在该快照中发生的变更  </li></ul><p>注意，实际写入 Parquet 文件并不在这段操作序列中，它是在此之前完成的。但无论添加多少数据，该操作序列的成本始终保持相同（且较低）。</p><h2 id="为什么我们需要-DuckLake？"><a href="#为什么我们需要-DuckLake？" class="headerlink" title="为什么我们需要 DuckLake？"></a>为什么我们需要 DuckLake？</h2><p>1. 数据湖的困境：虽然 Parquet + S3 的组合非常受欢迎，但一旦你需要做更复杂的操作，比如：</p><ul><li>  修改表结构   </li><li>  更新或删除数据  </li><li>  跨表事务  </li></ul><p>你就会发现，仅靠文件操作根本无法保证一致性和事务性。这时候就需要像 Iceberg 或 Delta Lake 这样的 Lakehouse 格式。</p><p>2. Iceberg/Delta 的折中与复杂性：为了支持上述需求，Iceberg 和 Delta 引入了大量 JSON/Avro 文件用于维护快照、schema、manifest 等。最终它们还不得不引入“Catalog”服务，而这个服务背后又是一个数据库。也就是说：</p><blockquote><p>为了避免用数据库，最终还是用了数据库，只不过是个更复杂的版本。</p></blockquote><h2 id="DuckLake-的核心设计"><a href="#DuckLake-的核心设计" class="headerlink" title="DuckLake 的核心设计"></a>DuckLake 的核心设计</h2><p>DuckLake 承认两件简单的事情：</p><ul><li>  <strong>用 Parquet 存储数据很棒，可以扩展且避免供应商锁定</strong>  </li><li><strong>但管理元数据这事，还是数据库更擅长</strong>  </li></ul><p>因此 DuckLake 的设计是：</p><ul><li>  <strong>所有元数据用 SQL 表结构来表达</strong>（例如：快照、文件统计、列统计、表结构等）  </li><li>  <strong>所有数据操作都是纯 SQL 事务</strong>，如插入、更新、结构变更、快照提交等  </li><li><strong>支持跨表事务、视图、嵌套类型、schema 演进等高级能力</strong>  </li></ul><p>这一切都通过一组干净、规范的 SQL 表来完成。没有 JSON、没有额外 API，只有 SQL。</p><h2 id="DuckLake-的三大原则"><a href="#DuckLake-的三大原则" class="headerlink" title="DuckLake 的三大原则"></a>DuckLake 的三大原则</h2><h2 id="1-简单（Simplicity）"><a href="#1-简单（Simplicity）" class="headerlink" title="1. 简单（Simplicity）"></a>1. 简单（Simplicity）</h2><p>DuckLake 延续了 DuckDB 的设计理念：<strong>简单、渐进</strong>。</p><ul><li><strong>轻量易用</strong>：只需在笔记本电脑上安装 DuckDB 及其 DuckLake 扩展即可运行，非常适合测试、开发和原型验证。在这种情况下，Catalog 存储就是一个本地的 DuckDB 文件。  </li><li><strong>灵活的存储支持</strong>：DuckLake 的数据文件是不可变的，无需就地修改或重用文件名，因此可以兼容各种存储系统，包括本地磁盘、NAS、S3、Azure Blob Store、GCS 等。只需在创建元数据表时指定数据文件的存储前缀（如 <code>s3://mybucket/mylake/</code>）即可。  </li><li><strong>元数据托管数据库可自由选择</strong>：只要是支持 ACID 和主键约束的 SQL 数据库（如 PostgreSQL 或 DuckDB 本身）都可以作为元数据存储。大多数组织已具备这类数据库的运维经验，因此无需额外的软件部署，降低了使用门槛。同时，SQL 数据库已被高度商品化，有大量托管服务可选，迁移也非常方便，不需要移动任何数据文件，因其模式简单且标准化。  </li><li><strong>完全基于 SQL</strong>：DuckLake 不需要 Avro 或 JSON 文件，也不需要额外的 Catalog 服务或自定义 API。所有操作只需 SQL，即可完成。我们都懂 SQL，这让一切变得更简单。  </li></ul><h2 id="2-可扩展（Scalability）"><a href="#2-可扩展（Scalability）" class="headerlink" title="2. 可扩展（Scalability）"></a>2. 可扩展（Scalability）</h2><p>DuckLake 实际上将数据架构中的职责进行了<strong>更清晰的三分</strong>：<strong>存储、计算、元数据管理</strong>，实现了更强的模块化和可扩展性。</p><ul><li>  **存储层:**数据仍然保存在专用的文件存储系统中（如 S3 等对象存储）。DuckLake 依赖开放格式（如 Parquet），可在存储层实现无限扩展。  </li><li>  **计算层:**任意数量的计算节点可以并发查询和更新元数据，同时独立地从存储层读取或写入数据。这意味着 DuckLake 在计算层也具备横向无限扩展能力。  </li><li>**元数据管理层（Catalog 数据库）:**Catalog 数据库仅处理由计算节点发起的元数据事务，其负载相比真实数据操作小几个数量级。因此，DuckLake 对 Catalog 的性能要求较低，而且支持替换，比如从 PostgreSQL 迁移到其他数据库。这种灵活性来源于 DuckLake 所用的数据结构仅为普通的 SQL 表，语义简单、易移植。  </li></ul><blockquote><p>实际上，这正是 BigQuery 和 Snowflake 管理超大规模数据集的核心设计思想。</p></blockquote><h2 id="3-高性能（Speed）"><a href="#3-高性能（Speed）" class="headerlink" title="3. 高性能（Speed）"></a>3. 高性能（Speed）</h2><p>DuckLake 延续 DuckDB 的理念，专注<strong>高性能和低复杂度</strong>，相较于 Iceberg 和 Delta Lake 有明显优势：</p><ul><li>  元数据集中管理，查询更快：所有元数据存在 SQL 数据库中，仅需<strong>一条查询</strong>即可完成分区裁剪、统计过滤，获取需要读的文件列表。无需多次 HTTP 请求，避免 S3 限速、失败和一致性问题。  </li><li>  小变更更高效：避免为小改动生成快照/manifest 文件；支持<strong>将小变更内联写入元数据库表</strong>，实现亚毫秒写入；大幅减少小文件，简化清理与压缩。  </li><li>  高并发写入，无压力：每次表变更仅需执行<strong>一条 SQL 事务</strong>；减少冲突窗口，SQL 数据库擅长处理并发事务；即使使用 PostgreSQL，也能支持每秒上千次提交，支持<strong>上千节点并发写入</strong>。  </li><li>快照轻量，支持百万级别：每个快照只是几行元数据；可共享数据文件的一部分；无需主动清理，也不会引发文件膨胀。  </li></ul><p>DuckLake 把元数据放入 SQL 数据库中，简化架构、加速读写、支持高并发，是一个<strong>更快、更轻、更强</strong>的 Lakehouse 新方案。</p><h2 id="DuckLake-特征"><a href="#DuckLake-特征" class="headerlink" title="DuckLake 特征"></a>DuckLake 特征</h2><p>DuckLake 拥有你喜爱的所有 Lakehouse 特性：</p><ul><li>  <strong>任意 SQL 查询</strong>：支持与 DuckDB 相同的丰富 SQL 功能。  </li><li>  <strong>数据更改支持</strong>：高效支持追加、更新和删除操作。  </li><li>  <strong>多 Schema、多表管理</strong>：可在同一元数据结构中管理任意数量的 schema 和其中的多张表。  </li><li>  <strong>跨表事务</strong>：支持完整的 ACID 跨表事务，涵盖所有 schema、表及其内容。  </li><li>  <strong>复杂类型支持</strong>：支持如列表、嵌套等各种复杂数据类型。  </li><li>  <strong>完整模式演进</strong>：表结构可任意变更，包括新增/删除列、修改列类型等。  </li><li>  <strong>模式级时间旅行与回滚</strong>：支持快照隔离和时间旅行，可查询任意时间点的表状态。  </li><li>  <strong>增量扫描</strong>：支持查询两个快照之间发生的数据变更。  </li><li>  <strong>SQL 视图支持</strong>：可定义惰性求值的 SQL 视图。  </li><li>  <strong>隐藏分区与扫描剪枝</strong>：自动感知分区和表/文件级统计信息，提前剪枝以提升效率。  </li><li>  <strong>事务性 DDL</strong>：创建、修改、删除 schema、表、视图等操作均支持事务处理。  </li><li>  <strong>避免频繁压缩</strong>：相比其他格式，DuckLake 需要的压缩操作更少，且支持高效快照压缩。  </li><li>  <strong>数据内联</strong>：小变更可直接写入 catalog 数据库，无需频繁写小文件。  </li><li>  <strong>数据加密</strong>：可选择加密所有数据文件，实现零信任数据托管，密钥由 catalog 数据库管理。  </li><li>  <strong>兼容性好</strong>：DuckLake 写入的存储文件（包括删除文件）<strong>完全兼容 Apache Iceberg</strong>，可实现<strong>仅元数据的迁移</strong>。  </li></ul><h2 id="DuckLake-DuckDB-扩展：让-Lakehouse-直接运行起来"><a href="#DuckLake-DuckDB-扩展：让-Lakehouse-直接运行起来" class="headerlink" title="DuckLake DuckDB 扩展：让 Lakehouse 直接运行起来"></a>DuckLake DuckDB 扩展：让 Lakehouse 直接运行起来</h2><p>定义一个 Lakehouse 格式很容易，真正让它跑起来却不简单。因此，我们同步发布了 DuckLake 的计算节点实现 —— <strong>ducklake DuckDB 扩展</strong>。</p><p>这个扩展完全实现了上文描述的 DuckLake 格式，具备全部特性。它是 MIT 许可下的免费开源软件，知识产权归属于非营利的 DuckDB 基金会。</p><p>ducklake 扩展将 DuckDB 从原本的单机分析工具，拓展为支持<strong>中心化数据仓库</strong>场景的 Lakehouse 引擎。企业只需部署一个中心 catalog 数据库和文件存储（如 RDS + S3 或本地部署），然后在任意设备上运行带有 ducklake 扩展的 DuckDB —— 包括员工电脑、手机、应用服务器，甚至无服务器代码。</p><p>扩展支持使用本地 DuckDB 文件作为元数据存储，也支持连接任意 DuckDB 支持的数据库，包括 PostgreSQL、SQLite、MySQL 和 MotherDuck 等。同时可使用本地磁盘、S3、Azure Blob、GCS 等存储系统。</p><p>DuckLake 扩展<strong>不会取代</strong> DuckDB 对 Iceberg 和 Delta 的原生支持，反而可以作为它们的本地缓存或加速层。</p><p>从 DuckDB v1.3.0（代号 “Ossivalis”）开始，DuckLake 扩展正式可用。</p><p>安装扩展：</p><p><img src="https://pic4.zhimg.com/v2-f173a48ebb23a80ad82bb719c8fd47f3_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>可以通过<code>ATTACH</code>DuckDB 中的命令来初始化一个 DuckLake。例如：</p><p><img src="https://pica.zhimg.com/v2-1ea85f7d98f92885a379912b1ff8fb12_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>接下来我们创建一个表并插入一些数据：</p><p><img src="https://pic1.zhimg.com/v2-2d9dc90c8151ee1d0f3198239f646d46_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>查询数据：</p><p><img src="https://pic1.zhimg.com/v2-275a13e6b2f550739ccf4fdf6346bfdc_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer">  </p><p>一个包含两行数据的 Parquet 文件已经创建完毕</p><p><img src="https://pic1.zhimg.com/v2-f24146d8c14501cb6923c7eaac976bba_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>删除数据：</p><p><img src="https://pic1.zhimg.com/v2-3b79a3852c95f0c5b3d912c9f528d5d0_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>再次检查该文件夹，我们会看到一个新文件出现，<code>-delete</code>出现了名称中带有的第二个文件，这也是一个包含已删除行的标识符的 Parquet 文件。</p><p><img src="https://pic1.zhimg.com/v2-d10f98eff685311f5ffa6b79e65181f2_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>当然，DuckLake支持_时间旅行，_我们可以使用该<code>ducklake_snapshots()</code>功能查询可用的快照。</p><p><img src="https://pic4.zhimg.com/v2-3850acb9413f666f1d395299bab02d09_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>假设我们想要读取第 43 行被删除之前的表，我们可以使用DuckDB 中的新<code>AT</code>语法：</p><p><img src="https://pic3.zhimg.com/v2-d51d6ebd70c190e9cbaf8867998e4092_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>版本 2 仍然有此行，所以就是这样。这也适用于快照时间戳而不是版本号：只需使用<code>TIMESTAMP</code>而不是<code>VERSION</code>。</p><p>我们还可以使用函数查看版本之间发生了什么变化<code>ducklake_table_changes()</code>，例如:</p><p><img src="https://pic4.zhimg.com/v2-6a24e780b91308ac76a52d265c6746f7_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>DuckLake 中的更改当然是事务性的，之前我们运行在“自动提交”模式下，每个命令都是一个独立的事务。但我们可以使用<code>BEGIN TRANSACTION</code>and<code>COMMIT</code>或 来更改这一点<code>ROLLBACK</code>。</p><p><img src="https://pic1.zhimg.com/v2-6e82988011b9d76fbe9c99ecbec89c46_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p><img src="https://pic1.zhimg.com/v2-77a68465d61b1f6406cad3bddb7b17aa_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>DuckLake 并不是另一个“炫酷但难用”的新格式，而是一次回归本质的尝试：<strong>我们为什么不直接用 SQL 数据库管理元数据？</strong></p><p>它保留了数据湖的开放性，又拥抱了数据库的强大管理能力，将复杂性降到最低，同时提升了可靠性和可扩展性。</p><p>正如 DuckDB 所追求的“轻盈、简洁、高效”，DuckLake 将继续沿着这条道路，为我们带来下一代的 Lakehouse 架构。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;在大数据领域，“&lt;a href=&quot;https://zhida.zhihu.c</summary>
      
    
    
    
    <category term="数据库" scheme="http://posts.hufeifei.cn/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    
    <category term="DuckDB" scheme="http://posts.hufeifei.cn/tags/DuckDB/"/>
    
    <category term="Lakehouse" scheme="http://posts.hufeifei.cn/tags/Lakehouse/"/>
    
  </entry>
  
  <entry>
    <title>边缘计算云原生开源方案选型比较</title>
    <link href="http://posts.hufeifei.cn/backend/edge-compute/"/>
    <id>http://posts.hufeifei.cn/backend/edge-compute/</id>
    <published>2025-07-02T00:00:00.000Z</published>
    <updated>2026-01-01T06:47:29.852Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>随着Kubernetes已经成为容器编排和调度的事实标准，各大公有云厂商都已经基于Kubernetes提供了完善的Kubernetes云上托管服务。</p><p>同时也看到越来越多的企业、行业开始在生产中使用Kubernetes, 拥抱云原生。在各行各业数字化转型和上云过程中，公有云厂商也在主动拥抱传统线下环境，在思考各种各样的解决方案使云上能力向边缘(或线下)延伸。</p><p>而Kubernetes由于屏蔽了底层架构的差异性，可以帮助应用平滑地运行在不同的基础设施上的特性，云上的Kubernetes服务也在考虑拓展其服务边界，云原生和边缘计算结合的想法自然就呼之欲出了。</p><p>目前国内各个公有云厂商也都开源了各自基于Kubernetes的边缘计算云原生项目。如华为云的KubeEdge，阿里云的OpenYurt，腾讯云的SuperEdge。</p><p>目前网上很少有从技术视角来介绍这几个项目优缺点的文章，本文试着从技术视角，从开源视角来分析这几个项目，希望可以给大家做项目选型时提供一些借鉴。</p><h2 id="1-比较思路"><a href="#1-比较思路" class="headerlink" title="1. 比较思路"></a>1. 比较思路</h2><p>这几个项目都是云边一体，云边协同的架构，走的是Kubernetes和边缘计算结合的路数，因此决定从以下几点比较:</p><p>（1） 各个项目的开源状况：比如开源项目的背景、开源的时间、是否进入了CNCF等；</p><p>（2）Kubernetes架构:</p><ul><li>先对比与Kubernetees的架构差异：主要关注是否修改Kubernetes，和；Kubernetes一键式转换等</li><li>根据架构差异对比和Kubernetes的能力增强点；主要关注边缘自治，边缘单元化，轻量化等能力</li><li>最后看一下架构差异可能带来的影响: 主要关注运维监控能力，云原生生态兼容性，系统稳定性等方面</li></ul><p>（3）对边缘计算场景支持能力:</p><ul><li>主要关注是否具备端设备的管理能力</li></ul><p>接下来以项目的开源顺序，从上述几个方面来介绍各个项目。</p><h2 id="2-边缘云原生开源项目对比"><a href="#2-边缘云原生开源项目对比" class="headerlink" title="2.边缘云原生开源项目对比"></a>2.边缘云原生开源项目对比</h2><h3 id="2-1-KubeEdge"><a href="#2-1-KubeEdge" class="headerlink" title="2.1 KubeEdge"></a>2.1 <a href="https://kubeedge.io/zh/">KubeEdge</a></h3><h4 id="（1）开源状况"><a href="#（1）开源状况" class="headerlink" title="（1）开源状况"></a>（1）开源状况</h4><p>KubeEdge是华为云于2018年11月份开源的，目前是CNCF孵化项目。其架构如下:</p><p><img src="https://release-1-20.docs.kubeedge.io/assets/images/kubeedge_arch-a0fa6324bc543a933d766e45d5f00f77.png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="KubeEdge"></p><h4 id="（2）与Kubernetes的架构差异"><a href="#（2）与Kubernetes的架构差异" class="headerlink" title="（2）与Kubernetes的架构差异"></a>（2）与Kubernetes的架构差异</h4><p>首先从架构图可以看到，云端(k8s master)增加了Cloud Hub组件和各类controller，而在边缘端(k8s worker)没有看到原生的kubelet和kube-proxy，而是一个对原生组件进行重写了EdgeCore组件。</p><p>从架构图看EdgeCore是基于kubelet重构的，为了保证轻量化，裁剪了原生kubelet的部分能力，同时也增加了很多适配边缘场景的能力。具体如下:</p><ul><li>Cloud Hub+EdgeHub模块: 抛弃了原生kubernetes 的组件间数据同步list/watch机制，改成基于websocket/quic协议从云端往边缘推送模式。</li><li>节点元数据缓存模块(MetaManager): 把节点维度的数据持久化在本机的SQLite数据库中，当云边网络不稳定时Edged模块将从本地数据库中获取数据用于业务的生命周期管控。</li><li>DeviceController+设备管理模块(DeviceTwin): 把设备管理能力直接集成到EdgeCore中，为用户提供原生的设备管理能力。</li></ul><p>上述的架构设计，<strong>对比Kubernetes的能力增强点主要有</strong>：</p><ul><li>边缘自治：通过增加节点元数据缓存，可以规避云边断网状态下，边缘业务或者节点重启时，边缘组件可以利用本地缓存数据进行业务恢复，这就带来了边缘自治的好处。</li><li>轻量化: 削减了部分kubelet功能(如CSI，CNI等)，从而使边缘EdgeCore组件相比原生kubelet组件更加轻量。同时因为节点上增加了SQLite数据库，所以节点维度相比原生节点是否轻量待确认，欢迎熟悉的同学提供数据。</li></ul><p><strong>架构差异可能带来的影响</strong>：</p><ul><li><strong>云原生生态兼容性不足</strong>：<ul><li>跟随社区同步演进挑战大: 由于对Kubernetes系统的侵入式修改，后续跟随Kubernetes社区的演进将会遇到很大挑战。</li><li>边缘节点无法运行Operator：因为云边通信机制的修改，Cloud Hub只能往边缘推送有限的几种资源(如Pod，ConfigMap等)。而Operator既需要自定义CRD资源，又需要list/watch云端获取关联资源，因此社区的Operator无法运行的KubeEdge的边缘节点上。</li><li>边缘节点不适合运行需要list/watch云端的应用: 因为云边通信机制的修改，导致原来需要使用list/watch机制访问kube-apiserver的应用，都无法通过hub tunnel 通道访问kube-apiserver，导致云原生的能力在边缘侧大打折扣。</li><li>运维监控能力支持有限：因为目前云边通信链路是kube-apiserver –&gt; controller –&gt; Cloud Hub –&gt;EdgeHub –&gt;MetaManager等，而原生Kubernetes运维操作(如kubectl proxy/logs/exec/port-forward/attch等)是kube-apiserver直接请求kubelet。目前KubeEdge社区最新版本也仅支持kubectl logs/exec/metric，其他运维操作目前还不支持。</li></ul></li><li><strong>系统稳定性提升待确定</strong>:<ul><li>基于增量数据的云边推送模式：可以解决边缘watch失败时的重新全量list从而引发的kube-apiserver 压力问题，相比原生Kubernetes架构可以提升系统稳定性。</li><li>Infra管控数据和业务管控数据耦合：Kubernetes集群的管控数据(如Pod，ConfigMap数据)和边缘业务数据(设备管控数据)使用同一条websocket链路，如果边缘管理大量设备或者设备更新频率过高，大量的业务数据将可能影响到集群的正常管控，从而可能降低系统的稳定性。</li></ul></li></ul><p><strong>边缘计算场景支持能力</strong></p><ul><li>设备管理能力: 这个能力直接集成在edged中，给iot用户提供了一定的原生设备管理能力。</li></ul><h3 id="2-2-OpenYurt"><a href="#2-2-OpenYurt" class="headerlink" title="2.2 OpenYurt"></a>2.2 <a href="https://openyurt.io/zh/">OpenYurt</a></h3><h4 id="（1）开源状况-1"><a href="#（1）开源状况-1" class="headerlink" title="（1）开源状况"></a>（1）开源状况</h4><p>OpenYurt是阿里云于2020年5月份开源的，目前是CNCF沙箱项目。架构如下:</p><p><img src="https://openyurt.io/zh/assets/images/arch-2c77ff23e9b7f4fe4956fe22700f5c0c.png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="OpenYurt"></p><h4 id="（2）与Kubernetes的架构差异-1"><a href="#（2）与Kubernetes的架构差异-1" class="headerlink" title="（2）与Kubernetes的架构差异"></a>（2）与Kubernetes的架构差异</h4><p>OpenYurt的架构设计比较简洁，采用的是无侵入式对Kubernetes进行增强。在云端(K8s Master)上增加Yurt Controller Manager, Yurt App Manager以及Tunnel Server组件。而在边缘端(K8s Worker)上增加了YurtHub和Tunnel Agent组件。从架构上看主要增加了如下能力来适配边缘场景：</p><ul><li>YurtHub: 代理各个边缘组件到K8s Master的通信请求，同时把请求返回的元数据持久化在节点磁盘。当云边网络不稳定时，则利用本地磁盘数据来用于边缘业务的生命周期管控。同时云端的Yurt Controller Manager会管控边缘业务Pod的驱逐策略。</li><li>Tunnel Server/Tunnel Agent: 每个边缘节点上的Tunnel Agent将主动与云端Tunnel Server建立双向认证的加密的gRPC连接，同时云端将通过此连接访问到边缘节点及其资源。</li><li>Yurt App Manager：引入的两个CRD资源: NodePool 和 UnitedDeployment. 前者为位于同一区域的节点提供批量管理方法。 后者定义了一种新的边缘应用模型以节点池维度来管理工作负载。</li></ul><p>上述的架构设计，对比Kubernetes的能力增强点主要有：</p><ul><li>边缘单元化：通过Yurt App Manager组件，从单元化的视角，管理分散在不同地域的边缘资源，并对各地域单元内的业务提供独立的生命周期管理，升级，扩缩容，流量闭环等能力。且业务无需进行任何适配或改造。</li><li>边缘自治: 因为每个边缘节点增加了具备缓存能力的透明代理YurtHub，从而可以保障云边网络断开，如果节点或者业务重启时，可以利用本地缓存数据恢复业务。</li><li>云边协同(运维监控)： 通过Tunnel Server/Tunnel Agent的配合，为位于防火墙内部的边缘节点提供安全的云边双向认证的加密通道，即使边到云网络单向连通的边缘计算场景下，用户仍可运行原生kubernetes运维命令(如kubectl proxy/logs/exec/port-forward/attach等)。同时中心式的运维监控系统(如prometheus, metrics-server等)也可以通过云边通道获取到边缘的监控数据。</li><li>云原生生态兼容:<ul><li>所有功能均是通过Addon或者controller形式来增强Kubernetes，因此保证来对Kubernetes以及云原生社区生态的100%兼容。</li><li>另外值得一提的是：OpenYurt项目还提供了一个YurtCtl工具，可以用于原生Kubernetes和OpenYurt集群的一键式转换，</li></ul></li></ul><p><strong>架构差异可能带来的影响</strong></p><ul><li>原生Kubernetes带来的系统稳定性挑战：因为OpenYurt没有修改Kubernetes，所以这个问题也是原生Kubernetes在边缘场景下的问题。当云边长时间断网再次恢复时，边缘到云端会产生大量的全量List请求，从而对kube-apiserver造成比较大的压力。边缘节点过多时，将会给系统稳定性带来不小的挑战。</li><li>边缘无轻量化解决方案: 虽然OpenYurt没有修改Kubernets，但是在边缘节点上增加YurtHub和Tunnel Agent组件。目前在最小的1C1G的系统上运行成功，更小规格机器待验证。</li></ul><h3 id="边缘计算场景"><a href="#边缘计算场景" class="headerlink" title="边缘计算场景"></a>边缘计算场景</h3><ul><li>无设备管理能力：OpenYurt目前没有提供设备管理的相关能力，需要用户以workload形式来运行自己的设备管理解决方案。虽然不算是架构设计的缺点，但是也算是一个边缘场景的不足点。</li></ul><h3 id="2-3-SuperEdge"><a href="#2-3-SuperEdge" class="headerlink" title="2.3.SuperEdge"></a>2.3.<a href="https://superedge.io/zh/">SuperEdge</a></h3><h4 id="（1）开源状况-2"><a href="#（1）开源状况-2" class="headerlink" title="（1）开源状况"></a>（1）开源状况</h4><p>SuperEdge是腾讯云于2020年12月底开源的，目前还是开源初期阶段。其架构如下：</p><p><img src="https://picx.zhimg.com/v2-d399ee534081687024d24d5068df7883_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="SuperEdge"></p><h4 id="（2）与Kubernetes的架构差异-2"><a href="#（2）与Kubernetes的架构差异-2" class="headerlink" title="（2）与Kubernetes的架构差异"></a>（2）与Kubernetes的架构差异</h4><p>SuperEdge的架构设计比较简洁，也是采用的无侵入式对Kubernetes进行增强。在云端(K8s Master)上增加Application-Grid Controller, Edge-Health Admission以及Tunnel Cloud组件。而在边缘端(K8s Worker)上增加了Lite-Apiserver和Tunnel Edge，Application-Grid Wrapper组件。从架构上看主要增加了如下能力来适配边缘场景：</p><ul><li>Lite-Apiserver: 代理各个边缘组件到K8s Master的通信请求，同时把请求返回的元数据持久化在节点磁盘。当云边网络不稳定时，则利用本地磁盘数据来用于边缘业务的生命周期管控。同时基于边缘Edge-Health上报信息，云端的Edge-Health Admission会管控边缘业务Pod的驱逐策略。</li><li>Tunnel Cloud/Tunnel Edge: 每个边缘节点上的Tunnel Edge将主动与云端Tunnel Cloud建立双向认证的加密的gRPC连接，同时云端将通过此连接访问到边缘节点及其资源。</li><li>Application-Grid Controller：引入的两个CRD资源: ServiceGrids和 DeploymentGrids. 前者为位于同一区域的业务流量提供闭环管理。 后者定义了一种新的边缘应用模型以节点池为单位来管理工作负载。</li></ul><h4 id="与OpenYurt对比"><a href="#与OpenYurt对比" class="headerlink" title="与OpenYurt对比"></a>与OpenYurt对比</h4><ul><li>从SuperEdge的架构以及功能分析下来，发现SuperEdge从架构到功能和OpenYurt基本一致。这也从侧面印证，边缘计算云原生这个领域，各大厂商都在如火如荼的投入。</li><li>SuperEdge与Kubernetes的对比分析可以参照OpenYurt的分析，这里我们从代码角度分析SuperEdge和OpenYurt的差异：</li><li>YurtHub和Lite-Apiserver: YurtHub采取了完善的证书管理机制和本地数据缓存设计，而Lite-Apiserver使用的是节点kubelet证书和数据简单缓存。</li><li>Tunnel组件：OpenYurt的Tunnel组件是基于Kubernetes社区的开源项目ANP (<a href="https://github.com/kubernetes-sigs/apiserver-network-proxy">https://github.com/kubernetes-sigs/apiserver-network-proxy</a>) ，同时实现了完善的证书管理机制。而SuperEdge的Tunnel组件同样也是使用节点证书，同时请求转发是基于自行封装的gRPC连接。OpenYurt底层的ANP相比原生gRPC，会更好适配kube-apiserver的演进。</li><li>单元化管理组件: OpenYurt单元化管理支持Deployment和StatefulSet,而SuperEdge的单元化管理只支持Deployment。另外OpenYurt的NodePool和UnitedDeployment的API定义是标准云原生的设计思路，而SuperEdge的ServiceGrids和 DeploymentGrids的API定义显得随意一些。</li><li>边缘状态检测，这个能力OpenYurt未实现，SuperEdge的设计需要kubelet监听节点地址用于节点间互相访问，有一定安全风险。同时东西向流量也有不少消耗在健康检查上。期待这个部分后续的优化。</li></ul><h2 id="5-对比结果一览"><a href="#5-对比结果一览" class="headerlink" title="5. 对比结果一览"></a>5. 对比结果一览</h2><p>根据上述的对比分析，结果整理如下表所示：</p><table><thead><tr><th>项目</th><th>华为KubeEdge</th><th>阿里OpenYurt</th><th>腾讯SuperEdge</th></tr></thead><tbody><tr><td>是否CNCF项目</td><td>是(孵化项目)</td><td>是(沙箱项目)</td><td>否</td></tr><tr><td>开源时间</td><td>2018.11</td><td>2020.5</td><td>2020.12</td></tr><tr><td>侵入式修改Kubernetes</td><td>是</td><td>否</td><td>否</td></tr><tr><td>和Kubernetes无缝转换</td><td>无</td><td>有</td><td>未知</td></tr><tr><td>边缘自治能力</td><td>有(无边缘健康检测能力)</td><td>有(无边缘健康检测能力)</td><td>有(安全及流量消耗待优化)</td></tr><tr><td>边缘单元化</td><td>不支持</td><td>支持</td><td>支持(只支持Deployment)</td></tr><tr><td>是否轻量化</td><td>是(节点维度待确认)</td><td>否</td><td>否</td></tr><tr><td>原生运维监控能力</td><td>部分支持</td><td>全量支持</td><td>全量支持(证书管理及连接管理待优化)</td></tr><tr><td>云原生生态兼容</td><td>部分兼容</td><td>完整兼容</td><td>完整兼容</td></tr><tr><td>系统稳定性挑战</td><td>大(接入设备数量过多)</td><td>大(大规模节点并且云边长时间断网恢复)</td><td>大(大规模节点并且云边长时间断网恢复)</td></tr><tr><td>设备管理能力</td><td>有(有管控流量和业务流量耦合问题)</td><td>无</td><td>无</td></tr></tbody></table><h2 id="3-总结"><a href="#3-总结" class="headerlink" title="3. 总结"></a>3. 总结</h2><p>各个开源项目，整个比较下来自己的感受是：KubeEdge和OpenYurt/SuperEdge的架构设计差异比较大，相比而言OpenYurt/SuperEdge的架构设计更优雅一些。而OpenYurt和SuperEdge架构设计相似，SuperEdge的开源时间晚于OpenYurt，项目成熟度稍差。</p><p>如果打算选择一个边缘计算云原生项目用于生产，我会从以下角度考虑：</p><ul><li>如果需要内置设备管理能力，而对云原生生态兼容性不在意，建议选择KubeEdge</li><li>如果从云原生生态兼容和项目成熟度考虑，而不需要设备管理能力或者可以自建设备管理能力，建议选择OpenYurt</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;随着Kubernetes已经成为容器编排和调度的事实标准，各大公有云厂商都已</summary>
      
    
    
    
    <category term="后端" scheme="http://posts.hufeifei.cn/categories/%E5%90%8E%E7%AB%AF/"/>
    
    
    <category term="KubeEdge" scheme="http://posts.hufeifei.cn/tags/KubeEdge/"/>
    
    <category term="OpenYurt" scheme="http://posts.hufeifei.cn/tags/OpenYurt/"/>
    
    <category term="SuperEdge" scheme="http://posts.hufeifei.cn/tags/SuperEdge/"/>
    
  </entry>
  
  <entry>
    <title>从LSP看MCP：协议标准化如何改变开发与AI生态</title>
    <link href="http://posts.hufeifei.cn/ai/lsp-mcp/"/>
    <id>http://posts.hufeifei.cn/ai/lsp-mcp/</id>
    <published>2025-06-02T00:00:00.000Z</published>
    <updated>2026-01-01T06:47:29.844Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>在技术领域，标准化协议往往是推动行业变革的隐形力量。语言服务器协议（LSP，Language Server Protocol）和模型上下文协议（MCP，Model Context Protocol）就是两个典型的例子。LSP在过去十年中彻底改变了开发工具的生态，而MCP则有望在AI领域掀起类似的革命。本文将通过对比LSP和MCP，探讨协议标准化如何为开发者带来便利，并展望MCP的未来潜力。</p><h2 id="语言服务器协议（LSP）：开发工具的革命"><a href="#语言服务器协议（LSP）：开发工具的革命" class="headerlink" title="语言服务器协议（LSP）：开发工具的革命"></a>语言服务器协议（LSP）：开发工具的革命</h2><h3 id="LSP出现前的混乱"><a href="#LSP出现前的混乱" class="headerlink" title="LSP出现前的混乱"></a>LSP出现前的混乱</h3><p>在2016年<a href="https://github.com/Microsoft/language-server-protocol">LSP</a>发布之前，开发工具生态可以用“各自为政”来形容。当时，集成开发环境（IDE）或代码编辑器（如VSCode、Sublime Text、VIM）需要为每种编程语言单独实现特定的工具支持。这意味着：</p><ul><li>语言开发者需要为不同的编辑器分别开发支持。例如，TypeScript团队可能需要实现“TypeScript-Sublime-Server”和“TypeScript-VSCode-Server”，这导致资源浪费和功能不一致。</li><li>编辑器开发者需要为每种语言单独开发支持。如果一个编辑器不支持某种语言，用户体验就会大打折扣。例如，VSCode可能对JavaScript支持很好，但在Sublime Text中表现不佳；而对于更小众的编辑器（如VIM），用户可能只能依赖简陋的开源插件，甚至完全没有工具支持。</li></ul><p>这种碎片化的生态让开发者不得不在语言支持和编辑器功能之间做出妥协：要么选择支持自己语言的IDE，要么放弃一些高级功能（如语法高亮、自动完成、错误检查）。</p><h3 id="LSP的诞生与变革"><a href="#LSP的诞生与变革" class="headerlink" title="LSP的诞生与变革"></a>LSP的诞生与变革</h3><p>2016年，微软推出了语言服务器协议（LSP），为开发工具生态带来了革命性的变化。LSP是一个基于JSON-RPC的开放协议，定义了编辑器/IDE（客户端）与语言工具（服务器）之间的通信标准。它支持的特性包括代码自动完成、语法高亮、错误检查、跳转定义等。</p><h3 id="LSP的核心创新在于解耦："><a href="#LSP的核心创新在于解耦：" class="headerlink" title="LSP的核心创新在于解耦："></a>LSP的核心创新在于解耦：</h3><ul><li>语言开发者（LSP服务器端）不再需要为每个编辑器单独实现支持。只需要开发一个通用的“LSP服务器”（如“TypeScript-LSP-Server”），就可以与所有支持LSP的编辑器协作。</li><li>编辑器开发者（LSP客户端端）无需为每种语言单独实现支持。只要编辑器实现了LSP客户端，它就能连接到任何LSP服务器，获得一致的语言支持。</li></ul><p>这种解耦带来的好处是显而易见的：</p><ul><li>语言生态：即使是小众语言也能通过实现一个LSP服务器，在主流编辑器中获得一流的开发体验。例如，Rust语言通过<a href="https://github.com/rust-lang/rust-analyzer">rust-analyzer</a>（一个LSP服务器）在VSCode、VIM等编辑器中提供了强大的支持。</li><li>编辑器生态：用户可以根据编辑器的功能（如VSCode的扩展生态、Cursor的AI功能）选择工具，而无需担心语言支持问题。</li><li>开发者体验：开发者不再需要在语言支持和编辑器功能之间妥协，可以专注于编码本身。</li></ul><h3 id="LSP的现状"><a href="#LSP的现状" class="headerlink" title="LSP的现状"></a>LSP的现状</h3><p><img src="https://code.visualstudio.com/assets/api/language-extensions/language-server-extension-guide/lsp-languages-editors.png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="LSP的现状"></p><p>截至2025年，LSP已经成为开发工具的默认标准。VSCode、VIM、Emacs等主流编辑器都支持LSP，许多编程语言（如TypeScript、Python、Rust）也提供了官方的<a href="https://microsoft.github.io/language-server-protocol/implementors/servers/">LSP服务器</a>。然而，LSP的普及并非一帆风顺。一些公司（如JetBrains、Apple）更倾向于使用自己的专有协议，例如JetBrains的IDE和Apple的XCode至今仍未完全拥抱LSP。尽管如此，LSP的开放性和跨平台特性使其逐渐成为连接编辑器与语言工具的桥梁。</p><h2 id="模型上下文协议（MCP）：AI领域的LSP？"><a href="#模型上下文协议（MCP）：AI领域的LSP？" class="headerlink" title="模型上下文协议（MCP）：AI领域的LSP？"></a>模型上下文协议（MCP）：AI领域的LSP？</h2><h3 id="MCP的背景"><a href="#MCP的背景" class="headerlink" title="MCP的背景"></a>MCP的背景</h3><p>2024年底，Anthropic推出了模型上下文协议（MCP），一个旨在解决AI模型与外部系统集成问题的开放协议。MCP的目标与LSP有异曲同工之妙：通过标准化协议，简化AI客户端（如Claude、Cursor、OpenAI）与外部服务（如数据库、第三方API）的交互。</p><p>在MCP出现之前，AI领域的集成生态与LSP出现前的开发工具生态非常相似：</p><ul><li>服务提供者（如Supabase、Stripe）需要为不同的AI客户端分别实现集成。例如，Supabase可能需要分别支持Claude、Cursor、OpenAI等，这增加了开发成本。</li><li>AI客户端需要为每种服务单独开发支持，或者依赖自己的“集成市场”（如OpenAI的GPT市场），这导致用户体验碎片化。</li><li>小型服务提供者由于资源有限，很难与主流AI平台集成，限制了他们的发展。</li></ul><p>这种碎片化让开发者在AI应用开发中面临诸多挑战：要么花费大量时间处理集成问题，要么局限于少数支持的平台。</p><h3 id="MCP的架构与目标"><a href="#MCP的架构与目标" class="headerlink" title="MCP的架构与目标"></a>MCP的架构与目标</h3><p>MCP的架构与LSP类似，分为三部分：</p><ul><li>MCP服务器：由服务提供者实现，负责与外部系统（如数据库、API）交互。例如，Supabase通过Postgres MCP服务器提供只读数据库查询支持。</li><li>MCP客户端：由AI工具实现，负责与MCP服务器通信。例如，Cursor可以通过MCP连接到Supabase，执行自然语言查询。</li><li>MCP主机：协调客户端和服务器之间的通信。</li></ul><h3 id="MCP的目标是通过标准化协议实现解耦："><a href="#MCP的目标是通过标准化协议实现解耦：" class="headerlink" title="MCP的目标是通过标准化协议实现解耦："></a>MCP的目标是通过标准化协议实现解耦：</h3><ul><li>服务提供者只需要实现一个MCP服务器，就能与所有支持MCP的AI客户端协作。例如，Supabase只需要一个“Supabase-MCP-Server”，无需为每个AI平台单独开发支持。</li><li>AI客户端无需为每种服务单独实现支持。只要实现了MCP客户端，就能连接到任何MCP服务器，获取外部数据或功能。</li><li>最终用户（开发者）可以自由选择AI工具，而无需担心服务支持问题。例如，用户可以在Claude或Cursor中使用Supabase的数据库查询功能，无需额外配置。</li></ul><h3 id="MCP与LSP的相似性"><a href="#MCP与LSP的相似性" class="headerlink" title="MCP与LSP的相似性"></a>MCP与LSP的相似性</h3><p>MCP的设计灵感很大程度上来源于LSP。David Soria Parra在X帖子中提到：“<a href="https://x.com/dsp_/status/1897821339332882617">LSP was a big inspiration</a>”（LSP是很大的灵感来源），并感叹LSP的伟大之处被低估。这种相似性不仅体现在架构上，还体现在目标上：</p><ul><li>LSP让编程语言与编辑器解耦，MCP则让外部服务与AI客户端解耦。</li><li>LSP通过标准化提升了小众语言的开发体验，MCP则有望让小型服务提供者更容易与AI平台集成。</li><li>LSP减少了编辑器开发者的负担，MCP则可能终结AI客户端的“集成市场”（如OpenAI的GPT市场），让用户更方便地使用第三方工具。</li></ul><p>一个简单的类比可以帮助理解两者的关系：如果将LSP中的“编程语言”替换为“第三方服务”（如Supabase），将“编辑器”替换为“AI客户端”（如Claude），你就得到了MCP。</p><h2 id="MCP的潜力与挑战"><a href="#MCP的潜力与挑战" class="headerlink" title="MCP的潜力与挑战"></a>MCP的潜力与挑战</h2><p><img src="https://i-blog.csdnimg.cn/direct/29100a3d093048a5aa8280c1b5ebf451.png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="MCP的潜力与挑战"></p><h3 id="MCP的潜力"><a href="#MCP的潜力" class="headerlink" title="MCP的潜力"></a>MCP的潜力</h3><p>MCP有潜力在AI领域复制LSP的成功,MCP 带来了几个核心优势</p><p>作者：谦行的总结 阅读<a href="https://juejin.cn/post/7482236799268864040">原文链接</a></p><ul><li><p><strong>协议标准化驱动生态统一：</strong> MCP通过统一的协议简化了AI与外部工具的连接，开发者无需为每个工具单独编写接口代码，实现一次开发多工具复用</p><blockquote><p>这里有数千开源的 MCP Server 实现可以使用、借鉴，Cursor、Windsurf 等 IDE 均已支持</p><ul><li><a href="https://glama.ai/mcp/tools">glama.ai/mcp/tools</a></li><li><a href="https://smithery.ai/">smithery.ai/</a></li><li><a href="https://github.com/punkpeye/awesome-mcp-servers">awesome-mcp-servers</a></li></ul></blockquote></li><li><p><strong>开发效率</strong>：MCP 官方提供了<a href="https://github.com/modelcontextprotocol">开发工具包 &amp; 调试工具</a>，相对于兼容各种 AI 模型的 Function Call，实现一个通用的 MCP Server 极其简单</p></li></ul><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> mcp.server.fastmcp <span class="keyword">import</span> FastMCP</span><br><span class="line"></span><br><span class="line"><span class="comment"># Create an MCP server</span></span><br><span class="line">mcp = FastMCP(<span class="string">"Demo"</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Add an addition tool</span></span><br><span class="line"><span class="meta">@mcp.tool()</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">add</span>(<span class="params">a: <span class="built_in">int</span>, b: <span class="built_in">int</span></span>) -&gt; <span class="built_in">int</span>:</span><br><span class="line">    <span class="string">"""Add two numbers"""</span></span><br><span class="line">    <span class="keyword">return</span> a + b</span><br><span class="line"></span><br><span class="line"><span class="comment"># Add a dynamic greeting resource</span></span><br><span class="line"><span class="meta">@mcp.resource(<span class="params"><span class="string">"greeting://{name}"</span></span>)</span></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_greeting</span>(<span class="params">name: <span class="built_in">str</span></span>) -&gt; <span class="built_in">str</span>:</span><br><span class="line">    <span class="string">"""Get a personalized greeting"""</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">f"Hello, <span class="subst">{name}</span>!"</span></span><br></pre></td></tr></table></figure><ul><li><strong>根治上下文爆炸：</strong> MCP 采用模块化上下文管理，将外部数据源抽象为独立模块，模型仅在需要时激活指定模块，同时通过增量索引，MCP 仅同步变更数据，相比 Function Call 的全量注入模式，Token 消耗显著降低</li><li><strong>动态发现与灵活性</strong>：MCP 支持动态发现可用工具，AI 可自动识别并调用新接入的数据源或功能，无需提前配置</li></ul><p>MCP 通过协议层革新重构了 AI 与外部系统的协作范式，其标准化、动态化、安全性的特征，正在解决 Function Call 面临的生态碎片化、上下文冗余、权限粗放等核心痛点。随着 Anthropic 携 Claude 之利 的生态推进，MCP 有望成为下一代智能系统的核心基础设施</p><h2 id="优势总结"><a href="#优势总结" class="headerlink" title="优势总结"></a>优势总结</h2><ol><li>标准化需求：AI领域的集成问题比开发工具领域更为迫切。随着AI应用的爆炸式增长，开发者需要一个统一的协议来简化与外部系统的交互。MCP正好满足了这一需求。</li><li>实际应用：MCP已经在一些场景中落地。例如，Supabase通过MCP服务器支持与AI工具（如Cursor）的集成，用户可以使用自然语言命令执行数据库查询。</li><li>社区支持：MCP是一个开源项目，由Anthropic推动，并吸引了大量社区参与。LSP的成功很大程度上得益于其开放性，MCP也有望通过社区力量加速普及。</li></ol><h3 id="MCP的挑战"><a href="#MCP的挑战" class="headerlink" title="MCP的挑战"></a>MCP的挑战</h3><p>尽管前景光明，MCP也面临一些挑战：</p><ol><li>生态系统阻力：与LSP类似，MCP可能会遇到大公司的阻力。例如，OpenAI可能更希望维护自己的GPT市场，而不是采用MCP。类似地，JetBrains和Apple在开发工具领域也更倾向于使用专有协议。</li><li>技术复杂性：AI领域的集成需求比开发工具更复杂。LSP主要处理语言特性（如语法高亮），而MCP需要处理更广泛的场景（如数据库查询、API调用）。目前，Supabase的MCP服务器仅支持只读查询，未来可能需要扩展到更复杂的交互。</li><li>普及速度：LSP用了近10年时间（2016-2025）才成为开发工具的默认标准。MCP在2024年底推出，普及可能需要时间，尤其是在开发者认知度不足的情况下。</li></ol><p>如果对MCP感兴趣，不妨关注它的实际应用案例（如Supabase的MCP服务器），或者参与社区讨论，分享您的经验和见解。协议标准化看似不起眼，但它往往是技术进步的基石——LSP已经证明了这一点，而MCP的征程才刚刚开始。</p><p>参考资料:</p><ul><li><a href="https://code.visualstudio.com/api/language-extensions/language-server-extension-guide#why-language-server">code.visualstudio.com/api/languag…</a></li><li><a href="https://www.reddit.com/r/mcp/comments/1jofsdz/hypeless_opinion_of_mcp/?$deep_link=true&correlation_id=fd5ab237-a01e-4c33-a746-1644e49beaa5&post_fullname=t3_1jofsdz&post_index=0&ref=email_digest&ref_campaign=email_digest&ref_source=email&utm_content=post_body&$3p=e_as&_branch_match_id=1426411557553020972&utm_medium=Email%20Amazon%20SES&_branch_referrer=H4sIAAAAAAAAA22P3U7EIBCFn6Z71/0p7G402Rij8TXIFKYtCgwZaOp64bM7ddUrEyCH75wZhqnWXO53O0bnfN1Cztvg09tO5Yem0ypf0EDZiCT2o08QzMzhMq1VjXpsuhdZy7Jsf+otRQEsO9osp9wjplpEHl5pKO5D1HTNGLAUQ9knT8nQYG7xRkm/Y6cdYjbrHI16rjxj050sMWOAuua9Ez64I/SdOrewP2CrrVItnPWpPZy0Rn3XI8BR6jKVaoY5hAQR13bK/E1yM31y+C7OXgDjIAoj+GCcH7HUGzQWYgY/pv/dQjNb/PUEzjUaS6nK34V+P9OTu24+JY3MPo2mZ1oK8uVpYor4BXjnH/eIAQAA">reddit</a></li><li><a href="https://supabase.com/docs/guides/getting-started/mcp">supabase.com/docs/guides…</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;在技术领域，标准化协议往往是推动行业变革的隐形力量。语言服务器协议（LSP，</summary>
      
    
    
    
    <category term="AI" scheme="http://posts.hufeifei.cn/categories/AI/"/>
    
    
    <category term="AI" scheme="http://posts.hufeifei.cn/tags/AI/"/>
    
    <category term="MCP" scheme="http://posts.hufeifei.cn/tags/MCP/"/>
    
    <category term="LSP" scheme="http://posts.hufeifei.cn/tags/LSP/"/>
    
  </entry>
  
  <entry>
    <title>开放数据标准：Postgres，OTel，与Iceberg</title>
    <link href="http://posts.hufeifei.cn/db/pg-otel-iceberg/"/>
    <id>http://posts.hufeifei.cn/db/pg-otel-iceberg/</id>
    <published>2025-06-02T00:00:00.000Z</published>
    <updated>2026-01-01T06:47:29.860Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>数据世界正在浮出水面的三大新标准：Postgres、Open Telemetry，以及 Iceberg。</p><p><a href="https://www.postgresql.org/">Postgres</a> 基本已经是事实标准；<a href="https://opentelemetry.io/">OTel</a> 和 <a href="https://iceberg.apache.org/">Iceberg</a> 尚在成长， 但它们具备当年让 Postgres 走红的同样配方。常有人问我：“为什么最后是 Postgres 赢了？” 标准答案是“<a href="https://mp.weixin.qq.com/s?__biz=MzU5ODAyNTM5Ng==&mid=2247487055&idx=1&sn=9d7bd8b6d9b07478dba7f87d0a663535&scene=21#wechat_redirect">可扩展性</a>” —— 对，但不完整。</p><p>除了产品本身优秀，Postgres 还踩中了开源生态爆点 —— 关键在于“开源的姿势”本身。</p><blockquote><p>作者：Paul Copplestone，Supabase CEO </p><p>译评：冯若航，Pigsty Founder，数据库老司机</p></blockquote><p><img src="https://mmbiz.qpic.cn/mmbiz_png/Wkpr3rA9wF16IwIWZsYu0QguoALgibg9NgcDyG0uddxum2smy5fZnzibrzqokAzxhxGyrGBuufscVBkm45NfmnMA/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=0" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="图片"></p><hr><h2 id="开源的三个信条"><a href="#开源的三个信条" class="headerlink" title="开源的三个信条"></a>开源的三个信条</h2><p>我逐渐悟到，开发者判断一个项目“开源味”浓不浓，大致看三点：</p><ol><li><strong>许可证</strong>：是否为 <a href="https://opensource.org/licenses">OSI 核准</a> 的开源协议。</li><li><strong>自托管</strong>：能否把<strong>完整产品</strong>端到端地自己部署。</li><li><strong>商业化</strong>：有没有商业中立、无厂商绑架；更妙的是，有 <strong>多家</strong> 公司背书而非一家独大。</li></ol><p>第三点我领悟得最慢 —— 是的，Postgres 赢在产品力，但更赢在 <strong>“谁也控不住”</strong> 。 治理结构与社区文化决定了它不可能被任何公司收编。它就像国际空间站，多家公司只能合作，因为谁都没本事说 “这就是我的”。</p><p>Postgres 点满了 “开源” 技能点，但它也并非在所有数据场景里都是银弹。</p><hr><h2 id="三类数据角色"><a href="#三类数据角色" class="headerlink" title="三类数据角色"></a>三类数据角色</h2><p>数据领域里主要有三种 “操盘手” 及其趁手工具：</p><ol><li><strong>OLTP 数据库</strong>：<strong>开发者</strong> 写应用用。</li><li><strong>遥测 / 观测</strong>：<strong>SRE</strong> 运维基建、调优应用用。</li><li><strong>OLAP / 数仓</strong>：<strong>数据工程师 / 科学家</strong> 挖掘洞见用。</li></ol><p>数据生命周期通常是 <code>1 → 2 → 3</code>：先有应用，再加点基础遥测（很多时候直接塞进 OLTP 系统），等表长到塞不下，就得上数仓了。</p><p>三类角色各玩各的，但行业正整体“左移”：工具越发友好，观测与数仓也慢慢被开发者收编。SRE 和数据岗并非故意让贤，只是数据库本身越来越能打，创业团队能撑更久再招专家。</p><hr><h2 id="三大开放数据标准"><a href="#三大开放数据标准" class="headerlink" title="三大开放数据标准"></a>三大开放数据标准</h2><p>围绕以上三大场景，正冒出三套满足同样开源三信条的开放标准：</p><ol><li><strong>OLTP</strong>： PostgreSQL</li><li><strong>遥测</strong>： Open Telemetry</li><li><strong>OLAP</strong>： Iceberg</li></ol><p>后两者更像“标准”而非“工具”，类似 HTML 与浏览器：大家约好格式，其他工具要么跟进要么淘汰。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/Wkpr3rA9wF16IwIWZsYu0QguoALgibg9NIicayNm4pKW9KshkbtO8MYTuHMkgFljwQpJfRujMdvib5xQP1ibFicbpJw/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=1" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="图片"></p><p>标准往往草根起家，商业公司则陷入经典的 <a href="https://en.wikipedia.org/wiki/Disruptive_innovation">颠覆式创新</a> 两难：</p><ul><li><strong>不跟</strong>？潮流跑了，错过增长趋势。</li><li><strong>跟了</strong>？自家产品锁定度变低。</li></ul><p>对开发者而言，这简直不能更香了 —— <a href="https://supabase.com/docs/guides/getting-started/architecture#everything-is-portable">我们坚信</a>：<strong>可迁移性会逼着厂商拼体验</strong>。</p><p>下面逐一展开深入探讨。</p><hr><h3 id="Postgres：开放式-OLTP-标准"><a href="#Postgres：开放式-OLTP-标准" class="headerlink" title="Postgres：开放式 OLTP 标准"></a>Postgres：开放式 OLTP 标准</h3><p>Postgres 虽是一款数据库，却已成 <strong>“标准接口”</strong>。 几乎所有新数据库都宣称“兼容 <a href="https://www.postgresql.org/docs/current/protocol.html">Postgres wire 协议</a>”。 因为谁也管不了 Postgres，各大云厂商要么主动，要么被用户倒逼着上架 Postgres —— 连 Oracle Cloud 都供着。 体验差？一句 <code>pg_dump</code> 走人。Postgres 用 <a href="https://www.postgresql.org/about/licence/">PostgreSQL License</a> —— 功能上和 MIT 相当。</p><hr><h3 id="OTel：开放式遥测标准"><a href="#OTel：开放式遥测标准" class="headerlink" title="OTel：开放式遥测标准"></a>OTel：开放式遥测标准</h3><p>“open telemetry” 的名字是字面含义：开放遥测。<a href="https://opentelemetry.io/">OTel</a> 仍年轻且颇为<a href="https://news.ycombinator.com/item?id=42655102">复杂</a>，但契合开源三信条：Apache 2.0，厂商中立。 正如云厂商拥抱 Postgres，主流观测平台也在集体投 OTel，包括 <a href="https://docs.datadoghq.com/integrations/otel/">Datadog</a>、<a href="https://docs.honeycomb.io/send-data/opentelemetry/">Honeycomb</a>、<a href="https://grafana.com/grafana/dashboards/15983-opentelemetry-collector/">Grafana Labs</a> 与 <a href="https://www.elastic.co/docs/solutions/observability/apm/use-opentelemetry-with-apm">Elastic</a>。 想自托管？可选 <a href="https://github.com/SigNoz/signoz">SigNoz</a>、<a href="https://github.com/openobserve/openobserve">OpenObserve</a>，再不济用官方 <a href="https://github.com/open-telemetry/opentelemetry-collector">OTel 工具集</a>。</p><hr><h3 id="Iceberg：开放式-OLAP-标准"><a href="#Iceberg：开放式-OLAP-标准" class="headerlink" title="Iceberg：开放式 OLAP 标准"></a>Iceberg：开放式 OLAP 标准</h3><p><a href="https://www.startdataengineering.com/post/what_why_table_format/">开放表格式</a> 算是新赛道：大家约定目录+元数据格式，任何计算引擎都能查询。 虽有 <a href="https://delta.io/">DeltaLake</a>、<a href="https://hudi.apache.org/">Hudi</a> 等对手，但目前 <a href="https://iceberg.apache.org/">Iceberg</a> 已然领跑。</p><p>各大数仓陆续“投靠” Iceberg：包括 <a href="https://docs.databricks.com/aws/en/delta/uniform">Databricks</a>、<a href="https://docs.snowflake.com/en/user-guide/tables-iceberg">Snowflake</a> 和 <a href="https://clickhouse.com/docs/engines/table-engines/integrations/iceberg">ClickHouse</a>。 最关键的商业推手是 AWS —— 2024 年底官宣 <a href="https://aws.amazon.com/blogs/aws/new-amazon-s3-tables-storage-optimized-for-analytics-workloads/">S3 Tables</a>，在 S3 上提供开箱即用的 Iceberg。</p><hr><h2 id="S3：终极数据基础设施"><a href="#S3：终极数据基础设施" class="headerlink" title="S3：终极数据基础设施"></a>S3：终极数据基础设施</h2><p>对象存储很便宜，已成三大标准的基石。今天凡是数据工具，不是原生 S3 就是兼容 S3。</p><p>AWS S3 团队连环上新，把 “S3 当数据库” 的幻想推向现实。诸如 <a href="https://aws.amazon.com/about-aws/whats-new/2024/08/amazon-s3-conditional-writes/">Conditional Writes</a> 和 <a href="https://aws.amazon.com/blogs/aws/new-amazon-s3-express-one-zone-high-performance-storage-class/">S3 Express</a> —— 速度比普通 S3 快 10 倍，最近还 逆天降价 <a href="https://aws.amazon.com/blogs/aws/up-to-85-price-reductions-for-amazon-s3-express-one-zone/">85%</a>。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/Wkpr3rA9wF16IwIWZsYu0QguoALgibg9NtN1zKaQw4Axicmx2aYO2ibRobAVA1UwhScKCTWiatywgmvtyqkPj70SibA/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=2" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="图片"></p><p>不同场景对 S3 的姿势略有差异：</p><ul><li><strong>OLTP</strong>：性能要命，S3 与 NVMe 永远隔着物理网线。因此重点是 Zero ETL &amp; 分层存储：冷热数据自由搬迁。Postgres 现有多种读 Iceberg 的方式，如 <a href="https://github.com/Mooncake-Labs/pg_mooncake">pg_mooncake</a>、<a href="https://github.com/duckdb/pg_duckdb">pg_duckdb</a> 及 <a href="https://github.com/supabase/wrappers/pull/462">Iceberg FDW</a>。</li><li><strong>遥测 / 数仓</strong>：关键字是“基数”。S3 越便宜，大家越把海量数据往里倒，催生“存算分离”的架构。于是出现一堆以计算层自居的嵌入式数据库：如 <a href="https://duckdb.org/2021/10/29/duckdb-wasm.html">DuckDB</a>（OLAP）、SQLite 的<a href="https://sqlite.org/cloudsqlite/doc/trunk/www/index.wiki">云后端存储</a>、<a href="https://turbopuffer.com/">turbopuffer</a>（向量）、<a href="https://slatedb.io/">SlateDB</a>（KV）、<a href="https://tonbo.io/">Tonbo</a>（Arrow）。它们既可嵌入应用，也能单飞。</li></ul><hr><h2 id="Supabase-的数据蓝图"><a href="#Supabase-的数据蓝图" class="headerlink" title="Supabase 的数据蓝图"></a>Supabase 的数据蓝图</h2><p>大家知道 Supabase 是 Postgres 服务商，我们花了 5 年打造让开发者舒爽的数据库平台，这仍是主航道。</p><p>不同的是，我们不止做 Postgres（虽然<a href="https://itsjustpostgres.com/">梗图</a>挺火）。我们还提供 <a href="https://supabase.com/storage">Supabase Storage</a>，一套兼容 S3 的对象存储。未来，Supabase 聚焦的不是“一个数据库”，而是“所有数据”：</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/Wkpr3rA9wF16IwIWZsYu0QguoALgibg9N8Y8EFLOf0KeoXB445VLTibPEpAljjOFWjLVCdcsPu2ibVle6mCSSs1sg/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=3" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="图片"></p><ul><li>给我们维护的所有开源工具加上 OTel。</li><li>在 Supabase Storage 引入 Iceberg。</li><li>在 <a href="https://github.com/supabase/supabase_etl">Supabase ETL</a> 里打通 Postgres ↔ Iceberg 零 ETL。</li><li>通过扩展和 FDW，让 Postgres 能读能写 Iceberg。</li></ul><p>接下来，我们押注三大开放数据标准：<strong>Postgres、OTel、Iceberg</strong>。敬请期待。</p><hr><h2 id="老冯点评"><a href="#老冯点评" class="headerlink" title="老冯点评"></a>老冯点评</h2><p>Supabase 是我最欣赏的数据库创业公司，他们的创始人认知水平非常在线。 例如在三年前 <a href="https://mp.weixin.qq.com/s?__biz=MzU5ODAyNTM5Ng==&mid=2247485589&idx=1&sn=931f2d794e9b8486f623f746db9f00cd&scene=21#wechat_redirect">OpenAI 插件带火向量数据库赛道之前</a>，Supabase 就已经发掘出 pgvector 进行 RAG 的玩法了。</p><p>YC S20 的项目走过五年发展到今天，已经是估值 2B 的独角兽了。目前 YC 80% 的初创公司都在用 Supabase 起步。 目前有小道消息称 <a href="https://mp.weixin.qq.com/s?__biz=MzU5ODAyNTM5Ng==&mid=2247489695&idx=1&sn=eb0aa2286ecdbb014fd6b38023ae6749&scene=21#wechat_redirect">OpenAI 即将收购 Supabase</a>，如果是真的，那他们也算功德圆满，实至名归。</p><h2 id="关于-Postgres"><a href="#关于-Postgres" class="headerlink" title="关于 Postgres"></a>关于 Postgres</h2><p>老冯非常认同 Paul 的观点，Postgres 已经成为 OLTP 世界的事实标准。 但至少在当下，还有几件事是 PostgreSQL “不擅长” （不是做不到）的：</p><ul><li>遥测</li><li>海量分析</li><li>对象存储</li></ul><p>所以如果你想要提供一个真正 “完全覆盖” 的数据基础设施，那么光有 PostgreSQL 是不行的。</p><p>我的意思是，你可以使用 TimescaleDB 扩展存储遥测数据，但体验与表现是比不上 Prometheus，VictoriaMetrics 的等专用 APM 组件的。 </p><p>你确实可以用原生 PG，TimescaleDB，Citus，以及好几个 DuckDB 缝合扩展做数仓 —— <a href="https://mp.weixin.qq.com/s?__biz=MzU5ODAyNTM5Ng==&mid=2247489279&idx=1&sn=ca161963f98ec000a4d3bba41edaea85&scene=21#wechat_redirect">尽管我认为 DuckDB PG 缝合有潜力解决这个问题</a>，但至少在当下，当数据量超过几十个 TB 时，专用数仓的性能依然还是压着 PG 打的。 </p><p><a href="https://mp.weixin.qq.com/s?__biz=MzU5ODAyNTM5Ng==&mid=2247489293&idx=1&sn=4d538e8ea2808e61ccccfca763473834&scene=21#wechat_redirect">有一些 “邪路” 可以将 PG 作为文件系统，例如 JuiceFS</a>，但这仅适用于小规模的数据存储（也许几十GB？），海量 PB 级对象存储依然是原生 PG 所望尘莫及的。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/Wkpr3rA9wF16IwIWZsYu0QguoALgibg9NP8rwMOuz980ibaFRASIbZp4w0Xyia5lKYdvItmSptiaRzFHf2LHryBz6Q/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=4" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="图片"></p><p>至于其他的细分领域，比如向量数据库，文档数据库，地理空间数据库，时序数据库，消息队列，全文检索引擎，乃至是图数据库，PostgreSQL 都已经 “足够好” 了。 留给其他产品的只剩下一个极端场景专用组件的 Niche，不会再有其他这种体量的玩家出现了。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/Wkpr3rA9wF16IwIWZsYu0QguoALgibg9NMI6mGQMCzBnyFtafjDI8Q3rhG4Q7ib9hic5PQcCl3H8V3BJE1lqvoCVw/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=5" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="图片"></p><p>因此，在我做 Pigsty 的时候，也是用相同的思路构建的，以 PostgreSQL 为核心，以可观测性作为这个发行版的基石（Postgres in Grafana Style：这是最初的缩写），以同心圆的方式对外摊大饼。 用 MinIO 补足对象存储，用 DuckDB / Greenplum 补足数仓分析能力，最后用数量惊人的扩展插件来覆盖其他细分领域。</p><p>其实在这一点上， Pigsty 跟 Supabase 很像，只不过人家是 2B 独角兽，老冯是数据库个体户，哈哈。但其实只要战略选对了，也不是不能打，比如， Supabase 骑在 PostgreSQL 肩膀上，而我可以骑在 Supabase 的肩膀上。 </p><h2 id="关于开源"><a href="#关于开源" class="headerlink" title="关于开源"></a>关于开源</h2><p>Paul 说关于开源的三点精髓，第三点他领悟的是最慢的：</p><blockquote><p>有没有商业中立、无厂商绑架；更妙的是，有 <strong>多家</strong> 公司背书而非一家独大。</p></blockquote><p>其实我非常理解 Paul 的感受，在前两年，Supabase 的想法可能是 —— “我要占领开源道德高地，但是也要用 PG 扩展构建自己的商业壁垒。”</p><p>虽然 Supabase 提供了 Docker Compose 自建模板，但那个数据库容器镜像充其量就是个玩具，而且里面包含着隐藏的壁垒。 主要是他们自己用 Rust 写了几个扩展插件，这几个扩展插件虽然是开源的，但打包构建的知识并没有在社区普及 —— 你无法指望让用户自己去编译这些东西。</p><p>老冯就干了件 “缺德” 或者说 “有德” 的事（取决于厂家还是用户视角），把他们的扩展插件全都编译打包成了 10 大 Linux 主流系统下的 RPM/DEB 包， 这样你就可以真的在自己的 PostgreSQL 上自建 Supabase 了。我们还提供了一个模板，可以在一台裸服务器上自建 Supabase，目前是 Supabase 官方推荐的三个三方自建教程之一。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/Wkpr3rA9wF16IwIWZsYu0QguoALgibg9N7arEKQWU70u2PMrQM6Vm9hDd9JXSDmzG7f4Y50UAkmoQoEgJnGwl5A/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=6" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="图片"></p><p>Supabase 还在想其他方法构建壁垒，例如他们去年收购了 OrioleDB，一个云原生，无膨胀的 PostgreSQL 存储引擎扩展（需要Patch内核）。 还没等正式 GA 上线，<a href="https://mp.weixin.qq.com/s?__biz=MzU5ODAyNTM5Ng==&mid=2247489444&idx=1&sn=840f6632e27899b6764b8c0f96cd368e&scene=21#wechat_redirect">老冯就也已经打好了 OrioleDB 的 RPM/DEB 包</a>，供用户自建使用了。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/Wkpr3rA9wF16IwIWZsYu0QguoALgibg9NxtePicV4uLRkMe7kmXJD8fuYJc3nkKkFyenFMhQMAsCFCAibQibJXsqjw/640?wx_fmt=png&from=appmsg&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=7" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="图片"></p><p>我估计 Paul 的心情是复杂的，一方面他想要将用户锁定在 Supabase 云服务上，看到别人真的用开源来拆台，心里肯定不爽。 但另一方面正是这些三方社区厂商的努力，反而让 Supabase 开枝散叶，不是一个 “只有我提供” 的东西，才有了开源的醍醐味。 所以最后也释然了，坦然接受了这种现状。</p><p>但这件事也对老冯有所触动，我也开始思考，Pigsty 作为一个开源项目，是否也有类似的 “开源三信条”？</p><p>老实说，老冯很怀念全职创业前的那种状态，完全不考虑商业化，为了兴趣，热情，公益而开源，所以使用的是 Apache 2.0 协议。 后来因为拿投资人钱要有一个交代，所以把协议修改为更严格的 AGPLv3 ，目标是为了阻止云厂商与同行白嫖。 但既然现在我又成了数据库个体户，其实也是可以回到那种开源初心状态的 —— “反正俺也不靠这个赚钱”。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;数据世界正在浮出水面的三大新标准：Postgres、Open Telemet</summary>
      
    
    
    
    <category term="数据库" scheme="http://posts.hufeifei.cn/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    
    <category term="OpenTelemetry" scheme="http://posts.hufeifei.cn/tags/OpenTelemetry/"/>
    
    <category term="PostgreSQL" scheme="http://posts.hufeifei.cn/tags/PostgreSQL/"/>
    
    <category term="Iceberg" scheme="http://posts.hufeifei.cn/tags/Iceberg/"/>
    
  </entry>
  
  <entry>
    <title>原始数据→数据资源→数据资产→数据产品→数据商品→数据资本</title>
    <link href="http://posts.hufeifei.cn/economic/data-capital/"/>
    <id>http://posts.hufeifei.cn/economic/data-capital/</id>
    <published>2025-05-27T00:00:00.000Z</published>
    <updated>2026-01-01T06:47:29.860Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><h3 id="理论研究论文（2025年3月4日）"><a href="#理论研究论文（2025年3月4日）" class="headerlink" title="理论研究论文（2025年3月4日）"></a>理论研究论文（2025年3月4日）</h3><p><strong>《<a href="https://zhida.zhihu.com/search?content_id=254662884&content_type=Article&match_order=1&q=%E6%95%B0%E6%8D%AE%E8%A6%81%E7%B4%A0%E4%BB%B7%E5%80%BC%E6%BC%94%E8%BF%9B%E8%B7%AF%E5%BE%84&zhida_source=entity">数据要素价值演进路径</a>研究》</strong>（胡良霖等，<a href="https://zhida.zhihu.com/search?content_id=254662884&content_type=Article&match_order=1&q=%E3%80%8A%E6%95%B0%E6%8D%AE%E4%B8%8E%E8%AE%A1%E7%AE%97%E5%8F%91%E5%B1%95%E5%89%8D%E6%B2%BF%E3%80%8B&zhida_source=entity">《数据与计算发展前沿》</a>，<a href="https://link.zhihu.com/?target=https://mp.weixin.qq.com/s?__biz=MzkxODEzMDIwNA==&mid=2247524950&idx=1&sn=21d452486eb1e4fb84277bfd45d53cd7&chksm=c03584ab2d11254ab7220a6431a4dc9e141ad647707b1e6e08b9bd457960f85736c906ad8743%23rd">【成果速递】国家基础学科公共科学数据中心数据要素论文在《数据与计算发展前沿（中英文）》上发表</a>）<br><strong>推荐理由</strong>：该研究系统提出数据要素价值化的演进模型，被纳入国家基础学科公共科学数据中心成果，理论框架完整且具实践指导意义。<br><strong>核心观点</strong>：</p><h3 id="数据要素六种形态"><a href="#数据要素六种形态" class="headerlink" title="数据要素六种形态"></a><strong><a href="https://zhida.zhihu.com/search?content_id=254662884&content_type=Article&match_order=1&q=%E6%95%B0%E6%8D%AE%E8%A6%81%E7%B4%A0%E5%85%AD%E7%A7%8D%E5%BD%A2%E6%80%81&zhida_source=entity">数据要素六种形态</a></strong></h3><ul><li>从原始数据到数据资本，分阶段定义<strong>原始数据→数据资源→数据资产→数据产品→数据商品→数据资本</strong>的演进逻辑，强调确权、计量和流通是关键节点。</li></ul><p><img src="https://pic2.zhimg.com/v2-9bfc1816cd7428e3042388208343d861_1440w.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><h3 id="五阶段价值释放路径"><a href="#五阶段价值释放路径" class="headerlink" title="五阶段价值释放路径"></a><strong>五阶段价值释放路径</strong></h3><ul><li><strong>资源化</strong>（清洗整合）、<strong>资产化</strong>（产权明晰）、<strong>产品化</strong>（计量评估）、<strong>商品化</strong>（市场定价）、<strong>资本化</strong>（增值分配），并指出反向迭代融合的增值潜力。<br><strong>应用价值</strong>：为政府制定<a href="https://zhida.zhihu.com/search?content_id=254662884&content_type=Article&match_order=1&q=%E6%95%B0%E6%8D%AE%E4%BA%A7%E6%9D%83%E5%88%B6%E5%BA%A6&zhida_source=entity">数据产权制度</a>、企业设计<a href="https://zhida.zhihu.com/search?content_id=254662884&content_type=Article&match_order=1&q=%E6%95%B0%E6%8D%AE%E8%B5%84%E4%BA%A7%E5%85%A5%E8%A1%A8%E7%AD%96%E7%95%A5&zhida_source=entity">数据资产入表策略</a>提供理论支撑，例如杭州市数据交易条例中“五首”政策（首登记、首挂牌等）即基于此逻辑<strong>37</strong>。</li></ul><h3 id="行业案例研究（2025年3月3日）"><a href="#行业案例研究（2025年3月3日）" class="headerlink" title="行业案例研究（2025年3月3日）"></a>行业案例研究（2025年3月3日）</h3><p><strong>《国家数据局首批20个“数据要素×”典型案例》</strong>（来源：国家数据局联合多部门发布，<a href="https://link.zhihu.com/?target=https://mp.weixin.qq.com/s?__biz=MzkwODIxMDkwOQ==&mid=2247496503&idx=4&sn=32e9414bcf7e8532b025964abb59c681&chksm=c11ab03f2dc83b4955dfe1bdf78b28c3211098eb3322dd23f6b614e369b0b5587b00ec133156%23rd">国家数据局第一批“数据要素×”典型案例（20个）</a>）<br><strong>推荐理由</strong>：该案例集由国家数据局联合生态环境部、交通运输部等部门发布，覆盖工业制造、现代农业、商贸流通等12个领域，代表当前数据要素应用的最高水平。<br><strong>核心内容</strong>：</p><h3 id="工业数据空间赋能产业链"><a href="#工业数据空间赋能产业链" class="headerlink" title="工业数据空间赋能产业链"></a><strong><a href="https://zhida.zhihu.com/search?content_id=254662884&content_type=Article&match_order=1&q=%E5%B7%A5%E4%B8%9A%E6%95%B0%E6%8D%AE%E7%A9%BA%E9%97%B4&zhida_source=entity">工业数据空间</a>赋能产业链</strong></h3><ul><li><p>四川长虹集团通过构建工业数据空间，整合产业链上下游的测试、生产、库存等数据，解决中小微企业融资难题。例如，利用<a href="https://zhida.zhihu.com/search?content_id=254662884&content_type=Article&match_order=1&q=%E4%BE%9B%E5%BA%94%E9%93%BE%E5%BA%94%E4%BB%98%E8%B4%A6%E6%AC%BE%E6%95%B0%E6%8D%AE&zhida_source=entity">供应链应付账款数据</a>为中小企业提升授信，融资总额超40亿元，利率降低1.05%<strong>12</strong>。</p></li><li><p><strong><a href="https://zhida.zhihu.com/search?content_id=254662884&content_type=Article&match_order=1&q=%E5%A4%9A%E5%BC%8F%E8%81%94%E8%BF%90%E6%95%B0%E6%8D%AE%E9%A9%B1%E5%8A%A8%E8%BF%90%E8%BE%93%E8%A3%85%E5%A4%87%E4%BC%98%E5%8C%96&zhida_source=entity">多式联运数据驱动运输装备优化</a></strong></p></li><li><p>国家能源集团整合铁路、港口、船舶装备的30亿条数据，构建智能模型优化运输效率，形成600类高质量数据集，并通过数据资产交易平台实现降本增效，吸引275家制造企业参与交易<strong>12</strong>。<br><strong>亮点</strong>：案例展示数据要素在实体产业中的深度渗透，涵盖数据共享、资产交易、金融赋能等多维度创新。</p></li></ul><h3 id="数据要素生命周期核心总结（2025年2月27日）"><a href="#数据要素生命周期核心总结（2025年2月27日）" class="headerlink" title="数据要素生命周期核心总结（2025年2月27日）"></a><strong><a href="https://zhida.zhihu.com/search?content_id=254662884&content_type=Article&match_order=1&q=%E6%95%B0%E6%8D%AE%E8%A6%81%E7%B4%A0%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F&zhida_source=entity">数据要素生命周期</a>核心总结（2025年2月27日）</strong></h3><h3 id="定义与重要性"><a href="#定义与重要性" class="headerlink" title="定义与重要性"></a><strong>定义与重要性</strong></h3><ul><li><p>数据要素是数字经济时代与土地、劳动力并列的核心生产要素，具有非排他性、非竞争性、非损耗性特征。</p></li><li><p>价值体现：驱动经济增长（优化资源配置）、促进社会进步（智慧医疗/交通）、提升国家竞争力。</p></li><li><p><strong>生命周期五大阶段</strong><br><strong>① 数据采集</strong></p></li><li><p><strong>来源</strong>：物联网设备（传感器）、互联网平台（爬虫/API）、企业系统（ERP/CRM）、公共数据（政府/科研）。</p></li><li><p><strong>方法</strong>：主动采集（摄像头）、被动接收（设备上传）、混合模式（气象站结合卫星与地面数据）。</p></li></ul><p><strong>② 数据存储</strong></p><ul><li><ul><li><strong>技术</strong>：关系数据库（结构化）、NoSQL（非结构化）、分布式文件系统（HDFS）、云存储（弹性扩展）、数据湖（原始数据整合）。</li><li><strong>策略</strong>：分类存储（热数据SSD/冷数据磁带）、加密备份、压缩降本、生命周期管理（迁移归档）。</li></ul></li></ul><p><strong>③ 数据处理与分析</strong></p><ul><li><ul><li><strong>清洗预处理</strong>：去重、填充缺失值（均值/模型预测）、纠正错误（格式校验）、标准化/离散化。</li><li><strong>分析挖掘</strong>：统计分析（描述性/回归）、数据挖掘（分类/聚类/关联规则）、时序预测（股票价格）。</li></ul></li></ul><p><strong>④ 数据使用与共享</strong></p><ul><li><ul><li><strong>使用场景</strong>：金融风控、医疗诊断、智能推荐（如电商用户画像）。</li><li><strong>共享机制</strong>：数据交易平台（市场化流通）、联邦学习（隐私保护）、区块链存证（防篡改）。</li><li><strong>合规要求</strong>：GDPR/《数据安全法》、匿名化处理（k-匿名）、权限控制（RBAC模型）。</li></ul></li></ul><p><strong>⑤ 数据销毁与归档</strong></p><ul><li><ul><li><strong>销毁技术</strong>：覆写法（多次覆盖）、消磁（物理破坏）、加密擦除（密钥销毁）。</li><li><strong>归档策略</strong>：冷存储（磁带库）、完整性校验（哈希值）、访问日志审计。</li></ul></li></ul><h3 id="管理最佳实践"><a href="#管理最佳实践" class="headerlink" title="管理最佳实践"></a><strong>管理最佳实践</strong></h3><ul><li><strong>顶层设计</strong>：建立数据治理体系（组织架构+流程规范）。</li><li><strong>技术支撑</strong>：引入AI/区块链工具提升效率与安全性。</li><li><strong>合规框架</strong>：动态跟踪国内外法规（如中国DSL、欧盟GDPR）。</li><li><strong>人才培养</strong>：跨领域团队（数据科学家+法律专家+业务分析师）。</li></ul><h3 id="关键结论"><a href="#关键结论" class="headerlink" title="关键结论"></a><strong>关键结论</strong></h3><p>数据要素全生命周期管理需贯穿“采-存-用-享-毁”各环节，通过技术+制度双轮驱动，实现数据资产的安全可控与价值最大化，赋能数字化转型。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;h3 id=&quot;理论研究论文（2025年3月4日）&quot;&gt;&lt;a href=&quot;#理论研究</summary>
      
    
    
    
    <category term="经济" scheme="http://posts.hufeifei.cn/categories/%E7%BB%8F%E6%B5%8E/"/>
    
    
    <category term="经济" scheme="http://posts.hufeifei.cn/tags/%E7%BB%8F%E6%B5%8E/"/>
    
  </entry>
  
  <entry>
    <title>数字化转型里的“数据”</title>
    <link href="http://posts.hufeifei.cn/economic/data-production-materials/"/>
    <id>http://posts.hufeifei.cn/economic/data-production-materials/</id>
    <published>2025-05-27T00:00:00.000Z</published>
    <updated>2026-01-01T06:47:29.860Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>数字化时代，不管你是公司的决策层、管理层、还是普通的基层员工，‍‍终都将与大量的数据亲密接触。最近，我们常听到这两句话：“数字化时代，数据成为生产资料”、“数据是数字化转型的核心驱动要素”。我想，这可能会给很多人带来疑问：“数据还能做生产资料？数据还能驱动一场变革？数据到底是什么呀？数据又将如何改变我们的工作和生活呢？”</p><h2 id="数据到底是什么"><a href="#数据到底是什么" class="headerlink" title="数据到底是什么?"></a><strong>数据到底是什么?</strong></h2><p>我们来看看数据的定义。字面上理解，数据是由“数”和“据”组成。“数”以数字、数值的形式存储的信息，“据”指证据或依据。综合起来，数据就是数字化的证据和依据，是事物发生、存在和发展过程中留存下来的数字化记录。数据的载体可以是数值、文本、图形、图像、声音和视频等格式。通过这个记录，我们可以还原事物在当时的状态和发生的活动，我们可以通过数据去追溯当时的情景。</p><p>也许，会有人提问：“数据，不就是一个计算机术语吗？”</p><p>是的，过去，数据常常被认为与计算机有关。早在1946年，data一词就首次被明确表示为“可传输和可存储的计算机信息”。新华词典的定义是：“电子计算机加工处理的对象”。国际数据管理协会（DAMA）认为“数据是以文本、数字、图形、图像、声音和视频等格式对事实进行表现，考虑到结构化数据和非结构化数据的差异性，在管理时需要采取不同的管理方式”。早期的计算机主要用于科学计算，故加工的对象主要是表示数值的数字。现代计算机的应用越来越广，能加工处理的对象包括数字、文字、字母、符号、文件、图像等。</p><p>随着互联网和大数据的发展，企业积累了大量的数据，数据来源也更加丰富多样，数据的处理、分析、挖掘、展现已不在拘泥于传统的方式，非结构化的海量数据亟待被开发和挖掘。市场需求的热度和技术的不断创新，推动着大数据价值的提升，未来，数据将成为一项重要的生产资料，而不仅仅是一项计算机专用素材。</p><h2 id="数字化时代，数据成为生产资料，"><a href="#数字化时代，数据成为生产资料，" class="headerlink" title="数字化时代，数据成为生产资料，"></a><strong>数字化时代，数据成为生产资料，</strong></h2><h2 id="成为了“新资源”、“新资产”、“新资本”"><a href="#成为了“新资源”、“新资产”、“新资本”" class="headerlink" title="成为了“新资源”、“新资产”、“新资本”"></a><strong>成为了“新资源”、“新资产”、“新资本”</strong></h2><p>数字化转型，将驱动人类社会从工业经济时代向数字经济时代迈进。而驱动本轮科技变革和产业变革的根本动力，是数据。数据将成为生产资料，改变生产力各要素，进而引发全社会一系列深刻的变革。</p><p>农业经济时代，土地是生产资料，锄、犁、锹是生产工具，以家庭为生产单元的组织模式是核心生产关系。</p><p>工业经济时代，煤、石油、矿、电是生产资料，机械设备是生产工具，以公司为生产单元的组织模式是核心生产关系；</p><p>数字经济时代，数据将成为重要的生产资料，而生产工具和核心生产关系也将随之而发生改变，云计算将成为生产力，而核心生产关系将基于互联网进行组织模式的构建。</p><p>在会计体系中，企业的存货通常包括原材料、在产品、半成品、产成品、商品。数据作为生产资料，是否也将建构和形成这些形态？</p><p><strong>是的</strong>。一般，原始数据的价值密度比较低，就像是原材料。我们需要将数据从原始、混沌、无序、动态的原始状态，转化为清晰、标准、有序、稳态的可被大规模生产所使用的基础生产原料，最后再经过一系列层层的治理、开发，才能最终成为具有实际应用价值的数据产品，进而成为可供交易的商品。</p><p>在这个过程中，大数据平台是存储数据的仓库。治理好的数据，将按照一定的方式进行结构化存放，以便使用时可以快速获取。生产工具是数据治理和大数据平台。</p><p><strong>数据从原材料转化为产品</strong>，最终成为可实现价值变现的商品，需历经四个阶段：<strong>采集、存储、生产和销售</strong>。</p><p><strong>数据采集阶段，目标是要拥有数据</strong>，就像从开采原油一样，首先要获取足够多的原始数据。这个阶段的关键是要打通物理世界和数字世界的边界，连接OT（操作技术）和IT（信息技术），将数据采集现场前移到生产作业现场，全面、准确和及时地将数据采集过程和生产作业过程合一。这个阶段需要大量前端感知设备收集生产数据，需要网络快速回传数据。感知设备和传输设备是采集阶段的重要生产工具。</p><p><strong>数据存储阶段，目标是解决数据可用的问题</strong>。原始数据采集回来后，跟原材料一样，有序存放，才能保证需要的时候用得上。数据资产目录，和实物资产盘点的作用类似，知道有什么、有多少，才能物尽其用。然后，需要建立数据标准，即数据按照什么方式存放，是1斤1斤的按照“重量”存放，还是10根10根的按照“数量”存放，是装在结构化数据的“箱子”里，还是装在图像、视频的“袋子”里，都需要根据数据对象的特点进行区别和规整，这样才能保证更高效的入库，更高效的出库。数据治理，是存储阶段的核心工作。</p><p><strong>数据生产阶段，目标是炼化数据的价值。</strong>数据最终要回归现实，实实在在地解决现实的业务问题，它才具有价值。在业务问题的牵引下，建立特定数据和特定业务场景之间的连接，构建各种数据模型进行大量、广泛的试错，在实际应用中不断调教数据模型的成熟度和精准度，最后固化成IT系统或人工智能，应用在业务场景中，持续稳定的解决问题，这就是数据价值的炼化过程。数据建模、人工智能是炼化数据价值的工具和手段，是生产阶段的核心工作。数据，在数据生产阶段，将历经半成品、在产品、产品等状态。</p><p><strong>数据销售阶段，目标是数据价值变现。</strong>任何商品、任何价值都需要找到愿意为此付费的买家才能变现，数据产品也不例外。建立数据驱动的业务场景和用户需求之间的连接，解决对人有意义、有价值的业务问题，是数据变现的关键。</p><p>数据从原材料转化为产品的不同阶段，使用的工具不同，组织生产的方式也不同由此，将引发整个社会生产关系的一系列根本性变化。同时，数据的巨大价值和潜能将被充分挖掘出来。</p><h3 id="（一）第一个层次，数据是一种新的“资源”，是一种新的生产资料"><a href="#（一）第一个层次，数据是一种新的“资源”，是一种新的生产资料" class="headerlink" title="（一）第一个层次，数据是一种新的“资源”，是一种新的生产资料"></a><strong>（一）第一个层次，数据是一种新的“资源”，是一种新的生产资料</strong></h3><p>在物理空间中，数据是对客观事物和事件的记录和反映。我们用“数据” 来指导生产活动，推动人类社会实现各种飞跃。数据对提高生产效率具有乘数作用，比如数控机床的生产效率就比传统机床要高出数量级。数据是可再生、无污染的，数据是可循环使用的，数据是越用越多、无限的延展的。数据是可以和土地、资本、技术、劳动力等生产要素一起，相互配合、相互融合的。数据成为了经济社会发展不可或缺的基础性战略资源。</p><p>在数字空间中，数据是构成、生成虚拟世界里事物和事件的基本元件。对于数字空间而言，数据就不仅仅是生产资料了，更是“生命”的基础。因为没有数据，数字空间无从谈起。如今，数字空间里的“经济活动”已创造出非常惊人的财富，并且这种创造价值的能力还在不断飞跃。微信、微博、抖音、淘宝、百度、携程等等上面的行为、内容、交流、信息等数据经过加工后又可成为新的资源，推动着新业务的产生。</p><h3 id="（二）第二个层次，数据是一种新的“资产”"><a href="#（二）第二个层次，数据是一种新的“资产”" class="headerlink" title="（二）第二个层次，数据是一种新的“资产”"></a><strong>（二）第二个层次，数据是一种新的“资产”</strong></h3><p>2023年10月14日，数据资产管理论坛于“全球资产管理中心上海国际活动周2023”期间举办。<strong>上海资产管理协会会长李文表示，数字经济时代，数据已成为重要的生产要素，而数据资产管理成为企业和组织的核心竞争力之一。</strong></p><p>一般来讲，资产是指由企业过去经营交易或由各项事项形成的、被企业拥有或控制的、预期会给企业带来经济利益的资源。</p><p>过去，企业在生产经营过程中形成了大量的数据。未来，企业有价值数据的规模和鲜活度，以及数据治理、开发的能力，可决定企业的核心竞争力，可为企业的后续发展带来经济利益。因此，数据成为了企业不可或缺的战略“资产”。而实现有价值的数据可控制、可量化、可交易、可变现的过程，就是数据资产化的过程。正如 IBM 执行总裁罗睿兰讲的：“数据将成为一切行业当中决定胜负的根本因素”。奥巴马政府2012 年发布的《大数据研究和发展倡议》，将数据定义为“未来的新石油”，并表示国家拥有数据的规模、活性及解释运用的能力将成为一个国家综合国力的重要组成部分，未来对数据的占有和控制甚至将成为海权、陆权、空权之外的另一种国家核心资产。</p><p><strong>在数字空间中，数据“新资产”的价值也越来越凸显。</strong>2012 年，李安导演的《少年派的奇幻漂流》风靡全球。电影里有一只让大家印象深刻的老虎，它是由计算机用算法和数据生成的。创作那只老虎一共花费了 1400 万美元，如果把这只老虎作为数据“新资产”，可以收费出让它的使用权，让它出现在一部新电影、广告或游戏之中；也可以收费出售老虎某个部位的原始数据，让其他人可以在此基础上进行二次开发得到一只新老虎，这样，这个老虎数据“新资产”就可以源源不断地产生新收益。在网络游戏业，有一个新奇的职业是帮别人打怪升级、练装备。通过用自己的技能、经验、时间，把自己的装备数据“新资产”越练越雄厚，价值也就越来越高。而练出的“装备”是可以交易的，顶级装备甚至能卖出天价。通过出售这种装备数据“新资产，也可以赚取不菲的收益。</p><p>随着云计算、物联网、大数据、数字孪生、元宇宙等新技术的不断发展，物理空间和数字空间互相交互、融合，将产生更多新的业态和数据资产。</p><h3 id="（三）第三个层次，数据是一种新的“资本”"><a href="#（三）第三个层次，数据是一种新的“资本”" class="headerlink" title="（三）第三个层次，数据是一种新的“资本”"></a><strong>（三）第三个层次，数据是一种新的“资本”</strong></h3><p>数据已经成为一种资本，和金融资本一样，能够产生新的产品和服务。</p><p>过去，货币这一传统意义上的资本，一直对经济有着巨大的驱动作用。随着数字技术对传统经济的改造和重构，海量数据对原有的市场形态和市场机制带来了重大变革，慢慢地数据成为了一项“新资本”，驱动这经济社会的发展。人类经济社会将进入数据资本新时代。</p><p>网络时代兴起的长尾理论指出：只要产品的存储和流通的渠道足够大，需求不旺或销量不佳的产品所共同占据的市场份额可以和那些少数热销产品所占据的市场份额相匹敌，甚至更大。也就是说，企业的销售量不在于传统需求曲线上那个代表 “畅销商品”的头部，而是那条代表“冷门商品”经常为人遗忘的长尾。长尾理论指导并创造了一大批新兴的互联网公司和创新商业模式。淘宝面向无数的小商家、小商铺提供了聚合的平台，创造了中国电子商务的神话;美团瞄准千万家小餐馆，把外卖这件事做成了几百亿的大生意；如今砸钱做广告已经 OUT 了，新的营销渠道是抖音、快手、直播等等。<strong>这其中的逻辑总结成一句话就是“给钱不如给流量”，而流量就是数据的凝聚，流量所代表的就是数据“新资本”的力量。</strong></p><p>当互联网从消费互联网的上半场进入到产业互联网的下半场，大家会逐渐发现，<strong>仅有流量是不够的，更重要的是数据本身的价值凝聚和价值创造</strong>。数据价值与新兴技术相结合，在数据“新资本”的驱动下，再造业务流程、企业结构，进而重构整个产业生态，才能实现价值的成倍递增。</p><h2 id="数据对企业管理各领域的驱动作用"><a href="#数据对企业管理各领域的驱动作用" class="headerlink" title="数据对企业管理各领域的驱动作用"></a><strong>数据对企业管理各领域的驱动作用</strong></h2><p>有着从原材料到产品的种种形态的物质有很多，比如粮食、比如石油、天然气。数据，有着什么不同呢？</p><p><strong>数据，更像是企业的血液，为企业提供着养分和能量</strong>。随着企业更多的数字化，企业的运营越来越像一台机器。它会需要你去供给企业发展的“养料”，也就是驱动企业发展的能量。“养料”是什么？就是数据。通过数据分析，可以让企业决策者充分客观地了解企业的业务情况和经营状况，并在数据分析的基础上快速调整公司的发展战略、精准定位客户需求。最终更高效地为企业创造收益。知己知彼才能保持企业在市场中的竞争力生存及可持续发展。</p><p>企业还需要定期进行“体检”，体检抽取的“血液”是什么？也是数据，需要去抽取企业的数据进行分析，去看它的运营上有哪些问题。因此，数字化转型后，数据会像血液一样，成为企业的一个必需品，所有企业的运作都可以基于数据来进行应用。</p><p>我们来看看，数字化转型后，数据将对企业管理各领域起到哪些驱动作用？</p><h3 id="（一）数据驱动战略管理"><a href="#（一）数据驱动战略管理" class="headerlink" title="（一）数据驱动战略管理"></a><strong>（一）数据驱动战略管理</strong></h3><p>我国工商管理硕士（MBA）学位培养计划中规定了一门核心课程：数据模型与决策，这门课旨在应用现有的科学技术知识和数学方法，通过建立合理的定量分析模型，对企业中的人力、物力、财力等资源进行统筹安排，为决策者选择解决各种经济问题、管理问题或生产问题的最优决策提供定量依据，以实现最有效的管理。可以看出，数据天然是战略管理的关键支持要素。</p><p>战略对企业的重要性毋庸赘言，但在如今这个充满复杂性、模糊性、不确定性、波动性的世界里，传统战略制定方法仅基于有限的内部数据与片面的外部数据，或依赖领导者的个人经验和行业常识认知的制定方法，已经无法适应瞬息万变的商业世界，更无法赶超“与时俱进”的竞争对手了。</p><p>尽管，企业战略始终是“科学与艺术”的结合。但企业依然开始努力探索通过数据驱动的战略与运营来实现业务增长。用更全面的数据进行客观度量，应用数据分析方法去发现业务增长机遇以及风险点，望能更好地依赖数据科学及量化逻辑进行创新性的战略制定，并针对数据反馈及时调整战略，制定更有效的解决方案。</p><h3 id="（二）数据驱动人力资源管理"><a href="#（二）数据驱动人力资源管理" class="headerlink" title="（二）数据驱动人力资源管理"></a><strong>（二）数据驱动人力资源管理</strong></h3><p>数字化时代下，企业人力资源管理面临新挑战：</p><h4 id="（1）从确定到不确定"><a href="#（1）从确定到不确定" class="headerlink" title="（1）从确定到不确定"></a><strong>（1）从确定到不确定</strong></h4><p>从2019年“经济寒冬”到如今新冠疫情影响全球经济，面对不断迭代变化的世界，企业不得不接受“不确定性”带来的一连串挑战，在不确定性的环境中进行决策似乎已经越来越常态化。这时，企业管理者会希望HR不仅仅做一些人事事务性工作和现状总结，更希望HR以动态、准确的人力资源数据分析为依据，来为决策提供基于数据的洞察支持，为业务提供基于数据的人力资源服务，以应对不确定性。</p><h4 id="（2）从经验主义到数据主义"><a href="#（2）从经验主义到数据主义" class="headerlink" title="（2）从经验主义到数据主义"></a><strong>（2）从经验主义到数据主义</strong></h4><p>传统人力资源管理，可以计算投入，却难以量化产出。很多企业管理者，对于内部人力资源的分析决策，主要依赖于经验和主观判断。从基于经验的决策到数据驱动的决策，是企业人力资源管理数字化转型升级的根本模式。</p><h4 id="（3）从职能角色到战略角色"><a href="#（3）从职能角色到战略角色" class="headerlink" title="（3）从职能角色到战略角色"></a><strong>（3）从职能角色到战略角色</strong></h4><p>传统人力资源管理，主要是完成企业管理中的一个职能。当外部环境瞬息万变，企业需要组织具备“快速学习、准确把握、迅速反应”的能力来适应环境，对人力资源的依赖比以往更加强烈，人力资源的价值定位升级为“变革推动者”、“业务战略伙伴”这样的战略角色，人力资源管理的升级转型势在必行。</p><p>让我们来看看让全球顶尖人才趋之若鹜的谷歌公司的人力资源管理。“出色的人才管理”是谷歌公司取得今日成就的关键要素之一。谷歌公司认为：人力资源专业已经是数据科学。谷歌的HR决策，全部来自内部数据的分析。</p><p>实现数据驱动的人力资源管理，需要基于企业人力资源管理的全业务链条，将贯穿于整个企业员工生命周期的数据串联起来，进行数据分析，指导人才管理战略的规划，进行人才选、用、育、留环节的决策。HR将由原来的感性判断转变为用可量化的数据进行理性分析。</p><p>在人力资源分析中，人力成本，人力效率，离职率等指标是企业决策者比较关心的，但仅凭公式计算出的指标数据并不能实现数据驱动决策。这些指标看似只是单个数据，实际上关联着一连串业务链条。比如，离职率指标关联着招聘、培训、岗位、绩效等一系列数据。想要降低离职率，需要对“如何进行招聘？”、“招进来后如何通过培训让他快速拥有上岗所需技能？”、“如何合理安排岗位？”、“如何设置考核激励方案？”等一系列人力资源管理方式进行数据分析与驱动。</p><h3 id="（三）数据驱动市场营销"><a href="#（三）数据驱动市场营销" class="headerlink" title="（三）数据驱动市场营销"></a><strong>（三）数据驱动市场营销</strong></h3><p>    有没有遇上过这样的情况：</p><p>    1.当你买了套房子后，无数的房产中介拨打电话进来，问您是否放卖、放租您的房子？</p><p>    2.当你为孩子报了个课程后，无数的课程销售代表拨打电话进来，问您要不要给孩子报编程课、英语课？</p><p>    3.当您贷过款后，无数的银行销售电话过来，问您需不需要资金？有低息贷款。</p><p>    传统的粗放式营销，缺乏场景化和针对性，不仅浪费成本，还会增加对客户的打扰、引起客户反感。针对业务和活动，投其所好的进行精准营销，才会受到用户的青睐。</p><p>    随着大数据的到来，越来越多的行业都在逐渐摸索适合自己的营销和传播方式，逐步调整自身的营销战略。</p><p>    精准营销的核心是对数据的分析洞察。通过数据采集、数据分析、数据挖掘，精准定位目标客户，了解客户在哪里、做过什么、需要什么。对客户进行了全方位了解后，才能制定出精准的差异化、个性化营销手段，才能基于各个环节的效果分析，反向指导用户筛选、模型优化、预算分配、资源采购、活动设计、渠道使用、触点优化。在有限资源的情况下，实现最大化客户转化，提升用户的活跃留存。</p><p>    执行精准营销的过程中，面临着如何寻找用户、如何执行营销、如何评估效果、如何打造闭环等主要挑战：</p><h4 id="（1）寻找用户"><a href="#（1）寻找用户" class="headerlink" title="（1）寻找用户"></a><strong>（1）寻找用户</strong></h4><p>    进行数据收集、数据整合、建立用户数据指标体系，进而进行用户分层、分群，为精准营销提供客群筛选。</p><p>    接下来，充分利用前期精准营销结果数据、活动参与数据、用户行为数据等，借助有监督机器学习预测用户行为，并根据实验结果不断迭代优化用户行为预测模型，提高精准营销转化率。</p><h4 id="（2）执行精准营销"><a href="#（2）执行精准营销" class="headerlink" title="（2）执行精准营销"></a><strong>（2）执行精准营销</strong></h4><p>    筛选出精准客群后，进一步选择推送渠道，在适当的时机，将合适的内容推送出去。</p><p>    在执行营销之前，可结合营销资源规划表、时事热点、公关日历表等，做年度、月度营销的精准营销计划表。</p><h4 id="（3）评估营销效果——目标导向的归因分析"><a href="#（3）评估营销效果——目标导向的归因分析" class="headerlink" title="（3）评估营销效果——目标导向的归因分析"></a><strong>（3）评估营销效果——目标导向的归因分析</strong></h4><p>    每次进行精准营销，都有个业务目标。因此，分析营销效果需基于不同的目标导向。全流程各个环节最好进行精准的数据监控，做好埋点和数据收集。设定好监控周期，不同营销活动的各个批次之间采用统一口径即可对比营销效果。</p><h4 id="（4）搭建营销闭环"><a href="#（4）搭建营销闭环" class="headerlink" title="（4）搭建营销闭环"></a><strong>（4）搭建营销闭环</strong></h4><p>    精准营销过程中，从筛选精准客群，到执行精准营销，到营销效果监测与评估，数据分析是贯穿始终的工作。</p><p>    精准营销如实现营销闭环，对精准营销效果进行复盘，可指导接下来的精准营销。这对客户标签沉淀、客户模型调优、客户营销方案完善、营销触点优化、自动化营销体系建设，起着非常重要的作用。</p><h3 id="（四）数据驱动运营管理"><a href="#（四）数据驱动运营管理" class="headerlink" title="（四）数据驱动运营管理"></a><strong>（四）数据驱动运营管理</strong></h3><p>    近年来，中国高速公路路网趋于饱和，基础建设需求正逐年降低，而管理需求却逐年增强。利用智能技术、数字技术建设智慧高速，盘活资产、提高管理效能和服务质量，降低运维成本及安全风险，成为高速公路运营机构和交通参与者的迫切需求。</p><p>    运营管理的本质，是匹配供给和需求。数据时代，大数据会让运营管理的颗粒度更细，会让供需的匹配更加精准和快速。</p><p>    从量化的数据指标来看，公司更多采用数据驱动去做决策，能够为市场决策收集基本数据，帮助识别财务风险和欺诈行为；可以利用数据优化营销投资和个性化客户互动，提升财务状况的实时可见性。在财务表现上来看，公司使用更多数据决策，则会在营收、毛利率、净收入等指标上表现更为优异。</p><h3 id="大数据在高速公路运营管理上的应用方向"><a href="#大数据在高速公路运营管理上的应用方向" class="headerlink" title="大数据在高速公路运营管理上的应用方向"></a><strong>大数据在高速公路运营管理上的应用方向</strong></h3><h4 id="（1）交通安全管理方面"><a href="#（1）交通安全管理方面" class="headerlink" title="（1）交通安全管理方面"></a><strong>（1）交通安全管理方面</strong></h4><p>    我们可以利用先进的电子传感技术、热成像技术、自动控制技术、视频监控技、数据传输技术等建立起一套实时、高效、准确的交通安全管理系统。通过分析各个传感器传过来的数据，建立数据间的函数关系，建模得知哪些情况下容易发生交通事故，提前通过各类新媒体进行信息推送、通知司乘人员注意行车安全。</p><h4 id="（2）路政管理方面"><a href="#（2）路政管理方面" class="headerlink" title="（2）路政管理方面"></a><strong>（2）路政管理方面</strong></h4><p>    路政管理工作主要是“保护路产、维护路权，治理和审批涉路和超限超载行为”。大部分高速公路是国有企业采用“贷款修路，收费还款”的方式修建的，为保护国家财产，路政管理工作尤为重要，</p><p>    高速公路路政管理部门可以将高速公路旁和中央隔离带的百米桩、公里桩、(电子)情报板等设备信息记录进路政的数据库中。设备如有损坏、缺失，可在数据库中快速更新，并记录下设备的更换信息，有利于国有资产的保护。</p><p>    对于车辆超载超限等行为，路政部门可以利用多方数据融合分析，得到超限超载车辆的相关数据，并进行车辆追踪和跟进处理。</p><h4 id="（3）收费管理方面"><a href="#（3）收费管理方面" class="headerlink" title="（3）收费管理方面"></a><strong>（3）收费管理方面</strong></h4><p>    收费管理方面，数据量是巨大的、价值也是最大的。对于高速公路而言，偷逃通行费的行为也很多。路段运营公司完全可以对其省内所有收费站以往的数据进行分析，得到一套可以用来实现收费稽查目的功能的平台。此平台定期对全省联网的收费数据进行逐条核对，只要符合偷、逃费特征的就标记出来，通过系统通知相关部门和各收费站。</p><h4 id="（4）服务管理方面"><a href="#（4）服务管理方面" class="headerlink" title="（4）服务管理方面"></a><strong>（4）服务管理方面</strong></h4><p>    对司乘人员来说，接受服务的地方最主要是服务区。近年来许多服务区已经升级改造，提供了各式各样的服务。数据可以帮助降低服务区提供服务的成本。如服务区的超市，可利用进销存数据管理货物交易，形成成本优势。随着以后城际高速上新能源车辆会越来越多，服务区可以通过数据分析提前布局电动汽车的充电设备以及服务设施。服务区可以通过进出口的视频车牌识别系统，准确知道车辆在服务区停留的时间。以提前准备服务供给。</p><h4 id="（5）养护管理方面"><a href="#（5）养护管理方面" class="headerlink" title="（5）养护管理方面"></a><strong>（5）养护管理方面</strong></h4><p>    高速公路一旦通车以后就面临着养护的问题，经常需要进行修修补补，以面对越来越大的交通量。</p><p>    养护单位要采集全路面结构方面和技术标准方面的数据，并根据车流量、路面载荷状况以及气象、环境等因素制定全生命周期的路面、桥梁、隧道养护计划。</p><h4 id="（6）监控管理方面"><a href="#（6）监控管理方面" class="headerlink" title="（6）监控管理方面"></a><strong>（6）监控管理方面</strong></h4><p>    高速公路上监控设备有：摄像头、可变情报板、桥梁和隧道状态监视系统等，这些设备都价值不菲，为高速公路管理者提供着最新的信息。因此，他们具体的位置和工作状态都需要在监控中心数据库中保存。</p><p>    设备的保养维修和更新换代情况都需要在数据库中进行记录。经营公司如能利用好设备数据优化设备升级批次，能节省一大笔费用。</p><h3 id="（五）数据驱动财务管理"><a href="#（五）数据驱动财务管理" class="headerlink" title="（五）数据驱动财务管理"></a><strong>（五）数据驱动财务管理</strong></h3><p>    财务工作，是一种天然要与数据打交道的工作。数字经济时代的到来，对原有的财务工作也产生了很大冲击。过去，传统的财务工作是做一些财务报表编制、资金结算、信息报送等财务核算和监督相关的工作。但在数字经济时代，财务的职能发生了很大的变化，一些重复的工作将被软件或财务机器人取代。财务人员将从重复性工作中释放出来，进行财务管理方面的工作。因此，传统财务将会有以下三方面转变：</p><h4 id="（1）财务职能向管理领域延伸"><a href="#（1）财务职能向管理领域延伸" class="headerlink" title="（1）财务职能向管理领域延伸"></a><strong>（1）财务职能向管理领域延伸</strong></h4><p>    传统的财务工作相对比较独立封闭，很难与业务进行有效融合。数字化时代，财务工作和业务工作将会高度融合。财务人员的部分职责将会转移到业务人员身上，而财务人员将会更多地参与经营决策，承担资金管理、预算管理、风险管控等更高价值的工作。</p><h4 id="（2）资金管理向链条化转型"><a href="#（2）资金管理向链条化转型" class="headerlink" title="（2）资金管理向链条化转型"></a><strong>（2）资金管理向链条化转型</strong></h4><p>    传统的财务工作一般包括账户管理、资金结算、资金划拨、资金对账等，主要是针对企业内部的资金管理。数字经济时代，企业的财务管理将走向立体化、链条化，企业的资金管理不再局限于内部资金的集中管控和调配，而是向供应链金融模式转变，可对资金流动进行静态或动态的监测管理。</p><h4 id="（3）财务决策向数据型转变"><a href="#（3）财务决策向数据型转变" class="headerlink" title="（3）财务决策向数据型转变"></a><strong>（3）财务决策向数据型转变</strong></h4><p>    传统的财务决策一般是基于收入、成本、利润、资产、负债等企业财务数据进行的，但这种决策往往不太科学，因为企业的经济活动不仅与内部资金流转紧密相连，还和外部环境、宏观经济政策等有着密切的联系。数字经济时代，我们可以用大数据收集、分析各类业务数据，可以用人工智能算法预测风险，财务决策也将变得更加科学高效。</p><h2 id="数据驱动和数字化转型"><a href="#数据驱动和数字化转型" class="headerlink" title="数据驱动和数字化转型"></a><strong>数据驱动和数字化转型</strong></h2><h2 id="会给企业管理带来哪些改变"><a href="#会给企业管理带来哪些改变" class="headerlink" title="会给企业管理带来哪些改变"></a><strong>会给企业管理带来哪些改变</strong></h2><h3 id="一-管理模式的转变"><a href="#一-管理模式的转变" class="headerlink" title="(一) 管理模式的转变"></a><strong>(一) 管理模式的转变</strong></h3><p>    过去，公司基本采取“控制+命令”这样的权威式管理，管理的重心在领导层。数字化时代，管理重心会从高层向中层、基层转移，企业管理的模式、赋能的机制，会从以前的“控制+命令”转变成“指导和服务”。企业内部，从决策层、管理层到执行层，将共同面对管理挑战。</p><p>    基于新一代的信息技术，企业的全部业务过程，都将以数据的方式被跟踪记录下来。数据不再只服务于高层，企业的各级管理人员、所有听得见炮火的人，都可以得到数据的支持。</p><p>    数字化时代，企业数据量大幅增加，数据的来源更加丰富多样，数据的标准化程度越来越高。我们可以围绕数据进行多维度的价值挖掘，用数据来赋能管理、赋能业务、赋能操作，用数据全面的驱动整个企业的经营和运作。</p><p>    高层管理者可以通过数据感受到全局经营情况，以此来进行敏锐的洞察规划和基于算法的智能辅助决策。中层管理者可以通过数据感知业务全程，更快预判业务发展中的风险和问题，找到更好的业务实现路径，基于业务标准化和规则来实现透明化的可控的管理、可视的结果。而执行层将被赋予更大的自主权和决策权。他们将在听得见炮声的同时，依赖更透明的数据体系和规则和更加协同高效的数字化能力的辅助，更好、更敏捷、更主动、更精准地进行执行决策并实现业务执行过程中的管理目标。</p><h3 id="二-企业运作模式的改变"><a href="#二-企业运作模式的改变" class="headerlink" title="(二) 企业运作模式的改变"></a><strong>(二) 企业运作模式的改变</strong></h3><p>    过去的企业运作是通过流程进行驱动的。而数字化时代的企业运作，将完成由流程驱动向数据驱动的变革。</p><p>    在过去的企业管理模式中，管理层常因受限于企业规模、业务复杂度与管理半径之间的矛盾，无法进行穿透式的向下管理，只能依赖于各层级员工主动发现问题，并基于繁琐的流程层层上报‍‍来处置各种事件、完成管理工作。这个时候，数据只能完成“呈现”。</p><p>    随着企业运作环境的日益多元化、复杂化，这种人海战术式“流程驱动”模式的成本高、效率低等瓶颈问题日益凸显，严重阻碍了企业竞争力的发展。</p><p>‍     全球知名的高端智库兰德公司的研究表明，世界上每100家破产倒闭的大企业中，85%是因为管理者的决策不慎造成的。华为公司总结特种部队作战的规律，形成并实践了：“让听得见炮声的人来做决策”这种模式。这与传统的依赖“能人做决策”的机制有着很大的不同，同时，要实现这样的管理模式升级，也非常的不容易，这‍‍需要一个“及时感知、科学决策、主动服务、高效运营、智能监控”的数字化新型经营机制和管理体系来支撑。在这种模式下，数据成为了维持这样的经营机制和管理体系运转的“血液”和驱动力。</p><h3 id="三-数据作用的改变"><a href="#三-数据作用的改变" class="headerlink" title="(三) 数据作用的改变"></a><strong>(三) 数据作用的改变</strong></h3><p>    过去，我们常常通过数据报表或简单BI的方式将管理者需要“看见”的数据的结果、经营的成果、企业的状态‍‍等客观事实呈现出来，这时，报表或BI不会做出好坏优劣的判断，而是由管理层通过数据进行“人的决策”。此时，数据处于支持的位置，最终的决策是由人做出来的。</p><p>    这时，想要有好的决策，除了要有高质量的统计分析报表，还依赖报表使用者的能力。有经验的人才能根据报告穿透数据看到本质、做出更好的决策。但是，我们作为一个人的局限性仍然无法避免。</p><p>    随着新时代管理的环节日趋复杂多变，高频次的决策、动态的决策、高难度的决策、快速精准的决策需求越来越多，企业业务流程环节多、角色链条长、信息不对称等传统决策模式下存在的问题越来越凸显，这样的决策模式已经很难匹配当下的竞争。</p><p>‍     随着AI技术的深入应用和人机协同工作方式的普及化深度应用，‍‍企业的自动化、智能化幅度大幅度提升。有些公司发现，‍‍基于数据加规则和算法来直接驱动业务，会更高效的执行整个业务运作。比如，在高速公路通行中，偶有车辆通过粤通卡“套牌”的方式“大车小标”，以偷缴通行费。过去，识别“大车小标”靠得是收费员“眼力”。现在，通过收费站安装的照相设备和后端系统的人工智能算法，通过基于数据来分辨“车脸”，来实现“稽查打逃”中的预警，会更加精准高效。</p><p>    所以，过去“人找数”的模式将会越来越多的转变成“数找人”。甚至，在未来，‍‍机器能够基于数据和对数据的收集、治理、加工、处理、洞察展开一系列的主动性的操作，‍‍可以预警，可以智能决策，可以自动化的执行一部分业务活动甚至是管理活动，‍‍可以主动推动决策和业务发展。</p><h3 id="对企业运作来说，数据，将从"><a href="#对企业运作来说，数据，将从" class="headerlink" title="对企业运作来说，数据，将从"></a><strong>对企业运作来说，数据，将从</strong></h3><h3 id="被动的“支持”作用转变为主动的“驱动”作用。"><a href="#被动的“支持”作用转变为主动的“驱动”作用。" class="headerlink" title="被动的“支持”作用转变为主动的“驱动”作用。"></a><strong>被动的“支持”作用转变为主动的“驱动”作用。</strong></h3><p>     按照人对数据的使用程度逐层展开，除了“呈现”和“预警”，数据驱动还将历经以下3个阶段：</p><h3 id="（一）建议阶段"><a href="#（一）建议阶段" class="headerlink" title="（一）建议阶段"></a><strong>（一）建议阶段</strong></h3><p>建议阶段的核心是帮你决策，也就是说我们将基于数据做出行动的建议。</p><p>比如客户的流失会成为粤通卡经营中一个非常挑战的问题。联合电服公司每年都会花费一定的成本‍‍来拓展新客户，‍‍但每年如老客户的流失，或者粤通卡使用次数减少，依然会成为管理者需要面对的挑战，因为这将导致业务的增长受到一定的局限。‍‍而更大的问题是‍‍，这些客户的流失可能都是在已经发生流失之后，公司才发现‍。也就是说，在一段的时间没有交易，客户抛弃了我们，我们才发现“噢，他已经不再使用粤通卡”。这个时候再来开展动因分析，再来制定对策，即使花费了大量的时间、精力和成本，往往可能都是亡羊补牢、为时已晚。‍</p><p>    但如果公司能借助充值、通行等线上数据的积累，‍‍形成大量跟客户行为相关的数据。基于这些数据，开展基于客户行为的数据分析，‍‍通过构建模式‍‍来提前预警客户流失的风险。对于有可能存在流失风险的客户，系统直接通知业务员迅速采取行动，‍‍并提供行动的标准动作，也许就能避免客户的流失。这时，就做到了数据驱动价值实现。</p><h3 id="（二）决策阶段"><a href="#（二）决策阶段" class="headerlink" title="（二）决策阶段"></a><strong>（二）决策阶段</strong></h3><p>    决策阶段，机器将基于数据和模型主动帮助或者替代一部分人作出决策判断。‍‍在这个过程中，基于的是企业内部的知识积累和管理经验，更重要的是基于能支持决策的数据，‍‍通过对数据的处理形成自动决策和自动执行。‍‍比如，在审核粤通ETC信用卡初始授信额度时，通过数据的处理形成粤通卡授信额度的建议。</p><h3 id="（三）通汇贯通阶段"><a href="#（三）通汇贯通阶段" class="headerlink" title="（三）通汇贯通阶段"></a><strong>（三）通汇贯通阶段</strong></h3><p>    在这个阶段，线上的决策和线下的行动将合二为一，‍‍企业在数据孪生虚拟空间产生的这些结果，将跟我们真实世界的真实交易有效的融合。‍‍将虚拟与实际打通，数据与现实融合，真正形成元宇宙这样的‍‍企业运作新阶段。</p><h2 id="要实现数据驱动，企业还需做什么努力？"><a href="#要实现数据驱动，企业还需做什么努力？" class="headerlink" title="要实现数据驱动，企业还需做什么努力？"></a><strong>要实现数据驱动，企业还需做什么努力？</strong></h2><h3 id="（一）我们已经有哪些数据驱动的基石？"><a href="#（一）我们已经有哪些数据驱动的基石？" class="headerlink" title="（一）我们已经有哪些数据驱动的基石？"></a><strong>（一）我们已经有哪些数据驱动的基石？</strong></h3><p>    数字化时代的新技术层出不穷，数据驱动的门槛也随之大幅降低，更多企业将享受数据带来的红利。那么，新技术对于驱动有哪些推动和提升呢？</p><p>    <strong>首先，</strong>我们有了云计算。数据驱动需要海量的、可弹性扩展存储空间和计算资源。云计算可以降低构建平台的复杂性，可以简化运维，可以大大减低企业的压力。基于云计算的大数据基础网络，企业也无需维护底层的硬件设施和网络。云上大量的SARS化应用，极大的降低了数据分析和处理的门槛。企业甚至无需聘请高端的数据分析师，就能完成复杂的数据分析和处理工作。</p><p>    <strong>其次，</strong>我们有了物联网。物联网技术是物理世界连接数字世界的通道。物理世界中，不但实体的物在被数字化，一系列抽象的范畴也在被数字化。充分数字化的进程中，物理世界的潜在价值在被不断挖掘出来。</p><h3 id="（二）要实现数据驱动，还需要有什么前提条件"><a href="#（二）要实现数据驱动，还需要有什么前提条件" class="headerlink" title="（二）要实现数据驱动，还需要有什么前提条件"></a><strong>（二）要实现数据驱动，还需要有什么前提条件</strong></h3><p>     想实现数据驱动，企业还得先做好一个基础工作：<strong>业务的标准化。</strong></p><p>    过去，企业常“依赖好人来做出好事”。后来，我们发现，通过不断复制、迭代、优化、固化我们的管理行为和动作，形成一套标准化的运作机制，企业更能实现快速扩张。</p><p>    数字化转型中，数据驱动的执行成为必选项，我们的数据驱动作业是标准化的，结果是稳定的，而非模糊的、不可控的。这时，只有在企业业务动作更加规范化、程序化和常态化的基础上，才能通过数据来驱动相关状态的优化，结果也才可以预知。</p><p>    因此，想做好数字化转型，实现数据驱动，企业内部各项作业的流程标准化和规则标准化、管理机制的标准化、判断场景的标准化，是非常重要的基础管理活动。</p><p>    在数字化技术的加持下，企业人员在数据应用中越来越多的需要跨越部门界限、企业围墙、行业边界去广泛使用数据。在这种环境下，企业想实现部门间协同、产业链协同，就会对这些环节活动的标准化提出更高的要求。数据和规则的标准化，犹如书同文、车同轨，“有一个统一的语言”是各个业务环节连接、共享信息互动的重要前提。</p><p>    <strong>未来，数据将通过多场景的链接汇集到一起，被分析、被处理。我们将推动数据更加快速、安全、全面、可靠的赋能到各个管理场景中。从”支持决策“到”主动赋能“，从“驱动决策”到同时驱动“业务自动化执行”，从服务决策层到服务所有用户，从提供报表到管理元宇宙，数据将成为新型生产要素，和土地、劳动力、资本、技术等传统生产要素一起，构成新时代的新生产力，驱动企业经营管控模式升级。从商业模式到运作机制，从业务操作到领导决策，企业的一切都将被数据转变。</strong></p><p>    <strong>随着新基建的不断推进，传统产业数字化转型升级的不断加速，数据生产要素的作用和价值必将更加凸显，推动着中国在数字经济新时代实现新跨越和弯道超车，成为新的引领者，也推动我们的社会到达下一站：万物智连。</strong></p><p>    <strong>当然，新生产力必然要求有新生产关系与之相适应，这是人类历史发展的必然，也是我们在全球格局新时代迎来的最重大、最关键的机遇和挑战。</strong></p><hr><p><strong>作者简介：</strong></p><p><strong>沈晶晶</strong>，女，毕业于中山大学，取得工商管理硕士、信息与计算科学学士、会计学学士学位。2006年开始从事信息化工作，2019年进入大数据和数字化转型相关领域。现担任广东交通集团大数据中心大客户技术服务代表，为数据中心的需求单位提供技术支持服务。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;数字化时代，不管你是公司的决策层、管理层、还是普通的基层员工，‍‍终都将与大</summary>
      
    
    
    
    <category term="经济" scheme="http://posts.hufeifei.cn/categories/%E7%BB%8F%E6%B5%8E/"/>
    
    
    <category term="经济" scheme="http://posts.hufeifei.cn/tags/%E7%BB%8F%E6%B5%8E/"/>
    
  </entry>
  
  <entry>
    <title>谷歌前工程师宣告“大数据已死”：喧嚣落幕与时代新启</title>
    <link href="http://posts.hufeifei.cn/db/big-data-is-dead/"/>
    <id>http://posts.hufeifei.cn/db/big-data-is-dead/</id>
    <published>2025-05-23T00:00:00.000Z</published>
    <updated>2026-01-01T06:47:29.860Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p><img src="https://mmbiz.qpic.cn/sz_mmbiz_png/bs6YzlUUjEWqCWZJu176TGFjXxqmfXbNVzUQp1Nrib3HFkdPEicCibrZPz0Ww2RtqXb8LYLbk9uMDDVne4rqU5ewA/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1#imgIndex=0" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="图片"></p><p>数字时代的浪潮滚滚向前，我们曾一度被“大数据”(Big Data)的宏大叙事所席卷，仿佛不拥抱大数据，就意味着被时代抛弃。然而，就在这股浪潮似乎要吞噬一切之时，一些清醒的声音开始浮现，质疑这股热潮的真实性与普适性。</p><p>时间回溯到2023年2月，一篇题为《大数据已死》(Big Data is Dead)  的文章（原文见：<a href="https://motherduck.com/blog/big-data-is-dead/">https://motherduck.com/blog/big-data-is-dead/</a> ）在技术圈和商业界激起了不小的涟漪。这篇文章的作者乔丹·蒂加尼 (Jordan Tigani)，曾是谷歌BigQuery的创始工程师之一，一个曾站在大数据神坛上振臂高呼的“信徒” 。他的“倒戈”无疑为这场关于数据规模与价值的讨论，投下了一颗重磅炸弹。蒂加尼以其十余年在数据领域摸爬滚打的深厚经验，特别是调试BigQuery客户问题、合著两本相关书籍以及后续转向产品管理与客户和产品指标打交道的经历，得出了一个令人颇感意外却又引人深思的结论：大多数使用“Big Query”的人并没有真正意义上的大数据 ，而数据规模本身，或许从来都不是问题的核心 。</p><p>这篇文章的出现，恰逢全球对数据价值的认知从盲目崇拜规模转向理性追求实效的关键节点。彼时，企业在经历了数年“买买买”的“军备竞赛”，购置了大量宣称能处理海量数据的“新奇技术”后 ，却发现自己在从数据中获取可行动洞察方面依然步履维艰 。这不禁让人反思，我们是否一开始就“诊断”错了方向？</p><p>本文将深入剖析蒂加尼的核心论点，结合最新的行业洞察与权威研究，探讨“大数据已死”这一论断在当前语境下的深刻含义。我们将一同回顾那段对数据规模的狂热崇拜，审视其背后的逻辑与现实的差距，并最终思考，在后大数据时代，我们应如何更智慧地与数据共舞，发掘其真正的价值，而非仅仅迷失在“大”的幻影之中。</p><h2 id="01、被误读的“数据洪流”：那场声势浩大的“规模恐慌”真的名副其实吗？"><a href="#01、被误读的“数据洪流”：那场声势浩大的“规模恐慌”真的名副其实吗？" class="headerlink" title="01、被误读的“数据洪流”：那场声势浩大的“规模恐慌”真的名副其实吗？"></a>01、被误读的“数据洪流”：那场声势浩大的“规模恐慌”真的名副其实吗？</h2><p>曾几何时，“大数据来了！”的呼声如同狼来了的警告，响彻在每一个会议室和每一个行业报告中 。各大供应商不遗余力地描绘着一幅数据即将淹没一切的可怕图景，而他们所销售的，正是那艘能载我们渡过“劫难”的方舟 。这种营销策略的核心，正如蒂加尼所描述的，就是那张经典的“恐吓幻灯片”(the “scare” slide) ：一条陡峭上扬的曲线，预示着未来几年数据量将达到“深不可测的程度”(unfathomably large amount) 。蒂加尼坦言，无论是在谷歌还是后来在SingleStore，他都见过类似图表的变种 。其潜台词清晰而急迫：“旧的数据处理方式已经行不通了！你需要购买我正在销售的产品！” 。</p><p>这种叙事的强大之处在于，它利用了人们对未知的恐惧和对错失机遇的焦虑。它宣称，数据生成的加速将使昨日的数据系统陷入泥潭，而拥抱新思想的人则能够超越竞争对手 。一时间，企业仿佛陷入了一场关于数据规模的“军备竞赛”，唯恐在数据积累上落后于人。然而，蒂加尼一针见血地指出：“当然，仅仅因为正在生成的数据量在增加，并不意味着它会成为每个人的问题；数据并非平均分配的。大多数应用程序不需要处理海量数据。” (“Of course, just because the amount of data being generated is increasing doesn’t mean that it becomes a problem for everyone; data is not distributed equally. Most applications do not need to process massive amounts of data.”) 。</p><p>事实也确实如此。虽然全球数据总量的确在飞速增长——根据Statista在2023年底的预测，到2025年，全球创建、获取、复制和消耗的数据总量预计将超过180泽字节 (ZB)，这一数字相较于几年前已是天文数字——但这并不等同于每个企业、每个应用都面临着泽字节级别的数据处理挑战。正如信息管理领域的专家道格拉斯·莱尼 (Douglas Laney) 在其著作《Infonomics》中提出的“3V”定义（Volume, Velocity, Variety）那样，大数据的特征不仅仅在于“体量大”，还在于“速度快”和“多样性”。然而，许多企业的数据困境，并非源于这三个V同时达到了极致，而更多的是在于如何从现有规模的数据中提取价值。</p><p>蒂加尼认为，这场被预言的数据灾难并没有真正发生 。他观察到：“数据规模可能略有增长，但硬件的增长速度更快。” (“Data sizes may have gotten marginally larger, but hardware has gotten bigger at an even faster rate.”) 。这是一个至关重要的观察。我们不妨回顾一下计算机硬件的发展史。摩尔定律虽然近年来有所放缓，但在过去几十年里极大地提升了计算能力。与此同时，存储技术也经历了翻天覆地的变化。例如，机械硬盘的存储密度不断提升，而固态硬盘 (SSD) 的价格则持续下降，性能大幅跃升。根据一些行业分析，硬盘的每GB成本在过去十年中显著降低，而单盘容量则大幅增加。这意味着，在同样的预算下，企业能够获得的存储空间和计算能力远非昔日可比。</p><p>《经济学人》曾在相关报道中指出，技术进步的步伐往往会改变我们对“大”的定义。过去被认为是“海量”的数据，在今天的硬件条件下可能只是“中等规模”。蒂加尼的观点与此不谋而合。他提到，在2004年谷歌MapReduce论文发表时，数据工作负载不适合单台商用机器的情况非常普遍，纵向扩展成本高昂 。2006年AWS推出EC2时，唯一的实例大小只有一个核心和2GB内存，很多工作负载都无法适应 。但如今，“AWS上的标准实例使用具有64核和256GB内存的物理服务器。这仅仅是内存就增加了两个数量级。如果你愿意为内存优化型实例多花一点钱，你可以再获得两个数量级的内存。有多少工作负载需要超过24TB的内存或445个CPU核心呢？” (“Today, however, a standard instance on AWS uses a physical server with 64 cores and 256 GB of RAM. That’s two orders of magnitude more RAM. If you’re willing to spend a little bit more for a memory-optimized instance, you can get another two orders of magnitude of RAM. How many workloads need more than 24TB of RAM or 445 CPU cores?”) 。</p><p>这种硬件能力的飞跃，实际上大大削弱了单纯以“数据量大”作为核心挑战的论据。如果一台机器就能轻松处理TB级别甚至数十TB级别的数据，那么对于绝大多数企业而言，“大数据”的门槛实际上是在不断后退的 。这并非否认确实存在拥有PB乃至EB级别数据的“大数据巨头”，而是强调这种规模并非普遍现象。</p><p>因此，当初那场声势浩大的“规模恐慌”，在很大程度上是被过度渲染了。它成功地推动了新技术的销售，但也可能误导了许多企业，使其将过多的精力投入到应对一个并不存在的“数据怪兽”上，而忽略了更根本的数据管理、数据分析和数据驱动决策能力的建设。Vendor们依旧在鼓吹他们的扩展能力，但从业者们已经开始反思，这些能力与他们面临的实际问题究竟有多大关联 。</p><h2 id="02、数据的真实面貌：大多数情况下，它并没有你想象的那么“大"><a href="#02、数据的真实面貌：大多数情况下，它并没有你想象的那么“大" class="headerlink" title="02、数据的真实面貌：大多数情况下，它并没有你想象的那么“大"></a>02、数据的真实面貌：大多数情况下，它并没有你想象的那么“大</h2><p>在“大数据”概念铺天盖地的宣传攻势下，人们很容易形成一种错觉：仿佛每个企业都坐拥金山银山般的海量数据，等待着被挖掘。然而，乔丹·蒂加尼用他在谷歌BigQuery的亲身经历和细致观察，为我们揭示了一个更为贴近现实的情况：绝大多数用户和企业，其数据规模远未达到“大数据”的量级 。</p><p>蒂加尼坦诚地分享道：“我学到的最令人惊讶的事情是，大多数使用‘Big Query’的人并没有真正的大数据。” (“The most surprising thing that I learned was that most of the people using ‘Big Query’ don’t really have Big Data.”) 。他进一步透露，尽管相关数据非常敏感无法直接分享具体数字 ，但“绝大多数客户的总数据存储量不到一个太字节 (terabyte)。” (“the vast majority of customers had less than a terabyte of data in total data storage.”) 。当然，确实存在拥有海量数据的客户，但即便是相当大的企业，其数据规模也往往是适中的 。</p><p>更有趣的是，他发现客户数据规模遵循一种幂律分布 (power-law distribution) 。这意味着，最大的客户拥有的存储量可能是第二大客户的两倍，而第二大客户又是再下一个客户的两倍，以此类推。因此，尽管确实有客户拥有数百PB的数据，但这个数字会迅速下降 。一个颇具说服力的数据点是：“有成千上万的客户每月为存储支付的费用不足10美元，这相当于半个太字节。” (“There were many thousands of customers who paid less than $10 a month for storage, which is half a terabyte.”) 。而在那些重度使用该服务的客户中，“中位数数据存储大小远低于100GB。” (“Among customers who were using the service heavily, the median data storage size was much less than 100 GB.”) 。这与许多人对“大数据时代”的普遍认知形成了鲜明对比。</p><p>这种观察不仅限于BigQuery内部。当蒂加尼团队向行业分析师（如Gartner、Forrester等）吹嘘其处理海量数据集的能力时，对方往往不以为然 。分析师们的反馈是：“这很好，但绝大多数企业的数据仓库都小于一个太字节。” (“This is nice,” they said, “but the vast majority of enterprises have data warehouses smaller than a terabyte.”) 。行业内的普遍反馈是，100GB是数据仓库规模的正确数量级，而这也正是他们进行基准测试时重点关注的范围 。</p><p>为了进一步验证这一观点，蒂加尼还提到了一位投资者的调研。这位投资者调查了其投资组合中的公司，其中不乏已经上市或被大型机构收购的科技公司——这些公司的数据规模理应偏大 。结果发现：“他投资组合中最大的B2B公司的总数据量约为1TB，而最大的B2C公司的数据量约为10TB。然而，大多数公司的数据量要少得多。” (“He found that the largest B2B companies in his portfolio had around a terabyte of data, while the largest B2C companies had around 10 Terabytes of data. Most, however, had far less data.”) 。这些来自一线的真实数据，无疑为“大多数人没有大数据”这一论点提供了有力支撑。</p><p>为什么海量数据规模如此罕见？蒂加尼通过一些简单的例子进行了说明，这些例子帮助我们从数据的实际来源理解这个问题 。他设想了一个中型企业，拥有一千名客户，每个客户每天下一张包含一百个项目的订单 。即便如此相对频繁的交易，每天产生的数据可能还不到1MB。三年下来也才1GB，要达到1TB则需要数千年 。再比如，一个市场营销数据库中有一百万个潜在客户线索，同时运行着数十个营销活动 。其潜在客户表格可能仍小于1GB，而跟踪每个潜在客户在每个营销活动中的数据，总共也可能只有几个GB 。在合理的规模假设下，很难看出这些场景如何能累积成所谓的海量数据集 。蒂加尼甚至以自己曾在2020-2022年工作过的SingleStore为例——这是一家快速增长的E轮公司，收入可观，估值达到独角兽级别——将其财务数据仓库、客户数据、营销活动跟踪和服务器日志加起来，可能也只有几个GB 。用任何标准来衡量，这都不是大数据 。</p><p>这些观察与近年来的一些行业报告不谋而合。例如，一些针对中小企业的调查显示，它们的数据管理挑战更多地在于数据质量、数据集成和缺乏分析技能，而非数据量过大。即使在大型企业中，虽然某些部门（如研发、物联网）可能会产生巨量数据，但核心业务系统的数据仓库规模往往比宣传的要小得多。根据一些咨询公司的报告，例如KPMG或Deloitte在探讨数据策略时，也越来越强调“智能数据”(Smart Data)而非单纯的“大数据”，即关注数据的相关性、质量和可操作性。</p><p>蒂加尼的这些揭示，并非要否定大数据的存在，而是要打破一种普遍的迷思：即认为所有企业都必须以处理“谷歌级别”的数据为目标。事实是，大多数企业的数据规模是可控的，其核心挑战在于如何从这些“恰到好处”的数据中提炼洞察，驱动业务增长。这种认知的转变，有助于企业将资源更有效地投入到真正能产生价值的数据活动中，而不是盲目追逐硬件和平台的无限扩展能力。</p><h2 id="03、架构的智慧演进：重要的不是无限扩展，而是“恰到好处”的灵活性"><a href="#03、架构的智慧演进：重要的不是无限扩展，而是“恰到好处”的灵活性" class="headerlink" title="03、架构的智慧演进：重要的不是无限扩展，而是“恰到好处”的灵活性"></a>03、架构的智慧演进：重要的不是无限扩展，而是“恰到好处”的灵活性</h2><p>当“大数据”的警钟敲响时，许多讨论都围绕着如何构建能够无限横向扩展的系统。然而，乔丹·蒂加尼在其文章中提出了一个深刻的观点：过去20年数据架构中最重要的变革，并非规模的无限扩展，而是“存储和计算分离”(separation of storage and compute) 。这一架构上的演进，使得企业能够更灵活、更经济地应对真实世界的数据挑战。</p><p>蒂加尼指出：“现代云数据平台都将存储和计算分离，这意味着客户不再受限于单一的形态因素。” (“Modern cloud data platforms all separate storage and compute, which means that customers are not tied to a single form factor.”) 。与以往难以在实际环境中管理的“无共享”(shared nothing)架构不同，共享磁盘架构允许存储和计算独立增长 。S3和GCS等可扩展且速度尚可的对象存储的兴起，极大地放宽了数据库构建的约束条件 。</p><p>这种分离的核心优势在于，它承认并适应了一个普遍现象：“在实践中，数据规模的增长速度远快于计算需求的增长速度。” (“In practice, data sizes increase much faster than compute sizes.”) 。尽管存储和计算分离的好处常被描述为可以随时独立扩展任一方，但这两个轴并非完全等同 。蒂加尼强调，对这一点的误解导致了许多关于大数据的讨论，因为处理大规模计算需求的技术与处理大规模数据的技术是不同的 。</p><p>为什么会存在这种“存储偏向”呢？蒂加尼解释道，所有大型数据集都是随时间生成的，时间几乎总是数据集中的一个轴 。新的订单、新的出租车行程、新的日志记录、新的游戏数据每天都在产生 。如果一个业务是静态的，既不增长也不萎缩，数据会随时间线性增加 。这意味着存储需求会线性增长（除非决定修剪数据）。但计算需求呢？“但计算需求可能不怎么需要随时间变化；大多数分析都是针对近期数据进行的。扫描旧数据非常浪费；它不会改变，那你为什么要花钱一遍又一遍地读取它呢？” (“But compute needs will likely not need to change very much over time; most analysis is done over the recent data. Scanning old data is pretty wasteful; it doesn’t change, so why would you spend money reading it over and over again?”) 。诚然，企业可能希望保留旧数据以备不时之需，比如回答新的问题，但构建包含重要答案的聚合数据其实相当简单 。</p><p>一个极具说服力的例子是，当数据仓库客户从没有存储和计算分离的环境迁移到有这种分离的环境时，“他们的存储使用量大幅增长，但他们的计算需求往往没有真正改变。” (“Very often when a data warehousing customer moves from an environment where they didn’t have separation of storage and compute into one where they do have it, their storage usage grows tremendously, but their compute needs tend to not really change.”) 。蒂加尼分享了BigQuery一个全球最大零售商客户的案例：他们本地数据仓库约有100TB数据，迁移到云后，数据量达到了30PB，增长了300倍 。如果他们的计算需求也以类似幅度扩展，他们将在分析上花费数十亿美元 。然而，他们实际的花费远低于此 。</p><p>这种存储规模优先于计算规模的偏向，对系统架构产生了深远影响 。它意味着，如果使用可扩展的对象存储，企业可能只需要远低于预期的计算资源 ，甚至可能根本不需要使用分布式处理 。这与早期大数据解决方案强调的“节点越多越好”的理念形成了鲜明对比。</p><p>与此相呼应的是，蒂加尼观察到传统数据库架构的复兴：“SQLite、Postgres、MySQL都在强劲增长，而‘NoSQL’甚至‘NewSQL’系统则停滞不前。” (“SQLite, Postgres, MySQL are all growing strongly, while “NoSQL” and even “NewSQL” systems are stagnating.”) 。他展示的图表也表明，MySQL等关系型数据库的受欢迎程度持续稳定在高位，而曾经被寄予厚望的NoSQL代表MongoDB虽然有过一段增长，但近年来略有下降，并未真正撼动MySQL或Postgres这些坚固的单体数据库的地位 。他认为：“如果大数据真的在主导一切，那么这么多年后，你应该会看到不同的景象。” (“If Big Data were really taking over, you’d expect to see something different after all these years.”) 。</p><p>这并不是说NoSQL或NewSQL系统没有其用武之地，尤其是在需要极高并发写入或特定数据模型的场景下。然而，对于大多数分析型工作负载，传统关系型数据库结合现代云架构（如存储计算分离）所提供的灵活性和成本效益，往往更具吸引力。学术界和行业分析也注意到了这一趋势。例如，DB-Engines网站的数据库流行度排名持续显示，PostgreSQL和MySQL等开源关系型数据库的受欢迎程度在稳步上升。许多分析指出，这些系统通过引入对JSON等半结构化数据的支持、改进查询优化器、以及更好地与云原生服务集成，已经能够满足更广泛的应用需求，包括那些曾被认为是NoSQL专属的领域。</p><p>AWS的Aurora（兼容MySQL和PostgreSQL）以及Google Cloud SQL等托管数据库服务，正是利用了存储与计算分离的架构优势，提供了高可用性、可扩展性和成本效益。它们允许用户根据实际需求独立调整存储容量和计算实例，避免了传统一体化架构中常见的资源浪费。</p><p>因此，数据架构的智慧演进，更多地体现在这种适应真实需求的灵活性上。企业不再需要为应对想象中的“数据海啸”而过度配置昂贵的计算集群，而是可以借助云平台提供的弹性，让存储自由增长，同时将计算资源精确匹配到实际的分析负载上。这不仅大大降低了成本，也使得更广泛的企业能够从数据中获益，而无需成为“大数据专家”。</p><h2 id="04、工作负载的真相：我们实际查询和分析的数据远少于存储的总量"><a href="#04、工作负载的真相：我们实际查询和分析的数据远少于存储的总量" class="headerlink" title="04、工作负载的真相：我们实际查询和分析的数据远少于存储的总量"></a>04、工作负载的真相：我们实际查询和分析的数据远少于存储的总量</h2><p>“我们存了很多数据，所以我们需要强大的计算能力来分析所有这些数据。”这似乎是一个合乎逻辑的推论。然而，乔丹·蒂加尼通过对实际查询行为的深入分析，揭示了这个逻辑链条中一个经常被忽视的关键环节：我们为分析工作负载实际处理的数据量，几乎总是比我们想象的要小得多 。这就像一座冰山，我们只看到了浮出水面的巨大存储量，却忽略了水下实际被频繁访问和分析的数据只是其中的一小部分。</p><p>蒂加尼指出，即使客户拥有庞大的数据集，他们执行的工作负载也往往只触及这些数据的一小部分 。一个典型的例子是仪表盘 (Dashboards)：“仪表盘通常是基于聚合数据构建的。人们关注的是过去一小时、过去一天或过去一周的数据。” (“Dashboards, for example, very often are built from aggregated data. People look at the last hour, or the last day, or the last week’s worth of data.”) 。较小的表更容易被频繁查询，而巨大的表则更有选择性地被查询 。</p><p>为了证实这一点，蒂加尼几年前对BigQuery的查询进行了分析，特别关注了每年花费超过1000美元的客户 。结果令人惊讶：“90%的查询处理的数据量少于100MB。” (“90% of queries processed less than 100 MB of data.”) 。他反复从不同角度切割数据以确保这不是少数客户运行大量小查询造成的偏差，并且排除了那些只查询元数据而不读取任何实际数据的查询 。图表显示，查询处理的数据量要达到GB级别，已经需要到非常高的百分位，而TB级别的查询则非常罕见 。</p><p>更有趣的发现是：“拥有巨量数据的客户几乎从不查询海量数据。” (“Customers with giant data sizes almost never queried huge amounts of data.”) 。当他们这样做时，通常是为了生成报告，此时性能并非首要考虑因素 。他举例说，一家大型社交媒体公司会在周末运行一些非常大的查询，为周一的高管会议做准备，但这些大型查询只占他们一周内运行的数十万次查询中的极小一部分 。</p><p>这种现象背后，其实是数据分析的普遍规律和现代数据库技术的共同作用。首先，大部分分析的价值集中在近期数据上。蒂加尼强调：“绝大多数被处理的数据都是24小时内产生的。当数据达到一周的‘高龄’时，它被查询的可能性大约是最新的数据的二十分之一。一个月后，数据基本上就静静地躺在那里了。” (“A huge percentage of the data that gets processed is less than 24 hours old. By the time data gets to be a week old, it is probably 20 times less likely to be queried than from the most recent day. After a month, data mostly just sits there.”) 。历史数据通常只是在运行罕见的报告时才会被触及 。他给出的数据访问模式估计是：“最近一年可能只占30%的数据，但占了99%的数据访问量。最近一个月可能只占5%的数据，但占了80%的数据访问量。” (“The most recent year might only have 30% of the data but 99% of data accesses. The most recent month might have 5% of data but 80% of data accesses.”) 。这意味着数据的“工作集”(working set)大小，即被频繁访问和处理的数据子集，比我们预期的要小得多且更易于管理 。例如，一个拥有10年数据的PB级表，可能很少访问当天（压缩后可能不到50GB）之前的数据 。</p><p>其次，现代分析型数据库通过各种技术手段，极大地减少了查询时实际需要处理的数据量 。即使是查询巨大的表，也很少需要处理大量数据 。这些技术包括：</p><ul><li>  列式存储与列裁剪 (Column Projection): 分析查询通常只关心表中的少数几列，列式存储使得数据库只需读取相关列的数据，而非整行数据。</li><li>  分区裁剪 (Partition Pruning): 许多大型表按时间或其他关键字段分区。查询如果带有对这些字段的过滤条件（例如，只查询特定日期范围），数据库就可以跳过不相关的分区，只读取所需数据 。</li><li>  段消除 (Segment Elimination): 通过数据聚类或自动微观分区，利用数据的局部性进一步减少扫描范围 。</li><li>  其他优化技巧: 如在压缩数据上进行计算、投影下推 (Projection Pushdown)、谓词下推 (Predicate Pushdown) 等，都是在查询时减少I/O的方法 。更少的I/O意味着更少的计算需求，从而降低成本和延迟 。</li></ul><p>这种“工作负载远小于存储总量”的现象，在许多数据分析的实际应用中都能得到印证。例如，在零售行业，分析师可能每天都会查看当天的销售额、库存周转率等指标，这些通常基于最近的数据。而季度或年度的趋势分析，虽然可能涉及更长时间跨度的数据，但其执行频率远低于日常查询。在网站分析领域，实时仪表盘主要关注当前的用户活动、页面浏览量等，历史数据的深入挖掘往往是特定研究项目的一部分。</p><p>这一洞察对于系统设计和成本优化具有重要意义。它意味着我们不必为存储的每一字节数据都配备等量的即时计算能力。通过智能分层存储（将热数据放在高性能存储，冷数据放在低成本存储）、高效的索引策略、查询优化以及构建预聚合的物化视图或数据集市，可以极大地提升分析性能并控制成本。这正是“恰到好处”的智慧，而非盲目追求“多多益善”的规模。</p><h2 id="05、数据留存的经济账与责任链：你真的是“数据收藏家”还是“数据价值挖掘者”？"><a href="#05、数据留存的经济账与责任链：你真的是“数据收藏家”还是“数据价值挖掘者”？" class="headerlink" title="05、数据留存的经济账与责任链：你真的是“数据收藏家”还是“数据价值挖掘者”？"></a>05、数据留存的经济账与责任链：你真的是“数据收藏家”还是“数据价值挖掘者”？</h2><p>在数字时代，数据被誉为“新石油”，于是许多组织倾向于尽可能多地收集和保留数据，生怕错失了未来的潜在价值。然而，乔丹·蒂加尼提醒我们，这种“数据囤积症”背后，隐藏着不容忽视的经济成本和法律责任 。他提出了一个关于大数据的另类定义：“当保留数据的成本低于弄清楚该扔掉什么的成本时”，大数据就出现了 。这个定义精辟地指出了许多组织最终拥有“大数据”的原因：“并非因为他们需要它；他们只是懒得删除它。” (“It isn’t because they need it; they just haven’t bothered to delete it.”) 。那些庞大而混乱、无人真正了解其内容或是否可以安全清理的数据湖，正是这种现象的完美写照 。</p><p>首先，我们来看看经济账。蒂加尼强调，存在着强烈的经济压力促使人们减少处理的数据量 。仅仅因为你能横向扩展并快速处理某些东西，并不意味着你能廉价地做到这一点 。如果你用一千个节点来获得结果，那可能会花费你一大笔钱 。他以自己曾经在台上演示的BigQuery查询为例：“我过去在台上运行以炫耀BigQuery的PB级查询，按零售价计算需要5000美元。很少有人愿意运行如此昂贵的东西。” (“The Petabyte query I used to run on stage to show off BigQuery cost $5,000 at retail prices. Very few people would want to run something so expensive.”) 。这种减少数据处理以降低成本的财务动机，即便在非按扫描字节付费的定价模型中也同样适用 。例如，如果你使用的是Snowflake实例，通过缩小查询范围，就可以使用更小的实例，从而支付更少的费用，查询也会更快，并发能力更强，长期来看总成本更低 。</p><p>除了直接的查询成本，保留数据的成本远不止存储物理字节那么简单 。日益严格的数据保护法规，如欧盟的《通用数据保护条例》(GDPR) 和美国的《加州消费者隐私法案》(CCPA)，要求组织必须跟踪特定类型数据的所有使用情况，并且某些数据需要在特定时间段内删除 。如果你数据湖中某个Parquet文件里的电话号码存放时间过长，就可能违反法规要求 。近年来，因违反GDPR而被处以巨额罚款的案例屡见不鲜，这足以警示所有企业，数据合规不再是可有可无的选项。例如，根据CMS Law的GDPR执法跟踪报告，自GDPR生效以来，罚款总额已达数十亿欧元，涉及从数据泄露到非法数据处理等多种违规行为。</p><p>数据还可能成为对你不利的法律证据 。正如许多组织为了减少潜在责任而执行有限的电子邮件保留策略一样，数据仓库中的数据同样可以在法律诉讼中被用来对付你 。蒂加尼提到：“如果你有五年前的日志，显示你的代码中存在安全漏洞或错过了服务等级协议 (SLA)，保留旧数据可能会延长你的法律风险暴露期。” (“If you’ve got logs from five years ago that would show a security bug in your code or missed SLA, keeping old data around can prolong your legal exposure.”) 。他甚至听说过一个可能是杜撰的故事：某公司为了防止其数据分析能力在法律发现过程中被利用而将其保密 。</p><p>此外，数据也会像未经维护的代码一样遭受“腐烂”(bit rot) 。人们会忘记特定字段的精确含义，或者过去的数据问题可能已从记忆中淡去 。例如，可能存在一个短暂的数据错误，导致所有客户ID都设置为空值 ，或者一次巨大的欺诈性交易使得2017年第三季度的业绩看起来比实际好得多 。从历史时期提取数据的业务逻辑可能会变得越来越复杂 。例如，可能会有这样的规则：“如果日期早于2019年，则使用收入字段；在2019年至2021年之间，使用revenue_usd字段；2022年之后，使用revenue_usd_audited字段。” (“For example, there might be a rule like, “ if the date is older than 2019 use the revenue field, between 2019 and 2021 use the revenue_usd field, and after 2022 use the revenue_usd_audited field.””) 。数据保留时间越长，跟踪这些特殊情况就越困难，尤其是在数据丢失的情况下，并非所有问题都能轻易解决 。</p><p>因此，在决定保留旧数据时，理解保留的原因至关重要 。蒂加尼提出了一系列值得深思的问题：</p><ul><li>  你是否一遍又一遍地问同样的问题？如果是这样，仅存储聚合结果在存储和查询成本方面不是会便宜得多吗？ </li><li>  你是为了未雨绸缪而保留它吗？你是否认为将来可能会有新的问题需要解答？ </li><li>  如果是这样，它有多重要？你真正需要它的可能性有多大？ </li><li>  你真的只是一个数据囤积者吗？ </li></ul><p>这些问题，尤其是在试图弄清楚保留数据的真实成本时，都非常重要 。有效的做法是建立明确的数据生命周期管理策略，定期评估数据的价值和风险，并根据业务需求和法规要求主动进行数据归档或清除。正如《麻省理工学院斯隆管理评论》中的一些文章所强调的，强大的数据治理不仅仅是技术问题，更是组织文化和战略优先级的体现。与其盲目地收集一切，不如有选择地收集和管理那些真正能够驱动洞察、优化决策并符合伦理与法规要求的数据。</p><h2 id="06、不断后退的“大数据边界”与那稀有的“百分之一”"><a href="#06、不断后退的“大数据边界”与那稀有的“百分之一”" class="headerlink" title="06、不断后退的“大数据边界”与那稀有的“百分之一”"></a>06、不断后退的“大数据边界”与那稀有的“百分之一”</h2><p>“大数据”的定义本身就颇具流动性。乔丹·蒂加尼引用了一个定义：“大数据”就是“任何无法在单台机器上处理的数据” (“whatever doesn’t fit on a single machine”) 。按照这个定义，他犀利地指出，符合“大数据”标准的工作负载数量每年都在减少 。这背后是计算机硬件性能，特别是单机处理能力的惊人飞跃。</p><p>回溯到2004年，当谷歌发表关于MapReduce的里程碑式论文时，一个数据工作负载无法在单台普通商用机器上处理的情况非常普遍，而向上扩展（即购买更强大的单机）的成本又极其高昂 。2006年，亚马逊网络服务 (AWS) 推出了EC2云服务器，当时你能获得的唯一实例配置仅为单核CPU和2GB内存 。在这样的硬件条件下，有大量的工作负载会轻易超出单机处理的极限。</p><p>然而，时至今日，正如蒂加尼在2023年所观察到的：“如今，AWS上的一个标准实例使用的是配备64个核心和256GB内存的物理服务器。这仅仅是内存就比2006年时增加了两个数量级。如果你愿意为内存优化型实例多花一点钱，你甚至可以获得再增加两个数量级的内存。有多少工作负载真的需要超过24TB的内存或445个CPU核心呢？” (“Today, however, a standard instance on AWS uses a physical server with 64 cores and 256 GB of RAM. That’s two orders of magnitude more RAM. If you’re willing to spend a little bit more for a memory-optimized instance, you can get another two orders of magnitude of RAM. How many workloads need more than 24TB of RAM or 445 CPU cores?”) 。</p><p>截至2024和2025年，这一趋势仍在延续。主流云服务提供商（如AWS、Azure、Google Cloud）持续推出性能更强、内存更大的虚拟机实例。例如，AWS的内存优化型实例（如X2iezn系列）可以提供高达12TiB的内存和数百个vCPU。即使不考虑这些顶级的“巨无霸”实例，普通的高性能计算实例也足以应对绝大多数企业的数据分析需求。这种单机能力的指数级增长，实质上是在不断“蚕食”曾经属于“大数据”范畴的领地。</p><p>更重要的是，过去那种大型机器成本远高于小型机器的局面也发生了改变。蒂加尼指出：“然而，在云中，使用整个服务器的虚拟机仅比使用服务器八分之一的虚拟机贵8倍。成本与计算能力成线性关系向上扩展，直至非常大的规模。” (“However, in the cloud, a VM that uses a whole server only costs 8x more than one that uses an 8th of a server. Cost scales up linearly with compute power, up through some very large sizes.”) 。这种线性的成本扩展性，使得企业在需要时可以经济高效地获得强大的单机处理能力，而无需过早地投入到复杂的分布式系统中。</p><p>一个极具说服力的例证是，蒂加尼提到：“事实上，如果你看看最初Dremel论文（BigQuery的前身）中发表的使用3000个并行节点的基准测试，你会发现如今在单个节点上就能获得类似的性能。” (“In fact, if you look at the benchmarks published in the original dremel paper using 3,000 parallel nodes, you can get similar performance on a single node today (more on this to come).”) 。这充分说明了单机性能的巨大进步是如何改变我们对“规模”的认知的。</p><p>基于这些观察，蒂加尼提出了一个发人深省的问题：你的企业真的属于那需要担心“大数据”的“百分之一”吗？ 。他给出了一系列自检问题，帮助组织判断自己是否真的面临大数据挑战，或者只是被时代的焦虑所裹挟：</p><ul><li>  你真的在产生海量数据吗？ </li><li>  如果是，你真的需要一次性使用海量数据吗？ </li><li>  如果是，这些数据真的大到一台机器装不下吗？ </li><li>  如果是，你确定你不是一个数据囤积者吗？ </li><li>  如果是，你确定进行数据汇总不是更好的选择吗？ </li></ul><p>蒂加尼的结论是：“如果你对这些问题中的任何一个回答‘否’，那么你可能适合新一代的数据工具，这些工具可以帮助你处理你实际拥有的数据规模，而不是人们试图吓唬你让你认为某天可能会拥有的数据规模。” (“If you answer no to any of these questions, you might be a good candidate for a new generation of data tools that help you handle data at the size you actually have, not the size that people try to scare you into thinking that you might have someday.”) 。</p><p>这种“恰如其分”的数据处理理念，与近年来强调“精益数据”(Lean Data)或“正确数据”(Right Data)的思潮不谋而合。例如，一些行业分析师开始倡导企业应关注数据的质量而非数量，确保收集的数据与业务目标紧密相关，并且能够被有效地分析和利用。与其追求无休止的数据扩张，不如优化现有数据的价值密度。</p><p>当然，这并非否定确实存在需要处理超大规模数据集的场景，例如在基因组学研究、高能物理实验、全球气候模拟或大型互联网公司的用户行为分析等领域。这些领域的“大数据”是真实存在的，并且对专门的分布式处理技术有着刚性需求。但关键在于，这些场景并不代表所有企业或所有应用的普遍情况。将这些极端案例的需求泛化为所有人的标准，本身就是一种误导。</p><p>因此，当“大数据”的边界因技术进步而不断后退时，绝大多数组织或许应该将目光从遥不可及的“数据地平线”收回，聚焦于脚下这片更真实、更具潜力的“数据沃土”。识别自己是否真的属于那稀有的“百分之一”，是做出明智数据战略决策的第一步。</p><h2 id="07、尾声：超越规模的迷思，拥抱数据的真实价值与智慧未来"><a href="#07、尾声：超越规模的迷思，拥抱数据的真实价值与智慧未来" class="headerlink" title="07、尾声：超越规模的迷思，拥抱数据的真实价值与智慧未来"></a>07、尾声：超越规模的迷思，拥抱数据的真实价值与智慧未来</h2><p>乔丹·蒂加尼以其在数据领域前沿的深厚积淀，为我们揭示了“大数据”喧嚣背后更为冷静的现实。他的核心论点——“大数据已死”，并非宣告数据不再重要，而是宣告那种对数据规模的盲目崇拜和由此引发的普遍焦虑已不合时宜 。当技术的飞轮不知疲倦地向前滚动，曾经被视为难以逾越的数据鸿沟，在硬件性能的指数级提升和架构模式的不断创新面前，其“天险”属性已大大削弱 。</p><p>正如蒂加尼所指出的，我们更应该关注的是如何利用数据做出更好的决策 。这一观点在当前的数字化转型浪潮中显得尤为重要。企业和组织面临的真正挑战，往往不是数据不够“大”，而是缺乏从数据中提取“大智慧”的能力。根据Gartner等咨询机构近年来的报告，数据素养、分析能力、数据驱动的文化以及将洞察转化为行动的敏捷性，仍然是许多组织在数据价值实现道路上的主要瓶颈。</p><p>放眼全球，对数据价值的认知正在经历一场深刻的变革。2024年欧盟《数据法案》(Data Act) 的全面生效，进一步强调了数据的公平访问、使用和共享，旨在打破数据孤岛，释放工业数据的潜力。这标志着政策层面也开始从单纯关注数据保护（如GDPR）向促进数据流动和价值创造拓展，但其核心依然是“有价值的数据”而非“海量但无序的数据”。该法案鼓励企业间（B2B）和企业对政府（B2G）的数据共享，特别是在物联网等领域，这无疑会产生更多数据，但其背后的驱动力是应用和价值，而非数据本身的堆积。</p><p>与此同时，人工智能，特别是生成式AI的崛起，为我们提供了前所未有的数据处理和洞察提炼工具。AI既是数据的“饕餮食客”，也可能成为帮助我们摆脱“数据泥潭”的“智慧向导”。一方面，训练先进的AI模型确实需要大量高质量数据；但另一方面，AI技术本身也在赋能更高效的数据管理、更智能的查询优化（例如，自然语言查询接口使得非技术用户也能与数据对话），以及从复杂数据集中自动发现模式和洞察。正如一些科技思想领袖所预见的，未来的数据战略可能不再是“越大越好”，而是“越聪明越好”。AI可以帮助我们识别哪些数据是真正有价值的“黄金”，哪些只是需要妥善管理的“矿渣”。</p><p>蒂加尼对“数据囤积”的警示也与当前对数据伦理和可持续性的日益关注相呼应。无限制地收集和存储数据，不仅带来了前文所述的经济和法律风险 ，也可能加剧数字鸿沟，并引发关于隐私和监控的伦理担忧。负责任的数据管理，意味着在数据的整个生命周期中都要审慎权衡其潜在价值与潜在风险，做到“取之有道，用之有度”。</p><p>因此，当我们告别“大数据”的喧嚣时代，并非是数据的旅程结束了，而是开启了一个更成熟、更聚焦价值的新篇章。企业需要从对规模的迷恋中解脱出来，转而投资于提升自身的数据理解能力、分析能力和治理能力。培养一支能够提出正确问题、解读数据含义并将结果应用于实际业务场景的团队，远比拥有一个号称能处理无限数据的平台更为关键。</p><p>未来的数据图景，或许不再是少数“数据巨头”的独角戏，而是更多组织能够基于自身“恰到好处”的数据，运用日益普及和智能化的工具，创造出独特的价值。这需要一种务实的态度，一种对数据真实面貌的清醒认知，以及一种将技术作为实现商业目标和解决现实问题的手段而非目的本身的智慧。正如蒂加尼在其文章末尾所期望的，新一代的数据工具将帮助我们处理实际拥有的数据规模，而非被虚幻的恐惧所驱动 。而我们，作为数据的驾驭者，更应以清明的心智，驶向那个数据真正为我们赋能的未来。这不仅关乎技术的选择，更关乎我们如何在这个信息爆炸的时代，保持独立的思考和清醒的判断，让数据真正服务于人类的福祉与进步。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;&lt;img src=&quot;https://mmbiz.qpic.cn/sz_mmb</summary>
      
    
    
    
    <category term="数据库" scheme="http://posts.hufeifei.cn/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    
    <category term="BigData" scheme="http://posts.hufeifei.cn/tags/BigData/"/>
    
  </entry>
  
  <entry>
    <title>玩一玩系列——玩玩pg_mooncake（PostgreSQL的高性能列存新贵）</title>
    <link href="http://posts.hufeifei.cn/db/pg_mooncake/"/>
    <id>http://posts.hufeifei.cn/db/pg_mooncake/</id>
    <published>2025-05-02T00:00:00.000Z</published>
    <updated>2026-01-01T06:47:29.860Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><h1 id="一、前言"><a href="#一、前言" class="headerlink" title="一、前言"></a>一、前言</h1><p>在上一篇<a href="https://www.modb.pro/db/1893536982911954944">玩一玩系列——玩玩pg_duckdb（我的开源之旅）</a>文章中，我们有讲到pg_duckdb对于SELECT语句的一个较为详细的处理流程，大概能了解到pg_duckdb是如何加速查询效率的（甚至在某些场景之下，获得上千倍的提升不在话下），</p><p>同样也知道了它的加速是有限制的，其限制的瓶颈点在于它需要将PostgreSQL中的行存储的数据一行一行的转换填充之duckdb的列存之中（实话实话，这成本过于高昂），所以当且仅当这个加载数据转换的时间和原本查询的时间越显得不值一提的时候，实际加速的效果就越好。</p><p>依旧是在上篇文章中，我有讲到pg_duckdb的技术路线是Custom Scan，但其实并不仅仅是Custom Scan，它也实现了TAM（Table Access Method），只不过限制仅支持临时表，所以我也没有多少想讲的意愿，而是留给今天的主角pg_mooncake。</p><p>pg_mooncake是在pg_duckdb上构建的，相对而言是青出于蓝而胜于蓝，因为它涵盖了pg_duckdb，所以它的技术路线也包括了Custom Scan，不过它在TAM（Table Access Method）方面做的更好，它不再局限于临时表的同时，给PostgreSQL带来了真正的列式存储。</p><p><img src="https://oss-emcsprod-public.modb.pro/image/editor/20250303-1896400651148603392_585460.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>这里我称呼它为PostgreSQL的列存新贵的原因在于：在它之前，其实就存在列式存储插件，因为要想AP跑的好，列式存储少不了，其实有了列存也还不够，计算方面也得跟上。</p><p>所以在pg_mooncake的前面还有些”老前辈”，比如说 hydradatabase（现在在整pg_duckdb的那位）的开源项目<a href="https://github.com/hydradatabase/columnar">columnar</a>，</p><p>加速效果也有，但是效果不是那么理想。但是它也解决了不少问题，比方说是数据压缩，因为列式存储在压缩上具有天然的优势，可以做到较高的压缩率，节省空间。</p><p>伴随着时代的进步，duckdb的出现，从而出现了更加优秀的列式存储插件pg_mooncake，还是非常值得高兴的。</p><p>关于TAM（Table Access Method）相关资料请参考PostgreSQL的官方文档<a href="https://www.postgresql.org/docs/16/sql-create-access-method.html">https://www.postgresql.org/docs/16/sql-create-access-method.html</a>。</p><h1 id="二、Mooncake-Labs团队"><a href="#二、Mooncake-Labs团队" class="headerlink" title="二、Mooncake Labs团队"></a>二、Mooncake Labs团队</h1><p>在正式玩起来之前呢，我想先简单介绍一下pg_mooncake的技术团队，毕竟我和他们团队的cc大佬还算是挺聊的来、也蛮有缘的（虽然我总是因为有事情要忙，以至于经常咕咕咕人家🕊🕊）。</p><p><img src="https://oss-emcsprod-public.modb.pro/image/editor/20250302-1896158854409367552_585460.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>pg_mooncake是由Mooncake Labs团队打造的一个开源项目，而Mooncake Labs是坐落在美国旧金山的一支技术非常NB的团队。</p><p><img src="https://oss-emcsprod-public.modb.pro/image/editor/20250302-bcd84d67-a95f-4a64-884a-5139218715a8.png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="昆汀牛逼动图 - 搜狗图片搜索"></p><p>他们中大多数的成员都来自海外的一家名为SingleStore的公司，如果大家对这家公司有过了解的话，应该能大概知道这家公司在海外HTAP还是算挺有名气的。</p><p>因此Mooncake Labs团队中的成员也积攒了大量关于这方面的经验，这可能也是他们能把pg_mooncake优化到ClickBench前10的一个原因吧（虽然现在可能降了一点，但是无伤大雅）。</p><p><img src="https://oss-emcsprod-public.modb.pro/image/editor/20250302-1896161419666665472_585460.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>                                                （这是之前的记录图片）</p><p>他们团队也是正在招人（有挺多的方向，这里我只提一嘴数据库内核方向），感兴趣的同学可以去试试。</p><p>官网主页：<a href="https://pgmooncake.com/">https://pgmooncake.com/</a></p><p>招聘信息：<a href="https://mooncakelabs.notion.site/build-the-mooncake-11cb7b68b5c1802a84a9e21649f49477">https://mooncakelabs.notion.site/build-the-mooncake-11cb7b68b5c1802a84a9e21649f49477</a></p><h1 id="三、pg-mooncake"><a href="#三、pg-mooncake" class="headerlink" title="三、pg_mooncake"></a>三、pg_mooncake</h1><p>接下来进入正篇，让我们走进pg_mooncake。</p><p>github项目地址：<a href="https://github.com/Mooncake-Labs/pg_mooncake">https://github.com/Mooncake-Labs/pg_mooncake</a></p><p>还是以源码安装为例、可参考<a href="https://github.com/Mooncake-Labs/pg_mooncake/blob/main/CONTRIBUTING.md">https://github.com/Mooncake-Labs/pg_mooncake/blob/main/CONTRIBUTING.md</a></p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># contrib目录</span></span><br><span class="line"><span class="built_in">cd</span> <span class="variable">$PostgreSQL</span>源码目录/contrib</span><br><span class="line"><span class="comment"># 拉取项目源码</span></span><br><span class="line">git <span class="built_in">clone</span> https://github.com/Mooncake-Labs/pg_mooncake.git</span><br><span class="line"><span class="comment"># 进入代码目录</span></span><br><span class="line"><span class="built_in">cd</span> pg_mooncake/</span><br><span class="line"><span class="comment"># 拉取duckdb子项目</span></span><br><span class="line">git submodule update --init --recursive</span><br><span class="line"><span class="comment"># 编译debug版本或者release版本</span></span><br><span class="line">make debug <span class="comment"># make release</span></span><br><span class="line"><span class="comment"># 等待编译完成即可 此处还有一个前提是安装好Rust的cargo</span></span><br><span class="line">make install </span><br></pre></td></tr></table></figure><p>和pg_duckdb不同的是在使用上pg_mooncake不需要设置shared_preload_libraries，直接连接数据库，创建拓展即可。</p><figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">CREATE</span> EXTENSION pg_mooncake;</span><br></pre></td></tr></table></figure><h2 id="3-1、简单罗列一下相关参数"><a href="#3-1、简单罗列一下相关参数" class="headerlink" title="3.1、简单罗列一下相关参数"></a>3.1、简单罗列一下相关参数</h2><p>和pg_duckdb相比，pg_mooncake的参数则是要少的多，很清爽（毕竟pg_duckdb要带货）。</p><p>如果只是简单体验体验的话，就默认设置什么都不动就可以了。这里简单罗列一下相关参数信息：</p><table><thead><tr><th>name</th><th>short_desc</th></tr></thead><tbody><tr><td>mooncake.allow_local_tables</td><td>Allow columnstore tables on local disk</td></tr><tr><td>mooncake.default_bucket</td><td>Default bucket for columnstore tables</td></tr><tr><td>mooncake.enable_local_cache</td><td>Enable local cache for columnstore tables</td></tr><tr><td>mooncake.enable_memory_metadata_cache</td><td>Enable memory cache for Parquet metadata</td></tr><tr><td>mooncake.maximum_memory</td><td>The maximum memory DuckDB can use (e.g., 1GB)</td></tr><tr><td>mooncake.maximum_threads</td><td>Maximum number of DuckDB threads per Postgres backend</td></tr></tbody></table><p>上面我们有说过pg_mooncake是将pg_duckdb糅合在一起，那可能有的朋友就很好奇，可能会提出这样子的疑问就是说那么pg_duckdb的参数在pg_mooncake中是否有效呢？</p><p>答案是无效的，让我们简单看一下pgmooncake.cpp中的_PG_init就一目了然了</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> _PG_init() {</span><br><span class="line">    <span class="built_in">MooncakeInitGUC</span>();</span><br><span class="line">    <span class="built_in">DuckdbInitHooks</span>(); </span><br><span class="line">    <span class="built_in">DuckdbInitNode</span>();</span><br><span class="line">    pgduckdb::<span class="built_in">RegisterDuckdbXactCallback</span>();</span><br><span class="line">　　<span class="comment">// 略过部分代码</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>可以看到pg_mooncake在_PG_init时不初始化pg_duckdb的相关参数，所以pg_duckdb的相关参数自然就无效了。</p><p>同时我们可以注意到它调用了pg_duckdb的DuckdbInitHooks、DuckdbInitNode这两个接口（内部实际会有些许改动），</p><p>也就是说从代码层面论证了我们在上一篇文章中讲到的内容在pg_mooncake依旧有效。所以实际上来说，什么都不需要设置，创建完拓展之后就可以愉快的玩耍了。</p><h2 id="3-2、简单使用"><a href="#3-2、简单使用" class="headerlink" title="3.2、简单使用"></a>3.2、简单使用</h2><p>在最开始的时候，我们指出了pg_duckdb的性能瓶颈可能在于将PostgreSQL中存储的行数据转换成duckdb的列数据。并且pg_duckdb仅对SELECT语句进行了额外的处理，对于其他SQL语句而言，如INSERT、UPDATE、DETELE都是交由PostgreSQL处理。</p><p>可能是pg_mooncake看到了这些痛点，所以它将INSERT、UPDATE、DETELE等等都给支持了，并在执行这些相关语句的时候，创建对应的Parquet文件，并存储相关数据。</p><p>当访问列存表时，对于数据加载那块便仅需要访问对应的Parquet文件即可，就这样避免了pg_duckdb在加载数据时的痛点。</p><p>而且物理文件使用Parquet文件作为外部存储，对于构建数据湖也很方便。</p><p>以官方的测试案例为例：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line">[postgres@halo-centos8 mooncake_local_tables]$ psql mooncake</span><br><span class="line">psql (16.8)</span><br><span class="line">Type <span class="string">"help"</span> <span class="keyword">for</span> <span class="built_in">help</span>.</span><br><span class="line"></span><br><span class="line">mooncake=# \dx</span><br><span class="line">                    List of installed extensions</span><br><span class="line">    Name     | Version |   Schema   |          Description          </span><br><span class="line">-------------+---------+------------+-------------------------------</span><br><span class="line"> pg_mooncake | 0.1.2   | public     | Columnstore Table <span class="keyword">in</span> Postgres</span><br><span class="line"> plpgsql     | 1.0     | pg_catalog | PL/pgSQL procedural language</span><br><span class="line">(2 rows)</span><br><span class="line"></span><br><span class="line">mooncake=# -- 创建列存表</span><br><span class="line">mooncake=# CREATE TABLE user_activity(</span><br><span class="line">mooncake(#   user_id BIGINT,</span><br><span class="line">mooncake(#   activity_type TEXT,</span><br><span class="line">mooncake(#   activity_timestamp TIMESTAMP,</span><br><span class="line">mooncake(#   duration INT</span><br><span class="line">mooncake(# ) USING columnstore;</span><br><span class="line">CREATE TABLE</span><br><span class="line">mooncake=# -- 通过explain来简单判断INSERT是否被支持</span><br><span class="line">mooncake=# EXPLAIN VERBOSE INSERT INTO user_activity VALUES</span><br><span class="line">mooncake-#   (1, <span class="string">'login'</span>, <span class="string">'2024-01-01 08:00:00'</span>, 120),</span><br><span class="line">mooncake-#   (2, <span class="string">'page_view'</span>, <span class="string">'2024-01-01 08:05:00'</span>, 30),</span><br><span class="line">mooncake-#   (3, <span class="string">'logout'</span>, <span class="string">'2024-01-01 08:30:00'</span>, 60),</span><br><span class="line">mooncake-#   (4, <span class="string">'error'</span>, <span class="string">'2024-01-01 08:13:00'</span>, 60);</span><br><span class="line">                             QUERY PLAN                             </span><br><span class="line">--------------------------------------------------------------------</span><br><span class="line"> Custom Scan (MooncakeDuckDBScan)  (cost=0.00..0.00 rows=0 width=0)</span><br><span class="line">   Output: duckdb_scan.explain_key, duckdb_scan.explain_value</span><br><span class="line">   DuckDB Execution Plan: </span><br><span class="line"> </span><br><span class="line"> ┌───────────────────────────┐</span><br><span class="line"> │     COLUMNSTORE_INSERT    │</span><br><span class="line"> └─────────────┬─────────────┘</span><br><span class="line"> ┌─────────────┴─────────────┐</span><br><span class="line"> │      COLUMN_DATA_SCAN     │</span><br><span class="line"> │    ────────────────────   │</span><br><span class="line"> │          ~4 Rows          │</span><br><span class="line"> └───────────────────────────┘</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line">(14 rows)</span><br><span class="line"></span><br><span class="line">mooncake=# -- 插入数据</span><br><span class="line">mooncake=# INSERT INTO user_activity VALUES</span><br><span class="line">mooncake-#   (1, <span class="string">'login'</span>, <span class="string">'2024-01-01 08:00:00'</span>, 120),</span><br><span class="line">mooncake-#   (2, <span class="string">'page_view'</span>, <span class="string">'2024-01-01 08:05:00'</span>, 30),</span><br><span class="line">mooncake-#   (3, <span class="string">'logout'</span>, <span class="string">'2024-01-01 08:30:00'</span>, 60),</span><br><span class="line">mooncake-#   (4, <span class="string">'error'</span>, <span class="string">'2024-01-01 08:13:00'</span>, 60);</span><br><span class="line">INSERT 0 4</span><br><span class="line">mooncake=# -- 查询数据</span><br><span class="line">mooncake=# SELECT * from user_activity;</span><br><span class="line"> user_id | activity_type | activity_timestamp  | duration </span><br><span class="line">---------+---------------+---------------------+----------</span><br><span class="line">       1 | login         | 2024-01-01 08:00:00 |      120</span><br><span class="line">       2 | page_view     | 2024-01-01 08:05:00 |       30</span><br><span class="line">       3 | <span class="built_in">logout</span>        | 2024-01-01 08:30:00 |       60</span><br><span class="line">       4 | error         | 2024-01-01 08:13:00 |       60</span><br><span class="line">(4 rows)</span><br><span class="line"></span><br><span class="line">mooncake=# -- 查看列存表对应物理文件位置</span><br><span class="line">mooncake=# SELECT * FROM mooncake.columnstore_tables;</span><br><span class="line">  table_name   |                                  path                                  </span><br><span class="line">---------------+------------------------------------------------------------------------</span><br><span class="line"> user_activity | /data/16/mooncake_local_tables/mooncake_mooncake_user_activity_279647/</span><br><span class="line">(1 row)</span><br></pre></td></tr></table></figure><p>如果你按照我的操作来进行的话，这个数据目录不一定和我这里一样，这个目录的命名规则为$PGDATA/mooncake_local_tables/mooncake_数据库名_表名_表oid</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">string <span class="title">ColumnstoreMetadata::GetTablePath</span><span class="params">(Oid oid)</span> </span>{</span><br><span class="line">    ::Relation table = <span class="built_in">table_open</span>(oid, AccessShareLock);</span><br><span class="line">    string path =</span><br><span class="line">        StringUtil::<span class="built_in">Format</span>(<span class="string">"mooncake_%s_%s_%d/"</span>, <span class="built_in">get_database_name</span>(MyDatabaseId), <span class="built_in">RelationGetRelationName</span>(table), oid);</span><br><span class="line">    <span class="built_in">table_close</span>(table, AccessShareLock);</span><br><span class="line">    <span class="keyword">if</span> (mooncake_default_bucket != <span class="literal">nullptr</span> &amp;&amp; mooncake_default_bucket[<span class="number">0</span>] != <span class="string">'\0'</span>) {</span><br><span class="line">        path = StringUtil::<span class="built_in">Format</span>(<span class="string">"%s/%s"</span>, mooncake_default_bucket, path);</span><br><span class="line">    } <span class="keyword">else</span> <span class="keyword">if</span> (mooncake_allow_local_tables) {</span><br><span class="line">        path = StringUtil::<span class="built_in">Format</span>(<span class="string">"%s/mooncake_local_tables/%s"</span>, DataDir, path);</span><br><span class="line">    } <span class="keyword">else</span> {</span><br><span class="line">        <span class="built_in">elog</span>(ERROR, <span class="string">"Columnstore tables on local disk are not allowed. Set mooncake.default_bucket to default "</span></span><br><span class="line">                    <span class="string">"S3 bucket"</span>);</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">return</span> path;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>而对应的parquet文件的命名则是非常明显的uuid</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">file_name = UUID::<span class="built_in">ToString</span>(UUID::<span class="built_in">GenerateRandomUUID</span>()) + <span class="string">".parquet"</span>;</span><br></pre></td></tr></table></figure><p>让我们看一下/data/16/mooncake_local_tables/mooncake_mooncake_user_activity_279647/这个目录存在哪些东西，</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">[postgres@halo-centos8 mooncake_local_tables]$ tree /data/16/mooncake_local_tables/mooncake_mooncake_user_activity_279647/</span><br><span class="line">/data/16/mooncake_local_tables/mooncake_mooncake_user_activity_279647/</span><br><span class="line">├── 721e8499-b4c3-4a4a-a822-5af1fc45e237.parquet</span><br><span class="line">└── _delta_log</span><br><span class="line">    ├── 00000000000000000000.json</span><br><span class="line">    └── 00000000000000000001.json</span><br><span class="line"></span><br><span class="line">1 directory, 3 files</span><br><span class="line">[postgres@halo-centos8 mooncake_local_tables]$ <span class="built_in">cat</span> /data/16/mooncake_local_tables/mooncake_mooncake_user_activity_279647/_delta_log/00000000000000000001.json</span><br><span class="line">{<span class="string">"add"</span>:{<span class="string">"path"</span>:<span class="string">"721e8499-b4c3-4a4a-a822-5af1fc45e237.parquet"</span>,<span class="string">"partitionValues"</span>:{},<span class="string">"size"</span>:699,<span class="string">"modificationTime"</span>:0,<span class="string">"dataChange"</span>:<span class="literal">true</span>,<span class="string">"stats"</span>:null,<span class="string">"tags"</span>:null,<span class="string">"deletionVector"</span>:null,<span class="string">"baseRowId"</span>:null,<span class="string">"defaultRowCommitVersion"</span>:null,<span class="string">"clusteringProvider"</span>:null}}</span><br><span class="line">{<span class="string">"commitInfo"</span>:{<span class="string">"timestamp"</span>:1740987491586,<span class="string">"operation"</span>:<span class="string">"WRITE"</span>,<span class="string">"operationParameters"</span>:{<span class="string">"mode"</span>:<span class="string">"Append"</span>},<span class="string">"clientVersion"</span>:<span class="string">"delta-rs.0.21.0"</span>}}</span><br></pre></td></tr></table></figure><p>这里我们想尝试去读一下721e8499-b4c3-4a4a-a822-5af1fc45e237.parquet，可以用mooncake.read_parquet也可以用duckdb，这里我还是用duckdb来读取</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">[postgres@halo-centos8 ~]$ ./duckdb </span><br><span class="line">v1.1.3 19864453f7</span><br><span class="line">Enter <span class="string">".help"</span> <span class="keyword">for</span> usage hints.</span><br><span class="line">Connected to a transient in-memory database.</span><br><span class="line">Use <span class="string">".open FILENAME"</span> to reopen on a persistent database.</span><br><span class="line">D <span class="keyword">select</span> * from read_parquet(<span class="string">'/data/16/mooncake_local_tables/mooncake_mooncake_user_activity_279647/721e8499-b4c3-4a4a-a822-5af1fc45e237.parquet'</span>);</span><br><span class="line">┌─────────┬───────────────┬─────────────────────┬──────────┐</span><br><span class="line">│ user_id │ activity_type │ activity_timestamp  │ duration │</span><br><span class="line">│  int64  │    varchar    │      timestamp      │  int32   │</span><br><span class="line">├─────────┼───────────────┼─────────────────────┼──────────┤</span><br><span class="line">│       1 │ login         │ 2024-01-01 08:00:00 │      120 │</span><br><span class="line">│       2 │ page_view     │ 2024-01-01 08:05:00 │       30 │</span><br><span class="line">│       3 │ <span class="built_in">logout</span>        │ 2024-01-01 08:30:00 │       60 │</span><br><span class="line">│       4 │ error         │ 2024-01-01 08:13:00 │       60 │</span><br><span class="line">└─────────┴───────────────┴─────────────────────┴──────────┘</span><br></pre></td></tr></table></figure><p>可以看到和在PostgreSQL中读取到的数据是一致的。</p><p>_delta_log中的相关json文件，就是实际对应的事务日志。json文件和parquet文件并不总是线性增长的，如下所示：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">[postgres@halo-centos8 ~]$ psql mooncake</span><br><span class="line">psql (16.8)</span><br><span class="line">Type <span class="string">"help"</span> <span class="keyword">for</span> <span class="built_in">help</span>.</span><br><span class="line"></span><br><span class="line">mooncake=# BEGIN; -- 开启事务块</span><br><span class="line">BEGIN</span><br><span class="line">mooncake=*# INSERT INTO user_activity VALUES (5, <span class="string">'test'</span>, <span class="string">'2024-01-01 08:13:00'</span>, 60);</span><br><span class="line">INSERT 0 1</span><br><span class="line">mooncake=*# INSERT INTO user_activity VALUES (6, <span class="string">'test'</span>, <span class="string">'2024-01-01 08:13:00'</span>, 60);</span><br><span class="line">INSERT 0 1</span><br><span class="line">mooncake=*# ROLLBACK; -- 回滚事务</span><br><span class="line">ROLLBACK</span><br></pre></td></tr></table></figure><p>让我们再次查看一下/data/16/mooncake_local_tables/mooncake_mooncake_user_activity_279647/的目录结构和json数据</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">[postgres@halo-centos8 ~]$ tree /data/16/mooncake_local_tables/mooncake_mooncake_user_activity_279647/</span><br><span class="line">/data/16/mooncake_local_tables/mooncake_mooncake_user_activity_279647/</span><br><span class="line">├── 721e8499-b4c3-4a4a-a822-5af1fc45e237.parquet</span><br><span class="line">├── 81d8cb44-0136-4cf6-8188-7a673ad1da92.parquet</span><br><span class="line">├── _delta_log</span><br><span class="line">│   ├── 00000000000000000000.json</span><br><span class="line">│   └── 00000000000000000001.json</span><br><span class="line">└── f7afb9b9-a876-4905-9990-74177f65ee22.parquet</span><br><span class="line"></span><br><span class="line">1 directory, 5 files</span><br><span class="line">[postgres@halo-centos8 ~]$ <span class="built_in">cat</span> /data/16/mooncake_local_tables/mooncake_mooncake_user_activity_279647/_delta_log/00000000000000000001.json</span><br><span class="line">{<span class="string">"add"</span>:{<span class="string">"path"</span>:<span class="string">"721e8499-b4c3-4a4a-a822-5af1fc45e237.parquet"</span>,<span class="string">"partitionValues"</span>:{},<span class="string">"size"</span>:699,<span class="string">"modificationTime"</span>:0,<span class="string">"dataChange"</span>:<span class="literal">true</span>,<span class="string">"stats"</span>:null,<span class="string">"tags"</span>:null,<span class="string">"deletionVector"</span>:null,<span class="string">"baseRowId"</span>:null,<span class="string">"defaultRowCommitVersion"</span>:null,<span class="string">"clusteringProvider"</span>:null}}</span><br><span class="line">{<span class="string">"commitInfo"</span>:{<span class="string">"timestamp"</span>:1740987491586,<span class="string">"operation"</span>:<span class="string">"WRITE"</span>,<span class="string">"operationParameters"</span>:{<span class="string">"mode"</span>:<span class="string">"Append"</span>},<span class="string">"clientVersion"</span>:<span class="string">"delta-rs.0.21.0"</span>}}</span><br></pre></td></tr></table></figure><p>可以发现json文件没有发生任何变化，但是新增了两个parquet文件，我们再次使用duckdb读取一下此目录中的所有parquet</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">[postgres@halo-centos8 ~]$ ./duckdb</span><br><span class="line">v1.1.3 19864453f7</span><br><span class="line">Enter <span class="string">".help"</span> <span class="keyword">for</span> usage hints.</span><br><span class="line">Connected to a transient in-memory database.</span><br><span class="line">Use <span class="string">".open FILENAME"</span> to reopen on a persistent database.</span><br><span class="line">D <span class="keyword">select</span> * from read_parquet(<span class="string">'/data/16/mooncake_local_tables/mooncake_mooncake_user_activity_279647/*'</span>);  -- * 代表所有的parquet</span><br><span class="line">┌─────────┬───────────────┬─────────────────────┬──────────┐</span><br><span class="line">│ user_id │ activity_type │ activity_timestamp  │ duration │</span><br><span class="line">│  int64  │    varchar    │      timestamp      │  int32   │</span><br><span class="line">├─────────┼───────────────┼─────────────────────┼──────────┤</span><br><span class="line">│       1 │ login         │ 2024-01-01 08:00:00 │      120 │</span><br><span class="line">│       2 │ page_view     │ 2024-01-01 08:05:00 │       30 │</span><br><span class="line">│       3 │ <span class="built_in">logout</span>        │ 2024-01-01 08:30:00 │       60 │</span><br><span class="line">│       4 │ error         │ 2024-01-01 08:13:00 │       60 │</span><br><span class="line">│       5 │ <span class="built_in">test</span>          │ 2024-01-01 08:13:00 │       60 │</span><br><span class="line">│       6 │ <span class="built_in">test</span>          │ 2024-01-01 08:13:00 │       60 │</span><br><span class="line">└─────────┴───────────────┴─────────────────────┴──────────┘</span><br></pre></td></tr></table></figure><p>可以看到我们回滚的那两个记录能被查询到，所以实际上那两个parquet分别对应一条INSERT语句。</p><h2 id="3-3、行列混存"><a href="#3-3、行列混存" class="headerlink" title="3.3、行列混存"></a>3.3、行列混存</h2><p>也正是因为pg_mooncake实现了列式存储，所以对于PostgreSQL而言，便出现了行列混存的情形。</p><p>最简单的场景便是构建一张堆表和一张列存表，那么这两张表可以join吗？</p><p>答案是可以的。如下所示：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line">[postgres@halo-centos8 ~]$ psql mooncake</span><br><span class="line">psql (16.8)</span><br><span class="line">Type <span class="string">"help"</span> <span class="keyword">for</span> <span class="built_in">help</span>.</span><br><span class="line"></span><br><span class="line">mooncake=# CREATE TABLE ta(user_id BIGINT); -- 简单堆表</span><br><span class="line">CREATE TABLE</span><br><span class="line">mooncake=# INSERT INTO ta SELECT * FROM generate_series(1, 6); -- 生成六行数据</span><br><span class="line">INSERT 0 6</span><br><span class="line">mooncake=# SELECT * FROM ta Inner Join user_activity on ta.user_id = user_activity.user_id; -- 简单测试查看结果 </span><br><span class="line"> user_id | user_id | activity_type | activity_timestamp  | duration </span><br><span class="line">---------+---------+---------------+---------------------+----------</span><br><span class="line">       1 |       1 | login         | 2024-01-01 08:00:00 |      120</span><br><span class="line">       2 |       2 | page_view     | 2024-01-01 08:05:00 |       30</span><br><span class="line">       3 |       3 | <span class="built_in">logout</span>        | 2024-01-01 08:30:00 |       60</span><br><span class="line">       4 |       4 | error         | 2024-01-01 08:13:00 |       60</span><br><span class="line">(4 rows)</span><br><span class="line"></span><br><span class="line">mooncake=# EXPLAIN VERBOSE SELECT * FROM ta Inner Join user_activity on ta.user_id = user_activity.user_id; -- 简单测试查看执行计划 </span><br><span class="line">                             QUERY PLAN                             </span><br><span class="line">--------------------------------------------------------------------</span><br><span class="line"> Custom Scan (MooncakeDuckDBScan)  (cost=0.00..0.00 rows=0 width=0)</span><br><span class="line">   Output: duckdb_scan.explain_key, duckdb_scan.explain_value</span><br><span class="line">   DuckDB Execution Plan: </span><br><span class="line"> </span><br><span class="line"> ┌───────────────────────────┐</span><br><span class="line"> │         PROJECTION        │</span><br><span class="line"> │    ────────────────────   │</span><br><span class="line"> │          user_id          │</span><br><span class="line"> │          user_id          │</span><br><span class="line"> │       activity_type       │</span><br><span class="line"> │     activity_timestamp    │</span><br><span class="line"> │          duration         │</span><br><span class="line"> │                           │</span><br><span class="line"> │         ~2260 Rows        │</span><br><span class="line"> └─────────────┬─────────────┘</span><br><span class="line"> ┌─────────────┴─────────────┐</span><br><span class="line"> │         HASH_JOIN         │</span><br><span class="line"> │    ────────────────────   │</span><br><span class="line"> │      Join Type: INNER     │</span><br><span class="line"> │                           │</span><br><span class="line"> │        Conditions:        ├──────────────┐</span><br><span class="line"> │     user_id = user_id     │              │</span><br><span class="line"> │                           │              │</span><br><span class="line"> │         ~2260 Rows        │              │</span><br><span class="line"> └─────────────┬─────────────┘              │</span><br><span class="line"> ┌─────────────┴─────────────┐┌─────────────┴─────────────┐</span><br><span class="line"> │     POSTGRES_SEQ_SCAN     ││     COLUMNSTORE_SCAN      │</span><br><span class="line"> │    ────────────────────   ││    ────────────────────   │</span><br><span class="line"> │         Function:         ││         Function:         │</span><br><span class="line"> │     POSTGRES_SEQ_SCAN     ││      COLUMNSTORE_SCAN     │</span><br><span class="line"> │                           ││                           │</span><br><span class="line"> │    Projections: user_id   ││        Projections:       │</span><br><span class="line"> │                           ││          user_id          │</span><br><span class="line"> │                           ││       activity_type       │</span><br><span class="line"> │                           ││     activity_timestamp    │</span><br><span class="line"> │                           ││          duration         │</span><br><span class="line"> │                           ││                           │</span><br><span class="line"> │         ~2260 Rows        ││          ~4 Rows          │</span><br><span class="line"> └───────────────────────────┘└───────────────────────────┘</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line">(41 rows)</span><br></pre></td></tr></table></figure><p>所以显而易见的是，pg_mooncake将数据都加载到了duckdb中，然后去执行了。</p><p>对于堆表而言，若当前SQL查询存在列存表，会走原本pg_duckdb的逻辑，会将元组数据转换成duckdb的列数据，对应POSTGRES_SEQ_SCAN。（如果当前查询并不包含列存表，则走的PostgreSQL的默认逻辑）</p><p>对于列存表而言，走的则是pg_mooncake自己提供的COLUMNSTORE_SCAN，实际上就是parquet_scan。</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">TableFunction <span class="title">ColumnstoreTable::GetScanFunction</span><span class="params">(ClientContext &amp;context, unique_ptr&lt;FunctionData&gt; &amp;bind_data)</span> </span>{</span><br><span class="line">    <span class="keyword">auto</span> file_names = metadata-&gt;<span class="built_in">DataFilesSearch</span>(oid, &amp;context, &amp;path, &amp;columns);</span><br><span class="line">    <span class="keyword">auto</span> file_paths = <span class="built_in">GetFilePaths</span>(path, file_names);</span><br><span class="line">    <span class="keyword">if</span> (file_paths.<span class="built_in">empty</span>()) {</span><br><span class="line">        <span class="keyword">return</span> <span class="built_in">TableFunction</span>(<span class="string">"columnstore_scan"</span>, {} <span class="comment">/*arguments*/</span>, EmptyColumnstoreScan);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    TableFunction columnstore_scan = <span class="built_in">GetParquetScan</span>(context);  -- 注意此处</span><br><span class="line">    columnstore_scan.name = <span class="string">"columnstore_scan"</span>;</span><br><span class="line">    columnstore_scan.init_global = ColumnstoreScanInitGlobal;</span><br><span class="line">    columnstore_scan.statistics = <span class="literal">nullptr</span>;</span><br><span class="line">    columnstore_scan.get_multi_file_reader = ColumnstoreScanMultiFileReader::Create;　　<span class="comment">// 省略代码...}</span></span><br><span class="line"><span class="function">TableFunction <span class="title">GetParquetScan</span><span class="params">(ClientContext &amp;context)</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> ExtensionUtil::<span class="built_in">GetTableFunction</span>(*context.db, <span class="string">"parquet_scan"</span>)  -- 实际是parquet_scan</span><br><span class="line">        .functions.<span class="built_in">GetFunctionByArguments</span>(context, {LogicalType::<span class="built_in">LIST</span>(LogicalType::VARCHAR)});</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>所以pg_mooncake还是设计的蛮巧妙的。欢迎感兴趣的同学可以去点点Star，提提PR。</p><h2 id="3-4、不足之处？"><a href="#3-4、不足之处？" class="headerlink" title="3.4、不足之处？"></a>3.4、不足之处？</h2><p>这其实到没啥好说的了，我能注意到的必然也逃不过pg_mooncake的大佬的法眼。</p><p>一般的都被记录到Issues中了，比方说资源管理之类的，如drop table之后，对应的物理目录及文件未被及时清除之类的；</p><p>比如说性能优化之类的，如<a href="https://github.com/Mooncake-Labs/pg_mooncake/issues/82">https://github.com/Mooncake-Labs/pg_mooncake/issues/82</a></p><p>听说cc他们规划后续会将pg_mooncake做成一个基于PostgreSQL的HTAP数据库，让人非常的期待呀。</p><h1 id="四、推荐阅读"><a href="#四、推荐阅读" class="headerlink" title="四、推荐阅读"></a>四、推荐阅读</h1><p>有意思的是pg_mooncake虽然使用的是duckdb的parquet功能，但是在某些场景下甚至能做到比duckdb还要更快</p><p>是因为他们做了大量的优化，技术拉满，推荐文章链接：<a href="https://www.mooncake.dev/blog/duckdb-parquet">https://www.mooncake.dev/blog/duckdb-parquet</a></p><p>官方博客：<a href="https://www.mooncake.dev/blog">https://www.mooncake.dev/blog</a></p><h1 id="五、声明"><a href="#五、声明" class="headerlink" title="五、声明"></a>五、声明</h1><p>若文中存在错误或不当之处，敬请指出，以便我进行修正和完善。希望这篇文章能够帮助到各位。</p><p>文章转载请联系，谢谢合作</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;h1 id=&quot;一、前言&quot;&gt;&lt;a href=&quot;#一、前言&quot; class=&quot;head</summary>
      
    
    
    
    <category term="数据库" scheme="http://posts.hufeifei.cn/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    
    <category term="Postgres" scheme="http://posts.hufeifei.cn/tags/Postgres/"/>
    
    <category term="OLAP" scheme="http://posts.hufeifei.cn/tags/OLAP/"/>
    
    <category term="pg_mooncake" scheme="http://posts.hufeifei.cn/tags/pg-mooncake/"/>
    
  </entry>
  
  <entry>
    <title>2025 年可观测 10 大趋势预测</title>
    <link href="http://posts.hufeifei.cn/backend/Observability-Trends-2025/"/>
    <id>http://posts.hufeifei.cn/backend/Observability-Trends-2025/</id>
    <published>2025-04-02T00:00:00.000Z</published>
    <updated>2026-01-01T06:47:29.852Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><h2 id="AIOps"><a href="#AIOps" class="headerlink" title="AIOps"></a>AIOps</h2><p>AIOps 本身就是可观测的一大重点方向，随着 LLM 的流行，AIOps 再次被吹到风口浪尖，几乎每篇预测 AIOps 都是一大主题。这里我们不纠结于专有名词，统一称为 AIOps 。涵盖的能力很广：</p><ol><li>AIOps 平台：AIOps 能力快速演进，最终会形成平台化，平台将能管理整个 AIOps 的生命周期，包括集成复杂的异常发现、根因分析和自动化功能，实现统一的 AIOps 能力整合。</li><li>AI 驱动的预测：AI 的故障发现和事后分析将转向 AI 驱动的预测，以应对数据量和复杂性带来的挑战。AI 和机器学习算法将用于在问题影响业务运营之前进行预测，从而提高系统性能并增强干预能力。</li><li>AIOps 自动化：AIOps 将显著提升 ITOps 的自动化水平，自动检测和发现潜在问题，减少根因分析所需的手动工作量。</li><li>自然语言交互：基于 LLM 的自然语言交互功能将使 IT 人员能够更方便地查询其可观测数据，例如 Chat2PromQL、Chat2SQL 等。</li><li>云计算下 AIOps 必要性：随着企业继续向云迁移，以及容器化、各类云原生产品的应用，企业更需要 AIOps 能力来快速获得云环境的可观测能力，自动化地监控、分析和优化云资源的使用，从而确保系统的高效运行。</li><li>DevOps 和 AIOps 的融合：DevOps 和 AIOps之间的界限将开始模糊，甚至形成统一的运营团队。这些团队将整合 AI 专业知识与传统的软件开发和 IT 运营，管理软件生命周期和 AI 模型生命周期，并持续改进。</li></ol><h2 id="OpenTelemetry"><a href="#OpenTelemetry" class="headerlink" title="OpenTelemetry"></a>OpenTelemetry</h2><p>OpenTelemetry 是可观测领域与 AIOps 不分上下的火热话题，在 CNCF 以及各大云厂商、可观测独立厂商的推动下，OpenTelemetry 已经称为可观测领域的事实标准，除 Trace、Metric、Log 外，OpenTelemetry 在 2024 还推出了 Profiling 标准，意图标准化可观测领域的所有数据格式并形成统一关联。由于 OpenTelemetry 协议、OpenTelemetry Collector 的厂商无关性，2025 将逐步巩固在遥测数据采集中的基石地位。OpenTelemetry 由于只定义数据格式和提供采集能力，后端服务由厂商实现，在 2025 年将会有更多厂商开发的工具</p><h2 id="统一观测平台"><a href="#统一观测平台" class="headerlink" title="统一观测平台"></a>统一观测平台</h2><p>2025年可观测领域的一个重要趋势是向统一平台的转变。这些平台将 Log、Trace、Metrc 、Event、Profile 整合到一个集中的视图中，提供以下优势：</p><ul><li>消除监控工具之间的数据孤岛，强化数据之间的关联。</li><li>在混合云和多云环境中实现无缝的可视化、问题排查能力。</li><li>通过从单一界面获取整体洞察结果，简化根因分析代价。<br>随着可观测性趋势的发展，Datadog、Splunk 和 New Relic 等供应商正引领着向更高集成度和效率的转变。</li></ul><h2 id="观测右移"><a href="#观测右移" class="headerlink" title="观测右移"></a>观测右移</h2><p>用于边缘计算环境的消费和工业设备的数量预计将迅速增加，这些设备继续提供更强大的计算和连接能力。它们的增加也意味着观测和监控必须扩展到边缘设备。对于尚未提供此功能的观测公司来说，在2025年解决这一需求将至关重要，以满足那些正在将其技术栈扩展到边缘环境的客户。</p><p>此外公司将更关注涉及用户真实体验的前端监控，这些监控手段需要具备扩展到各类边缘、端设备的能力。观测目标从整体变为细节，企业将更关注对每个客户的监控，而不是整体的分位数情况。对于观测工具的核心能力要求：</p><ul><li>轻量级的数据采集能力，具备部署到资源受限的 IoT 场景中，并具备一定的端处理能力。</li><li>高效、低延迟的全球化网络支持，具备网络加速能力。</li><li>支持大规模数据低成本存储和计算的数据平台。</li><li>全球化实时的数据汇聚能力，并支持在不移动数据的情况下完成统一视图。</li></ul><h2 id="观测左移"><a href="#观测左移" class="headerlink" title="观测左移"></a>观测左移</h2><p>平台工程师、运维工程师、DevOps和所有利益相关者正在意识到，在开发周期中引入观测对开发者非常有用。对于像 Kubernetes 这样高度分布和互联的服务和应用程序而言，这一点尤为重要。除了测试，在非常详细的层次上观察堆栈及其在整个开发周期中与应用程序其他部分的交互是观测的另一个关键方面。预计这一方面将在 2025 年得到更广泛的部署。</p><p>随着近两年 Profiling 相关技术的成熟，开发者可以快速地在初期阶段引入 Profile、Trace 等技术观测软件的细节行为。这一增强大大改善了开发者的体验，因为分析提供了对代码影响的无与伦比的视图，从而促进更快、更具成本效益的优化。</p><p>Gartner 将这一左移趋势描述为观测驱动开发（ODD）工程实践的一部分，通过设计可观测的系统，提供对系统状态和行为的细粒度可见性和上下文，从而使得在开发周期的早期阶段以及生产环境中更容易检测、诊断和解决意外异常。</p><h2 id="平台工程的下一个前沿：eBPF"><a href="#平台工程的下一个前沿：eBPF" class="headerlink" title="平台工程的下一个前沿：eBPF"></a>平台工程的下一个前沿：eBPF</h2><p>平台团队正在经历显著的增长，Grafana 对于可观测的调查中有近 25% 的人在这个角色中工作。随着平台团队重要性的增加，他们的职责将扩展到包括新兴工具和技术——例如eBPF。eBPF起初是一种时髦的技术，如今将成为现代平台工程的支柱，从根本上重塑组织处理可观察性和安全性的方式。当前 eBPF正处于“重大转型的边缘”。</p><p>eBPF 带来一个显著的变化将是把 Profiling 甚至是整体观测责任从应用程序团队转移到平台团队。尤其是 OpenTelemetry Profiling 协议的完善以及与 eBPF 的集成，能够以标准化平台的方式收集和处理这些可观测数据。</p><h2 id="下一代可观测主力军：Log"><a href="#下一代可观测主力军：Log" class="headerlink" title="下一代可观测主力军：Log"></a>下一代可观测主力军：Log</h2><p>随着企业数字化在 2024 年达到历史新高，我们预期开发、安全和运营团队需要更加紧密地合作，以帮助解决业务、技术和安全运营面临的最难问题。这一演变导致了 AI 驱动的观测平台的迅速崛起，并使人们更加广泛地理解日志作为系统记录的重要性。在 2025 年，组织的结构化和非结构化日志数据中蕴含的洞察力将通过传统 AI/ML 和生成式 AI 技术得以释放。这将提供无与伦比的上下文和洞察力，并最终兑现对应用程序和数字服务观测的承诺。</p><p>此外，Log 分析管理工具也将产生巨大的技术提升，包括大规模分析技术、低成本冷热存储分离、数据湖能力等。</p><h2 id="Cost-Effective-Observability"><a href="#Cost-Effective-Observability" class="headerlink" title="Cost-Effective Observability"></a>Cost-Effective Observability</h2><p>随着系统复杂性的增加，观测成本也在逐渐上涨，关注成本逐渐成为可观测发展趋势中的一个关键方面。到 2025 年，企业将采用以下策略节省成本：</p><ul><li>更智能的数据采样和保留策略以减少存储开销。</li><li>采用按使用量付费模式的 Serverless 观测工具。</li><li>选择在功能性和成本效益之间取得平衡的解决方案。</li></ul><h2 id="超越传统运维的观测性"><a href="#超越传统运维的观测性" class="headerlink" title="超越传统运维的观测性"></a>超越传统运维的观测性</h2><p>2025年的观测性趋势将超越传统的基础设施、中间、应用程序的监控和观测，涵盖以下方面：</p><ul><li>业务流程观测性：提供对客户产品使用流程和公司运营效率的洞察。</li><li>DevSecOps观测性：确保高效、安全的部署。</li><li>可持续性观测性：通过遥测跟踪和优化碳中和足迹。</li></ul><p>这些发展将重新定义观测性的潜力和实现范围。</p><h2 id="从事后回溯到事前预防"><a href="#从事后回溯到事前预防" class="headerlink" title="从事后回溯到事前预防"></a>从事后回溯到事前预防</h2><p>随着客户对应用体验要求的逐渐提升，企业越来越希望观测系统能提前预测潜在的服务中断、容量问题和性能下降。这种主动的方法帮助企业在问题影响最终用户之前，减轻风险并有效管理资源，增强服务可靠性并减少计划外停机时间。</p><p>与早期因缺乏上下文理解而难以实现的传统 AIOps 方法不同，新一代 AI 驱动的观测性整合了跨系统的观测数据，能够快速识别根因、预测级联故障，使事前预防成为可能。</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul><li><a href="https://thenewstack.io/observability-in-2025-opentelemetry-and-ai-to-fill-in-gaps/">Observability in 2025: OpenTelemetry and AI to Fill In Gaps</a></li><li><a href="https://www.skedler.com/blog/the-future-of-observability-trends-to-watch-in-2025/">The Future of Observability: Trends to Watch in 2025</a></li><li><a href="https://grafana.com/blog/2024/12/16/2025-observability-predictions-and-trends-from-grafana-labs/">2025 observability predictions and trends from Grafana Labs</a></li><li><a href="https://www.dynatrace.com/news/blog/observability-predictions-for-2025/">Five observability predictions for 2025</a></li><li><a href="https://www.intellectyx.com/best-data-observability-tools-2025-a-buyers-guide/">Best Data Observability Tools in 2025: A Buyer’s Guide</a></li><li><a href="https://www.constellationr.com/research/2025-observability-trends">2025 Observability Trends</a></li><li><a href="https://www.apmdigest.com/2025-observability-predictions-part-1">2025 Observability Predictions - Part 1</a></li><li><a href="https://medium.com/@bijit211987/observability-driven-development-2bc2cdde8661">Observability Driven Development (ODD)-Enhancing System Reliability</a></li><li><a href="https://www.splunk.com/en_us/blog/learn/odd-observability-driven-development.html">Observability-Driven Development Explained: 8 Steps for ODD Success</a></li><li><a href="https://www.elastic.co/observability/universal-profiling">Drive optimization and sustainability with continuous profiling</a></li><li><a href="https://docs.datadoghq.com/profiler/">Continuous Profiler</a></li><li><a href="https://opentelemetry.io/blog/2024/profiling/">OpenTelemetry announces support for profiling</a></li><li><a href="https://github.com/open-telemetry/opentelemetry-ebpf-profiler">opentelemetry-ebpf-profiler</a></li><li><a href="https://grafana.com/oss/pyroscope/">Grafana pyroscope</a></li><li><a href="https://www.datadoghq.com/blog/datadog-acquires-quickwit/">Datadog acquires Quickwit</a></li><li><a href="https://help.aliyun.com/zh/opentelemetry/">Aliyun 可观测链路 OpenTelemetry 版</a></li><li><a href="https://quickwit.io/">quickwit</a></li><li><a href="https://opentelemetry.io/blog/2024/elastic-contributes-continuous-profiling-agent/">Elastic Contributes its Continuous Profiling Agent to OpenTelemetry</a></li><li><a href="https://www.pingcap.com/chat2query-an-innovative-ai-powered-sql-generator-for-faster-insights/">Chat2Query：创新的 AI 驱动的 SQL 生成器，可更快地获得见解</a></li><li><a href="https://help.aliyun.com/zh/arms/application-monitoring-ebpf/product-overview/what-is-alibaba-cloud-application-monitoring-ebpf-version">什么是阿里云应用监控 eBPF 版</a></li><li><a href="https://prometheus.io/blog/2024/11/14/prometheus-3-0/">Announcing Prometheus 3.0</a></li><li><a href="https://github.com/Jun-jie-Huang/awesome-LLM-AIOps">Awesome LLM AIOps</a></li><li><a href="https://openobserve.ai/articles/logs-metrics-traces-observability/">Navigating Observability: Logs, Metrics, and Traces Explained</a></li><li><a href="https://www.elastic.co/docs/explore-analyze/machine-learning/nlp/ml-nlp-rerank">Elastic Rerank</a></li><li><a href="https://www.sumologic.com/blog/dynamic-observability-ai-innovations-logs/">The future is now, introducing Dynamic Observability from AI innovations built on logs</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;h2 id=&quot;AIOps&quot;&gt;&lt;a href=&quot;#AIOps&quot; class=&quot;he</summary>
      
    
    
    
    <category term="后端" scheme="http://posts.hufeifei.cn/categories/%E5%90%8E%E7%AB%AF/"/>
    
    
    <category term="AIOps" scheme="http://posts.hufeifei.cn/tags/AIOps/"/>
    
    <category term="Observability" scheme="http://posts.hufeifei.cn/tags/Observability/"/>
    
    <category term="OpenTelemetry" scheme="http://posts.hufeifei.cn/tags/OpenTelemetry/"/>
    
    <category term="eBPF" scheme="http://posts.hufeifei.cn/tags/eBPF/"/>
    
  </entry>
  
  <entry>
    <title>谁整合好DuckDB，谁赢得OLAP世界</title>
    <link href="http://posts.hufeifei.cn/db/duckdb/"/>
    <id>http://posts.hufeifei.cn/db/duckdb/</id>
    <published>2025-04-02T00:00:00.000Z</published>
    <updated>2026-01-01T06:47:29.860Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>在 《PostgreSQL正在吞噬世界中》 一文中，我曾经抛出过这个问题：<strong>谁会最终统一数据库世界？</strong>。我认为是 PostgreSQL 生态与各种各样的扩展插件 —— 而我的判断是，要想征服 OLAP 这个最大也是最显著的数据库独立王国，这个分析扩展一定与 <strong>DuckDB</strong> 有关。</p><p>PostgreSQL 一直以来都是我最喜欢的数据库，然而我第二喜欢的数据库在这两年中从 Redis 变为了 DuckDB。DuckDB 是一个非常小巧且强大的 <strong>嵌入式</strong> OLAP 分析数据库，在分析性能、易用性上都做到了极致水平，并且在所有数据库中有着仅次于 PostgreSQL 的可扩展性。</p><p><img src="https://pigsty.cc/blog/pg/pg-duckdb/extensibility.webp" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="extensibility.webp"></p><p>正如两年前开展的向量数据库扩展插件赛马一样，当下 PG 生态进行的扩展竞赛已经开始围绕 DuckDB 进行 —— “<em><strong>谁更好地在PG中整合DuckDB，谁就赢得OLAP世界的未来</strong></em>”。尽管已经有许多玩家在摩拳擦掌，但 DuckDB 官方亲自下场，毫无疑问宣告着这场竞争即将进入白热化。</p><hr><h2 id="DuckDB：OLAP的新兴挑战者"><a href="#DuckDB：OLAP的新兴挑战者" class="headerlink" title="DuckDB：OLAP的新兴挑战者"></a>DuckDB：OLAP的新兴挑战者</h2><p>DuckDB 是由 Mark Raasveldt 和 Hannes Mühleisen 两位数据库研究员在荷兰阿姆斯特丹的国家数学与计算机科学研究所（Centrum Wiskunde &amp; Informatica, CWI）开发的。CWI 不仅仅是一个研究机构，可以说是分析型数据库领域发展背后的幕后推手与功臣，是列式存储引擎与向量化查询执行的先驱。现在你能看到的各种分析数据库产品 ClickHouse，Snowflake，Databricks 背后，都有 CWI 的影子。顺便一提，Python之父龟叔也是在 CWI 时创建 Python 语言的。</p><p>然而，现在这些分析领域的先锋们自己亲自下场来做分析数据库了，他们选择了一个非常好的时机与生态位切入，搞出了 <strong>DuckDB</strong> 来。</p><p>DuckDB 的起源来自作者们对数据库用户痛点的观察：数据科学家主要使用像 Python 与 Pandas 这样的工具，不怎么熟悉经典的数据库。经常被如何连接，身份认证，数据导入导出这些工作搞的一头雾水。那么有没有办法做一个简单易用的嵌入式分析数据库给他们用呢？ —— 就像 SQLite 一样。</p><p>DuckDB 整个数据库软件源代码就是一个头文件一个c++文件，编译出来就是一个独立二进制，数据库本身也就一个简单的文件。使用兼容 PostgreSQL 的解析器与语法，简单到几乎没有任何上手门槛。尽管 DuckDB 看上去非常简单，但它最了不起的一点在于 —— <strong>简约而不简单，分析性能也是绝冠群雄</strong>。例如，在 ClickHouse 自己的主场 ClickBench 上，有着能够吊打东道主 ClickHouse 的表现。</p><p>另外非常值得称道的一点是，因为作者的薪水由政府税收支付，他们认为将自己的工作成果免费提供给任何人是他们对社会的责任。因此，DuckDB 是在非常宽松的 MIT 许可证下发布的。</p><hr><p>我认为 DuckDB 的崛起是必然的：一个有着顶尖性能表现，而使用门槛低到地板，还开源免费的数据库，想不火都难。在 StackOverflow 2023 年的开发者调研中，DuckDB 以 0.61% 的使用率第一次进入“最流行的数据库” 榜单中（第29名，倒数第四），结果仅仅一年过去，在 2024 年度开发者调研中，它就实现了 2.3 倍的流行度增长，前进到（1.4%）与 ClickHouse （1.7%）非常接近的流行度。</p><p><img src="https://pigsty.cc/blog/pg/pg-duckdb/used-prof-2023-2024.png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="used-prof-2023-2024.png"></p><p>同时，DuckDB 也在用户中攒下的极好的口碑，在开发者中受欢迎与喜爱的程度（69.2%）在主要数据库中仅次于 PostgreSQL （74.5%）。如果我们观察 DB-Engine 的热度趋势，更是不难看出它在 2022 年中开始一飞冲天的狂飙增长态势 —— 虽然没法跟 PostgreSQL 这种数据库比，但目前甚至已经超过了所有 NewSQL 数据库产品的热度分了。</p><p><img src="https://pigsty.cc/blog/pg/pg-duckdb/db-engine-duckdb.png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="db-engine-duckdb.png"></p><hr><h2 id="DuckDB的短板与其中的机遇"><a href="#DuckDB的短板与其中的机遇" class="headerlink" title="DuckDB的短板与其中的机遇"></a>DuckDB的短板与其中的机遇</h2><p>DuckDB 是一个可以独立使用的数据库，但更是一个嵌入式的分析数据库。嵌入式有好处也有坏处 —— DuckDB 尽管有着最强分析性能，但它最大的短板就在于薄弱的数据管理能力 —— 也就是数据科学家们不喜欢的那些东西 —— ACID，并发访问，访问控制，数据持久性，高可用，数据库导入导出，等等等，而这恰好是经典数据库的长处，也是企业级分析系统的核心痛点之一。</p><p>可以预期的是，市面上一定会很快出现一系列的 DuckDB 套壳产品来解决这里的摩擦与GAP。正好比当年 Facebook 开源了 KV 数据库 RocksDB ，无数 “新的数据库” 给 RocksDB 套了一层 SQL 解析器，就号称自己是新一代数据库去圈钱了 —— Yet another SQL Sidecar for RocksDB。 向量检索库 hnswlib 开源后，无数 “专用向量数据库” 给它套了薄薄一层皮，就去市场上圈钱了。然后搜索引擎 Lucene 和下一代替代 Tantivy 开源之后，又有无数“全文检索数据库”来给他们套壳贩卖。</p><p><img src="https://pigsty.cc/blog/pg/pg-duckdb/ecosystem.jpg" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="ecosystem.jpg"></p><p>实际上，这样的事情已经在 PostgreSQL 生态中发生了。在其他数据库产品和公司还没来得及反应之前，PG 生态已经有五个玩家下场赛马了，包括 ParadeDB 的 <code>pg_lakehouse</code>，国内个人开发者李红艳编写的 <code>duckdb_fdw</code>，CrunchyData 的 <code>crunchy_bridge</code>， Hydra 出品的 <code>pg_quack</code>；以及目前 MotherDuck 原厂也跑过来做 PG 扩展了 —— <code>pg_duckdb</code>。</p><hr><h2 id="第二届PG扩展竞速比赛"><a href="#第二届PG扩展竞速比赛" class="headerlink" title="第二届PG扩展竞速比赛"></a>第二届PG扩展竞速比赛</h2><p>这不禁让我想起了过去一年中，PG生态里向量数据库扩展的例子。AI爆火之后，PG 生态里就涌现出了至少六款向量数据库扩展（ <code>pgvector</code>，<code>pgvector.rs</code>，<code>pg_embedding</code>，<code>latern</code>，<code>pase</code>，<code>pgvectorscale</code>），并在你追我赶的赛马中卷出了新高度。最后 <code>pgvector</code> 在以 AWS 为代表的厂商大力投入加持之下，在其他数据库比如 Oracle / MySQL / MariaDB 姗姗来迟的糊弄版本出来之前，就已经把整个专用向量数据库细分领域给摧毁荡平了。</p><p>那么，谁会成为 PG OLAP 生态的 PGVECTOR 呢？我个人的判断还是原厂吊打同人，尽管 <code>pg_duckdb</code> 才刚刚新鲜出炉，甚至连 v0.0.1 版本都还没发布。但从其架构设计上，已经不难判断，它大概率会是最后的赢家。实际上这个生态赛道才刚刚展开，就立即有收敛的趋势了：</p><p>原本 Fork Citus 列存扩展的 Hydra （YC W22），在尝试构建 <code>pg_quack</code> 感受到 DuckDB 震撼后，立刻抛弃原有的引擎和 MotherDuck 合作，搞出来了 <code>pg_duckdb</code>。融合了 PG 生态经验的 Hydra 与 DuckDB 原厂弄的扩展，可以直接在数据库内丝滑地读取 PG 数据表，并使用 DuckDB 引擎进行计算，并且可以直接从文件系统/S3 上读取 Parquet / IceBerg 格式的文件，实现湖仓的效果。</p><p><img src="https://pigsty.cc/blog/pg/pg-duckdb/hydra-pg-quack.png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="hydra-pg-quack.png"></p><p>同样是 YC 投的初创数据库公司 ParadeDB （YC S23），在尝试了自己用 Rust 构建类似的分析产品 pg_analytics 并取得了不俗的成绩之后，也选择改换了路线，基于 DuckDB 打造 pg_lakehouse 扩展。当然，创始人 Phillipe 在 pg_duckdb 刚刚官宣之后也立刻宣布投降，准备在 <code>pg_duckdb</code> 的基础上进行进一步的开发而不是当竞品。</p><p><img src="https://pigsty.cc/blog/pg/pg-duckdb/paradedb.png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="paradedb.png"></p><p>国内个人开发者李红艳开发的 <a href="/ext/olap/duckdb_fdw/"><code>duckdb_fdw</code></a> 是另一条另辟蹊径的道路。不是直接利用 PG的存储引擎接口，而是直接用外部数据源包装器（FDW）的基础设施，将 PG 和 DuckDB 对接到了一起。这引发了官方亲自下场吐槽，将其作为反例批判，也许是 MotherDuck 亲自下场的一个动机：“我还在构思伟大蓝图，如何融合PG与Duck的力量，你小子动作也太快了，得给你一点官方震撼看看”。</p><p>至于 CrunchyData 搞的 <code>cunchy_bridge</code> ，或者其他数据库公司搞的闭源套壳扩展，我个人感觉是很难有出息的。</p><hr><p>当然，作为 PostgreSQL 发行版 Pigsty 的作者，我的策略始终是 —— 你们赛你们的马，反正所有这些扩展我都会打包并分发给用户，让用户自己选择与决策。就好比当初向量数据库崛起的时候一样，我就把 <code>pgvector</code> ，<code>pg_embedding</code>，<code>pase</code>，<code>pg_sparse</code> 等等这几个最有前途的扩展打包分发出去。不管谁是最后的胜利者，反正 PG 和 Pigsty 都是摘桃子的赢家。</p><p>天下武功，唯快不破，在 Pigsty v3 中已经实装了这三个最有前途的扩展插件： <a href="/ext/olap/pg_duckdb/"><code>pg_duckdb</code></a>，<a href="/ext/olap/pg_lakehouse"><code>pg_lakehouse</code></a>，以及 <a href="/ext/olap/duckdb_fdw/"><code>duckdb_fdw</code></a>，当然还有 <code>duckdb</code> 二进制本体，开箱即用，让用户体验一个 PostgreSQL 包打天下，OLTP / OLAP 双冠全能王合体，真正 HTAP 的快乐。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;在 《PostgreSQL正在吞噬世界中》 一文中，我曾经抛出过这个问题：&lt;</summary>
      
    
    
    
    <category term="数据库" scheme="http://posts.hufeifei.cn/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    
    <category term="DuckDB" scheme="http://posts.hufeifei.cn/tags/DuckDB/"/>
    
    <category term="Postgres" scheme="http://posts.hufeifei.cn/tags/Postgres/"/>
    
    <category term="OLAP" scheme="http://posts.hufeifei.cn/tags/OLAP/"/>
    
  </entry>
  
  <entry>
    <title>搜索型数据库的技术发展历程与趋势前瞻</title>
    <link href="http://posts.hufeifei.cn/db/search-db-history/"/>
    <id>http://posts.hufeifei.cn/db/search-db-history/</id>
    <published>2025-03-05T00:00:00.000Z</published>
    <updated>2026-01-01T06:47:29.860Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>随着数字科技的飞速发展和信息量的爆炸性增长，搜索引擎已成为我们获取信息的首选途径之一，典型的代表厂商如 Google。然而，随着用户需求的不断演变，传统的搜索技术已经无法满足人们对信息的实时性、个性化和多样性的需求。</p><p>在企业内部，这种需求更加显著。随着企业数字化转型的持续深化，非结构化数据正日益成为各类组织数据增长的主要来源，也是数据体系中至关重要的组成部分，蕴含着巨大的价值。如何高效地存储和利用非结构化数据的重要性也日益凸显。企业需要更高效地管理和检索内部的海量数据，以支持业务决策和运营需求。</p><p><img src="https://static001.geekbang.org/infoq/80/8020d531bbd458efaab014e23ed1407d.png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="结构化数据与非结构化数据"></p><p>据 IDC 数据预计，到 2025 年，80%的数据将是非结构化数据；而根据 Gartner 的数据显示，从 2019 年到 2024 年，非结构化数据容量预计将增加两倍。然而，目前非结构化数据面临着表现形式多样、管理复杂性高、价值挖掘难度大等诸多挑战。传统的数据库系统往往无法满足企业对实时性和多样性的搜索需求，为了解决这些挑战，以自动分词、倒排索引、相关度计算、向量检索引擎等技术为核心构建的搜索型数据库应运而生。这些数据库自上世纪 90 年代诞生以来不断发展演进，正在成为数据库领域中不可或缺的一个重要分支。</p><h2 id="什么是搜索型数据库？"><a href="#什么是搜索型数据库？" class="headerlink" title="什么是搜索型数据库？"></a>什么是搜索型数据库？</h2><p>搜索型数据库早期又称全文数据库，或者企业搜索引擎，是一种专门用于存储和管理大规模文本数据，并支持高效的文本搜索和信息检索的数据库系统，不过随着技术不断发展和应用场景日益丰富，目前搜索型数据库不仅仅可以处理长文本数据，也可以处理常见的数值、日期等结构化数据，IP、地理位置信息、图片、音视频等非结构化数据，搜索型数据库的应用范畴不断拓展，正在由支撑业务系统检索加速、IT 运维可观测性、聚合查询分析等向多场景、多模态数据搜索方向发展。</p><p><img src="https://static001.geekbang.org/infoq/07/077f577cc56904b6e77ec34d1533ac01.jpeg?x-oss-process=image/resize,p_80/auto-orient,1" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>典型的搜索数据库一般具有以下特点：</p><ol><li>灵活的索引能力：搜索数据库能够处理多种类型的数据，包括文本、图像、音频、视频等非结构化数据。它们采用自动分词、倒排索引等技术，能够高效地处理不同格式和类型的数据，提供灵活的搜索和检索功能。</li><li>高效的查询性能：搜索数据库具有高效的查询处理能力，能够快速索引和检索大规模的数据。借助优化的索引结构和查询算法，搜索数据库能够在短时间内准确地返回与查询相关的结果，提高用户的搜索效率，常用于解决关系型数据库的高并发检索需求。</li><li>支持复杂的搜索功能：搜索数据库提供多样化的搜索功能，包括全文检索、模糊搜索、精确搜索、范围搜索、向量搜索、地理信息检索等。用户可以根据不同的需求和场景，灵活地选择和组合不同的搜索功能，以获取符合期望的搜索结果。</li><li>高性能和可扩展性：搜索数据库具有高性能和可扩展性的特点，能够处理大规模数据和高并发访问。它们采用分布式架构和并行计算技术，实现了水平扩展，能够满足不断增长的数据量和用户访问量的需求。</li></ol><p>综上所述，搜索数据库具有处理非结构化数据、实时搜索和更新、多样化的搜索功能、个性化推荐和智能搜索、高性能和可扩展性、全面的搜索结果展示等特点，是处理大规模数据和提供高效搜索服务的重要工具。</p><h2 id="搜索型数据库的应用场景"><a href="#搜索型数据库的应用场景" class="headerlink" title="搜索型数据库的应用场景"></a>搜索型数据库的应用场景</h2><p><img src="https://static001.geekbang.org/infoq/3a/3a618a7dc440dcde243601bfa0e252f8.png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>搜索型数据库在各行各业都有广泛的应用，以下是一些典型的应用场景：</p><ul><li>零售和电商：在零售和电商行业，搜索型数据库被广泛应用于产品搜索和推荐系统中。通过搜索功能，顾客可以轻松查找所需商品，而个性化推荐系统则可以根据用户的搜索历史和行为习惯推荐相关的产品，提高购物体验和交易转化率。</li><li>医疗保健：在医疗保健行业，搜索型数据库被用于医学文献检索、疾病诊断和药物搜索等方面。医生和研究人员可以利用搜索功能找到相关的医学文献和研究成果，帮助诊断疾病和制定治疗方案。</li><li>金融服务：在金融服务行业，搜索型数据库被用于金融数据检索、市场分析和投资决策等方面。投资者可以通过搜索功能查找相关的金融数据和市场资讯，帮助他们做出更加准确的投资决策。</li><li>制造业：在制造业中，搜索型数据库被用于生产过程监控、质量控制和故障诊断等方面。工程师可以利用搜索功能查找相关的生产数据和技术资料，帮助他们解决生产中的问题和挑战。</li><li>媒体和娱乐：在媒体和娱乐行业，搜索型数据库被用于内容检索、版权管理和用户推荐等方面。用户可以通过搜索功能查找感兴趣的新闻、音乐和视频等内容，而个性化推荐系统则可以根据用户的搜索历史和偏好推荐相关的内容。</li><li>教育和培训：在教育和培训行业，搜索型数据库被用于学习资源检索、课程管理和学习分析等方面。学生和教师可以利用搜索功能查找相关的学习资源和课程内容，而学习分析系统则可以分析学生的搜索行为和学习表现，为教学提供参考和支持。</li><li>IT 运维可观测性：通过搜索型数据库，可以实时监控系统的运行状况、性能指标和日志数据，帮助运维团队及时发现和解决系统故障、性能问题和异常情况，确保系统的稳定运行。</li><li>安全监测和威胁检测：利用搜索型数据库对系统的安全日志进行审计和监控，监测用户的访问行为和系统操作，及时发现异常行为和安全事件。同时，搜索型数据库还可以与威胁情报数据集成，对内部日志数据进行关联分析，快速识别并应对各种安全威胁和攻击行为，保障系统和数据的安全。</li></ul><p>综上所述，搜索型数据库在各行各业都发挥着重要作用，数据规模从 GB 到 PB 不等，体现在生活中的方方面面，为用户提供了高效、准确和个性化的信息搜索和检索服务，推动了各行业的发展和进步。随着搜索技术的不断创新和发展，搜索型数据库在各行业中的应用将会越来越广泛，并持续为用户带来更加便捷和智能的搜索体验。</p><h2 id="搜索型数据库的发展历程"><a href="#搜索型数据库的发展历程" class="headerlink" title="搜索型数据库的发展历程"></a>搜索型数据库的发展历程</h2><p>搜索型数据库的发展历程可以概括如下四个阶段：</p><p><img src="https://static001.geekbang.org/infoq/25/257aa748b6a305a737e4272ff418833b.png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><ol><li>起步阶段（1990 年代）：搜索数据库的雏形开始于上世纪 90 年代，当时以全文检索为主要技术手段，最初用于文档检索和网络搜索。典型代表包括 AltaVista、Excite 等。</li><li>技术突破（2000 年代）：随着互联网的快速发展，搜索数据库开始应用于更多领域，如电子商务、社交网络等。Lucene、Sphinx 等开源搜索引擎的出现推动了搜索技术的进步。</li><li>商业化发展（2010 年代）：搜索数据库进入商业化阶段，以 Elasticsearch 等为代表的商业搜索引擎崭露头角。企业开始大规模应用搜索数据库来管理和检索大量数据。</li><li>智能化转型（2020 年代）：随着人工智能技术的发展，搜索数据库逐渐向智能化转型，开始引入机器学习、自然语言处理等技术，提供个性化推荐和智能搜索服务。同时，搜索数据库也在更多领域得到应用，如医疗保健、金融服务等。</li></ol><p>综上所述，搜索数据库经历了从起步阶段到技术突破、商业化发展再到智能化转型的发展历程，表明了其在信息检索领域的重要性和不断演进的趋势，不并断推动着搜索技术的进步和应用范围的扩展。随着人工智能技术的不断成熟，搜索数据库将会在智能化、个性化等方面取得更大的进步，为用户提供更加优质的搜索体验。</p><h2 id="搜索型数据库的发展情况"><a href="#搜索型数据库的发展情况" class="headerlink" title="搜索型数据库的发展情况"></a>搜索型数据库的发展情况</h2><p>搜索型数据库市场上已经有不少成熟的产品和厂商，但是总的来说，搜索型数据库的界限范围有点模糊，当然其他数据库也有同样的问题，有很多数据库既是文档数据库，又是多模态数据库，还是向量数据库等等，而常见的搜索型数据库主要诞生于：</p><ul><li>由搜索引擎内核库发展而来的搜索数据库，如 Elasticsearch</li><li>由其他数据库扩展而来的搜索数据库，如 Postgres Full-Text Search</li><li>从零开始整体设计的搜索数据库：如 INFINI Pizza</li></ul><p>通过流行的 DB-Engines 的搜索引擎排行榜，可以初探国外主流的搜索型数据库的流行趋势，如下图：</p><p><img src="https://static001.geekbang.org/infoq/88/887690074979dc4d0be30518a46a5e07.png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>可以看到 Elastic 公司的 Elasticsearch 还是依旧保持强悍，自从 Elasticsearch 十多年前掀翻了 Splunk 的桌子，硬生生的在日志领域杀出一条新路，随后大杀四方，碾压整个搜索行业，霸榜至今。Elastic 商业化增长稳健，2023 年收入超过 10 亿美金。</p><p><img src="https://static001.geekbang.org/infoq/f6/f66a3d2622d226cd9aa6a6107c764333.png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>OpenSearch 是由 AWS 发起的 Elasticsearch 开源分支，起因是由于 Elastic 针对云厂商采取的协议变更为 Elastic+SSPL，OpenSearch 基于 Apache 2.0 协议的 Elasticsearch 7.10 版本衍生而来，目前也具备了一定的用户基础。</p><p><img src="https://static001.geekbang.org/infoq/99/99a39a199a1834876077e637ebc9914c.png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>Splunk 是一款用于搜索、监控和分析大规模机器生成的数据的软件平台，主要用于日志和安全分析领域，属于商业闭源产品。2023 年中被思科（Cisco） 以 230 亿美元现金收购，瞬间刷爆朋友圈。另外有意思的是，前四名除了 Splunk，底层都是 Lucene 内核。</p><p><img src="https://static001.geekbang.org/infoq/63/6368291a33ca1ba338929a753cd86ce0.png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>MarkLogic 成立于 2001 年，自我定位是一个 NoSQL 多模态数据库厂商，也是商业闭源软件，生态成熟但是系统过于复杂，学习曲线较陡， 2023 年初被 Progress Software 以 3.55 亿美元收购算是一个比较好的结局。</p><p><img src="https://static001.geekbang.org/infoq/53/53bd6c3307245f8b57c33adffcf2bbfc.png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>当然了，除了榜上的这些产品，还有很多优秀的挑战者正摩拳擦掌，跃跃欲试。如下面的这些项目：vespa、Rockset、Doris，Clickhouse、quickwit、Pinot、SingleStore、qdrant、milvus、algolia、meilisearch、typesense、Manticore Search 等等。这些项目不一定都是自己定位是搜索型数据库，有侧重在 AI 领域的，有侧重在实时分析领域的等等，可谓各有千秋，不过都具备一定的搜索和分析能力，不出意外，基本上每家都要号称吊打 Elasticsearch 一番。</p><h2 id="国内搜索型数据库的发展情况"><a href="#国内搜索型数据库的发展情况" class="headerlink" title="国内搜索型数据库的发展情况"></a>国内搜索型数据库的发展情况</h2><p>搜索型数据库已经成为企业事实上的重要基础设施，而国内搜索型数据库的发展近些年也是开始得到重视，2023 年初，由中国信通院云计算与大数据研究所牵头，依托中国通信标准化协会大数据技术标准推进委员会，联合拓尔思、极限科技、星环科技等 30 余家企业编制的《搜索型数据库技术要求》正式出炉，该标准已成为行业内搜索型数据库技术选型和产品开发的风向标，极限科技的 INFINI Easysearch 率先通过了该标准。</p><p>墨天轮社区也开辟了搜索型数据库的<a href="https://www.modb.pro/dbRank">排行榜</a>，共有 6 家企业的产品上榜：</p><p><img src="https://static001.geekbang.org/infoq/4a/4a99294b52d93a44dff03ccf19ec87a8.jpeg?x-oss-process=image/resize,p_80/auto-orient,1" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>国内搜索型数据库的市场还在起步阶段，厂商和可选的产品也还比较少，不过随着市场的成熟，相信未来将迎来一波高速的发展。</p><h2 id="搜索型数据库的趋势前瞻"><a href="#搜索型数据库的趋势前瞻" class="headerlink" title="搜索型数据库的趋势前瞻"></a>搜索型数据库的趋势前瞻</h2><p><img src="https://static001.geekbang.org/infoq/1f/1f4fdeb262f1bcd902ffd006b62a1a40.png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>技术在演变，场景在演变，数据也在演变，搜索数据库领域的发展也呈现出多个显著的趋势，这些趋势将进一步推动搜索技术的演进和应用范围的扩展。笔者观测到的主要的发展趋势包括以下方向供参考：</p><h3 id="1-趋势一：实时搜索与分析"><a href="#1-趋势一：实时搜索与分析" class="headerlink" title="1. 趋势一：实时搜索与分析"></a>1. 趋势一：实时搜索与分析</h3><ul><li>实时搜索是搜索数据库领域的一个重要发展趋势，业务应用都在朝实时方向演进，用户对信息的即时性需求不断增加，要求搜索结果能够及时反映最新的数据和内容。</li><li>实时搜索技术通过实时索引和实时更新机制，能够实现快速的数据检索和更新，提供与时俱进的搜索结果，满足用户对信息的即时性需求。</li><li>目前以 Lucene 为内核的搜索型数据库基本上都只能做到 NRT（近实时）搜索，并且频繁更新带来的挑战和资源的浪费比较高，如果能做到更高效的实时性，可以大大提升用户的搜索体验和实时决策能力。</li></ul><h3 id="2-趋势二：多模态混合搜索"><a href="#2-趋势二：多模态混合搜索" class="headerlink" title="2. 趋势二：多模态混合搜索"></a>2. 趋势二：多模态混合搜索</h3><ul><li>多模态搜索是指在搜索过程中同时考虑多种信息形式，如文本、图像、视频等，以提高搜索结果的准确性和全面性。</li><li>这种技术能够通过分析和理解多种信息形式之间的关联性，为用户提供更加全面、丰富的搜索结果，适用于需要综合不同媒体形式的搜索场景。</li><li>现实世界的数据越来越复杂化，非结构化数据的利用的场景也越来越多，多模态可以为业务提供更加灵活的分析和探索能力，混合搜索的能力非常具有吸引力。</li></ul><h3 id="3-趋势三：AI-智能语义搜索"><a href="#3-趋势三：AI-智能语义搜索" class="headerlink" title="3. 趋势三：AI 智能语义搜索"></a>3. 趋势三：AI 智能语义搜索</h3><ul><li>大模型、AI 智能搜索技术的探索可谓是一日千里，通过利用人工智能技术来实现搜索过程中的智能化、语义化和个性化，结合自然语言处理、机器学习等技术分析用户意图，提供更加智能、个性化的搜索服务。</li><li>随着大模型的兴起，搜索数据库开始采用像 RAG（Retriever-Reader for Generative Question Answering）这样的大型预训练模型来提升搜索的效果。RAG 模型结合了检索器和阅读器的功能，能够实现更加准确和全面的搜索结果，为用户提供更加智能和个性化的搜索服务。</li><li>搜索型数据库可谓是 AI 落地最好的是试验田，Elasticsearch 通过拥抱 AI 和大模型，目前股价又重回巅峰，可喜可贺。</li></ul><h3 id="4-趋势四：云原生、存算分离、Serverless"><a href="#4-趋势四：云原生、存算分离、Serverless" class="headerlink" title="4. 趋势四：云原生、存算分离、Serverless"></a>4. 趋势四：云原生、存算分离、Serverless</h3><ul><li>随着云计算技术的发展，搜索数据库正逐渐向云原生架构转变。云原生搜索数据库利用容器化、微服务架构等技术，实现了更高的灵活性、可扩展性和容错性，为企业提供了更加稳定和高效的搜索服务，并且成本更低，更加弹性。</li><li>存算分离是搜索数据库发展的另一重要趋势。通过将存储与计算分离，搜索数据库可以更好地适应数据存储和计算需求的变化，提高系统的性能和效率。存算分离技术使得搜索数据库能够实现更高的并发访问和更快的数据处理速度，为用户提供更加流畅和稳定的搜索体验。</li><li>Serverless 提供开箱即用的体验，成本更低，使用更加灵活，也是目前很多搜索服务提供商正在积极探索的方向。</li></ul><h3 id="5-趋势五：增强现实搜索"><a href="#5-趋势五：增强现实搜索" class="headerlink" title="5. 趋势五：增强现实搜索"></a>5. 趋势五：增强现实搜索</h3><ul><li>随着增强现实技术的发展，尤其是 Apple 发布的头戴式 Vision Pro，一部革命性的空间运算设备，将数位内容无缝融入实体世界，而搜索技术也将逐渐与增强现实相结合，为用户提供更加直观和沉浸式的搜索体验。增强现实搜索能够将搜索结果与现实世界相结合，结合 AI 技术为用户提供更加个性化和便捷的搜索服务，这是一个全新的领域，也意味着巨大的机会。</li></ul><h3 id="6-趋势六：现代硬件的高效利用"><a href="#6-趋势六：现代硬件的高效利用" class="headerlink" title="6. 趋势六：现代硬件的高效利用"></a>6. 趋势六：现代硬件的高效利用</h3><ul><li>现代硬件及软件运行环境已发生翻天覆地的变化， 片上计算，边缘计算，FPGA，DPU，GPU，一台设备几百核上 TB 内存已经成为现实，可运行之上的软件却还是停留在几十年前的架构。 如 Elasticsearch 其核心 Lucene（及类似实现） 是在 1997 建立的，距今已有 27 年了，虽然也在与时俱进，但是部分架构和设计理念已不具备先进性。</li><li>在现代的硬件上采用更先进的算法，更新的数据结构、更新的设计理论，利用最新的 CPU 指令集，向量化，批处理，充分发挥多核、大内存和 SSD 的优势，从而达到更高的效率，更低的成本，去解决之前不可能实现的问题，大有可为，也是下一代引擎需要关注的方向。</li></ul><p><img src="https://static001.geekbang.org/infoq/42/4278da22c7d3fe428eaae1ea60122897.jpeg?x-oss-process=image/resize,p_80/auto-orient,1" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>随着各类数据库功能的边界越来越模糊，应用场景高度交叉重叠，市场竞争也变得白热化，不过笔者认为垂直领域的搜索型数据库机会还是很大，而想做大而全的数据库产品已经没有太多的市场生存空间，一定要在垂直领域有特别专注的地方，我们 INFINI Labs 正在基于 Rust 研发的下一代搜索引擎 INFINI Pizza，就侧重于面向终端用户场景，解决海量数据更新情况下，同时满足高并发和低延迟的核心业务实时检索需求。</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>综上所述，搜索数据库领域正处于快速发展的阶段。随着互联网数据量的不断增长和用户需求的不断变化，搜索数据库技术将不断创新和进步，以满足用户对信息获取的更加即时、个性化和多样化的需求。未来，随着人工智能技术的进一步发展和应用，搜索数据库将会变得更加智能化、普及化和多样化，为用户提供更加高效、准确和个性化的搜索服务，推动互联网信息的更加便捷获取和利用。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;h2 id=&quot;概述&quot;&gt;&lt;a href=&quot;#概述&quot; class=&quot;headerli</summary>
      
    
    
    
    <category term="数据库" scheme="http://posts.hufeifei.cn/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    
    <category term="DB" scheme="http://posts.hufeifei.cn/tags/DB/"/>
    
    <category term="VectorSearch" scheme="http://posts.hufeifei.cn/tags/VectorSearch/"/>
    
  </entry>
  
  <entry>
    <title>黄东旭：2025 数据库技术展望</title>
    <link href="http://posts.hufeifei.cn/db/2025-database-future/"/>
    <id>http://posts.hufeifei.cn/db/2025-database-future/</id>
    <published>2025-03-02T00:00:00.000Z</published>
    <updated>2026-01-01T06:47:29.852Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>又到了一年一度的数据库行业总结的时间，2024 年其实并不算数据库技术的大年， 倒反而是 AI + 数据相关的应用开始蓬勃发展，但数据库内核技术也并不是没有进步，在对云基础设施的使用已经开始变成行业的共识，下面就集中写一下我今年的一些观察。</p><p><img src="https://static001.geekbang.org/infoq/fe/fe7cc2042ba14bb7f77030eaa855e027.webp?x-oss-process=image/resize,p_80/format,png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="1"></p><p>当然了，这两年 IT 世界最大的变化一定是 GenAI 带来的。</p><p>距离 ChatGPT 惊艳的发布已经过去 2 年，现在开发者社区对于 GenAI 的态度大概分两派：</p><ul><li><p>一派是 Scaling Law 的忠实信徒，在朝着更强的模型和 AGI 的目标前进。</p></li><li><p>另一派是实用主义者，接受现在 LLM 的现状，思考如何利用数据和 Agent 做一些有用的东西。</p></li></ul><p>我个人是属于后者，在这个方向上今年大热的方案自然是 RAG ，其实这个选择也很自然，目前的 LLM 有以下的限制：</p><ul><li><p>LLM 的 Context Window（ 上下文窗口 ） 有限，不可能回答每个问题都将完整的知识库放到 LLM 的 Context Window 中。</p></li><li><p>Transformer 的注意力机制本身有产生幻觉的可能性，需要一些机制对 LLM 的回答进行校验。</p></li></ul><p>去年和更早的时期，大家对于制作特定领域服务的 LLM App 通常的方案是对模型进行 Finetune（ 微调 ），但是 Finetune 的问题是，就像合金一样，你很难精确地控制配方，而且为了 Finetune 准备的数据到底多少才能精准影响某个问题的回答？这个并没有标准答案。并且 Finetune 通常也需要价值不菲的硬件和以天为单位的等待时间，这很不利于迭代。</p><p>不过万幸的是，过去这一年，基础模型（ 尤其是开源模型 ）的能力进步很快，例如 Llama3、Qwen、Mistral 等开源模型的能力已经基本超越 GPT-3.5，接近 GPT-4 的能力，另外开源模型的 Context Window 也越来越大，已经出现 32k 甚至更大的上下文空间，这些进步让 RAG 变成越来越合理的选择。</p><p>另一方面，RAG 也在发展，从最朴素的向量索引+LLM，到现在的 <a href="https://github.com/run-llama/llama_cloud_services/blob/main/examples/advanced_rag/dynamic_section_retrieval.ipynb">GraphRAG/LlamaParse</a>。其实核心的思想就是：对于给定的问题，能够在数据库中尽可能召回与这个问题最相关的上下文。</p><p>这个召回出来的上下文直接决定了 RAG 回答这个问题的质量，其实 LLM 在其中只是起到一个阅读理解的作用，关键还是数据的质量，以及召回的精度。这就意味着：RAG 事实上是一个更接近数据处理/数据库的生意，而不是一个 AI 的生意。</p><p>RAG 给数据基础设施带来哪些新的需求呢？有几个东西是必须的：</p><ul><li><p>向量索引，2025 年，主流的数据库都会支持向量索引类型，单独的向量数据库的市场增长会停滞。</p></li><li><p>一站式，多模态的数据库解决方案会越发流行。</p></li></ul><p>为什么？我从去年开始就一直强调：<strong>向量数据库是个很奇怪的东西</strong>。从数据库开发者的角度来看，向量只是一种索引类型，你真正需要的是根据向量索引检索你的数据，过去传统的数据库没有向量索引是因为没有足够需求，于是在 AI 的 workload 开始爆发，向量搜索变成强需求的时候，就有了“向量数据库”这个 workaround，但是随着这个需求被确认，主流数据库跟进的速度是很快的，毕竟更高的门槛是“做好一个数据库”。</p><p>一站式的数据方案会流行是因为：只有向量搜索并不够，随着业界对 RAG 的实践越来越深，大家发现朴素的只检索 Embedding 的召回率和准确率都不够，需要其它的检索方式进行补充。例如我们自己的例子：tidb.ai，同时使用了全文检索+Graph+向量，对这些检索结果再进行 Rerank 找到最相关的再送给 LLM。这里的挑战是多种数据技术栈带来的易用性和数据同步的挑战，如果能在一个数据库完成这些查询，为什么我要请求多个数据库？</p><p>说完 AI 对数据库的新需求，来说说数据库内核技术的一些趋势。</p><p><img src="https://static001.geekbang.org/infoq/40/4006e0ae3267d4d8d5e1b04553c87c47.webp?x-oss-process=image/resize,p_80/format,png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="2"></p><p><strong>S3 正在变成新的磁盘</strong></p><p>从 3 年前开始构建 TiDB Serverless 开始，我就开始惊艳于 S3 简洁和扩展性，并对 S3 在未来成为新一代数据库的基石深信不疑，如果说 3 年前“S3 is the new disk”是个猜想，那么现在看来已经成为了行业共识。今年 re:Invent 上 AWS CEO Matt Garman 分享了一些关于 S3 的数字：</p><p><img src="https://static001.geekbang.org/infoq/7a/7a515c210c046671d31eefc04051fb91.webp?x-oss-process=image/resize,p_80/format,png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>充分说明了 S3 已经不是仅仅是 AWS 的一条产品线，更是成为整个社会的重要基础设施，而且 Too big to fail，这对于基础设施来说是一个好消息，意味着可以被依赖。这两年新出现的数据库基本都是基于 S3 的：NeonDB、RockSet、Supabase、Databend、TiDB Serverless……S3 也是目前各种 Data lake（ 数据湖 ）实现的存储组件；很多基础设施的头部公司在开始收购各自领域中尝试用 S3 作为新基座的创业公司，例如 Confluent 收购 <a href="https://www.warpstream.com/">WarpStream</a> 就是一个很好的例子。</p><p>S3 用来构建数据库有几个好处：</p><ul><li>真正的弹性和 Serverless 的存储；</li><li>极低的成本；</li><li>可以线性扩展的吞吐（上限极其高）；</li><li>11 个 9 的数据可靠性，基本不用的担心数据丢失。</li></ul><p>今年 AWS S3 也有一些新的能力：</p><ul><li>S3 Metadata</li><li>Conditional writes (Compare and Swap)</li></ul><p>尤其是第二个，这意味着 S3 现在可以实现安全的并发写入，这对构建数据库来说是一个重要的能力。类似事情还有很多，这些能力让构建复杂的分布式数据库变得简单很多，毕竟分布式数据库大量的代码其实是在处理底层存储的状态，尤其是异常情况的状态（ 例如分布、数据复制、异常恢复等 ），将 S3 作为一个 always-on 且可扩展的基础服务，会让上层的逻辑简化很多。</p><p>而且新的架构解锁的还不仅仅是弹性和吞吐，我预期还会带来一些额外的收益，这里由于数据和篇幅问题就不展开，后续有更多数据后再开专题和大家分享。</p><p><strong>“数据库”正在变成”数据库服务”，而分布式数据库会是其重要的底座</strong></p><p>从 TiDB 自己在海内外客户的使用观察来看，最近这几年 TiDB Cloud 的使用量增速惊人，尤其在海外，除了一些银行或者金融机构出于合规的需求的暂时没有办法使用云以外，其他的用户更加接受云的使用，下面这张图是 TiDB Cloud 这两年的集群数的增长情况，相比两年前有了 10 倍的惊人增长。</p><p><img src="https://static001.geekbang.org/infoq/50/5054054ebf2645c9e0fceec8ac6c16ad.webp?x-oss-process=image/resize,p_80/format,png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="TiDB Cloud 在过去两年的使用量趋势"></p><p>数据库和数据库服务的区别？从用户角度看简单了很多，运维交给平台，自己可以专注在应用开发。</p><p>对于厂商来说，可能事情就没有那么简单了，构建一个云服务的复杂性不亚于做一个数据库内核。例如下面这张图其实是 TiDB Cloud 的一个系统架构的概览，很多做数据库内核不用考虑的工作在构建云平台的时候都需要考虑：比如计费，管理租户的管理，各种各样的元信息、可观测性，而且注意，所有上面这些都要考虑多租户。</p><p><img src="https://static001.geekbang.org/infoq/95/950cb33e8a1f601899445c7b2866e25c.webp?x-oss-process=image/resize,p_80/format,png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="TiDB Cloud 系统架构图"></p><p>虽然这类工作可能繁琐，但有云平台之后相比私有化部署也有明显的优势。首先，环境相对标准化，便于统一管理和维护。其次，我们能够基于这一标准化环境，构建多种自动化运维手段，例如在故障分析、故障处理以及可观测性等方面，通过自动化工具显著提高工作效率。与 OP 部署的客户环境下相比，在标准化环境下进行这些工作要更加高效，也更易于持续改进和规模化落地，我们也正在尝试使用 GenAI 来进一步提升效率，这会进一步提升利润的同时提供更好的用户体验。</p><p>还有一个挑战是，适配多个云平台。虽然我们已经尽量减少对基础云平台的依赖，但各家云服务商在 API 层面依旧存在诸多差异。此外，即使是看着相似的能力（ 例如对象存储和分布式块设备 ），在不同云平台和不同机型上的表现也并不完全相同，而且不同云厂商对于安全的能力也不尽相同（ 例如 VPC 的抽象或者密钥管理 ），也进一步增加了适配的难度。然而，我认为正是这类工作体现了独立数据库厂商的竞争力所在。</p><p>即便在云普及度相对有限的国内市场，我们依然在越来越大的大客户的环境中看到类似的需求：他们<strong>希望借助统一的数据库管理平台，尽可能隐藏数据库底层的复杂细节，从而让业务层专注于核心功能的开发与迭代</strong>。</p><p>除此之外，背后隐藏的另一个重要原因是成本，这几年国内轰轰烈烈的数据库国产替代已经进入收敛阶段，其实大家发现<strong>替换成国产数据库并不省钱，而且靠谱的国产数据库厂商大多选择了分布式的路线</strong>，但是如果按照以前『一个应用一个库』的方式进行替代是不现实的，因为很多应用本身不大，分配一个完整的分布式数据库集群肯定是过犹不及，当然你可以说也可以用国产的单机数据库，但事实上很多业务的数据库分配给它 1 个 CPU 都算多余，但是又不能停，即使通过容器做资源切分，积少成多也会造成大量的浪费，更不用说为每个单机数据库都需要配置主备高可用的成本。</p><p>在我看来，解决之道是：数据库多租户的实现不仅要关注物理层面的隔离，更需要在逻辑层面引入一套完善的 Flow Control（ 流控 ）机制。具体而言，让高优先级的用户、表、库的访问优先获得资源；但是同时对低优先级的应用可以贡献资源并设置上限以保证硬件资源冲突的时候优先保证高优先级应用。</p><p>这种思路与传统的“单一实例 - 单一应用”模式存在本质差异，后者通常会为不同应用分别分配数据库实例，以求最大化地避免竞争。然而，随着数据库规模和需求场景的不断扩张，单纯依靠物理隔离不仅资源利用率不高，而且极易造成分布碎片化。通过在逻辑层面引入流控策略，数据库就可以在多租户环境下实现更精细化的资源管理。高优先级任务不会被低优先级任务“抢占”，而低优先级任务则依旧能够在空闲资源下正常运转，从而在总体上提高资源利用率并保障关键业务的连续性与高可用。</p><p>这也是 TiDB 在 v7 引入的 Resource Control 机制的核心思想，也是 TiDB 强调的“真正的”多租户的技术基础，而且这个能力只有分布式数据库才能够提供，毕竟只有分布式数据库才能对大量的硬件资源有全局视角以及调度能力。</p><p>但是与做公共的 DBaaS 不一样，企业客户强调的不是租户的概念，更多的是多应用或者多集群的概念，更加强调多集群运维能力以及的与具体应用关联的数据库可观测性。在 2025 年，我们会发布新一代的可视化 TiDB Management Tool：TEM ，不同于已有的 Dashboard，这个工具面向的是企业级多集群管理的场景设计，目前已经在一些超大规模的 TiDB 客户中得到了初步的验证，也收获了不少好评，大家可以期待一下。</p><p><strong>不只是数据量，而是下一代的扩展性</strong></p><p>另外，这种集中化的趋势也对分布式数据库的扩展性和稳定性提出了更高的要求，我想起今年 TiDB 好几个大客户都提出了：TiDB 是否能支持创建百万甚至上千万表、库？当时听到这个需求我还是挺诧异的，后来仔细了解了一下，发现确实是个真需求：因为对于很多 SaaS 应用来说，最简单自然的开发方式就是针对每一个租户创建自己应用的表、库，只访问自己租户内部的数据，这样对于新来的客户，只需要简单的复制粘贴就可以了，这样做的好处不必赘述，但是对数据库就有了更多的挑战。</p><p>通常我们说的扩展性只关注数据量和吞吐，但是经过那么多客户真实场景的教育，我渐渐理解到当我们谈论扩展性时，实际会有很多维度，例如：能保持的连接数，支持的表库数量，后台任务（ 例如 DDL ）的扩展性，导入导出和 CDC 任务的速度和吞吐，多租户下的可观测性和运维性等等。这些都与扩展性相关但是又常常被数据库内核开发者忽略，但是真实世界的扩展性又是满足木桶效应：你的能力取决于你最短的那一块。于是从 TiDB v7 到 v8，我们在扩展性的这些不容易被人看见，但是又实实在在大家都会遇到的问题做了<a href="https://mp.weixin.qq.com/s?__biz=MzI3NDIxNTQyOQ==&mid=2247524055&idx=1&sn=ac892fa9f155263d568426eed970f6ae&scene=21#wechat_redirect">很多工作</a>，例如：支持超过百万的表库数量、支持 PB 以上的数据规模、针对多集群的管理工具等。</p><p>这些工作看起来不 Fancy，但我相信这是 TiDB 要支撑下一个量级规模必须打下的基础，相信 TiDB 老用户们一定有感觉：TiDB 越来越稳定了，其实这种感觉正是一个个这些不起眼的优化带来的质变。</p><p><img src="https://static001.geekbang.org/infoq/6d/6d75e3d82111e1b5c51502763bb311d5.webp?x-oss-process=image/resize,p_80/format,png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="3"></p><p>一句话来说，根据我的观察，今天的开发者是越来越「傻瓜」了。今年随着 Cursor 的普及，我感觉我又能重新写代码了！作为一个前系统工程师，我曾经对这种无脑上 Python 的趋势是嗤之以鼻的，但是真的动手做了几个项目以后，发现确实真香。尤其在一些对于性能要求不高的场景（ 毕竟 AI 一个个吐字的才是性能瓶颈 ），Python 完全够用，AI 生态的亲缘性加上完善的第三方库，整体的开发体验很流畅。另外当代 Python 的类型标注（ 虽然是假的 ）和越来越完善的第三方包管理工具，让开发中型应用也不至于代码腐化得太快。</p><p>而且对于数据库的访问也被各种 ORM 和开发框架层层封装，就小型应用而言，直接写 SQL 的场合其实已经不多，Pydantic 已经有点一统江湖的趋势。尤其是 AI 应用开发更关注的是数据本身，而非数据的存储和管理方式，大家希望直接通过简单的 API 访问所需的数据，而不是处理繁琐的数据库连接、查询优化或索引管理。例如，向量搜索 API 或 RAG 服务已经逐渐成为演变成开发者可以直接通过 RESTful API 或 GraphQL 获取上下文数据，而不需要理解底层的数据库逻辑，这个和上面的数据库云化的趋势是不谋而合的。</p><p>未来的数据库平台对于用户的数据可能在 SQL 之外，也会提供 API 形式的访问（ 公开或非公开的 ），其实现在已经开始出现一些标准化趋势，尤其是针对 AI 应用的数据需求：</p><ul><li><p>场景化 API：开发者可以通过一个 API 获取特定场景下的数据集。例如，某些平台提供“针对法律问题优化的检索 API”或“为医疗领域定制的诊断数据 API”。</p></li><li><p>智能化 API：Data API 不再仅仅是简单的查询接口，还可以提供实时增强、推荐等功能。例如，通过查询 API，可以直接返回优化后的上下文，甚至包含初步的模型推理结果。</p></li></ul><p>从开发者的角度来看，这种转变意味着：</p><ul><li>更低的上手成本：开发者可以直接调用服务完成复杂的数据检索，而无需学习复杂的数据库系统。</li><li>更快的开发周期：数据服务将大幅简化数据处理流程，开发者可以将更多精力集中在应用逻辑和模型优化上。</li><li>生态系统的协同效应：数据服务通过标准化的 API 和 SDK，可以轻松接入各种 AI 工具链（如 LLM、RAG 引擎、AutoML 框架）。</li></ul><p>其实今年在 TiDB Serverless 的产品线中，我们也提供了 Data API 的能力，让数据库访问通过 RESTful API 进行，也是顺应这个潮流做的特性。</p><p><img src="https://static001.geekbang.org/infoq/7f/7fcfb2d5ff6ffb09501208ea4b7f5fc8.webp?x-oss-process=image/resize,p_80/format,png" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer" alt="4"></p><p>写了那么多，还是有很多没有覆盖的，但是那么多工作也不可能和大家一一分享，但是也想代表所有的 TiDB 用户对这些工作背后的同事道一声感谢。回看一下，2025 年是我们创业的第十个年头，也是距离写下 TiDB 的第一行代码的第十年。我也从一个二十来岁的愣头青变成了一个中年大叔，要说这两年的变化，大约就是更有耐心也更沉默，开始习惯用做的事情和成绩来回应这个世界，同时对越来越多的人和事情发自内心的感激。</p><p>随着对于基础软件这个生意的理解越来越深，也越觉得这是一个长期的工作，我们也就才刚刚上路。就像一场马拉松，很多事情着急也没用，很多弯路必然要走，很多学费也必然要交，这就是我们这代人的责任。尤其在这个浮躁内卷的大环境下，如果用一个百米冲刺的姿态来看待竞争和产品，反而不是最优解。如果从更长期的视角来看，我始终相信：新技术打败旧技术，简单的产品打败复杂的产品，先进的商业模式打败陈旧的商业模式，美的打败丑的。最后分享一句我很喜欢的道德经中的话，作为本文的结语给大家共勉：</p><p>曲则全，枉则直，洼则盈，敝则新，少则得，多则惑。是以圣人抱一为天下式。不自见，故明；不自是，故彰，不自伐，故有功；不自矜，故长。夫唯不争，故天下莫能与之争。</p><p>新年快乐。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;又到了一年一度的数据库行业总结的时间，2024 年其实并不算数据库技术的大年</summary>
      
    
    
    
    <category term="数据库" scheme="http://posts.hufeifei.cn/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    
    <category term="TiDB" scheme="http://posts.hufeifei.cn/tags/TiDB/"/>
    
    <category term="VectorSearch" scheme="http://posts.hufeifei.cn/tags/VectorSearch/"/>
    
  </entry>
  
  <entry>
    <title>“国有企业”是如何诞生的</title>
    <link href="http://posts.hufeifei.cn/economic/chinese-socialism/"/>
    <id>http://posts.hufeifei.cn/economic/chinese-socialism/</id>
    <published>2025-02-27T00:00:00.000Z</published>
    <updated>2026-01-01T06:47:29.860Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p>国有企业是那种“看上去像企业的政府”，而政府则是“看上去像政府的企业”，它们从各自的利益诉求出发，成为微观经济领域中的逐利集团。这种制度一旦形成，民营企业集群就被间夹其中，进退失措，成为被博弈的对象。这一中国式经济体制延续千年，迄今未变，而管仲，正是“始作俑者”。</p><p>在宏观管制的战略思想下，管仲最重要的制度创新是盐铁专营。它的影响绵延两千余年，迄今犹存，几乎成为中国式中央集权制度的经济保障。</p><p>齐桓公与管仲多次切磋富国之策，齐桓公建议对人口、房屋楼台、树木、六畜征税，管仲一一否定，在他看来，税收是有形的，直接向人民收取财物，自然会招致人民的不满。最好、最理想的办法是“取之于无形，使人不怒”。据此，管仲提出了“寓税于价”的办法–把税收隐藏在商品里，实行间接征收，使纳税者看不见、摸不着，在不知不觉中就纳了税，而且不至于造成心理上的抵抗。</p><p>在具体办法上，管仲给出了简单的七个字：“唯官山海为可耳。”——只要把山、海的资源垄断起来就可以了，山上出铁矿，海里产海盐，是为盐铁专卖制度。</p><p>在农耕时期，盐和铁是最为重要的两大支柱性产业，无一民众可以须臾离开。管仲对盐和铁的专卖收入做过举例说明。他说，万乘之国的人口约为千万，如按成人征人头税，应缴纳者约为一百万人，每人每月征三十钱，为三千万钱。如果进行盐的专卖，每升盐酌量提价出售，每月可能得到六千万钱，就可望得到一倍于征人头税的收入。而在表面上，政府确乎不曾征税，不致引起人民的“嚣号”反对。不仅在国内如此，还可运盐出口而获取重利，这等于煮沸取之不尽的海水就可以迫使天下人向齐国纳税，即“煮沸水以籍天下”。</p><p>铁的专卖也是一样。管仲说，大凡一个农户，无论是从事耕作还是做女工，都需要针、刀、耒、耜、铫、锯、锥、凿等铁制工具，只要在一根针上加价一钱，三十根针就可收三十钱，即等于一人应缴的人头税了，由此类推，则全国收入总数亦不下于人头税的征收总额。表面上，国家并没征税，实际是“无不服籍者”。</p><p>管仲提倡盐铁专营，但不是主张政府亲自下场，创办国营盐场或国营铁厂–后世之人学管仲，认为专营就是国营，多入歧途。</p><p>比如盐业，管仲实行的是专卖政策，开放盐池让民间自由生产，然后由国家统一收购。由于控制了盐业的销售和产量，进而控制了价格，齐国的盐销售到别国去，售价可以抬高到成本价的四十倍，国家和商贾都得利颇丰。</p><p>在冶铁业上，管仲实行的是国有民营。他首先严厉地强调了国家对所有矿山资源的垄断，所谓“泽立三虞，山立三衡”，他出台法令宣布，只要一发现矿苗，就马上要由国家保护和封存起来，有敢于擅自开采者，左脚伸进去的，砍左脚，右脚伸进去的，砍右脚。之后，政府又控制了铁器的定价权，并对所生产出来的铁器进行统购统销。在这些前提之下，管仲开放冶铁作坊业，允许由民间商人自主经营，其增值部分，民商得七成，政府得三成，相当于征收30%的所得税。</p><p>由政府控制资源所有权，然后把经营权下放给民间商人，以一定比例分配利润，这就是后世非常流行的“资产国有、承包经营”的雏形。</p><p>盐铁专营的政策，对后世政权产生了重大且根本性的影响，在某种意义上，它让中国从此成为一个“独特的国家”。我们说“中国特色”，无此为过。</p><p>在西方的经济理论中，国家财政收入的主要来源，甚至唯一的来源是税赋，在这一点上，无论是社会主义经济学家或资本主义的自由经济学派都无分歧。卡尔·马克思就曾言，“赋税是政府机器的经济基础，而不是其他任何东西”，“国家存在的经济体现就是捐税”。即便在当代的制度经济学理论中，这一认识也未有改变，道格拉斯·诺斯认为，政府是“一种提供保护和公正而收取税金作为回报的组织，即我们雇政府建立和实施所有权”。</p><p>在西方的法治意识中，从来强调公民的纳税人角色，从14世纪开始，“无纳税人同意不得征税”这个理念在法国和英国似乎都牢固地确定了下来。人们经常提起这句话，违反它相当于实行暴政，恪守它相当于服从法律。特别是在美国，商店直接把商品价格与消费税分列出来，让你买一杯咖啡都意识到自己在纳税。可是在中国，统治者更愿意“寓税于价”。陈寅恪曾说中国的统治术中有“诈术”的成分在里面，管仲那句“取之于无形，使人不怒”便是最好的印证。</p><p>“管仲变法”之后，中国的政府收入由税赋收入和专营收入两项构成，后者的实现，正是通过控制战略性的、民生必需之物资，以垄断专卖的方式来达成的。在这种体制内，政府其实变成了一个有赢利任务的“经济组织”，从而也衍生出一种根深蒂固的治理思想，即国家必须控制“关系到国计民生的支柱性产业”，国有企业应当在这些产业中“处于主导地位”。</p><p>在这种经济环境中，国有企业是那种“看上去像企业的政府”，而政府则是“看上去像政府的企业”，它们从各自的利益诉求出发，成为微观经济领域中的逐利集团。这种制度一旦形成，民营企业集群就被间夹其中，进退失措，成为被博弈的对象。这一中国式经济体制延续千年，迄今未变，而管仲，正是“始作俑者”。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;国有企业是那种“看上去像企业的政府”，而政府则是“看上去像政府的企业”，它们</summary>
      
    
    
    
    <category term="经济" scheme="http://posts.hufeifei.cn/categories/%E7%BB%8F%E6%B5%8E/"/>
    
    
    <category term="经济" scheme="http://posts.hufeifei.cn/tags/%E7%BB%8F%E6%B5%8E/"/>
    
  </entry>
  
  <entry>
    <title>处理海量数据：列式存储综述（存储篇）</title>
    <link href="http://posts.hufeifei.cn/db/columnar-storage-overview-storage/"/>
    <id>http://posts.hufeifei.cn/db/columnar-storage-overview-storage/</id>
    <published>2024-10-02T00:00:00.000Z</published>
    <updated>2026-01-01T06:47:29.860Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p><img src="/images/2018/04/banner-warehouse.jpg"></p><p><strong>列式存储</strong>（Column-oriented Storage）并不是一项新技术，最早可以追溯到 1983 年的论文 Cantor。然而，受限于早期的硬件条件和使用场景，主流的事务型数据库（OLTP）大多采用行式存储，直到近几年分析型数据库（OLAP）的兴起，列式存储这一概念又变得流行。</p><p>总的来说，列式存储的优势一方面体现在存储上能节约空间、减少 IO，另一方面依靠列式数据结构做了计算上的优化。本文中着重介绍列式存储的数据组织方式，包括数据的布局、编码、压缩等。在下一篇文章中将介绍计算层以及 DBMS 整体架构设计。</p><h2 id="什么是列式存储"><a href="#什么是列式存储" class="headerlink" title="什么是列式存储"></a>什么是列式存储</h2><p>传统 OLTP 数据库通常采用行式存储。以下图为例，所有的列依次排列构成一行，以行为单位存储，再配合以 B+ 树或 SS-Table 作为索引，就能快速通过主键找到相应的行数据。</p><p><img src="/images/2018/04/row-oriented-example.png"></p><p>行式存储对于 OLTP 场景是很自然的：大多数操作都以实体（entity）为单位，即大多为<strong>增删改查一整行记录</strong>，显然把一行数据存在物理上相邻的位置是个很好的选择。</p><p>然而，对于 OLAP 场景，一个典型的查询需要遍历整个表，进行分组、排序、聚合等操作，这样一来按行存储的优势就不复存在了。更糟糕的是，分析型 SQL 常常不会用到所有的列，而仅仅对其中某些感兴趣的列做运算，那一行中那些无关的列也不得不参与扫描。</p><p>列式存储就是为这样的需求设计的。如下图所示，同一列的数据被一个接一个紧挨着存放在一起，表的每列构成一个长数组。</p><p><img src="/images/2018/04/column-oriented-example.png"></p><p>显然，列式存储对于 OLTP 不友好，一行数据的写入需要同时修改多个列。但对 OLAP 场景有着很大的优势：</p><ul><li>当查询语句只涉及部分列时，只需要扫描相关的列</li><li>每一列的数据都是相同类型的，彼此间相关性更大，对列数据压缩的效率较高</li></ul><blockquote><p><strong>BigTable（HBase）是列式存储吗？</strong></p><p>很多文章将 BigTable 归为列式存储。但严格地说，BigTable 并非列式存储，虽然论文中提到借鉴了 C-Store 等列式存储的某些设计，但 BigTable 本身按 Key-Value Pair 存储数据，和列式存储并无关系。</p><p><img src="/images/2018/04/bigtable-column-families-example.png"></p><p>有一点迷惑的是 BigTable 的列簇（column family）概念，列簇可以被指定给某个 locality group，决定了该列簇数据的物理位置，从而可以让同一主键的各个列簇分别存放在最优的物理节点上。由于 column family 内的数据通常具有相似性，对它做压缩要比对整个表压缩效果更好。</p><p>另外，值得强调的一点是：列式数据库可以是关系型、也可以是 NoSQL，这和是否是列式并无关系。本文中讨论的 C-Store 就采用了关系模型。</p></blockquote><h2 id="起源：DSM-分页模式"><a href="#起源：DSM-分页模式" class="headerlink" title="起源：DSM 分页模式"></a>起源：DSM 分页模式</h2><p>我们知道，由于机械磁盘受限于磁头寻址过程，读写通常都以一块（block）为单位，<strong>故在操作系统中被抽象为块设备</strong>，与流设备相对。这能帮助上层应用更好地管理储存空间、增加读写效率等。这一特性直接影响了数据库储存格式的设计：数据库的 Page 对应一个或几个物理扇区，让数据库的 Page 和扇区对齐，提升读写效率。</p><p>那如何将数据存放到页上呢？</p><p>大多数服务于在线查询的 DBMS 采用 NSM (N-ary Storage Model) 即按行存储的方式，将完整的行（即关系 relation）从 Header 开始依次存放。页的最后有一个索引，存放了页内各行的起始偏移量。由于每行长度不一定是固定的，索引可以帮助我们快速找到需要的行，而无需逐个扫描。</p><p>NSM 的缺点在于，如果每次查询只涉及很小的一部分列，那多余的列依然要占用掉宝贵的内存以及 CPU Cache，从而导致更多的 IO；为了避免这一问题，很多分析型数据库采用 DSM (Decomposition Storage Model) 即按列分页：将 relation 按列拆分成多个 sub-relation。类似的，页的尾部存放了一个索引。</p><p><img src="/images/2018/04/nsm-dsm-pax-comparation.png"></p><p>顺便一提，2001 年 Ailamaki 等人提出 PAX (Partition Attributes Cross) 格式，尝试将 DSM 的一些优点引入 NSM，将两者的优点相结合。具体来说，NSM 能更快速的取出一行记录，这是因为一行的数据相邻保存在同一页；DSM 能更好的利用 CPU Cache 以及使用更紧凑的压缩。PAX 的做法是将一个页划分成多个 minipage，minipage 内按列存储，而一页中的各个 minipage 能组合成完整的若干 relation。</p><p>如今，随着分布式文件系统的普及和磁盘性能的提高，<strong>很多先进的 DBMS 已经抛弃了按页存储的模式</strong>，但是其中的某些思想，例如<strong>数据分区、分区内索引、行列混合</strong>等，仍然处处可见于这些现代的系统中。</p><blockquote><p>分布式储存系统虽然不再有页的概念，但是仍然会将文件切割成分块进行储存，但分块的粒度要远远大于一般扇区的大小（如 HDFS 的 Block Size 一般是 128MB）。更大的读写粒度是为了适应网络 IO 更低的带宽以获得更大的吞吐量，但另一方面也牺牲了细粒度随机读写。</p></blockquote><h2 id="列数据的编码与压缩"><a href="#列数据的编码与压缩" class="headerlink" title="列数据的编码与压缩"></a>列数据的编码与压缩</h2><p>无论对于磁盘还是内存数据库，IO 相对于 CPU 通常都是系统的性能瓶颈，<strong>合理的压缩手段不仅能节省空间，也能减少 IO 提高读取性能</strong>。列式存储在数据编码和压缩上具有天然的优势。</p><p>以下介绍的是 C-Store 中的数据编码方式，具有一定的代表性。根据 1) 数据本身是否按顺序排列（self-order） 2) 数据有多少不同的取值（distinct values），分成以下 4 种情况讨论：</p><ul><li><p><strong>有序且 distict 值不多</strong>。使用一系列的三元组  对列数据编码，表示数值  从第  行出现，一共有  个（即  到  行）。例如：数值 4 出现在 12-18 行，则编码为  。</p></li><li><p><strong>无序且 distict 值不多</strong>。对于每个取值  构造一个二进制串  ，表示  所在位置的 bitmap。例如：如果一列的数据是  ，则编码为 <code>(0, 110000100)</code>、<code>(1, 001101001)</code> 和 <code>(2,000010010)</code>。由于 bitmap 是稀疏的，可以对其再进行行程编码。</p></li><li><p><strong>有序且 distict 值多</strong>。对于这种情况，把每个数值表示为前一个数值加上一个变化量（delta），当然第一个数值除外。例如，对于一列数据  ，可以表示为序列  。显然编码后的数据更容易被 densepack，且压缩比更高。</p></li><li><p><strong>无序且 distict 值多</strong>。对于这种情况没有很好的编码方式。</p></li></ul><p>编码之后，还可以对数据进行压缩。由于一列的数据本身具有相似性，即使不做特殊编码，也能取得相对较好的压缩效果。通常采用 Snappy 等支持流式处理、吞吐量高的压缩算法。</p><p>最后，编码和压缩不仅是节约空间的手段，更多时候也是组织数据的手段。在 PowerDrill、Dremel 等系统中，我们会看到<strong>很多编码本身也兼具了索引的功能</strong>，例如在扫描中跳过不需要的分区，甚至完全改表查询执行的方式。</p><h2 id="列式存储与分布式文件系统"><a href="#列式存储与分布式文件系统" class="headerlink" title="列式存储与分布式文件系统"></a>列式存储与分布式文件系统</h2><p>在现代的大数据架构中，GFS、HDFS 等分布式文件系统已经成为存放大规模数据集的主流方式。分布式文件系统相比单机上的磁盘，具备多副本高可用、容量大、成本低等诸多优势，但也带来了一些单机架构所没有的问题：</p><ol><li>读写均要经过网络，吞吐量可以追平甚至超过硬盘，但是<strong>延迟要比硬盘大得多</strong>，且受网络环境影响很大。</li><li>可以进行大吞吐量的顺序读写，但随机访问性能很差，大多<strong>不支持随机写入</strong>。为了抵消网络的 overhead，通常写入都以几十 MB 为单位。</li></ol><p>上述缺点对于重度依赖随机读写的 OLTP 场景来说是致命的。所以我们看到，很多定位于 OLAP 的列式存储选择放弃 OLTP 能力，从而能构建在分布式文件系统之上。</p><p>要想将分布式文件系统的性能发挥到极致，无非有几种方法：<strong>按块（分片）读取数据、流式读取、追加写入等</strong>。我们在后面会看到一些开源界流行的列式存储模型，将这些优化方法体现在存储格式的设计中。</p><h2 id="列式存储系统案例"><a href="#列式存储系统案例" class="headerlink" title="列式存储系统案例"></a>列式存储系统案例</h2><h3 id="C-Store-2005-Vertica"><a href="#C-Store-2005-Vertica" class="headerlink" title="C-Store (2005) / Vertica"></a>C-Store (2005) / Vertica</h3><p>大多数 DBMS 都是为写优化，而 C-Store 是第一个为读优化的 OLTP 数据库系统，虽然从今天的视角看它应当算作 HTAP 。在 ad-hoc 的分析型查询、ORM 的在线查询等场景中，大多数操作都是查询而非写入，在这些场景中列式存储能取得更好的性能。像主流的 DBMS 一样，C-Store 支持标准的关系型模型。</p><p>就像本文开头即提到 —— 列式存储不是新鲜事。C-Store 的主要贡献有以下几点：<strong>通过精心设计的 projection 同时实现列数据的多副本和多种索引方式；用读写分层的方式兼顾了（少量）写入的性能</strong>。此外，C-Store 可能是第一个现代的列式存储数据库实现，其的设计启发了无数后来的商业或开源数据库，就比如 <a href="https://www.vertica.com/">Vertica</a>。</p><h4 id="数据模型"><a href="#数据模型" class="headerlink" title="数据模型"></a>数据模型</h4><p>C-Store 是关系型数据库，它的逻辑表和其他数据库中的并没有什么不同。但是在 C-Store 内部，逻辑表被纵向拆分成 projections，每个 projection 可以包含一个或多个列，甚至可以包含来自其他逻辑表的列（构成索引）。当然，每个列至少会存在于一个 projections 上。</p><p>下图的例子中，EMP 表被存储为 3 个 projections，DEPT 被存储为 1 个 projection。每个 projection 按照各自的 sort key 排序，在图中用下划线表示 sort key。</p><p><img src="/images/2018/04/c-store-projections-example.png"></p><p>Projection 内是以列式存储的：里面的每个列分别用一个数据结构存放。为了避免列太长引起问题，也支持每个 projection 以 sort key 的值做横向切分。</p><p>查询时 C-Store 会先选择一组能覆盖结果中所有列的 projections 集合作为 covering set，然后进行 join 计算重构出原来的行。为了能高效地进行 projections 的 join（即按照另一个 key 重新排序），引入 join index 作为辅助，其中存储了 proj1 到 proj2 的下标映射关系。</p><p><strong>Projection 是有冗余性的</strong>，常常 1 个列会出现在多个 projection 中，但是它们的顺序也就是 sort key 并不相同，因此 <strong>C-Store 在查询时可以选用最优的一组 projections</strong>，使得查询执行的代价最小。</p><p>巧妙的是，<strong>C-Store 的 projection 冗余性还用来实现 K-safe 高可用</strong>（容忍最多 K 台机器故障），当部分节点宕机时，只要 C-Store 还能找到某个 covering set 就能执行查询，虽然不一定是最优的 covering set 组合。</p><blockquote><p>从另一个角度看，C-Store 的 Projection 可以看作是一种物化（materialized）的查询结果，即查询结果在查询执行前已经被预先计算好；并且由于每个列至少出现在一个 Projection 当中，没有必要再保存原来的逻辑表。</p><p>为任意查询预先计算好结果显然不现实，但是如果物化某些经常用到的中间视图，就能在预计算代价和查询代价之间获得一个平衡。C-Store 物化的正是以某个 sort key 排好序（甚至 JOIN 了其他表）的一组列数据，同时预计算的还有 join index。</p></blockquote><p>C-Store 对写入的处理将在下一篇文章中呈现。</p><h3 id="Apache-ORC"><a href="#Apache-ORC" class="headerlink" title="Apache ORC"></a>Apache ORC</h3><p>Apache ORC 最初是为支持 Hive 上的 OLAP 查询开发的一种文件格式，如今在 Hadoop 生态系统中有广泛的应用。ORC 支持各种格式的字段，包括常见的 int、string 等，也包括 struct、list、map 等组合字段；字段的 meta 信息就放在 ORC 文件的尾部（这被称为自描述的）。</p><h4 id="数据结构及索引"><a href="#数据结构及索引" class="headerlink" title="数据结构及索引"></a>数据结构及索引</h4><p><strong>为分区构造索引是一种常见的优化方案</strong>，ORC 的数据结构分成以下 3 个层级，在每个层级上都有索引信息来加速查询。</p><p><img src="/images/2018/04/orc-file-structure.png"></p><ul><li><strong>File Level</strong>：即一个 ORC 文件，Footer 中保存了数据的 meta 信息，还有文件数据的索引信息，例如各列数据的最大最小值（范围）、NULL 值分布、布隆过滤器等，这些信息可用来<strong>快速确定该文件是否包含要查询的数据</strong>。每个 ORC 文件中包含多个 Stripe。</li><li><strong>Stripe Level</strong> 对应原表的一个范围分区，里面包含该分区内各列的值。每个 Stripe 也有自己的一个索引放在 footer 里，和 file-level 索引类似。</li><li><strong>Row-Group Level</strong> ：一列中的每 10000 行数据构成一个 row-group，每个 row-group 拥有自己的 row-level 索引，信息同上。</li></ul><p>ORC 里的 Stripe 就像传统数据库的页，它是 ORC 文件批量读写的基本单位。这是由于分布式储存系统的读写延迟较大，一次 IO 操作只有批量读取一定量的数据才划算。这和按页读写磁盘的思路也有共通之处。</p><blockquote><p>像其他很多储存格式一样，ORC 选择将统计数据和 Metadata 放在 File 和 Stripe 的尾部而不是头部。</p><p>但 ORC 在 Stripe 的读写上还有一点优化，那就是把分区粒度小于 Stripe 的结构（如 Column 和 Row-Group）的索引统一抽取出来放到 Stripe 的头部。这是因为在批处理计算中一般是把整个 Stripe 读入批量处理的，将这些索引抽取出来可以减少在批处理场景下需要的 IO（批处理读取可以跳过这一部分）。</p></blockquote><h4 id="ACID-支持"><a href="#ACID-支持" class="headerlink" title="ACID 支持"></a>ACID 支持</h4><p>Apache ORC 提供有限的 ACID 事务支持。受限于分布式文件系统的特点，文件不能随机写，那如何把修改保存下来呢？</p><p>类似于 LSM-Tree 中的 MVCC 那样，writer 并不是直接修改数据，而是为每个事务生成一个 delta 文件，文件中的修改被叠加在原始数据之上。当 delta 文件越来越多时，通过 minor compaction 把连续多个 delta 文件合成一个；当 delta 变得很大时，再执行 major compaction 将 delta 和原始数据合并。</p><p><strong>这种保持基线数据不变、分层叠加 delta 数据的优化方式在列式存储系统中十分常见，是一种通用的解决思路</strong>。</p><blockquote><p>别忘了 ORC 的 delta 文件也是写入到分布式储存中的，因此每个 Delta 文件的内容不宜过短。这也解释了 ORC 文件虽然支持事务，但是主要是对批量写入的事务比较友好，不适合频繁且细小的写入事务的原因。</p></blockquote><h3 id="Dremel-2010-Apache-Parquet"><a href="#Dremel-2010-Apache-Parquet" class="headerlink" title="Dremel (2010) / Apache Parquet"></a>Dremel (2010) / Apache Parquet</h3><p>Dremel 是 Google 研发的用于大规模只读数据的查询系统，用于进行快速的 ad-hoc 查询，弥补 MapReduce 交互式查询能力的不足。为了避免对数据的二次拷贝，Dremel 的数据就放在原处，通常是 GFS 这样的分布式文件系统，为此需要设计一种通用的文件格式。</p><p>Dremel 的系统设计和大多 OLAP 的列式数据库并无太多创新点，但是其精巧的存储格式却变得流行起来，Apache Parquet 就是它的开源复刻版。注意 Parquet 和 ORC 一样都是一种存储格式，而非完整的系统。</p><h4 id="嵌套数据模型"><a href="#嵌套数据模型" class="headerlink" title="嵌套数据模型"></a>嵌套数据模型</h4><p>Google 内部大量使用 Protobuf 作为跨平台、跨语言的数据序列化格式，相比 JSON 要更紧凑并具有更强的表达能力。Protobuf 不仅允许用户定义必须（required）和可选（optinal）字段，<strong>还允许用户定义 repeated 字段，意味着该字段可以出现 0～N 次，类似变长数组</strong>。</p><p>Dremel 格式的设计目的就是按列来存储 Protobuf 的数据。由于 repeated 字段的存在，这要比按列存储关系型的数据困难一些。一般的思路可能是用终止符表示每个 repeat 结束，<strong>但是考虑到数据可能很稀疏</strong>，Dremel 引入了一种更为紧凑的格式。</p><p>作为例子，下图左半边展示了数据的 schema 和 2 个 Document 的实例，右半边是序列化之后的各个列。序列化之后的列多出了 R、D 两列，分别代表 Repetition Level 和 Definition Level，<strong>通过这两个值就能确保唯一地反序列化出原本的数据</strong>。</p><p><img src="/images/2018/04/google-dremel-example.png"></p><p><strong>Repetition Level</strong> 表示当前值在哪一个级别上重复。对于非 repeated 字段只要填上 trivial 值 0 即可；否则，只要这个字段可能出现重复（无论本身是 repeated 还是外层结构是 repeated），应当为 R 填上当前值在哪一层上 repeat。</p><p>举个例子说明：对于 Name.Language.Code 我们一共有三条非 NULL 的记录。</p><ol><li>第一个是 <code>en-us</code>，出现在第一个 Name 的第一个 Lanuage 的第一个 Code 里面。在此之前，这三个元素是没有重复过的，都是第一次出现。所以其 R=0</li><li>第二个是 <code>en</code>，出现在下一个 Language 里面。也就是说 Language 是重复的元素。Name.Language.Code 中 Language 排第二个，所以其 R=2</li><li>第三个是 <code>en-gb</code>，出现在下一个 Name 中，Name 是重复元素，排第一个，所以其 R=1</li></ol><p>注意到 <code>en-gb</code> 是属于第 3 个 Name 的而非第 2 个 Name，为了表达这个事实，我们在 <code>en</code> 和 <code>en-gb</code> 中间放了一个 R=1 的 NULL。</p><p><strong>Definition Level</strong> 是为了说明 NULL 被定义在哪一层，也就宣告那一层的 repeat 到此为止。对于非 NULL 字段只要填上 trivial 值，即数据本身所在的 level 即可。</p><p>同样举个例子，对于 Name.Language.Country 列</p><ol><li><code>us</code> 非 NULL 值填上 Country 字段的 level 即 D=3</li><li><code>NULL</code> 在 R1 内部，表示当前 Name 之内、后续所有 Language 都不含有 Country 字段。所以 D 为 2。</li><li><code>NULL</code> 在 R1 内部，表示当前 Document 之内、后续所有 Name 都不含有 Country 字段。所以 D 为 1。</li><li><code>gb</code> 非 NULL 值填上 Country 字段的 level 即 D=3</li><li><code>NULL</code> 在 R2 内部，表示后续所有 Document 都不含有 Country 字段。所以 D 为 0。</li></ol><p>可以证明，结合 R、D 两个数值一定能唯一构建出原始数据。<strong>为了高效编解码，Dremel 在执行时首先构建出状态机，之后利用状态机处理列数据</strong>。不仅如此，状态机还会结合查询需求和数据的 structure 直接跳过无关的数据。</p><blockquote><p>状态机实现可以说是 Dremel 论文的最大贡献。但是受限于篇幅，有兴趣的同学请参考原论文。</p></blockquote><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文介绍了列式存储的存储结构设计。抛开种种繁复的细节，我们看到，以下这些思想或设计是具有共性的。</p><ol><li><strong>跳过无关的数据</strong>。从行存到列存，就是消除了无关列的扫描；ORC 中通过三层索引信息，能快速跳过无关的数据分片。</li><li><strong>编码既是压缩，也是索引</strong>。Dremel 中用精巧的嵌套编码避免了大量 NULL 的出现；C-Store 对 distinct 值的编码同时也是对 distinct 值的索引；PowerDrill 则将字典编码用到了极致（见下一篇文章）。</li><li><strong>假设数据不可变</strong>。无论 C-Store、Dremel 还是 ORC，它们的编码和压缩方式都完全不考虑数据更新。如果一定要有更新，暂时写到别处、读时合并即可。</li><li><strong>数据分片</strong>。处理大规模数据，既要纵向切分也要横向切分，不必多说。</li></ol><p>下一篇文章中，将会结合 C-Store、MonetDB、Apache Kudu、PowerDrill 等现代列式数据库系统，侧重描述列式 DBMS 的整体架构设计以及独特的查询执行过程。<strong>敬请期待！</strong></p><h2 id="References"><a href="#References" class="headerlink" title="References"></a>References</h2><ol><li><a href="http://dbmsmusings.blogspot.jp/2010/03/distinguishing-two-major-types-of_29.html">Distinguishing Two Major Types of Column-Stores - Daniel Abadi</a></li><li><a href="https://docs.aws.amazon.com/redshift/latest/dg/c_columnar_storage_disk_mem_mgmnt.html">Columnar Storage - Amazon Redshift</a></li><li><a href="http://www.vldb.org/conf/2001/P169.pdf">Weaving Relations for Cache Performance - A Ailamaki, DJ DeWitt, MD Hill, M Skounakis</a></li><li><a href="http://glinden.blogspot.jp/2006/05/c-store-and-google-bigtable.html">C-Store and Google BigTable - Greg Linden</a></li><li><a href="http://db.csail.mit.edu/pubs/abadi-column-stores.pdf">The Design and Implementation of Modern Column-Oriented Database Systems - D Abadi, P Boncz, S Harizopoulos…</a></li><li><a href="http://people.csail.mit.edu/tdanford/6830papers/stonebraker-cstore.pdf">C-store: a column-oriented DBMS - M Stonebraker, DJ Abadi, A Batkin, X Chen…</a></li><li><a href="https://orc.apache.org/docs/">Apache ORC Docs</a></li><li><a href="https://research.google.com/pubs/archive/36632.pdf">Dremel: Interactive Analysis of Web-Scale Datasets - S Melnik, A Gubarev, JJ Long, G Romer…</a></li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;&lt;img src=&quot;/images/2018/04/banner-wareh</summary>
      
    
    
    
    <category term="数据库" scheme="http://posts.hufeifei.cn/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    
    <category term="Lakehouse" scheme="http://posts.hufeifei.cn/tags/Lakehouse/"/>
    
    <category term="Parquet" scheme="http://posts.hufeifei.cn/tags/Parquet/"/>
    
    <category term="Dremel" scheme="http://posts.hufeifei.cn/tags/Dremel/"/>
    
  </entry>
  
  <entry>
    <title>eBPF 和可观测性，不得不说的关系</title>
    <link href="http://posts.hufeifei.cn/backend/ebpf/"/>
    <id>http://posts.hufeifei.cn/backend/ebpf/</id>
    <published>2024-01-22T00:00:00.000Z</published>
    <updated>2026-01-01T06:47:29.852Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><p><img src="https://mmbiz.qpic.cn/mmbiz_png/1DjrTiaica04mINJHI0GE81AM8S2JTW609nSxfTNSuhFEpTDrc7elBw76ibDGrkKSj4Cq3XhdeTHR6bPVzhz95ticw/640" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>当下，eBPF 技术无疑是最火的技术之一，它大大提高了云原生网络、安全和可观测性的能力。其中，提起可观测性，我们首先又会想到 OpenTelemetry。那么 eBPF 是如何运作在可观测性中的呢？三者之间又有什么样的关系？让我们来看一看。</p><h2 id="1、什么是-eBPF"><a href="#1、什么是-eBPF" class="headerlink" title="1、什么是 eBPF"></a>1、什么是 eBPF</h2><p><strong>eBPF 是一种无需更改 Linux 内核代码，便能让程序在内核中运行的技术</strong>。开发者可以通过执行 eBPF 程序，给运行中的操作系统添加额外的能力。这催生了很多基于 eBPF 的项目，涵盖了广泛用例，包括云原生网络、安全和可观测性。例如：当下正流行的 Cilium ，是基于eBPF 实现数据转发的 CNI 网络插件；Falco 是 CNCF 开源孵化的运行时安全工具，专门为 Kubernetes 、Linux 和云原生构建；Pixie 使用 eBPF 自动收集遥测数据，也已开源应用，并进入了 CNCF 沙箱的可观测项目；同时一些服务网格产品也在探索使用 eBPF。eBPF 似乎改变了竞争环境。</p><p><img src="https://mmbiz.qpic.cn/mmbiz_png/1DjrTiaica04mINJHI0GE81AM8S2JTW609Egnw3rQjYkdPxbjKMmIPN7MI6CXkfQtMJ0rBwWXLQCZ0yNX38ibN4CA/640" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><h2 id="2、两者的关系"><a href="#2、两者的关系" class="headerlink" title="2、两者的关系"></a>2、两者的关系</h2><h3 id="为什么-eBPF-与可观测性相关？"><a href="#为什么-eBPF-与可观测性相关？" class="headerlink" title="为什么 eBPF 与可观测性相关？"></a>为什么 eBPF 与可观测性相关？</h3><p>传统意义上的观测性，是指在外部洞悉应用程序运行状况的能力。而 eBPF 是一种<strong>无需入侵应用代码，直接向操作系统内核层添加黑盒代码的革命性技术</strong>。这种查看内核中的操作，却不会 “干扰” 应用程序或内核本身的技术，从而使得 eBPF 获得可观测性强大能力。</p><p>因此使用 eBPF，即使不依赖操作系统公开的固定指标，我们也能直接从内核中收集和聚合自定义指标，并根据各种可能性来源，生成可见性事件。通过这种方式，我们将<strong>可见性扩展到内核</strong>，甚至可以<strong>通过仅收集所需的可见性数据，实现整体系统开销的降低</strong>。</p><h3 id="eBPF-和-OpenTelemetry-之间有什么关系？"><a href="#eBPF-和-OpenTelemetry-之间有什么关系？" class="headerlink" title="eBPF 和 OpenTelemetry 之间有什么关系？"></a>eBPF 和 OpenTelemetry 之间有什么关系？</h3><p>让我们从 OpenTelemetry 开始。</p><p>OpenTelemetry 是 CNCF 的开源项目。它为可观测性带来了新的标准规范，用于生成和采集日志、指标和链路追踪数据。OpenTelemetry 能检测分布式服务，从系统发生事件中收集遥测数据，帮助我们了解程序和软件的性能和行为。因此，与 eBPF 类似，OpenTelemetry 也能使我们获得可观测性，但两者之间并不可以完全替代。</p><p>我们可以从两个角度来对比：</p><ul><li>遥测数据的收集</li><li>遥测数据格式规范</li></ul><h3 id="遥测数据的收集：OpenTelemetry-和-eBPF-部分重叠"><a href="#遥测数据的收集：OpenTelemetry-和-eBPF-部分重叠" class="headerlink" title="遥测数据的收集：OpenTelemetry 和 eBPF 部分重叠"></a>遥测数据的收集：OpenTelemetry 和 eBPF 部分重叠</h3><p>OpenTelemetry 和 eBPF 都允许我们收集遥测数据 (OpenTelemetry，使用 OTEL SDK )。二者收集遥测数据的方法各有取舍：</p><ul><li><p>eBPF 由于内核的邻近性，在收集操作系统指标并生成深度分析和数据可见性时，功能尤为强大。</p></li><li><p>而 OpenTelemetry SDK 部署在应用代码中，它更易于开发与维护，同时在分布式系统中更方便实现端到端追踪。</p></li></ul><p>对于开发人员来说，他们更熟悉在应用程序中修改遥测代码，而 eBPF 是一项全新的技术，则需要更高的使用门槛与学习成本。</p><h3 id="遥测数据格式规范：OpenTelemetry-是标准"><a href="#遥测数据格式规范：OpenTelemetry-是标准" class="headerlink" title="遥测数据格式规范：OpenTelemetry 是标准"></a>遥测数据格式规范：OpenTelemetry 是标准</h3><p>尽管收集遥测数据的方法有很多种，但事实上 OpenTelemetry 已经成为了遥测数据格式的唯一标准。</p><p>选择采用 eBPF 来收集遥测数据时，也应使用 OpenTelemery 规范中的数据格式，让 eBPF 可以与 OpenTelemery 的组件无缝集成，从而将遥测数据的范围从应用层扩展到了内核层。</p><h3 id="OpenTelemetry-eBPF-的结论"><a href="#OpenTelemetry-eBPF-的结论" class="headerlink" title="OpenTelemetry + eBPF 的结论"></a>OpenTelemetry + eBPF 的结论</h3><p>OpenTelemetry 和 eBPF <strong>在可观测性领域有一些重叠的功能，他们适用于不同的应用场景</strong>。</p><p>比如，我们希望实现分布式跟踪时，建议使用 OpenTelemetry SDK，而 eBPF 在一些特殊场景下功能十分强大。</p><p>未来，eBPF 与 OpenTelemetry 标准的集成将不断加强，让用户从不同来源收集的数据，也能够在同一环境下处理。</p><p>当然，这两种工具提供的不仅仅是收集遥测数据。OpenTelemetry 是一组 SDK 和一系列遥测规范，而 eBPF 除了具备实现可观测性的能力，同时在安全、网络等领域也有无限的潜力。</p><h2 id="3、应用与展望"><a href="#3、应用与展望" class="headerlink" title="3、应用与展望"></a>3、应用与展望</h2><h3 id="过去：分散监控带来的痛点"><a href="#过去：分散监控带来的痛点" class="headerlink" title="过去：分散监控带来的痛点"></a>过去：分散监控带来的痛点</h3><p>一般的软件公司都有一个用于日志记录的平台，以及使用其他应用、平台或开源工具查看指标和链路追踪。</p><p>因此，我们不难想象到：</p><p>首先，工程师从监控告警平台收到警报，指出服务器 (包含所有微服务) 的 CPU 占用 90%，内存即将打满。</p><p>接着，工程师访问日志记录的平台，搜索该时间段内的事件，但不一定会找到相关的日志记录。</p><p>然后，可能会转到链路追踪的程序查看哪些请求花费的时间最长，前提是这些请求也占用了最多的 CPU。</p><p>于是很可能在这些应用程序之间来回切换，直到到达有问题的代码行或峰值波动的部分，然后修复它。</p><p>使用不同的三种工具查看指标、日志和链路追踪的过程，不仅繁琐缓慢，还很容易出错。同时，在这些应用程序之间跳转，也会消耗大量的精力和时间。由此可见，<strong>把这三者统一到一个可视化平台，并提供快速查看的能力十分重要</strong>。</p><h3 id="现在与未来：统一的可观测性"><a href="#现在与未来：统一的可观测性" class="headerlink" title="现在与未来：统一的可观测性"></a>现在与未来：统一的可观测性</h3><p>OpenTelemetry 已经为可观测性定义了规范，并且还在不断发展。</p><p>让我们回到之前的场景：</p><ol><li>我们收到一条警报，指出服务器的 CPU 使用率为 90%。当我们使用了一个统一的可观测性系统时，就可以看到与之前不采用统一系统的区别；</li><li>工程师去供应商的平台上查看指标，指标将通过带有示例的 OpenTelemetry SDK 发送，这些示例是时间序列中的突出显示值，包含一组键值对，这些键值对将存储相关信息，包括在此期间处于活动状态的trace ID。</li><li>单击 trace ID 可以让我们立即查看该 trace，或识别可能存在问题的 trace 之后再进行更深入的调查。这个系统甚至可以让我们按执行时间对这些 ID 进行排序，在 trace 的视图中，我们将能够看到与该 trace 关联的日志，因此我们可以在给定时间内看到相关的内容。</li><li>由于 OpenTelemtry 的规范能够让日志存储 trace ID，所以我们能够确切地知道每个日志的 trace ID，同时，我们也可以按 trace ID 查找日志。</li></ol><p>这样一来，工程师在<strong>特定时间查看到正确信息的几率就会呈指数级增长</strong>。在出现问题时关联日志、链路和指标的时间范围，只需单击一个按钮就可以在 OpenTelemetry 的日志、链路和指标之间跳转。仅此一点就可以为工程师节省无数的时间。</p><p>另外，如果在使用 OpenTelemetry 的基础上再加上 eBPF，还会得到更多的好处：</p><ul><li>eBPF 可以将处理数据包从用户空间移动到内核空间，提高网络速度和性能。</li><li>在与内核及其资源交互时，它专注于尽可能减少干扰，具有低侵入性。</li><li>与构建和维护内核模块相比，eBPF 创建挂载内核函数的代码工作量更少，也更方便。</li><li>eBPF 提供了一个单一、功能强大且易于访问的框架，用于统一的追踪进程，增强了可见性和安全性等。</li></ul><p>总结：OpenTelemetry 加上 eBPF 的技术等于强强联合，为可观测性打造了光明未来。</p><p>以上是我对于 eBPF 、OpenTelemetry 和可观测性的简短概述，当然还有更多值得深入研究的地方，让我们继续关注。</p><h2 id="参考内容："><a href="#参考内容：" class="headerlink" title="参考内容："></a>参考内容：</h2><ul><li><a href="https://ebpf.io/what-is-ebpf">https://ebpf.io/what-is-ebpf</a></li><li><a href="https://www.cncf.io/blog/2021/06/07/what-is-ebpf-and-why-does-it-matter-for-observability/">https://www.cncf.io/blog/2021/06/07/what-is-ebpf-and-why-does-it-matter-for-observability/</a></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;p&gt;&lt;img src=&quot;https://mmbiz.qpic.cn/mmbiz_</summary>
      
    
    
    
    <category term="后端" scheme="http://posts.hufeifei.cn/categories/%E5%90%8E%E7%AB%AF/"/>
    
    
    <category term="eBPF" scheme="http://posts.hufeifei.cn/tags/eBPF/"/>
    
    <category term="K8S" scheme="http://posts.hufeifei.cn/tags/K8S/"/>
    
  </entry>
  
  <entry>
    <title>OLAP入门问答-进阶篇</title>
    <link href="http://posts.hufeifei.cn/db/olap/"/>
    <id>http://posts.hufeifei.cn/db/olap/</id>
    <published>2023-01-23T00:00:00.000Z</published>
    <updated>2026-01-01T06:47:29.860Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><h3 id="续言"><a href="#续言" class="headerlink" title="续言"></a>续言</h3><p>前一篇文章从OLTP出发，通过对比引出OLAP，进一步介绍了数仓的基本概念，包括多维数据模型、数据立方体及其典型操作等。本篇再进一步，将介绍OLAP的类型及其代表产品，并分析主流开源OLAP产品的核心技术点。</p><p>未看过前一篇文章的读者，欢迎点击链接（<a href="https://zhuanlan.zhihu.com/p/144926830">温正湖：OLAP数仓入门问答-基础篇</a>）做进一步了解。</p><h3 id="有哪些类型的OLAP数仓？"><a href="#有哪些类型的OLAP数仓？" class="headerlink" title="有哪些类型的OLAP数仓？"></a>有哪些类型的OLAP数仓？</h3><h3 id="按数据量划分"><a href="#按数据量划分" class="headerlink" title="按数据量划分"></a>按数据量划分</h3><p>对一件事物或一个东西基于不同角度，可以进行多种分类方式。对数仓产品也一样。比如我们可以基于数据量来选择不同类型的数量，如下图所示：</p><p><img src="https://pic4.zhimg.com/80/v2-cde95a6639efa514bea331ac821bf387_1440w.webp" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>本系列文章主要关注的是数据量处于百万到百亿级别的偏实时的分析型数仓，Cloudera的Impala、Facebook的Presto和Pivotal的GreenPlum均属于这类系统；如果超过百亿级别数据量，那么一般选择离线数仓，如使用Hive或Spark等（SparkSQL3.0看起来性能提升很明显）；对于数据量很小的情况，虽然是分析类应用，也可以直接选择普通的关系型数据库，比如MySQL等，“杀鸡焉用牛刀”。</p><p>这些系统均属于网易杭研大数据和数据库团队的研究范畴，对各系统均有深入研究和优化，对外提供网易猛犸、网易有数和网易云RDS等服务。</p><h3 id="按建模类型划分"><a href="#按建模类型划分" class="headerlink" title="按建模类型划分"></a>按建模类型划分</h3><p>下面我们主要关注数据量中等的分析型数仓，聚焦OLAP系统。 根据维基百科对<a href="https://link.zhihu.com/?target=https://en.wikipedia.org/wiki/Online_analytical_processing%23Types">OLAP</a>的介绍，一般来说OLAP根据建模方式可分为MOLAP、ROLAP和HOLAP 3种类型，下面分别进行介绍并分析优缺点。</p><h3 id="MOLAP"><a href="#MOLAP" class="headerlink" title="MOLAP"></a>MOLAP</h3><p>这应该算是最传统的数仓了，1993年Edgar F. Codd提出OLAP概念时，指的就是MOLAP数仓，M即表示多维（Multidimensional）。大多数MOLAP产品均对原始数据进行预计算得到用户可能需要的所有结果，将其存储到优化过的多维数组存储中，可以认为这就是上一篇所提到的“数据立方体”。</p><p>由于所有可能结果均已计算出来并持久化存储，查询时无需进行复杂计算，且以数组形式可以进行高效的免索引数据访问，因此用户发起的查询均能够稳定地快速响应。这些结果集是高度结构化的，可以进行压缩/编码来减少存储占用空间。</p><p>但高性能并不是没有代价的。首先，MOLAP需要进行预计算，这会花去很多时间。如果每次写入增量数据后均要进行全量预计算，显然是低效率的，因此支持仅对增量数据进行迭代计算非常重要。其次，如果业务发生需求变更，需要进行预定模型之外新的查询操作，现有的MOLAP实例就无能为力了，只能重新进行建模和预计算。</p><p>因此，<strong>MOLAP适合业务需求比较固定，数据量较大的场景</strong>。在开源软件中，由eBay开发并贡献给Apache基金会的Kylin即属于这类OLAP引擎，支持在百亿规模的数据集上进行亚秒级查询。</p><p><img src="https://pic1.zhimg.com/80/v2-dcdbc7bafdd6f75bb32e7ebc7d06c7e4_1440w.webp" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>其架构图较直观得反映了基于cube的预计算模型（build），如下所示：</p><p><img src="https://pic3.zhimg.com/80/v2-48153557a5055d0e6ec3161d1fa2a99e_1440w.webp" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><h3 id="ROLAP"><a href="#ROLAP" class="headerlink" title="ROLAP"></a>ROLAP</h3><p>与MOLAP相反，ROLAP无需预计算，直接在构成多维数据模型的事实表和维度表上进行计算。R即表示关系型（Relational）。显然，这种方式相比MOLAP更具可扩展性，增量数据导入后，无需进行重新计算，用户有新的查询需求时只需写好正确的SQL语句既能完成获取所需的结果。</p><p>但ROLAP的不足也很明显，尤其是在数据体量巨大的场景下，用户提交SQL后，获取查询结果所需的时间无法准确预知，可能秒回，也可能需要花费数十分钟甚至数小时。本质上，ROLAP是把MOLAP预计算所需的时间分摊到了用户的每次查询上，肯定会影响用户的查询体验。</p><p>当然ROLAP的性能是否能够接受，取决于用户查询的SQL类型，数据规模以及用户对性能的预期。对于相对简单的SQL，比如TPCH中的Query响应时间较快。但如果是复杂SQL，比如TPC-DS中的数据分析和挖掘类的Query，可能需要数分钟。</p><p>相比MOLAP，ROLAP的使用门槛更低，在完成星型或雪花型模型的构建，创建对应schema的事实表和维度表并导入数据后，用户只需会写出符合需求的SQL，就可以得到想要的结果。相比创建“数据立方体”，显然更加方便。</p><p>有分析表明，虽然<strong>ROLAP的性能比如MOLAP，但由于其灵活性、扩展性，ROLAP的使用者是MOLAP的数倍</strong>。</p><blockquote><p>The survey shows that ROLAP tools have 7 times more users than MOLAP tools within each company  </p></blockquote><h3 id="HOLAP"><a href="#HOLAP" class="headerlink" title="HOLAP"></a>HOLAP</h3><p>MOLAP和ROLAP各有优缺点，而且是互斥的。如果能够将两者的优点进行互补，那么是个更好的选择。而HOLAP的出现就是这个目的，H表示混合型（Hybrid），这个想法很朴素直接。对于查询频繁而稳定但又耗时的那些SQL，通过预计算来提速；对于较快的查询、发生次数较少或新的查询需求，像ROLAP一样直接通过SQL操作事实表和维度表。</p><p>目前似乎没有开源的OLAP系统属于这个类型，一些大数据服务公司或互联网厂商，比如HULU有类似的产品。相信未来HOLAP可能会得到进一步发展，并获得更大规模的使用。</p><h3 id="HTAP"><a href="#HTAP" class="headerlink" title="HTAP"></a>HTAP</h3><p>从另一个维度看，HTAP也算是一种OLAP类型的系统，是ROLAP的一个扩展，具备了OLAP的能力。最新发展显示，有云厂商在HTAP的基础上做了某种妥协，<a href="https://link.zhihu.com/?target=https://mp.weixin.qq.com/s/oIlntVO9ZXimqphlO5ERrA">将T（transaction）弱化为S（Serving），朝HSAP方向演进</a>。关于HTAP/HSAP，本文不做进一步展开，可自主查询其他资料。</p><p>主流的OLAP数仓系统很多，包含上面所述的各种类型，下图是Gartner 2019 年发布的数据分析市场排名（<a href="https://link.zhihu.com/?target=https://www.infoq.cn/article/8kwSM0CaUVYW1EzeO7GT">数据来源</a>）</p><p><img src="https://pic2.zhimg.com/80/v2-ef0e0b493d7f8c7a6ec232d4ec6fa50d_1440w.webp" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>可以发现，传统的商业厂商和闭源的云服务厂商占据了绝大部分市场。大部分系统笔者只听过而没有研究过。作为屁股在互联网公司的数据库/数据仓库开发者，本文后续主要聚焦在基于Hadoop生态发展的开源OLAP系统（SQL on Hadoop）。</p><h3 id="有哪些常用的开源ROLAP产品？"><a href="#有哪些常用的开源ROLAP产品？" class="headerlink" title="有哪些常用的开源ROLAP产品？"></a>有哪些常用的开源ROLAP产品？</h3><p>目前生产环境使用较多的开源ROLAP主要可以分为2大类，一个是宽表模型，另一个是多表组合模型（就是前述的星型或雪花型）。</p><p><img src="https://pic1.zhimg.com/80/v2-36a8ca216e06f1012d530ae0e190d740_1440w.webp" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><h3 id="宽表模型"><a href="#宽表模型" class="headerlink" title="宽表模型"></a>宽表模型</h3><p>宽表模型能够提供比多表组合模型更好的查询性能，不足的是支持的SQL操作类型比较有限，比如对Join等复杂操作支持较弱或不支持。</p><p>目前该类OLAP系统包括<strong>Druid和ClickHouse</strong>等，两者各有优势，Druid支持更大的数据规模，具备一定的预聚合能力，通过倒排索引和位图索引进一步优化查询性能，在广告分析场景、监控报警等时序类应用均有广泛使用；ClickHouse部署架构简单，易用，保存明细数据，依托其向量化查询、减枝等优化能力，具备强劲的查询性能。两者均具备较高的数据实时性，在互联网企业均有广泛使用。</p><p>除了上面介绍的Druid和ClickHouse外，<strong>ElasticSearch和Solar</strong>也可以归为宽表模型。但其系统设计架构有较大不同，这两个一般称为搜索引擎，通过倒排索引，应用Scatter-Gather计算模型提高查询性能。对于搜索类的查询效果较好，但当数据量较大或进行扫描聚合类查询时，查询性能会有较大影响。</p><h3 id="多表组合模型"><a href="#多表组合模型" class="headerlink" title="多表组合模型"></a>多表组合模型</h3><p>采用星型或雪花型建模是最通用的一种ROLAP系统，常见的包括GreenPlum、Presto和Impala等，他们均基于MPP架构，采用该模型和架构的系统具有支持的数据量大、扩展性较好、灵活易用和支持的SQL类型多样等优点。</p><p>相比其他类型ROLAP和MOLAP，该类系统性能不具有优势，实时性较一般。通用系统往往比专用系统更难实现和进行优化，这是因为通用系统需要考虑的场景更多，支持的查询类型更丰富。而专用系统只需要针对所服务的某个特定场景进行优化即可，相对复杂度会有所降低。</p><p>对于ROLAP系统，尤其是星型或雪花型的系统，如果能够尽可能得缩短响应时间非常重要，这将是该系统的核心竞争力。这块内容，我们放在下一节着重进行介绍。</p><h3 id="有哪些黑科技用于优化ROLAP系统性能？"><a href="#有哪些黑科技用于优化ROLAP系统性能？" class="headerlink" title="有哪些黑科技用于优化ROLAP系统性能？"></a>有哪些黑科技用于优化ROLAP系统性能？</h3><p>目前生产环境使用的ROLAP系统，均实现了大部分的该领域性能优化技术，包括采用MPP架构、支持基于代价的查询优化（CBO）、向量化执行引擎、动态代码生成机制、存储空间和访问效率优化、其他cpu和内存相关的计算层优化等。下面逐一进行介绍。</p><h3 id="什么是MPP架构？"><a href="#什么是MPP架构？" class="headerlink" title="什么是MPP架构？"></a>什么是MPP架构？</h3><p>首先来聊聊系统架构，这是设计OLAP系统的第一次分野，目前生产环境中系统采用的架构包括基于传统的MapReduce架构加上SQL层组装的系统；主流的基于MPP的系统；其他非MPP系统等。</p><h3 id="MR架构及其局限"><a href="#MR架构及其局限" class="headerlink" title="MR架构及其局限"></a>MR架构及其局限</h3><p>在Hadoop生态下，最早在Hive上提供了基于MapReduce框架的SQL查询服务。</p><p><img src="https://pic4.zhimg.com/80/v2-63552f91763fc0ccbb77ffd81c655fdf_1440w.webp" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>但基于MR框架局限性明显，比如：</p><ul><li><p>每个MapReduce 操作都是相互独立的，Hadoop不知道接下来会有哪些MapReduce。</p></li><li><p>每一步的输出结果，都会持久化到硬盘或者HDFS 上。</p></li></ul><p>第一个问题导致无法进行跨MR操作间的优化，第二个问题导致MR间数据交互需要大量的IO操作。两个问题均对执行效率产生很大影响，性能较差。</p><h3 id="MPP优缺点分析"><a href="#MPP优缺点分析" class="headerlink" title="MPP优缺点分析"></a>MPP优缺点分析</h3><p>MPP是massively parallel processing的简称，即大规模并行计算框架。相比MR等架构，MPP查询速度快，通常在秒计甚至毫秒级以内就可以返回查询结果，这也是为何很多强调低延迟的系统，比如OLAP系统大多采用MPP架构的原因。</p><p>下面以Impala为例，简单介绍下MPP系统架构。</p><p><img src="https://pic2.zhimg.com/80/v2-3ee5e0d130f3b00031fd46741f4d6dd5_1440w.webp" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>上图即为Impala架构图，展示了Impala各个组件及一个查询的执行流程。</p><ol><li>用户通过Impala提供的impala-shell或beeline等客户端/UI工具向Impala节点下发查询SQL；接收该SQL的Impala节点即为Coordinator节点，该节点负责进行SQL解析；</li><li>首先产生基于单节点的执行计划；再对执行计划进行分布式处理，比如将Join、聚合（aggregation）等并行化到各Impala Executor节点上。执行计划被切分为多个Plan Fragment（PF），每个PF又由一到多个Operator组成；</li><li>接着，下发经过优化后的执行计划的PF到对应的Executor节点，多个执行节点并行处理任务，缩短整个任务所需时间；</li><li>执行节点扫描HDFS/Hbase等存储上的数据，并逐层进行处理，比如进行跨节点的数据shuffe，Join等操作；</li><li>执行节点完成任务并将输出结果统一发送到Coordinator节点；</li><li>Coordinator节点汇总各个执行节点数据，做最后处理，最终返回给用户想要的结果集。</li></ol><p>MPP架构之所以性能比MR好，原因包括：</p><ul><li><p>PF之间的数据交互（即中间处理结果）驻留在内存Buffer中不落盘（假设内存够大）；</p></li><li><p>Operator和PF间基于流水线处理，不需要等上一个Operator/PF都完成后才进行下一个处理。上下游之间的关系和数据交互式预先明确的。</p></li></ul><p>这样可以充分利用CPU资源，减少IO资源消耗。但事情往往是两面的，MPP并不完美，主要问题包括：</p><ul><li><p>中间结果不落盘，在正常情况下是利好，但在异常情况下就是利空，这意味着出现节点宕机等场景下，需要重新计算产生中间结果，拖慢任务完成时间；</p></li><li><p>扩展性没有MR等架构好，或者说随着MPP系统节点增多到一定规模，性能无法线性提升。有个原因是“木桶效应”，系统性能瓶颈取决于性能最差的那个节点。另一个原因是规模越大，出现节点宕机、坏盘等异常情况就会越频繁，故障率提高会导致SQL重试概率提升；</p></li></ul><p>基于上述分析，MPP比较适合执行时间不会太久的业务场景，比如数小时。因为时间越久，故障概率越大。</p><h3 id="其他非MPP架构"><a href="#其他非MPP架构" class="headerlink" title="其他非MPP架构"></a>其他非MPP架构</h3><p>基于MR系统局限性考虑，除了采用MPP架构外，Hive和Spark均使用不同方式进行了优化，包括Hive的Tez，SparkSQL基于DAG（Directed Acyclic Graph）等。</p><p>不同架构有不同优缺点，重要的是找到其适用的场景，并进行靠谱地优化，充分发挥其优势。</p><h3 id="什么是基于代价的查询优化？"><a href="#什么是基于代价的查询优化？" class="headerlink" title="什么是基于代价的查询优化？"></a>什么是基于代价的查询优化？</h3><p>有了适合的系统架构并不一定能够带来正向收益，“好马配好鞍”，执行计划的好坏对最终系统的性能也有着决定性作用。执行计划及其优化，就笔者的理解来说，其来源于关系型数据库领域。这又是一门大学问，这里仅简单介绍。</p><p>分布式架构使得执行计划能够进行跨节点的并行优化，通过任务粒度拆分、串行变并行等方式大大缩短执行时间。除此之外，还有2个更重要的优化方式，就是传统的基于规则优化以及更高级的基于代价优化。</p><h3 id="基于规则优化"><a href="#基于规则优化" class="headerlink" title="基于规则优化"></a>基于规则优化</h3><p>通俗来说，基于规则的优化（rule based optimization，RBO）指的是不需要额外的信息，通过用户下发的SQL语句进行的优化，主要通过改下SQL，比如SQL子句的前后执行顺序等。比较常见的优化包括谓语下推、字段过滤下推、常量折叠、索引选择、Join优化等等。</p><p><strong>谓语下推，即PredicatePushDown</strong>，最常见的就是where条件等，举MySQL为例，MySQL Server层在获取InnoDB表数据时，将Where条件下推到InnoDB存储引擎，InnoDB过滤where条件，仅返回符合条件的数据。在有数据分区场景下，谓语下推更有效；</p><p><strong>字段过滤下推，即ProjectionPushDown</strong>，比如某个SQL仅需返回表记录中某个列的值，那么在列存模式下，只需读取对应列的数据，在行存模式下，可以选择某个索引进行索引覆盖查询，这也是索引选择优化的一种场景；</p><p><strong>常量或函数折叠</strong>也是一种常见的优化方式，将SQL语句中的某些常量计算（加减乘除、取整等）在执行计划优化阶段就做掉；</p><p><strong>Join优化</strong>有很多方法，这里说的基于规则优化，主要指的是Join的实现方式，比如最傻瓜式的Join实现就是老老实实得读取参与Join的2张表的每条记录进行Join条件比对。而最普遍的优化方式就是Hash Join，显然效率很高。不要认为这是想当然应该有的功能，其实MySQL直到8.0版本才具备。另外Join的顺序及合并，有部分也可以直接通过SQL来进行判断和选择。</p><h3 id="基于代价优化"><a href="#基于代价优化" class="headerlink" title="基于代价优化"></a>基于代价优化</h3><p>基于规则的优化器简单，易于实现，通过内置的一组规则来决定如何执行查询计划。与之相对的是基于代价优化（cost based optimization，CBO）。</p><p>CBO的实现依赖于详细可靠的统计信息，比如每个列的最大值、最小值、平均值、区分度、记录数、列总和，表大小分区信息，以及列的直方图等元数据信息。</p><p>CBO的一大用途是在Join场景，决定Join的执行方式和Join的顺序。这里所说的Join我们主要是讨论Hash Join。</p><p><img src="https://pic3.zhimg.com/80/v2-33872b6a316c3a77a8437a9daf09ba6a_1440w.webp" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p><strong>Join执行方式</strong></p><p>根据参与Join的<strong>驱动表（Build Table）和被驱动表（Probe Table）</strong>的大小，Hash Join一般可分为broadcast和partition两种。</p><p><img src="https://pic4.zhimg.com/80/v2-a686847a853612d438b66c4e26effc53_1440w.webp" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p><strong>广播方式</strong>适用于大表与小表进行Join，在并行Join时，将小表广播到大表分区数据所在的各个执行节点，分别与大表分区数据进行Join，最后返回Join结果并汇总。</p><p><img src="https://pic3.zhimg.com/80/v2-08f39b8a4725501c48c28428bf7e7eea_1440w.webp" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>而<strong>分区方式</strong>是最为一般的模式，适用于大表间Join或表大小未知场景。分别将两表进行分区，每个分区分别进行Join。</p><p><img src="https://pic1.zhimg.com/80/v2-f2e4fe9d83f1d9376d9ab008463901ec_1440w.webp" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>显然，判断大小表的关键就看是否能够通过某种方式获取表的记录数，如果存储层保存了记录数，那么可从元数据中直接获取。</p><p>如果Join的两表都是大表，但至少有个表是带Where过滤条件的，那么在决定走分区方式前还可进一步看满足条件的记录数，这时候，物理上进行分区的表存储方式可发挥作用，可以看每个分区的最大值和最小值及其记录数来估算过滤后的总记录数。当然，还有种更精确的方式是列直方图，能够直接而直观得获取总记录数。</p><p>如果上述的统计信息都没有，要使用CBO还有另一种方式就是进行记录的动态采样来决定走那种Join方式。</p><p><strong>Join顺序</strong></p><p>如果一个查询的SQL中存在多层Join操作，如何决定Join的顺序对性能有很大影响。这块也已是被数据库大佬们充分研究过的技术。</p><p><img src="https://pic2.zhimg.com/80/v2-659e39bea5507b6de68612defb2b0489_1440w.webp" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>一个好的CBO应该能够根据SQL 语句的特点，来自动选择使用<strong>Left-deep tree</strong>（LDT，左图）还是 <strong>bushy tree</strong>（BYT，右图）执行join。</p><p>两种Join顺序没有好坏之分，关键看进行Join的表数据即Join的字段特点。</p><p>对于LDT，如果每次Join均能够过滤掉大量数据，那么从资源消耗来看，显然是更优的。对于给每个列都构建了索引的某些系统，使用LDT相比BYT更好。</p><p>一般来说，选择BYT是效率更高的模式，通过串行多层Join改为并行的更少层次Join，可以发挥MPP架构的优势，尽快得到结果，在多表模式ROLAP场景常采用。</p><h3 id="为什么需要向量化执行引擎？其与动态代码生成有何关系？"><a href="#为什么需要向量化执行引擎？其与动态代码生成有何关系？" class="headerlink" title="为什么需要向量化执行引擎？其与动态代码生成有何关系？"></a>为什么需要向量化执行引擎？其与动态代码生成有何关系？</h3><p><strong>查询执行引擎 (query execution engine)</strong> 是数据库中的一个核心组件，用于将查询计划转换为物理计划，并对其求值返回结果。查询执行引擎对系统性能影响很大，在一项针对Impala和Hive的对比时发现，Hive在某些简单查询上（TPC-H Query 1）也比Impala慢主要是因为Hive运行时完全处于CPU bound的状态中，磁盘IO只有20%，而Impala的IO至少在85%。</p><p>什么原因导致这么大的差别呢？首先得简单说下火山模型的执行引擎。</p><h3 id="火山模型及其缺点"><a href="#火山模型及其缺点" class="headerlink" title="火山模型及其缺点"></a>火山模型及其缺点</h3><p><strong>火山模型（Volcano-style execution）是最早的查询执行引擎</strong>，也叫做迭代模型 (iterator model)，或 one-tuple-at-a-time。在这种模型中，查询计划是一个由operator组成的DAG，其中每一个operator 包含三个函数：open，next，close。Open 用于申请资源，比如分配内存，打开文件，close 用于释放资源，next方法递归的调用子operator的 next方法生成一个元组（tuple，即行row在物理上的表示）。</p><p>下图描述了“select sum(C1) from T1 where C2 &gt; 15”的查询计划，该查询计划包含Project，HashAgg，Scan等operator，每个 operator的next方法递归调用子节点的 next，一直递归调用到叶子节点Scan operator，Scan operator的next 从文件中返回一个元组。</p><p><img src="https://pic1.zhimg.com/80/v2-6f96d3408831f1dd52fd9ca3e477e490_1440w.webp" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>其缺点主要在于：</p><p>- <strong>大量虚函数调用</strong>：火山模型的next方法通常实现为一个虚函数，在编译器中，虚函数调用需要查找虚函数表, 并且虚函数调用是一个非直接跳转 (indirect jump), 会导致一次错误的CPU分支预测 (brance misprediction), 一次错误的分支预测需要十几个周期的开销。火山模型为了返回一个元组，需要调用多次next 方法，导致昂贵的函数调用开销</p><p>- <strong>类型装箱</strong>：对于a + 2 * b之类表达式，由于需要对不同数据类型的变量做解释，所以在Java中需要把这些本来是primitive（如int等类型）的变量包装成Object，但执行时又需要调用具体类型的实现函数，这本质上也是虚函数调用的效率问题；</p><p>- <strong>CPU Cache利用效率低</strong>：next方法一次只返回一个元组，元组通常采用行存储，如果仅需访问第一列而每次均将一整行填入CPU Cache，将导致Cache Miss；</p><p>- <strong>条件分支预测失败</strong>：现在的CPU都是有并行流水线的，但是如果出现条件判断会导致无法并行。比如判断数据的类型（是string还是int），或判断某一列是否因为其他字段的过滤条件导致本行不需要被读取等场景；</p><p>- <strong>CPU与IO性能不匹配</strong>：每次从磁盘读取一个行数据，经过多次调用交给CPU进行处理，显然，大部分时间都是CPU等待数据就绪，导致CPU空转。</p><p>通过上述描述，可以得出解决问题的基本方法。可以将问题分为2大类，分别用下述的向量化引擎和动态代码生成技术来解决。</p><h3 id="向量化执行引擎"><a href="#向量化执行引擎" class="headerlink" title="向量化执行引擎"></a>向量化执行引擎</h3><p>向量化执行以列存为前提，主要思想是每次从磁盘上读取一批列，这些列以数组形式组织。每次next都通过for循环处理列数组。这么做可以大幅减少next的调用次数。相应的CPU的利用率得到了提高，另外数据被组织在一起。可以进一步利用CPU硬件的特性，如SIMD，将所有数据加载到CPU的缓存当中去，提高缓存命中率，提升效率。在列存储与向量化执行引擎的双重优化下，查询执行的速度会有一个非常巨大的飞跃。</p><h3 id="动态代码生成"><a href="#动态代码生成" class="headerlink" title="动态代码生成"></a>动态代码生成</h3><p>向量化执行减少CPU等待时间，提高CPU Cache命中率，通过减少next调用次数来缓解虚函数调用效率问题。而动态代码生成，则是进一步解决了虚函数调用问题。</p><p>动态代码生成技术不使用解释性的统一代码，而是直接生成对应的执行语言的代码并直接用primitive type。对于判断数据类型造成的分支判断，动态代码的效果可以消除这些类型判断，使用硬件指令来进一步提高循环处理效率。</p><p>具体实现来说，JVM系如Spark SQL，Presto可以用反射，C++系的Impala则使用了llvm生成中间码。相对来说，C++的效率更高。</p><p>向量化和动态代码生成技术往往是一起工作达到更好的效果。</p><h3 id="都有哪些存储空间和访问效率优化方法？"><a href="#都有哪些存储空间和访问效率优化方法？" class="headerlink" title="都有哪些存储空间和访问效率优化方法？"></a>都有哪些存储空间和访问效率优化方法？</h3><p>存储和IO模块的优化方法很多，这里我们还是在Hadoop生态下来考虑，当然，很多优化方法不是Hadoop特有的，而是通用的。OLAP场景下，数据存储最基础而有效的优化是该行存储为列存储，下面讨论的优化措施均基于列存。</p><h3 id="数据压缩和编码"><a href="#数据压缩和编码" class="headerlink" title="数据压缩和编码"></a>数据压缩和编码</h3><p>数据压缩是存储领域常用的优化手段，以可控的CPU开销来大幅缩小数据在磁盘上的存储空间，一来可以节省成本，二来可以减小IO和数据在内存中跨线程和跨节点网络传输的开销。目前在用的主流压缩算法包括zlib、snappy和lz4等。压缩算法并不是压缩比越高越好，压缩率越高的算法压缩和解压缩速度往往就越慢，需要根据硬件配置和使用场景在cpu 和io之间进行权衡。</p><p>数据编码可以理解为轻量级压缩，包括<strong>RLE和数据字典编码</strong>等。</p><p><img src="https://pic1.zhimg.com/80/v2-bc965a8e4b0e1a336dd5cae28ea2b3ac_1440w.webp" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>上图截至Presto论文，展示了RLE编码和数据字典编码的使用方式。RLE用在各列都是重复字符的情况，比如page0中6行记录的returnflag都是”F”。数据字典可高效使用在区分度较低的列上，比如列中只有几种字符串的场景。考虑到同个表的列的值相关性，数据字典可以跨page使用。</p><p>与数据压缩相比，数据编码方式在某些聚合类查询场景下，无需对数据进行解码，直接返回所需结果。比如假设T1表的C1列为某个字符，RLE算法将16个C1列的值“aaaaaabbccccaaaa”编码为6a2b4c4a，其中6a表示有连续6个字符a。当执行 select count(*) from T1 where C1=’a’时，不需要解压6a2b4c4a，就能够知道这16行记录对应列值为a有10行。</p><p>在列存模式下，数据压缩和编码的效率均远高于行存。</p><h3 id="数据精细化存储"><a href="#数据精细化存储" class="headerlink" title="数据精细化存储"></a>数据精细化存储</h3><p>所谓数据精细化存储，是通过尽可能多得提供元数据信息来减少不必要的数据扫描和计算，常用的方法包括但不限于如下几种：</p><p>- <strong>数据分区</strong>：数据分区可用于将表中数据基于hash或range打散到多个存储节点上，配合多副本存储。可以提高数据容灾和迁移效率。除此之外，在查询时可以快速过滤掉不符合where条件要求的数据分区，无需逐列读取数据进行判断。</p><p>- <strong>行组</strong>：与数据分区类似，Hadoop中常用的parquet和orcfile还将表数据分为多个行组（row group），每个行组内的记录按列存储。这样即达到列存提高OLAP查询效率，同时能够兼顾查询多行的需求；</p><p>- <strong>局部索引</strong>：在数据分区或行组上创建索引，可以提高查询效率。如下图所示，orcfile在每个行组的头部维护了Index Data来，保存最大值和最小值等元数据，基于这些信息可以快速决定是否需扫描该行组。某些OLAP系统进一步丰富了元数据信息，比如建立该行组记录的倒排索引或B+树索引，进一步提高扫描和查询效率。</p><p><img src="https://pic1.zhimg.com/80/v2-cfa177b7fedb7679534a0374cea9ee70_1440w.webp" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>- <strong>富元数据</strong>：除了提供最大值和最小值信息外，还可进一步提供平均值、区分度、记录数、列总和，表大小分区信息，以及列的直方图等元数据信息。</p><h3 id="数据本地化访问"><a href="#数据本地化访问" class="headerlink" title="数据本地化访问"></a>数据本地化访问</h3><p>数据本地化读写是常见的优化方法，在Hadoop下也提供了相应的方式。</p><p>一般来说，读HDFS上的数据首先需要经过NameNode获取数据存放的DataNode信息，在去DataNode节点读取所需数据。</p><p>对于Impala等OLAP系统，可以通过HDFS本地访问模式进行优化，直接读取磁盘上的HDFS文件数据。HDFS这个特性称为”Short Circuit Local Reads”，其相关的配置项（在hdfs-site.xml中）如下：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">&lt;property&gt;</span><br><span class="line">    &lt;name&gt;dfs.client.read.shortcircuit&lt;/name&gt;</span><br><span class="line">    &lt;value&gt;true&lt;/value&gt;</span><br><span class="line">  &lt;/property&gt;</span><br><span class="line">  &lt;property&gt;</span><br><span class="line">    &lt;name&gt;dfs.domain.socket.path&lt;/name&gt;</span><br><span class="line">    &lt;value&gt;/var/lib/hadoop-hdfs/dn_socket&lt;/value&gt;</span><br><span class="line">  &lt;/property&gt;</span><br></pre></td></tr></table></figure><p>其中：dfs.client.read.shortcircuit是打开这个功能的开关，dfs.domain.socket.path是Datanode和DFSClient之间沟通的Socket的本地路径。</p><h3 id="运行时数据过滤"><a href="#运行时数据过滤" class="headerlink" title="运行时数据过滤"></a>运行时数据过滤</h3><p>这是少部分OLAP系统才具有的高级功能，比如<strong>Impala的RunTime Filter（RF）运行时过滤</strong>，和<strong>SparkSQL 3.0的 Dynamic Partition Pruning动态分区裁剪</strong>，可以将驱动表的bloomfilter（BF）或过滤条件作用在被驱动表的数据扫描阶段，从而极大减少需扫描/返回的数据量。下面分别用一个图进行简述，在后续分析具体OLAP系统时再详述。</p><p><img src="https://pic3.zhimg.com/80/v2-67eb935c66bb3aecd43e5139a4401d0a_1440w.webp" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>上图直观得展示了Impala runtime filter的实现。流程如下：</p><ol><li><p>同时下发两个表的SCAN操作。左边是大表，右边是小表（相对而言，也有可能是同等级别的），但是左表会等待一段时间（默认是1s），因此右表的SCAN会先执行；</p></li><li><p>右表的扫描的结果根据join键哈希传递扫不同的Join节点，由Join节点执行哈希表的构建和RF的构建；</p></li><li><p>Join节点读取完全部的右表输入之后也完成了RF的构建，它会将RF交给Coordinator节点（如果是Broadcast Join则会直接交给左表的Scan节点）；</p></li><li><p>Coordinator节点将不同的RF进行merge，也就是把Bloom Filter进行merge，merge之后的Bloom Filter就是一个GLOBAL RF，它将这个RF分发给每一个左表Scan；</p></li><li><p>左表会等待一段时间（默认1s）再开启数据扫描，为了是尽可能的等待RF的到达，但是无论RF什么时候到达，RF都会在到达那一刻之后被应用；</p></li><li><p>左表使用RF完成扫描之后同样以Hash方式交给Join节点，由Join节点进行apply操作，以完成整个Join过程。</p></li></ol><p><img src="https://pic1.zhimg.com/80/v2-aa6e3230f2d40ae7cd2e6ebdec08887c_1440w.webp" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>sparksql图1（官方这个图有误，右边应该是Scan Date）</p><p><img src="https://pic4.zhimg.com/80/v2-7a8de9e0e91df3f0c8d45e03c10d200f_1440w.webp" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>sparksql图2</p><p>上面2幅图是SparkSQL 3.0的动态分区裁剪示意图。将右表的扫描结果（hashtable of table Date after filter）广播给左表的Join节点，在进行左表扫描时即使用右表的hashtable进行条件数据过滤。</p><h3 id="除了上面这些，还有其他优化方法吗？"><a href="#除了上面这些，还有其他优化方法吗？" class="headerlink" title="除了上面这些，还有其他优化方法吗？"></a>除了上面这些，还有其他优化方法吗？</h3><p>还有个极为重要的技术是集群资源管理和调度。Hadoop使用YARN进行资源调度，虽然带来了很大遍历，但对性能要求较高的OLAP系统却有些不适合。</p><p>如启动AppMaster和申请container会占用不少时间，尤其是前者，而且container的供应如果时断时续，会极大的影响时效性。</p><p>目前的优化方法主要包括让AppMaster启动后长期驻守，container复用等方式。让资源在需要用时已经就位，查询无需等待即可马上开始。</p><h3 id="做个总结"><a href="#做个总结" class="headerlink" title="做个总结"></a>做个总结</h3><p>本系列通过2篇文章，总结了下笔者最近看的一些OLAP相关文献材料。笔者通过这两篇文章主要是想说下自己对数仓和OLAP系统的理解，之所以采用问答形式，是因为笔者就是带着这些问题去google网上或公司内部的资料，或者直接请教在这个领域的大佬。</p><p>由于水平有限，难免有所错误，非常欢迎大家看后能够指出，让笔者有进步的机会。这两篇文章可以理解为是对他人文章的一次汇总加工。部分内容直接参考了其他文章，这也是在笔者先前其他文章中极少出现的情况，这些内容均在文末“引用”小结列出。</p><p><a href="https://www.infoq.cn/article/NTwo*yR2ujwLMP8WCXOE">最快开源OLAP引擎！ClickHouse在头条的技术演进_开源_陈星_InfoQ精选文章</a></p><p><a href="https://snappydata-cn.github.io/2018/04/04/SnappyData%E4%B8%8EPresto-Druid-Kylin-ES%E7%9A%84%E5%AF%B9%E6%AF%94-2/">SnappyData与Presto,Druid,Kylin,ES的对比-2 | SnappyData中文博客 (snappydata-cn.github.io)</a></p><p><a href="http://blog.daas.ai/2018/11/26/ClickHouse_vs._Druid_Pinot/">ClickHouse vs. Druid/Pinot | Xavier’s Lab (daas.ai)</a></p><p><a href="https://waltyou.github.io/Spark-DAG/">Spark 中 DAG 介绍 (waltyou.github.io)</a></p><p><a href="http://hbasefly.com/">有态度的HBase/Spark/BigData (hbasefly.com)</a></p><p><a href="https://www.infoq.cn/article/an-article-mastering-sql-on-hadoop-core-technology">一篇文章掌握Sql-On-Hadoop核心技术_语言 &amp; 开发_王宝生_InfoQ精选文章</a></p><p><a href="https://mp.weixin.qq.com/s/4O07cECjLbUQ4H-5K8f3gQ">盘点：SQL on Hadoop中用到的主要技术 (qq.com)</a></p><p><a href="https://www.infoq.cn/article/columnar-databases-and-vectorization">列式数据库和向量化_语言 &amp; 开发_Siddharth Teotia_InfoQ精选文章</a></p><p><a href="http://mysql.taobao.org/monthly/2017/01/06/">PgSQL · 引擎介绍 · 向量化执行引擎简介 (taobao.org)</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;h3 id=&quot;续言&quot;&gt;&lt;a href=&quot;#续言&quot; class=&quot;headerli</summary>
      
    
    
    
    <category term="数据库" scheme="http://posts.hufeifei.cn/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    
    <category term="OLAP" scheme="http://posts.hufeifei.cn/tags/OLAP/"/>
    
    <category term="ROLAP" scheme="http://posts.hufeifei.cn/tags/ROLAP/"/>
    
    <category term="MOLAP" scheme="http://posts.hufeifei.cn/tags/MOLAP/"/>
    
  </entry>
  
  <entry>
    <title>2023，可观测性需求将迎来“爆发之年”？ | 解读可观测技术的 2022</title>
    <link href="http://posts.hufeifei.cn/backend/observability/"/>
    <id>http://posts.hufeifei.cn/backend/observability/</id>
    <published>2022-12-28T00:00:00.000Z</published>
    <updated>2026-01-01T06:47:29.852Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><blockquote><p>本文是 “<a href="https://www.infoq.cn/theme/168" title="xxx">2022 InfoQ 年度技术盘点与展望</a>” 系列文章之一，由 InfoQ 编辑部制作呈现，重点聚焦可观测技术领域在 2022 年的重要进展、动态，希望能帮助你准确把握 2022 年可观测技术领域的核心发展脉络，在行业内始终保持足够的技术敏锐度。</p><p>“InfoQ 年度技术盘点与展望”是 InfoQ 全年最重要的内容选题之一，将涵盖操作系统、数据库、AI、大数据、云原生、架构、大前端、编程语言、开源安全、数字化十大方向，后续将聚合延展成专题、迷你书、直播周、合集页面，在 InfoQ 媒体矩阵陆续放出，欢迎大家持续关注。</p></blockquote><p>随着 5G、云计算、微服务等技术的广泛应用，企业所面临的 IT 运维环境越来越复杂，需要运维的系统不仅数量多，而且网络架构复杂、基础设施多样。在信息化建设日益普及的当下，快速提升企业 IT 资产管理能力开始成为企业迫切解决的问题。</p><p>过去一年，可观测技术得到了极大关注，并被 Gartner 列为 2023 年十大战略技术趋势之一。然而，企业在实际落地可观测的过程中依旧面临很多问题，比如如何选择合适的方案、平衡新旧系统之间的关系等等。</p><p>其实，一个企业 IT 系统的可观测性建的好不好，在新员工入职时就能看出来。如果新员工去看文档时，看的是公司某个架构师画的系统架构图，显然这个公司技术系统是缺乏可观测性的。因为企业系统一直在迭代，而系统的架构图却很难及时更新。如果新员工看到的是完整的可观测性平台数据看板，那这个公司的“可观测性”构建的应该算是比较完善的。当企业的可观测性构建趋于成熟后，开发者可以快速定位系统问题，精准的“观测性数据”可以帮助开发者完成包括自动化巡检在内的各种各样的 Control 或者是金丝雀发布的控制等等。</p><p>所以，可观测技术目前发展到哪个阶段了？开发者们对可观测性平台的需求到底是怎样的？带着这些问题我们对观测云 CEO 蒋烁淼进行了专访，我们一起对可观测技术过去一年的发展做了一个盘点。</p><h2 id="一、国内外“可观测性”的认知尚存在较大差异"><a href="#一、国内外“可观测性”的认知尚存在较大差异" class="headerlink" title="一、国内外“可观测性”的认知尚存在较大差异"></a>一、国内外“可观测性”的认知尚存在较大差异</h2><p>当我们想要盘点一项技术过去一年的发展状况时，首先要看它过去一年的应用率是否有所提升。去年，领域里有相关数据表明，大部分公司都在进行可观测实践，但国外也仅有 27% 的公司实现了全栈的可观测，国内可能更少，可能 10% 的比例都不到。而今年，这个比例似乎并没有提高。</p><p>正如蒋总所说的那样，“在中国，真正地构建了完全可观测性的企业非常的少，做的好的企业少之又少。”</p><p>放眼过去，目前国内确实很缺乏好的可观测性的服务商和技术提供商，这让中国的可观测性技术发展与国外产生了不小的距离。包括字节、阿里云等大厂在内的国内大部分厂商在可观测性方面都还是处于“零散拼装”的状态，他们大多把开源的 Prometheus、Loki、OpenTelemetry Collector 组装在一起形成“可观测性套件”，然后提供一个开源的托管版本给开发者使用。很少有人敢称自己为“可观测性平台”，因为没有一个完整的可观测性的概念。</p><p>然而，过去一年里，无论是在国外还是国内，企业 CTO 们对于可观测性的关注度有了明显大幅提升。从全球视角来看，不管是 Grafana 的 ObservabilityCON，还是今年谷歌云举办的 Google Cloud Next’22 Recap、北美的 KubeCon 2022 大会，亦或者是前不久亚马逊云科技举办的 re:Invent 全球大会、NGINX 举办的 NGINX Sprint China2022，可观测性话题的探讨比例都有了明显提升，像 Google Cloud Next’22 Recap 大会谈可观测性几乎占到了大会整体内容的三分之一。</p><p>切回国内视角，当我们和国内的企业 CTO 们去聊天的时候会发现，他们今年明显会更关注可观测性这件事，无论是因为业务层面譬如 B 站 713 事件带来的启发，还是受行业大会演讲内容的影响，国内在可观测性方面没有明显的技术发展，大多是因为疫情下的市场经济不景气、企业预算不足下的“按兵不动”之举。</p><p>聚焦到具体的业务需求上，目前企业技术决策者正处于一个认知迭代的过程中——“将监控升级为可观测性”。原来的“监控”大多都是分裂的，可能是每一个项目都有一套监控工具，可能是是一个项目中的日志平台、APM 平台单独运行。所以现在不少开发者会认为可观测性就是新瓶装旧酒，他们认为把传统的监控工具整合在一起，就是可观测性了，甚至不少创业公司的 CTO 会将一些 K8S 监控工具或者 APM 混同于可观测性。</p><p>事实上，可观测性相关的工具如果被称为“下一代监控”的话，它的投资占 IT 的比重并不小，可观测性的应用场景远远要超过目前大部分开发者的认知。“可观测性”这件事在大部分 CTO 的意识中，无论是横向扩展还是垂直扩展，大家的认知都还需要有一个“求同”的过程。</p><p>就像现在许多人会把“可观测性”定义为是一个降本增效的好工具，很多相关产品厂商会将“降本增效”作为产品的推广利益点，这让许多开发者认为安装了可观测性平台，就可以降低成本、为业务创造价值。事实上，可观测性工具从来不是一个“便宜”的东西，可观测性平台除了基本的工具投入，还需要有一套完整的数据存储方案，企业自研一套优秀的可观测性解决方案投入成本并不低。以美国企业的可观测性相关投入为例，其会占企业整体 IT 支出的 5%-10%。而且，它的“降本增效”不是短时间内就可以显现出来的，而是通过长时间的应用，为业务提供大量的数据支撑进行技术性优化，从而产生价值。</p><p>总体来说，真正的可观测性平台应该是，能够将各种各样对于系统的形态、实时的状态进行有结构性的收集并提供一系列的观察、测量手段的平台。简单来说，就是用各种各样的技术，像传感器一样，能够让开发者们的开发、测试、运维过程变的更容易，能够即时了解系统的运行状态，而并不是简简单单的“监控”。</p><p>可观测性平台一定是数据组织统一化的，否则分开的日志系统、链路系统、指标系统很难实现数据与数据的关联。比如日志里的字段，主机系统中叫 host，在指标系统里叫 host name，然后有的系统用 IP 地址，有的系统用主机名，那在做关联分析和连接查询时会发现根本无从下手。有的公司也会将不同系统的数据分散开进行监控，利用“数据中台”的一个构建方案将所有数据汇聚到同一个平台中去做分析，但在实践过程中会发现，数据缺少实时性的结构化，无法做实时预警预测，只能做事后故障定位。</p><h2 id="二、全球可观测性技术演进主要聚焦在-6-个方面"><a href="#二、全球可观测性技术演进主要聚焦在-6-个方面" class="headerlink" title="二、全球可观测性技术演进主要聚焦在 6 个方面"></a>二、全球可观测性技术演进主要聚焦在 6 个方面</h2><h4 id="1、OpenTelemetry-逐渐成为行业标准"><a href="#1、OpenTelemetry-逐渐成为行业标准" class="headerlink" title="1、OpenTelemetry 逐渐成为行业标准"></a>1、OpenTelemetry 逐渐成为行业标准</h4><p>OpenTelemetry 作为一套由 CNCF 主导的云原生可观测性的标准协议，目前已经是海外企业在该领域的实践标准。从数据收集的角度来说，基于 OpenTelemetry 或者兼容 OpenTelemetry 的方案，从今年到明年都应该会逐步成为主流。OpenTelemetry 已迭代到了 1.0 版本，作为一个客户端，日志和网络等部分也都正在得到逐步补强，严格意义上讲，OpenTelemetry 1.0 已经算是完成了整体的标准化，OpenTelemetry 标准协议已称得上是一个行业标准。</p><p>反观国内，虽未形成类似的标准，却也涌现了诸如 CAT 和 SkyWalking 等一系列国产开源的 APM 系统，通过实时监控企业的应用系统，系统化地提供应用性能管理和故障定位的解决方案，在运维中被广泛使用。得益于对业务代码无侵入，性能表现优秀，社区活跃，中文文档齐全等众多优秀特性，SkyWalking 在国内异常火爆。</p><h4 id="2、Grafana-8-0-修改了-AGPL-协议"><a href="#2、Grafana-8-0-修改了-AGPL-协议" class="headerlink" title="2、Grafana 8.0 修改了 AGPL 协议"></a>2、Grafana 8.0 修改了 AGPL 协议</h4><p>开源项目背后的商业公司只有在商业上取得成功，才能更好地为开源项目提供新特性的开发、支持与维护，形成良性循环，这样开源项目的维护才会更加长久。Grafana 在云原生技术领域的监控工具中几乎是标准一般的存在，而在可观测性的投资方面，Grafana 也写下了浓墨重彩的一笔，推出了 Loki 日志存储、时序引擎 Mimir 和调用堆栈的存储引擎等一系列产品，力图通过提供一个复刻 DataDog 所有功能的开源方案，打包售卖 Grafana Cloud 服务。</p><p>因此在 Grafana 8.0 后修改了 AGPL，允许甲方有技术实力的公司基于它的开源方案做二次开发，而不允许乙方在没有商业授权的时候拿着 Grafana 的产品包装出售，此举对国内不少的信创公司都造成了巨大的冲击。</p><h4 id="3、eBPF-技术日趋成熟"><a href="#3、eBPF-技术日趋成熟" class="headerlink" title="3、eBPF 技术日趋成熟"></a>3、eBPF 技术日趋成熟</h4><p>内核通过文件系统或系统调用暴露出的信息有限，单纯从用户态切入无法抓取这些信息，只有从内核本身调用的过程中获取调用信息才可以补足相关能力。eBPF 是一项革命性的技术，它可以在 Linux 内核中运行沙盒程序，而无需更改内核源代码或加载内核模块。通过使 Linux 内核可编程，基础架构软件可以利用现有的层，使它们更加智能和功能丰富，无需继续为系统增加额外的复杂性层。</p><p>eBPF 促进了网络，安全性，应用程序配置 / 跟踪和性能故障排除等领域的新一代工具的开发，这些工具不再依赖现有的内核功能，而是在不影响执行效率或安全性的情况下主动重新运行。在对内核无侵入的前提下，它通过动态地向内核中插入一段自己的代码，实现定义监控及跟踪能力。</p><p>随着内核的发展，eBPF 逐步从最初的数据包过滤拓展到了网络、内核、安全、跟踪等领域，并且它的功能特性还在快速发展中，通过内核虚拟机，所有的 IO 包括程序运行、进程的网络通信等各种细部数据尽收眼底，大大加强了系统的可观测能力。</p><p>然而，目前大多数的只通过 eBPF 获取数据并把自己标榜为可观测平台的公司，也仅仅是做了一个 eBPF 的支持而已。从第三方视角看，eBPF 是对传统观测能力一个非常好的补充，可以通过它拿到更多的数据，用户获取内核丰富观测指标的门槛被极大降低。</p><h4 id="4、可观测性开始更“注意安全”"><a href="#4、可观测性开始更“注意安全”" class="headerlink" title="4、可观测性开始更“注意安全”"></a>4、可观测性开始更“注意安全”</h4><p>云原生数据库等新兴概念的出现，大大消除了大家对于存储成本方面的顾虑，使得企业可以更聚焦于提升观测能力本身，而不必在成本和性能方面分神，这方面的发展也有迹可循。比如今年 DataDog 推出了一个名为哈士奇的存储架构，它是一个完全云原生面向可观测性的数据库，得益于云原生的存储能力，可以在同等价格下多存储 5-10 倍数据，以往即使收集到了海量的数据，但面对高昂的存储成本，这些数据也只能被丢弃或者针对这些数据进行采样，本质上看，这对客户需要的结果也是有浪费的。但这也并不意味着使用这种新技术之后并不需要进行采样，只是新的更低成本更高性能的数据库技术，可以大大提升可观测性能够覆盖的范围，降低存储成本，这也是一个很重要的发展。</p><p>安全和可观测性的合并，已在全球范围内形成一种趋势。摩根士丹利在《安全分析和可观测性》文章中也提到，在国外，以 DataDog 为代表的公司在上市之后发布的新增功能中有 70% 都是安全相关的，这其中的道理非常简单，可观测性是通过检查其输出来衡量系统内部状态的能力，它收集了系统的方方面面，通过这些数据可以分析出系统的故障，自然也就能够分析出系统有没有被入侵。比如 DataDog 就提供了通过分析当前访问请求，区分哪些可能是黑客在嗅探，或者准备未来做 DDoS 攻击的接口的功能。</p><p>也就是说，采集的数据在安全方面也能够发挥作用，而不像传统安全工具那样，需要针对安全场景再进行一次数据采集。所以，安全和可观测性的合并在全球范围内已经成为一种趋势，当然抗 DDoS 、挖防火墙这些并不会合并，针对攻击现场的追踪，比如国内的态势感知、国外的 SIEM 这些安全产品都选择了和可观测性进行融合。</p><h4 id="5、建立“业务的可观测性”越来越重要"><a href="#5、建立“业务的可观测性”越来越重要" class="headerlink" title="5、建立“业务的可观测性”越来越重要"></a>5、建立“业务的可观测性”越来越重要</h4><p>可观测性的概念起源于工业领域，在该领域中，可观测性被定义为从系统外部输出推断系统内部健康状态的能力；在软件产品和服务领域，可观测性就是从应用系统中收集尽可能多的遥测数据，以便可以调查和解决新出现的复杂问题，确保企业能够主动观察系统，在影响客户体验之前解决故障及问题，安全地进行测试并实施优化，它可以更好地管理和控制业务风险，有助于我们了解“正在发生什么”以及“为什么会这样”。可观测性使团队能够更有效地监控现代系统，帮助他们找到并连接复杂链中的影响，并将其追溯到原因。此外，它还使系统管理员、IT 运营分析师和开发⼈员能够了解他们的整个架构。</p><p>如今的 IT 系统，迭代发布更迅速，业务系统更庞大，网络链路更复杂，运行环境更动态。在“业务至上”的互联网时代，技术工程师们保障的核心其实并不是这套 IT 系统或软件，他们保障的核心其实是业务，一笔业务可能会涉及到多个微服务系统，技术工程师们不再追踪一个 API 的全链路调用关系，而是要追踪到整个 API 关联的订单、用户甚至具体到哪一笔交易，这也是可观测性和业务结合的一个重要发展趋势。Gartner 也提到，“未来一切业务皆需可观测性”，简单地讲就是把运营人员、运维人员、IT 人员看到的数据做统一，而不是互相甩锅。</p><h4 id="6、多个“新玩家”入局"><a href="#6、多个“新玩家”入局" class="headerlink" title="6、多个“新玩家”入局"></a>6、多个“新玩家”入局</h4><p>随着微服务架构的流行，一些微服务架构下的问题也日渐突出，比如一个请求会涉及到多个服务，而服务本身可能也会依赖其他业务，整个请求路径就构成了一个网状的调用链，而在这个调用链中，一旦某个节点发生异常，整个调用链的稳定性就会受到影响。</p><p>据此，我们需要一些可以帮助理解系统行为、用于分析性能问题的工具，以便发生故障的时候，能够快速定位和解决问题。近年来，各种调用链监控产品层出不穷，呈现百花齐放的态势，各家你方唱罢我登场，比如 New Relic 收购的那家叫 PIXIE 的公司可以称得上是一个新玩家，又如最近出现了一个面向 K8S 的叫做 Kubeshark 的公司，它专门研发用于网络分析的开源组件。</p><p>此外，随着 eBPF 的出现，未来会涌现越来越多的解决方案，尤其是面向整个 K8s 生态环境，这也是全球可观测性技术的一个演进方向。</p><h4 id="三、具有“4-个统一性”的可观测性平台是企业刚需"><a href="#三、具有“4-个统一性”的可观测性平台是企业刚需" class="headerlink" title="三、具有“4 个统一性”的可观测性平台是企业刚需"></a>三、具有“4 个统一性”的可观测性平台是企业刚需</h4><p>当我们盘点完可观测性的行业趋势，所以到底什么样的平台才是目前企业所需要的呢？国内首批获得中国信通院颁发的「可观测性平台技术能力」先进级认证，新一代云原生全链路数据可观测平台——观测云给出了答案。</p><p>观测云能全环境高基数采集数据，支持多维度信息智能检索分析，及提供强大的自定义可编程能力，使系统运行状态尽在掌控，故障根因无所遁形，实现了统一采集、统一标签、统一存储和统一界面，带来全功能的一体化可观测体验。观测云平台从技术层面主要实现了 4 个统一 —— 统一采集、统一治理、统一分析和统一编程：</p><p>第一个统一，是统一的数据收集。数据采集是数据关联的基础能力，现今，国内外都有大量的数据采集器，但大多数采集能力单一，比如 Telegraf 仅支持指标，Filebeat 只服务日志，OpenTelemetry 的 Collector 对非云原生的组件并不友好，需要大量安装 Exporter 插件。</p><p>为了实现系统的可观测性，一台主机上可能需要安装无数个 Agent，这既引入了管理上的问题，又产生了成本的问题，一定程度上造成了资源的浪费。观测云的 DataKit 是目前唯一的真正一体化实现各种环境（如传统环境、云原生等）统一数据采集平台，它通过一个进程或 Daemonset Pod 就可以实现全方位的数据采集，配置体验良好，开源且拓展性强，涵盖了更为全面的数据采集类型，有海量的技术栈的指标收集能力，采集器的配置更简单，数据质量更好。</p><p>第二个统一，是数据采样格式实现统一。可观测性数据的一个重要特性就是实时性，Logstash 等传统的开源方案均在中心或边缘中心完成数据治理工作，而受限于中心处理的延迟和 delay，在日志收集方面通常有很大的时延，实时性也就无从得到保障。而在观测云的解决方案中，客户只需多拿出 5% 的 CPU 用于数据的处理，一方面可以发挥可观测数据的实时性。另一方面，还可以利用数据本身的现状还原。比如当把所有数据收集到中心的时候，实际上很多环境信息或多或少的都会存在遗漏或缺失的情况，如数据来源于哪个容器、哪个 pod、哪个部署单元或者哪个项目等，在边缘侧可以随时获取这些数据作为补充。</p><p>行内人都知道，观测云是一个 SaaS 方案，在边缘侧还可以实现采样功能。比如数据中存在的一些电话号码、金额、用户 ID 等信息，完全可以通过边缘脱敏或者删除掉一些不需要上传或者不易上传的数字。这样一来，数据在上传前就已经完成了脱敏、加密或者丢弃，在丢弃情况下，还能大幅降低传输的带宽；数据无需上传到中心再做治理，这也进一步提升了客户的信任。此外，由于减少了一个中间处理模块，系统的整体实时性也得到了很大的保障。</p><p>目前观测云也在考虑把端上的统一治理能力做成一个插件，贡献给 OpenTelemetry，使得 OpenTelemetry 的 Agent 也具备边缘计算的能力。据悉，目前观测云平台完全兼容 OpenTelemetry，单一采集器即可实现容器指标采集、应用链路追踪和日志采集。同时能和各种常用采集器实现串联，包括 Promethues、Skywalking、Fluentd 等十几种常见工具，可以替换更可以共存，轻松实现数据高密采集。</p><p>第三个统一，是统一的数据治理。用户的数据在被传输到中心后，将会被统一存储、统一压缩、统一部署，保存在观测云自己定义的逻辑数据存储架构中，相较于开源方案，观测云主要有两个优势：</p><ul><li>在存储的整合性角度，观测云的成本低于开源方案；</li><li>不论是指标数据、日志数据、链路数据、用户行为甚至未来开展到安全数据，区别于其他厂商平台，观测云通过自研的 DQL (Debug Query Language) 语言在数据分析平台进行整体分析，甚至针对不用用户使用不同时代的存储技术。</li></ul><p>第四个统一，是统一的拓展开发平台。当 DQL 或者软件本身的能力无法满足用户的需要时，用户可以通过 DataFlux Function 平台进行无限的拓展。比如出于中大型公司的安全合规要求，直接通过第三方平台发送 SaaS 平台的告警邮件到内部邮箱，会被直接屏蔽掉，而如果用户把自己的发件服务器注册到 DataFlux Function，就可以实现向内部邮箱发送告警邮件，类似的能力还有短信、电话等等。</p><p>除此之外，DataFlux Function 平台还可以实现对各种云平台甚至业务数据的载入，最近，观测云使用 DataFlux Function 帮客户实现了业务对账系统和 IT 代码调用间的关联。据悉，未来观测云会基于 DataFlux Function 平台推出一个抢占型实例量化交易的 Demo。</p><p>除了这四个“统一”，观测云还提供 Site Reliability Engineering (SRE) 和 Observability Engineering 的最佳实践，支持智能推测算法，可自动识别基础设施和应用程序的潜在问题。坚持每两周迭代升级，使工程师们可随时体验最前沿的技术和最尖端的能力。是目前市面上少见的，真正意义上做到了“观测性”实现的平台，真正地满足了企业系统及开发者们在观测性方面的需求。</p><p>百闻不如一试。当我们回溯平台选型的根本，发现当前市面上的可观测性平台质量良莠不齐，大多还处于“监控”状态，系统整体性较差，无论是对于企业来说还是对于开发者来说，试错成本高。而目前观测云 SaaS 版完全按量计费，没有初始成本，拒绝为闲置功能付费，可提供专属服务经理，让初创团队也能体验一流的全链路系统可观测能力，切实地解决了平台选型难的问题。</p><p>正如蒋烁淼所说的，“一个好的系统首先要做到采集、治理、分析、变成这四个方面的统一；其次，需要尽量兼容开源，将整个技术栈和开源进行一个双向的连接。观测云在未来将进一步下探存储成本，最终让利用户。”</p><h4 id="四、国产可观测性的未来需要更多厂商共同努力"><a href="#四、国产可观测性的未来需要更多厂商共同努力" class="headerlink" title="四、国产可观测性的未来需要更多厂商共同努力"></a>四、国产可观测性的未来需要更多厂商共同努力</h4><p>目前，可观测性还在发展初期，很多产品仍在探索阶段，也有很多问题亟待解决，未来人们对可观测性的需求只会越来越高。</p><p>谈及“可观测性”的未来，蒋烁淼认为，可观测性未来主要有四大趋势。观测性平台的开发厂商未来需要在以下几个方面做出改进和优化：</p><ul><li>提供更大的技术环境支持能力，支持标准的可观测协议；</li><li>引入更多的算法，以智能的方式做数据的巡检；</li><li>连接安全产品；</li><li>进一步降低存储成本。</li></ul><p>这与媒体第三方视角观察到的趋势保持相似。然而，纸上得来终觉浅，绝知此事要躬行。虽然国内像观测云等厂商紧跟行业需求及技术发展趋势，提供了自定义数据大盘、基础设施监控、日志分析、用户访问监测、应用性能检测、Profiling、云拨测、智能巡检等多种解决方案，力图打造可观测性最佳实践。但是，仅仅是一家或者几家的努力，是远远不够的。</p><p>行业的发展需要更多的人参与进来。观测性平台开发厂商需要挖掘更多用户需求，而普通开发者也应该将更多的注意力放到“可观测性”上，让可观测性工具成为业务优化的得力助手。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;blockquote&gt;
&lt;p&gt;本文是 “&lt;a href=&quot;https://www</summary>
      
    
    
    
    <category term="后端" scheme="http://posts.hufeifei.cn/categories/%E5%90%8E%E7%AB%AF/"/>
    
    
    <category term="OpenTelemetry" scheme="http://posts.hufeifei.cn/tags/OpenTelemetry/"/>
    
    <category term="eBPF" scheme="http://posts.hufeifei.cn/tags/eBPF/"/>
    
    <category term="Grafana" scheme="http://posts.hufeifei.cn/tags/Grafana/"/>
    
  </entry>
  
  <entry>
    <title>13 种高维向量检索算法全解析！</title>
    <link href="http://posts.hufeifei.cn/db/vector-search/"/>
    <id>http://posts.hufeifei.cn/db/vector-search/</id>
    <published>2022-11-02T00:00:00.000Z</published>
    <updated>2026-01-01T06:47:29.860Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css"><blockquote><p>编者按：<br>以图搜图、商品推荐、社交推荐等社会场景中潜藏了大量非结构化数据，这些数据被工程师们表达为具有隐式语义的高维向量。为了更好应对高维向量检索这一关键问题，杭州电子科技大学计算机专业硕士王梦召等人探索并实现了「效率和精度最优权衡的近邻图索引」，并在数据库顶会 VLDB 2021 上发表成果。<br>作为连接生产和科研的桥梁，Zilliz 研究团队一直与学界保持交流、洞察领域前沿。此次，王梦召来到 Z 星，从研究动机、算法分析、实验观测和优化讨论等维度展开讲讲最新的科研成果。<br>以下是他的干货分享，<a href="https://arxiv.org/pdf/2101.12631.pdf">点击这里可获得论文全文</a></p></blockquote><h2 id="高维数据检索：基于近邻图的近似最近邻搜索算法实验综述"><a href="#高维数据检索：基于近邻图的近似最近邻搜索算法实验综述" class="headerlink" title="高维数据检索：基于近邻图的近似最近邻搜索算法实验综述"></a>高维数据检索：基于近邻图的近似最近邻搜索算法实验综述</h2><h3 id="导言"><a href="#导言" class="headerlink" title="导言"></a>导言</h3><p>向量检索是很多 AI 应用必不可少的基础模块。近年来，学术界和工业界提出了很多优秀的基于近邻图的ANNS 算法以及相关优化，以应对高维向量的检索问题。但是针对这些算法，目前缺乏统一的实验评估和比较分析。为了优化算法设计、进一步落地工业应用，我们完成了论文《A Comprehensive Survey and Experimental Comparison of Graph-Based Approximate Nearest Neighbor Search》。该论文聚焦实现了效率和精度最优权衡的近邻图索引，综述了 13 种具有代表性相关算法，实验在丰富的真实世界数据集上执行。我们的贡献可总结如下：</p><ol><li>根据图索引的特征，我们将基于近邻图的 ANNS 算法划分为四类，这给理解现存算法提供了一个新的视角。在此基础上，我们阐述了算法间的继承和发展关系，从而梳理出算法的发展脉络；</li><li>我们分解所有算法为 7 个统一的细粒度组件，它们构成一个完整的算法执行流程，从而实现了算法的原子级分析。我们可以公平评估当前工作在某一组件的优化通过控制其它组件一致；</li><li>我们采用多样的数据集（包括 8 个真实世界数据集，它们包括视频、语音、文本和图像生成的高维向量）和指标评估当前算法的全面性能；</li><li>我们提供了不同场景下最优算法推荐、算法优化指导、算法改进示例、研究趋势和挑战的分析讨论。</li></ol><h3 id="研究动机"><a href="#研究动机" class="headerlink" title="研究动机"></a>研究动机</h3><p>根据以下观测，我们对 13 种基于近邻图的 ANNS 算法进行了比较分析和实验综述：</p><ul><li>目前，学术界和工业界提出 10 余种常见的近邻图算法，但对于这些算法的合理分类和比较分析较为缺乏。根据我们的研究，这些算法的索引结构可归结为 4 种基础的图结构，但这些图存在着非常多的问题，如复杂度太高等。所以，在这 4 种图结构基础上有多种优化，如对相对邻域图（RNG）优化就包含 HNSW、DPG、NGT、NSG、SPTAG 等算法。</li><li>很多现有的论文从“分子”角度评估基于近邻图的 ANNS 算法（宏观角度）。然而，我们发现，这些算法有一个统一的“化学表达式”，它们还可再向下细分为“原子”（微观角度），从“原子”角度分析可以产生一些新发现，比如很多算法都会用到某一“原子”（HNSW，NSG，KGraph，DPG的路由是相同的）。</li><li>除了搜索效率和精度的权衡之外，基于近邻图的 ANNS 算法的其它特征（包含在索引构建和搜索中）也间接影响了最终的搜索性能。在搜索性能逐渐达到瓶颈的情况下，我们对于索引构建效率、内存消耗等指标给予了重视。</li><li>一个算法的优越性并不是一成不变的，数据集的特征在其中起着至关重要的作用。比如，在 Msong（语音生成的向量数据集）上NSG 的加速是 HNSW 的 125 倍；然而，在 Crawl（文本生成的向量数据集）上 HNSW 的加速是 NSG 的 80 倍。我们在多样的数据集上执行实验评估，从而对算法形成更全面的认识。</li></ul><p><strong>近邻图算法“分子”级分析</strong></p><p>在分析基于近邻图的 ANNS 算法之前，首先给大家介绍下 4 个经典的图结构，即：德劳内图（DG）、相对领域图（RNG）、K 近邻图（KNNG）、最小生成树（MST）。如图1所示，这四种图结构的差异主要体现在选边过程，简单总结如下：DG 确保任意 3 个顶点 x, y, z 形成的三角形 xyz 的外接圆内部及圆上不能有除了 x, y, z 之外的其它顶点；RNG 要确保(b)中月牙形区域内不能有其它点，这里的月牙形区域是分别以x和y为圆心，x 与 y 之间的距离为半径的两个圆的交集区域；KNNG 每个顶点连接 K 个最近的邻居；MST 在保证联通性的情况下所有边的长度（对应两个顶点的距离）之和最小。</p><p><img src="https://pic4.zhimg.com/80/v2-5f757eeabfdd24f73d2c9af11c70c137_1440w.webp" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>图1 四种图结构在相同的数据集上的构建结果</p><p>接下来，我将基于图1 中的 4 个图结构来梳理 13 个基于近邻图的ANNS算法。为了避免翻译造成了理解偏差，算法名使用英文简称，算法的原论文链接、部分高质量的中文介绍、部分代码请见参考资料。各算法之间更宏观的联系可参考论文中的表2 和图3。</p><p><strong>算法1：NSW</strong></p><p>NSW 是对 DG 的近似，而 DG 能确保从任意一个顶点出发通过贪婪路由获取精确的结果（即召回率为 1 ）。NSW 是一个类似于“交通枢纽”的无向图，这会导致某些顶点的出度激增，从论文的表11 可知，NSW 在某些数据集上的最大出度可达几十万。NSW 通过增量插入式的构建，这确保了全局连通性，论文表4 中可知，NSW的连通分量数均为1。NSW 具有小世界导航性质：在构建早期，形成的边距离较远，像是一条“高速公路”，这将提升搜索的效率；在构建后期，形成的边距离较近，这将确保搜索的精度。</p><p>原文：<a href="https://www.sciencedirect.com/science/article/abs/pii/S0306437913001300">https://www.sciencedirect.com/science/article/abs/pii/S0306437913001300</a></p><p>中文介绍：<a href="https://blog.csdn.net/u011233351/article/details/85116719">https://blog.csdn.net/u011233351/article/details/85116719</a></p><p>代码：<a href="https://github.com/kakao/n2">https://github.com/kakao/n2</a></p><p><strong>算法2：HNSW</strong></p><p>HNSW 在 NSW 的基础上有两个优化：“层次化”和“选边策略”。层次化的实现较为直观：不同距离范围的边通过层次呈现，这样可以在搜索时形成类似于跳表结构，效率更高。选边策略的优化原理是：如果要给某个顶点连接 K 个邻居的话，NSW 选择 K 个距离最近的，而 HNSW 从大于 K 个最近的顶点里面选出更离散分布的邻居（见参考资料1）。因此，从选边策略考虑，HNSW 是对 DG 和 RNG 的近似。</p><p>原文：<a href="https://ieeexplore.ieee.org/abstract/document/8594636">https://ieeexplore.ieee.org/abstract/document/8594636</a></p><p>中文介绍：<a href="https://blog.csdn.net/u011233351/article/details/85116719">https://blog.csdn.net/u011233351/article/details/85116719</a></p><p>代码：<a href="https://github.com/kakao/n2">https://github.com/kakao/n2</a></p><p><strong>算法3：FANNG</strong></p><p>FANNG 的选边策略与 HNSW 是一样的，都是对RNG近似。FANNG 比 HNSW 更早提出，不过当前 HNSW 得到更普遍的应用，可能的原因是：（1）FANNG 的选边策略是在暴力构建的近邻图的基础上实现的，构建效率很低；（2）HNSW 通过增量式构建且引入分层策略，构建和搜索效率都很高；（3）HNSW 开源了代码，FANNG 则没有。</p><p>原文：<a href="https://www.cv-foundation.org/openaccess/content_cvpr_2016/html/Harwood_FANNG_Fast_Approximate_CVPR_2016_paper.html">https://www.cv-foundation.org/openaccess/content_cvpr_2016/html/Harwood_FANNG_Fast_Approximate_CVPR_2016_paper.html</a></p><p><strong>算法4：NGT</strong></p><p>NGT 是雅虎日本开源的向量检索库，核心算法基于近邻图索引。NGT 在构建近邻图时类似于 NSW，也是对 DG 的近似，后续有一些度调整优化，其中最有效的路径优化也是对 RNG 的近似（论文的附件B 也给出了证明）。</p><p>原文1：<a href="https://link.springer.com/chapter/10.1007/978-3-319-46759-7_2">https://link.springer.com/chapter/10.1007/978-3-319-46759-7_2</a></p><p>原文2：<a href="https://arxiv.org/abs/1810.07355">https://arxiv.org/abs/1810.07355</a></p><p>代码：<a href="https://github.com/yahoojapan/NGT">https://github.com/yahoojapan/NGT</a></p><p><strong>算法5：SPTAG</strong></p><p>SPTAG 是微软发布的向量检索库，它的构建过程基于分治策略，即迭代地划分数据集，然后在每个子集上构建近邻图，接着归并子图，最后通过邻域传播策略进一步优化近邻图。上述过程旨在构建一个尽可能精确的 KNNG。在搜索时，SPTAG 采用树索引和图索引交替执行的方案，即先从树上获取距查询较近的点作为在图上搜索的起始点执行路由，当陷入局部最优时继续从树索引上获取入口点，重复上述操作直至满足终止条件。</p><p>原文1：<a href="https://dl.acm.org/doi/abs/10.1145/2393347.2393378">https://dl.acm.org/doi/abs/10.1145/2393347.2393378</a></p><p>原文2：<a href="https://ieeexplore.ieee.org/abstract/document/6247790">https://ieeexplore.ieee.org/abstract/document/6247790</a></p><p>原文3：<a href="https://ieeexplore.ieee.org/abstract/document/6549106">https://ieeexplore.ieee.org/abstract/document/6549106</a></p><p>中文介绍1：<a href="https://blog.csdn.net/whenever5225/article/details/108013045">https://blog.csdn.net/whenever5225/article/details/108013045</a></p><p>中文介绍2：<a href="https://cloud.tencent.com/developer/article/1429751">https://cloud.tencent.com/developer/article/1429751</a></p><p>代码：<a href="https://github.com/microsoft/SPTAG">https://github.com/microsoft/SPTAG</a></p><p>代码使用：<a href="https://blog.csdn.net/qq_40250862/article/details/95000703">https://blog.csdn.net/qq_40250862/article/details/95000703</a></p><p><strong>算法6：KGraph</strong></p><p>KGraph 是对 KNNG 的近似，是一种面向一般度量空间的近邻图构建方案。基于邻居的邻居更可能是邻居的思想，KGraph 能够快速构建一个高精度的 KNNG。后续的很多算法（比如 EFANNA、DPG、NSG、NSSG）都是在该算法的基础上的进一步优化。</p><p>原文：<a href="https://dl.acm.org/doi/abs/10.1145/1963405.1963487">https://dl.acm.org/doi/abs/10.1145/1963405.1963487</a></p><p>中文介绍：<a href="https://blog.csdn.net/whenever5225/article/details/105598694">https://blog.csdn.net/whenever5225/article/details/105598694</a></p><p>代码：<a href="https://github.com/aaalgo/kgraph">https://github.com/aaalgo/kgraph</a></p><p><strong>算法7：EFANNA</strong></p><p>EFANNA 是基于 KGraph 的优化。两者的差别主要体现在近邻图的初始化：KGraph 是随机初始化一个近邻图，而 EFANNA 是通过 KD 树初始化一个更精确的近邻图。此外，在搜索时，EFANNA 通过 KD 树获取入口点，而 KGraph 随机获取入口点。</p><p>原文：<a href="https://arxiv.org/abs/1609.07228">https://arxiv.org/abs/1609.07228</a></p><p>中文介绍：<a href="https://blog.csdn.net/whenever5225/article/details/104527500">https://blog.csdn.net/whenever5225/article/details/104527500</a></p><p>代码：<a href="https://github.com/ZJULearning/ssg">https://github.com/ZJULearning/ssg</a></p><p><strong>算法8：IEH</strong></p><p>类比 EFANNA，IEH 暴力构建了一个精确的近邻图。在搜索时，它通过哈希表获取入口点。</p><p>原文：<a href="https://ieeexplore.ieee.org/abstract/document/6734715/">https://ieeexplore.ieee.org/abstract/document/6734715/</a></p><p><strong>算法9：DPG</strong></p><p>在 KGraph 的基础上，DPG 考虑顶点的邻居分布多样性，避免彼此之间非常接近的邻居重复与目标顶点连边，最大化邻居之间的夹角，论文的附件4 证明了 DPG 的选边策略也是对 RNG 的近似。此外，DPG 最终添加了反向边，是无向图，因此它的最大出度也是非常高的（见论文附件表11）。</p><p>原文：<a href="https://ieeexplore.ieee.org/abstract/document/8681160">https://ieeexplore.ieee.org/abstract/document/8681160</a></p><p>中文介绍：<a href="https://blog.csdn.net/whenever5225/article/details/106176175">https://blog.csdn.net/whenever5225/article/details/106176175</a></p><p>代码：<a href="https://github.com/DBW">https://github.com/DBW</a></p><p><strong>算法10：NSG</strong></p><p>NSG 的设计思路与 DPG 几乎是一样的。在 KGraph 的基础上，NSG 通过 MRNG 的选边策略考虑邻居分布的均匀性。NSG 的论文中将 MRNG 的选边策略与 HNSW 的选边策略做了对比，例证了 MRNG 的优越性。论文中的附件1 证明了NSG的这种选边策略与 HNSW 选边策略的等价性。NSG 的入口点是固定的，是与全局质心最近的顶点。此外，NSG 通过 DFS 的方式强制从入口点至其它所有点都是可达的。</p><p>原文：<a href="https://www.vldb.org/pvldb/vol12/p461-fu.pdf">http://www.vldb.org/pvldb/vol12/p461-fu.pdf</a></p><p>中文介绍：<a href="https://zhuanlan.zhihu.com/p/50143204">https://zhuanlan.zhihu.com/p/50143204</a></p><p>代码：<a href="https://github.com/ZJULearning/nsg">https://github.com/ZJULearning/nsg</a></p><p><strong>算法11：NSSG</strong></p><p>NSSG 的设计思路与 NSG、DPG 几乎是一样的。在 KGraph 的基础上，NSSG 通过 SSG 选边策略考虑邻居分布的多样性。NSSG 认为，NSG 过度裁边了（见论文表4），相比之下 SSG 的裁边要松弛一些。NSG 与 NSSG 另一个重要的差异是，NSG 通过贪婪路由获取候选邻居，而 NSSG 通过邻居的一阶扩展获取候选邻居，因此，NSSG 的构建效率更高。</p><p>原文：<a href="https://ieeexplore.ieee.org/abstract/document/9383170">https://ieeexplore.ieee.org/abstract/document/9383170</a></p><p>中文介绍：<a href="https://zhuanlan.zhihu.com/p/100716181">https://zhuanlan.zhihu.com/p/100716181</a></p><p>代码：<a href="https://github.com/ZJULearning/ssg">https://github.com/ZJULearning/ssg</a></p><p><strong>算法12：Vamana</strong></p><p>简单来说，Vamana 是 KGraph、HNSW 和 NSG 三者的结合优化。在选边策略上，Vamana 在 HNSW （或 NSG）的基础上增加了一个调节参数，选边策略为 HNSW 的启发式选边，取不同的值执行了两遍选边。</p><p>原文：<a href="https://harsha-simhadri.org/pubs/DiskANN19.pdf">http://harsha-simhadri.org/pubs/DiskANN19.pdf</a></p><p>中文介绍：<a href="https://blog.csdn.net/whenever5225/article/details/106863674">https://blog.csdn.net/whenever5225/article/details/106863674</a></p><p><strong>算法13：HCNNG</strong></p><p>HCNNG 是目前为止唯一一个以 MST 为基本构图策略的向量检索算法。类似SPTAG，HCNNG 的构建过程基于分治策略，在搜索时通过 KD 树获取入口点。</p><p>原文：<a href="https://www.sciencedirect.com/science/article/abs/pii/S0031320319302730">https://www.sciencedirect.com/science/article/abs/pii/S0031320319302730</a></p><p>中文介绍：<a href="https://whenever5225.github.io/2019/08/17/HCNNG/">https://whenever5225.github.io/2019/08/17/HCNNG/</a></p><h3 id="近邻图算法“原子”级分析"><a href="#近邻图算法“原子”级分析" class="headerlink" title="近邻图算法“原子”级分析"></a><strong>近邻图算法“原子”级分析</strong></h3><p>我们发现所有的基于近邻图的 ANNS 算法都遵循统一的处理流程框架，该流程里面有多个公共模块（如图2 的 C1-&gt;C7）。当一个近邻图算法按照该流程被解构后，我们可以很容易了解该算法是如何设计组装的，这将给后续近邻图向量检索算法的设计带来很大的便利性。此外，我们也可固定其它模块，保持其他模块完全相同，从而更公平地评估不同算法在某一模块实现上的性能差异。</p><p><img src="https://pic3.zhimg.com/80/v2-c0bf2630a39dc9dbd98a8e83b994b7d6_1440w.webp" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>图2 近邻图向量检索算法遵循的统一流程框架图</p><p>接下来，我们以 HNSW 和 NSG 为例，说明如何实现图2 的流程框架分析算法。在此之前，我们要先熟悉这两个算法的索引构建和搜索过程。首先是 HNSW 分解，HNSW 的构建策略是增量式的。因此，每插入一个数据点都要执行一遍 C1-&gt;C3 过程。</p><p><strong>HNSW 分解流程：</strong></p><table><thead><tr><th>模块</th><th>HNSW 具体实现</th></tr></thead><tbody><tr><td>C1</td><td>生成新插入点所处的最大层；获取搜索入口点</td></tr><tr><td>C2</td><td>新插入点作为查询点，从入口点开始，贪婪搜索，返回新插入点一定量最近邻作为邻居候选</td></tr><tr><td>C3</td><td>启发式选边策略</td></tr><tr><td>C4</td><td>无额外步骤，最高层中的顶点作为入口</td></tr><tr><td>C5</td><td>无额外步骤，增量式构建已隐式确保连通性（启发式选边又一定程度破坏连通性）</td></tr><tr><td>C6</td><td>最高层的顶点作为入口</td></tr><tr><td>C7</td><td>最佳优先搜索</td></tr></tbody></table><p><strong>NSG 分解流程：</strong></p><table><thead><tr><th>模块</th><th>NSG 具体实现</th></tr></thead><tbody><tr><td>C1</td><td>NN-Descent 初始化近邻图</td></tr><tr><td>C2</td><td>顶点作为查询，贪婪搜索获取邻居候选</td></tr><tr><td>C3</td><td>MRNG 选边策略</td></tr><tr><td>C4</td><td>全局质心作为查询，贪婪搜索获取最近顶点作为入口</td></tr><tr><td>C5</td><td>从入口开始，DFS 确保连通性</td></tr><tr><td>C6</td><td>C4 获取的入口</td></tr><tr><td>C7</td><td>最佳优先搜索</td></tr></tbody></table><p>由于 HNSW 的 C3 与 NSG 的 C3 是等价的，因此，从上面两个表格可知，HNSW 与 NSG 这两个算法差别并不大。其实，论文涉及到的 13 种算法中很多算法之间都是很相似的，详见论文第 4 章。</p><h3 id="实验观测和讨论"><a href="#实验观测和讨论" class="headerlink" title="实验观测和讨论"></a><strong>实验观测和讨论</strong></h3><p>具体的实验评估请参考论文第 5 章，接下来将概括介绍一下实验的观测结果和讨论：</p><p><strong>不同场景下的算法推荐</strong></p><p>NSG 和 NSSG 普遍有最小的索引构建时间和索引尺寸，因此，它们适用于有大量数据频繁更新的场景；如果我们想要快速构建一个精确的 KNNG（不仅用于向量检索），KGraph、EFANNA 和 DPG 更适合；DPG 和 HCNNG 有最小的平均搜索路径长度，因此，它们适合需要 I/O 的场景；对于 LID 值高的较难数据集，HNSW、NSG、HCNNG 比较适合；对于 LID 值低的简单数据集，DPG、NSG、HCNNG 和 NSSG 较为适合；NGT 有更小的候选集尺寸，因此适用于 GPU 加速（考虑到 GPU 的内存限制）；当对内存消耗要求较高时，NSG 和 NSSG 适合，因为它们内存占用更小。</p><p><strong>算法设计向导</strong></p><p>一个实用的近邻图向量检索算法一般满足以下四个方面：</p><ol><li>高构建效率</li><li>高路由效率</li><li>高搜索精度</li><li>低内存负载</li></ol><p>针对第一方面，我们不要在提升近邻图索引质量（即一个顶点的邻居中真实的最近邻居所占的比例）上花费太多的时间。因为最好的图质量（可通过图中与距它最近的顶点有边相连的顶点所占比例度量）不一定实现最佳搜索性能（结合论文中表4 和图7、8）。针对第二方面，我们应当控制适当的平均出度，多样化邻居的分布，减少获取入口点的花费，优化路由策略，从而减少收敛到查询点的邻域所需的距离计算次数。针对第三方面，我们应当合理设计邻居的分布，确保连通性，从而提升对陷入局部最优的”抵抗力”。针对第四方面，我们应当优化邻居选择策略和路由策略，从而减小出度和候选集尺寸。</p><p><strong>优化算法示例</strong></p><p>基于上面的向导，我们组装了一个新的近邻图算法（图3 中的 OA），该算法在图2 中的 7 个组件中每个组件选中现存算法的一个具体实现，即 C1 采用 KGraph 算法的实现；C2 采用 NSSG 算法的实现；C3 采用 NSG 算法的实现；C4 采用 DPG 算法的实现；C5 采用 NSSG 算法的实现；C6 采用 DPG 算法的实现；C7 采用 HCNNG 和 NSW 算法的实现。OA 算法实现了当前最优的综合性能，详见论文原文。因此，我们甚至不用优化当前算法，仅仅把现存算法的不同部分组装起来就可以形成一个新算法。</p><p><img src="https://pic3.zhimg.com/80/v2-01854acaec370d48268c141d3ca1ad7e_1440w.webp" rel="external noreferrer nofollow noopener" referrerpolicy="no-referrer"></p><p>图3 OA 算法与当前最优的近邻图算法的搜索性能对比</p><p><strong>趋势与挑战</strong></p><p>基于 RNG 的选边策略设计在当前近邻图向量检索算法的效率提升中起到了关键作用。在论文的评估中，唯一一个基于 MST 的算法 HCNNG 也表现出来很好的综合性能。在上述纯近邻图算法基础上，后续发展有三个主要方向：</p><ol><li>硬件优化；</li><li>机器学习优化；</li><li>更高级的查询需求，比如结构化和非结构化混合查询。</li></ol><p>我们未来面对这三个挑战：</p><ol><li>数据编码技术与图索引如何有机结合以解决近邻图向量检索算法高内存消耗问题；</li><li>硬件加速近邻图索引构建减少近邻图索引构建时间；</li><li>根据不同场景的特征自适应选择最优的近邻图算法。  </li></ol><p><strong>参考资料</strong></p><p><a href="https://blog.csdn.net/whenever5225/article/details/106061653%3Fspm%3D1001.2014.3001.5501">https://blog.csdn.net/whenever5225/article/details/106061653?spm=1001.2014.3001.5501</a></p><p><strong>✏️ 关于作者：</strong></p><p>王梦召，杭州电子科技大学计算机专业硕士。主要关注基于近邻图的向量相似性检索、多模态检索等研究内容，并在相关方向申请发明专利三项，在数据库顶会 VLDB 和 SCI 一区 top 期刊 KBS 等发表论文两篇。</p><p>日常爱好弹吉他、打乒乓球、跑步、看书，他的个人网站是 <a href="https://mzwang.top/">http://mzwang.top</a>，Github主页是 <a href="http://github.com/whenever5225">http://github.com/whenever5225</a></p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;https://cdn.jsdelivr.net/hint.css/2.4.1/hint.min.css&quot;&gt;&lt;blockquote&gt;
&lt;p&gt;编者按：&lt;br&gt;以图搜图、商品推荐、社交推荐等社会</summary>
      
    
    
    
    <category term="数据库" scheme="http://posts.hufeifei.cn/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    
    
    <category term="VectorSearch" scheme="http://posts.hufeifei.cn/tags/VectorSearch/"/>
    
    <category term="SPTAG" scheme="http://posts.hufeifei.cn/tags/SPTAG/"/>
    
    <category term="KGraph" scheme="http://posts.hufeifei.cn/tags/KGraph/"/>
    
  </entry>
  
</feed>
