<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>PostgreSQL - Tag - SaiyuDoc</title><link>https://www.wangsaiyu.com/tags/postgresql/</link><description>PostgreSQL - Tag - SaiyuDoc</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><managingEditor>panicwang@outlook.com (王赛宇)</managingEditor><webMaster>panicwang@outlook.com (王赛宇)</webMaster><lastBuildDate>Wed, 14 Oct 2026 14:38:00 +0800</lastBuildDate><atom:link href="https://www.wangsaiyu.com/tags/postgresql/" rel="self" type="application/rss+xml"/><item><title>PostgreSQL CDC 实践：统一分析库、断点恢复与 DTS 取舍</title><link>https://www.wangsaiyu.com/posts/engineering/postgresql-cdc-analytics/</link><pubDate>Wed, 14 Oct 2026 14:38:00 +0800</pubDate><author>王赛宇</author><guid>https://www.wangsaiyu.com/posts/engineering/postgresql-cdc-analytics/</guid><description><![CDATA[<p>业务拆成多个服务之后，数据也分散到了不同的数据库中。项目、内容、推荐和通知各自维护自己的表，但运营分析往往需要把这些数据放在一起：按天看项目生成情况，关联内容的发布与曝光，再按不同维度计算中间结果。</p>
<p>我们为此建设了独立的分析库，并把生产 PostgreSQL 到分析库的同步交给 GamePlatform 管理。底层使用 Debezium 读取数据库变更，平台负责接入范围、表映射、运行调度和故障恢复。本文介绍这条链路如何工作、上线后遇到的具体问题，以及我们为什么在已有阿里云 DTS 的情况下继续建设它。</p>
<p>以下实现与运行记录核对至 2026 年 10 月 14 日 14:36（北京时间）；文中的示例表名、主键和执行代次均为示意。</p>
<h2 id="1-统一分析库要解决什么问题">1. 统一分析库要解决什么问题</h2>
<p>最初讨论这个需求时，我们希望把生产数据库的数据集中到一个独立的 AnalyticDB PostgreSQL 实例，以下简称 ADB。在原始副本之上，按小时或按天计算中间表，最后由 Grafana、运营后台查询这些结果。</p>
<p>这里有两层不同的工作。第一层是让分析侧持续获得业务数据，第二层是定义指标、执行聚合并产出报表。CDC，也就是 Change Data Capture，负责捕获数据库里的变化，主要解决第一层问题。把表同步过去，并不会自动得到留存率、转化率或推荐效果，这些仍然依赖后续的数据模型和计算任务。</p>
<p>独立分析库让跨服务查询有了统一的位置，也把长时间扫描、复杂关联和报表计算移出了业务数据库。业务库依然会承担首次补数读取、逻辑解码和日志保留的成本，因此这不是一条没有源端开销的链路。收益在于，后续每一种分析需求都可以复用同一份副本，不必再向各个在线服务增加一套查询接口。</p>
<p>我们最初考虑通过平台自动创建 DTS 任务。随着纳管范围从少数数据库扩大到多个实例、二十多个逻辑库，逐库维护任务以及对应的资源成本，开始成为需要单独处理的问题。我们最终保留已有 DTS，同时为新的统一分析库建设共享的 CDC 执行层。</p>
<p>这个决定包含两个目标：让多库同步复用计算资源；让数据库的发现、接入、变更和排障，都能使用研发平台已有的目录与操作记录。是否比 DTS 更便宜，还需要把开发、维护和故障处理一起算进去，后文会展开。</p>
<h2 id="2-三部分如何配合">2. 三部分如何配合</h2>
<p>整条链路由源数据库、同步进程和目标数据库组成，GamePlatform 在外层管理它们之间的关系。</p>
<figure><a class="lightgallery" href="/images/postgresql-cdc-analytics/01-architecture.svg" title="GamePlatform 控制计划和租约，每个源数据库由独立 Debezium reader 将业务表和 WAL 变更写入 ADB" data-thumbnail="/images/postgresql-cdc-analytics/01-architecture.svg" data-sub-html="<h2>图 1：控制记录与数据传输的职责分工；图中的两个数据库为结构示意</h2>">
        
    </a><figcaption class="image-caption">图 1：控制记录与数据传输的职责分工；图中的两个数据库为结构示意</figcaption>
    </figure>
<h3 id="21-源数据库保存业务数据和-wal">2.1. 源数据库保存业务数据和 WAL</h3>
<p>PostgreSQL 在写入数据时生成 WAL，即预写日志。逻辑解码可以从日志中提取行的插入、更新和删除。我们使用 PostgreSQL 的 <code>pgoutput</code> 插件，并为每个逻辑数据库建立独立的 publication 和 replication slot。</p>
<p>publication 描述需要发布哪些表的变化；replication slot，也就是复制槽，跟踪消费者需要的日志位置。一个物理 RDS 实例可以有多个逻辑数据库，而复制槽属于具体数据库。因此，平台界面可以按实例管理接入，底层仍然需要按库组织复制进程。<a href="https://www.postgresql.org/docs/current/logicaldecoding-explanation.html" target="_blank" rel="noopener noreffer ">PostgreSQL 的逻辑解码说明</a>介绍了复制槽的这一边界。</p>
<h3 id="22-同步进程负责读取转换和写入">2.2. 同步进程负责读取、转换和写入</h3>
<p>每个数据库对应一个 reader。reader 使用独立的 Java 进程运行 Debezium Server，通过 JDBC 把数据写入 ADB。多个 reader 共享 GamePlatform 的 analytics 容器，由 TypeScript 编写的管理逻辑负责启动、停止、续租和重试。</p>
<p>我们没有重新实现 PostgreSQL 日志解析，也没有在这条链路中部署 Kafka。Debezium 负责快照与变更捕获，平台增加表名映射、目标写入隔离、断点校验和扫描失败保护。这一分工使平台代码主要围绕“哪些数据可以同步、谁能写、失败后如何继续”展开。</p>
<p>不经过独立消息队列也有直接后果：ADB 写入变慢时，变更无法无限堆在一个中间系统里等待。尚未安全消费的日志仍依赖源数据库保留，源端 WAL 容量因此成为运行约束。</p>
<h3 id="23-目标数据库保存副本和恢复信息">2.3. 目标数据库保存副本和恢复信息</h3>
<p>业务副本统一写入 ADB 的 <code>game_analytics.public</code>。目标表名由来源实例、数据库、schema 和表名生成，以避免多个数据库中的同名表互相覆盖。下面是命名示意，名称不对应实际业务表：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">prod_a / project / public.projects
</span></span><span class="line"><span class="cl">    → prod_a_project_projects
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">prod_b / project / public.projects
</span></span><span class="line"><span class="cl">    → prod_b_project_projects
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">prod_a / project / archive.projects
</span></span><span class="line"><span class="cl">    → prod_a_project_archive_projects
</span></span></code></pre></div><p>对于非 <code>public</code> schema，名称中额外保留 schema。超过标识符长度限制时，保留可读前缀并追加稳定摘要。完整映射另行持久保存，实例改名不会自动改掉已经使用的目标表名。发现命名碰撞，或者目标库已有同名表却没有对应归属记录时，平台会停止接入。</p>
<p>除了业务副本，ADB 的独立元数据 schema 还保存原生 offset、快照完成状态和 writer epoch。offset 是引擎的恢复位置；epoch 是递增的执行代次，用来拒绝旧进程继续写入。平台自身的控制账本则保存接入意图、执行计划、租约和恢复操作。两边的记录用途不同，不能只凭平台显示 <code>running</code> 就判断目标数据已经完整。</p>
<h2 id="3-全量数据与增量变化如何衔接">3. 全量数据与增量变化如何衔接</h2>
<h3 id="31-先判断一张表适合哪种方式">3.1. 先判断一张表适合哪种方式</h3>
<p>平台先读取源库结构，检查读取权限、主键、字段类型、复制标识和 publication 成员，再生成执行计划。当前实现把表分成四类：</p>
<table>
  <thead>
      <tr>
          <th>类型</th>
          <th>当前处理方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>满足条件的普通主键表</td>
          <td>分块补齐存量，并持续消费 WAL 变更</td>
      </tr>
      <tr>
          <td>无主键、含 vector 或生成列的表</td>
          <td>每小时执行有界完整快照</td>
      </tr>
      <tr>
          <td>系统表、平台内部表、同步设施表</td>
          <td>按明确规则排除</td>
      </tr>
      <tr>
          <td>权限不足、类型未支持或结构不符合要求的表</td>
          <td>保留阻塞原因，停止相应 reader 接入</td>
      </tr>
  </tbody>
</table>
<p>小时快照中，vector 按文本表示保存，生成列复制源端已经计算出的值。执行器在源端开启只读的可重复读事务，在目标端用一个事务替换本轮表集合；某张表读取失败或超过限额时，目标事务回滚，查询者继续看到上一份已提交快照。</p>
<p>当前这条备用路径按每个 reader 的整轮任务限制为 10 万行、64 MiB 和 60 秒。它适合已验证规模内的小表，也意味着这些表只有小时级刷新能力。不能因为它们被纳入“CDC 项目”，就把所有表都描述为实时同步。</p>
<h3 id="32-用主键分块建立基线">3.2. 用主键分块建立基线</h3>
<p>最早的实现使用初始全量快照，未完成时遇到进程重启，往往需要重新开始。平台持续发布，而大表可能需要很长时间才能复制完成，两者叠加后，已有进度会反复丢失。</p>
<p>后来我们把新建和受控重建的 reader 改为 Debezium 的只读增量快照：先建立 WAL 消费边界，再按主键区间分块读取存量数据。这里的“增量快照”是分块补数机制，仍然可以用来复制整张表的已有数据；它与持续消费 WAL 的“增量同步”是两个概念。</p>
<p>假设有一张示意表 <code>projects(id, title, status)</code>，我们正在读取包含 <code>id=42</code> 的分块。读取时，这行的状态是 <code>draft</code>；分块处理期间，业务把它更新为 <code>published</code>，WAL 中也出现了相应更新。快照记录与日志变化必须协调，否则较旧的快照值可能覆盖较新的状态。</p>
<figure><a class="lightgallery" href="/images/postgresql-cdc-analytics/02-snapshot.svg" title="快照读到 id 为 42 的旧状态，同时 WAL 捕获新状态，Debezium 协调快照窗口中的同主键事件后写入目标" data-thumbnail="/images/postgresql-cdc-analytics/02-snapshot.svg" data-sub-html="<h2>图 2：同一快照窗口内，日志变化覆盖同主键的旧快照记录；数据和主键区间均为示意</h2>">
        
    </a><figcaption class="image-caption">图 2：同一快照窗口内，日志变化覆盖同主键的旧快照记录；数据和主键区间均为示意</figcaption>
    </figure>
<p>Debezium 在快照窗口中比较主键，去掉已经被对应日志事件取代的旧快照记录，同时继续处理实时变化。分块状态可以进入恢复断点，使中断后的任务从已经提交的位置继续。窗口与去重由引擎实现，平台不自行拼接两路无序的数据流。<a href="https://debezium.io/documentation/reference/3.6/connectors/postgresql.html#postgresql-incremental-snapshots" target="_blank" rel="noopener noreffer ">Debezium 增量快照文档</a>说明了这一机制。</p>
<p>普通记录库当前采用 512 行分块，大消息库保持 64 行分块。行数需要结合单行大小选择：几千万条短记录和包含几十 MB 内容的记录，并不适合相同参数。</p>
<h3 id="33-写入与断点之间允许重放">3.3. 写入与断点之间允许重放</h3>
<p>目标写入使用主键 upsert，并处理删除。upsert 的含义是：目标已有这个主键时更新它，否则插入。假设 <code>id=42</code> 的更新已经提交到 ADB，但 reader 在保存下一次 offset 前退出，恢复时就可能再次收到这条更新。</p>
<figure><a class="lightgallery" href="/images/postgresql-cdc-analytics/03-checkpoint.svg" title="目标数据先提交，随后持久化断点，再确认日志消费进度；两次提交之间退出时会重放" data-thumbnail="/images/postgresql-cdc-analytics/03-checkpoint.svg" data-sub-html="<h2>图 3：正常提交顺序的简化示意。数据写入与 offset 使用不同连接，不是同一个原子事务</h2>">
        
    </a><figcaption class="image-caption">图 3：正常提交顺序的简化示意。数据写入与 offset 使用不同连接，不是同一个原子事务</figcaption>
    </figure>
<p>再次 upsert 相同主键，可以把目标恢复到同一行状态；重复删除也不应生成额外业务动作。因此，我们按“至少一次传递，加幂等写入”理解这条链路。JDBC sink 的幂等写入能力也依赖合适的主键和 upsert 配置，不能由它推导出跨数据库事务或端到端 exactly-once 保证。<a href="https://debezium.io/documentation/reference/3.6/connectors/jdbc.html" target="_blank" rel="noopener noreffer ">Debezium JDBC sink 文档</a>对此作了区分。</p>
<p>平台增加的约束是：每次原生 offset 提交都在事务内校验当前 epoch，快照完成标记与 offset 一起提交。扫描发生失败或跳过表时，保护逻辑拒绝继续提交检查点，避免把不完整副本报告为完成。</p>
<p>“完成”的判定本身也需要验证。10 月 14 日的专项排查发现，快速完成的空表快照可能在两次 offset flush 之间结束，中间快照状态没有被持久化，旧判定就可能一直等不到完成证明。这是已经找到代码及隔离实验依据的边界问题；同次生产异常的最初触发原因仍有未确认部分。把完成标记写入事务，只解决记录的原子性，还需要证明这个标记确实对应所要求的数据集合。</p>
<h2 id="4-重启之后谁有权继续写">4. 重启之后，谁有权继续写</h2>
<p>平台有多个副本，网络断开或进程暂停时，旧执行器可能尚未退出，新执行器已经尝试接管。只在控制账本中设置“任务被谁领取”，不足以阻止旧进程向 ADB 写数据。</p>
<p>我们把执行权分成几层约束。平台用有期限的租约分配 reader；原生 sink 在实际目标连接上持有该 reader 的 advisory lock，排除同时存在的写入连接；持久化 epoch 再排除失联后重新连接的旧进程。</p>
<figure><a class="lightgallery" href="/images/postgresql-cdc-analytics/04-writer-fence.svg" title="旧 reader 持有 epoch 7，新 reader 在旧连接退出后接管 epoch 8，旧 reader 重连时因代次过旧被拒绝" data-thumbnail="/images/postgresql-cdc-analytics/04-writer-fence.svg" data-sub-html="<h2>图 4：连接锁防止同时写入，持久代次防止旧进程重新回来；7 和 8 为示意代次</h2>">
        
    </a><figcaption class="image-caption">图 4：连接锁防止同时写入，持久代次防止旧进程重新回来；7 和 8 为示意代次</figcaption>
    </figure>
<p>例如旧进程属于 epoch 7，新进程接管后推进到 epoch 8。旧连接仍在时，新进程不能取得同一个 reader 的写锁；旧连接消失后，新进程才可以接管。即使 epoch 7 的进程稍后恢复网络，也会在重新连接或提交断点时被拒绝。</p>
<p>这些保护还依赖正确的排空顺序：先使旧计划失效，等待旧写入者停止，再准备目标并启动新一代。平台对 reader 的长期运行采用有限重试与退避，预算不足时等待资源，不把等待本身计为一次运行失败。</p>
<p>另一条恢复边界在源端。复制槽要求 PostgreSQL 保留后续仍需消费的 WAL，消费者停滞会扩大保留区间。同一物理实例其他数据库的写入，也会增加这段 WAL 的体积。若日志已经被回收、复制槽失效，或者目标断点早于源端可恢复范围，就不能简单从最新位置继续并声称没有漏数。</p>
<p>这时需要停止普通重试，根据来源、目标归属和连续性证据选择受控重建或定向对账补数。重建有自己的操作编号和范围检查；有效断点、其他 reader 的数据和历史保留表，不应被一次通用“重试”顺手清掉。</p>
<h2 id="5-表结构变化也是同步流程的一部分">5. 表结构变化也是同步流程的一部分</h2>
<p>业务持续新增表和修改字段，同步计划也必须跟着变化。我们的实现读取数据库目录，比较新旧计划，再决定是否可以接续；它没有把 PostgreSQL 所有 DDL 原样转发给 ADB。</p>
<p>计划包含来源身份、表映射、主键、列类型和同步方式。行数估计变化、实例展示名称变化、被排除的内部表变化，不应使同一份有效执行计划失效。真正改变数据含义的差异，则需要分类处理。</p>
<table>
  <thead>
      <tr>
          <th>变化</th>
          <th>当前处理原则</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>兼容的追加列</td>
          <td>排空旧 writer，只修改受影响目标表，保留原断点</td>
      </tr>
      <tr>
          <td>新增符合条件的主键表</td>
          <td>只为新增表安排分块快照，保留其他表及 WAL 位置</td>
      </tr>
      <tr>
          <td>源表退役</td>
          <td>在通过相应检查的路径下保留历史目标副本与归属</td>
      </tr>
      <tr>
          <td>改主键、删列、不兼容类型或映射变化</td>
          <td>保留已有数据，阻塞并等待复核</td>
      </tr>
      <tr>
          <td>源槽丢失</td>
          <td>在明确允许的范围内，进入受控恢复流程</td>
      </tr>
  </tbody>
</table>
<p>追加列也不等于任何 <code>ALTER TABLE</code> 都能自动执行。例如新列要求非空，却没有可移植的常量默认值，平台不能凭空为历史行生成合法值。对于允许接续的非空新列，分析目标还需要容纳加列前保留的 WAL 记录，因此不会机械照搬所有源约束。</p>
<p>新增表的另一个问题在源端。表存在，不代表复制账号能够读取它，也不代表它已加入 publication。我们曾遇到按月建表成功，而 SELECT 权限、<code>REPLICA IDENTITY FULL</code> 和 publication 准备缺失，导致新分区阻塞同步的情况。</p>
<p>在已经接入的业务建表流程中，我们把所属表的 CDC 准备放进同一事务，准备失败就不能把建表工作报告为完整成功。平台也增加了按数据库显式启用的新增表准备策略，但它有独立的权限和范围限制，不能把“代码支持”理解为所有生产库默认开放。创建新来源实例，仍需要纳管、配置和预检。</p>
<p>要求 <code>REPLICA IDENTITY FULL</code> 是当前链路的具体选择：它帮助我们处理更新事件中大型 TOAST 字段的旧值与完整行内容，同时增加日志和内存开销。不同于应用层双写，这些准备不会要求每个业务写接口再调用一次分析服务；但数据库建表方需要承担结构接入责任。</p>
<h2 id="6-上线后遇到的几类问题">6. 上线后遇到的几类问题</h2>
<p>这套系统的工作量，大部分出现在第一次把数据写入 ADB 之后。下面选取几类实际问题，说明平台为什么需要超出“启动一个 Debezium 进程”的能力。</p>
<h3 id="61-大记录与大表是两种负载">6.1. 大记录与大表是两种负载</h3>
<p>一次生产排查发现，消息表中存在约 41 MB 的单行记录。复制时可能同时存在更新前后图像、解码对象和写入参数，额外生成一份并不使用的 JSON 副本，也会放大内存。即使容器仍有空闲内存，单个 JVM 也可能先耗尽自己的堆。</p>
<p>我们因此去掉无用序列化，限制事件批次和队列，并为大消息 reader 预留更大的堆。队列的 4 MiB 字节限制只约束排队部分，不能限制已出队批次和单条超大记录的全部内存占用。</p>
<p>另一次问题恰好相反：一张约 5600 万行的表仍使用 64 行分块，任务一直前进，但大量数据库往返与提交拖慢了补数，源端 WAL 积压一度达到约 13 GiB。普通记录库改为 512 行分块后继续完成补数，大消息库仍保留较小分块。</p>
<p>这两次经历不能合并成“批次越大越好”或“批次越小越稳定”。需要同时观察记录大小、每次往返成本、目标写入速度和 WAL 余量。</p>
<h3 id="62-类型正确性需要真实数据验证">6.2. 类型正确性需要真实数据验证</h3>
<p>早期使用的 JDBC 批量优化路径绕过了正确的逻辑类型绑定，数组和高精度数值出现写入失败，部分二进制值还曾被写成对象的描述文字。后来改回普通 JDBC 类型绑定，并针对已经写错的历史目标做了单独核验与受控修复。</p>
<p>分块边界也有类型问题。带时间戳的复合主键，如果把边界值按字符串绑定，PostgreSQL 可能拒绝比较；表现就是首个分块已经完成，后续却不再推进。修复参数类型推断后，还需要验证微秒精度、分区和中途续跑。</p>
<p>这些问题说明，小量字符串和整数的测试，只能证明那一组输入能够通过。数组、精确数值、二进制、大字段和复合主键，都需要进入相应的验证样本。</p>
<h3 id="63-恢复范围过大会制造新的停顿">6.3. 恢复范围过大会制造新的停顿</h3>
<p>新表出现时重补整个数据库，会把局部变化放大成所有表的恢复工作。源表退役后，如果要求“当前活动表集合”与“历史目标归属集合”完全相等，又会把应当保留的旧副本误判成冲突。</p>
<p>我们逐步把这些情况拆开：新表只补新表，兼容加列只更新对应目标结构，退役表保留经过核验的历史副本。恢复时仍然核对归属，但不再把不同语义的集合强行当作同一集合。</p>
<p>10 月 14 日还发现了恢复操作影响其他 reader 的问题：长时间对账过早更新共享的围栏元数据表，并持有写锁直到复制结束，ADB 的锁行为让其他 reader 的断点提交也受到影响。相应修改把 epoch 的比较更新移到最终提交阶段，复制期间保留该 reader 自身的互斥。截至本文核查时间，这项修改已经进入主干，发布验收仍由专项任务继续跟进；不能把本地并发回归通过写成生产验收完成。</p>
<h3 id="64-运行状态不能替代数据进度">6.4. 运行状态不能替代数据进度</h3>
<p>我们也遇到过进程和 HTTP 健康检查正常，但原生复制线程已经失败的情况；以及所有业务表都处于 blocked，健康计算却误把它们当作空库的情况。</p>
<p>现在的检查需要把几种证据放在一起：控制器是否持续工作，reader 的租约和心跳是否有效，原生错误是否出现，持久 LSN 是否推进，分块是否提交，以及源槽是否仍有足够日志保留空间。</p>
<p>低流量数据库尤其容易误判。“最后一条业务数据很久没有变化”可能只是没有新业务，不一定是同步停了。每库的独立心跳行参与 publication，也写入目标，使静默库能够推进复制位置。反过来，只更新平台时间戳、或者仅有 WAL 心跳，也不能证明一张大表的分块补数仍在前进。</p>
<p>最后还要确认告警规则确实部署成功。曾经有三条 CDC 规则因权限或配额失败而没有生效，后续条件恢复后也没有自动重试。监控代码存在、页面有状态、告警实际能够发现异常，是三个需要分别核实的事实。</p>
<h2 id="7-与阿里云-dts-相比优势在哪里">7. 与阿里云 DTS 相比，优势在哪里</h2>
<p>比较之前，需要确定具体链路。这里讨论的是我们使用的生产 PostgreSQL 到 ADB PostgreSQL，而不是把 DTS 所有数据源的限制混在一起。以下产品能力按 2026 年 10 月 14 日查阅的官方文档描述。</p>
<h3 id="71-dts-已经提供的能力">7.1. DTS 已经提供的能力</h3>
<p>DTS 的 RDS PostgreSQL → ADB PostgreSQL 链路支持结构、全量和增量同步，并在版本、账号和任务条件满足时支持创建表、追加列、删除表等多类 DDL。特定调用方式和事务组合仍有限制，应按实际链路核对。<a href="https://help.aliyun.com/zh/dts/user-guide/synchronize-data-from-an-apsaradb-rds-for-postgresql-instance-to-an-analyticdb-for-postgresql-instance-new" target="_blank" rel="noopener noreffer ">阿里云官方链路文档</a>给出了支持范围。</p>
<p>DTS 也提供任务管理、同步对象修改和告警等 API，可以被研发平台统一编排；它还提供结构、全量及增量数据校验，具体适用性需要核对源端、目标端与校验方式。因此，自动化接入、DDL 和一致性检查，都不能笼统地写成自建方案独有的功能。参见 <a href="https://help.aliyun.com/zh/dts/developer-reference/dts-api" target="_blank" rel="noopener noreffer ">DTS API 概览</a>和<a href="https://help.aliyun.com/zh/dts/user-guide/what-is-data-verification" target="_blank" rel="noopener noreffer ">数据校验说明</a>。</p>
<h3 id="72-我们已经获得的几项价值">7.2. 我们已经获得的几项价值</h3>
<p><strong>第一项是资源组织方式。</strong> 底层仍然一库一个 reader，但多个 reader 可以共享已有计算容器，并按内存预算分配资源。新增一个逻辑库，不必同时新建一个对应的 DTS 计费实例。对于数据库较多、负载差异较大的场景，这提供了合并资源预算的空间；高负载库也仍然需要独立评估容量。</p>
<p><strong>第二项是接入规则与业务生命周期结合。</strong> 来源使用平台已纳管目录，目标名称、表归属、生产环境边界和建表准备遵循同一套规则。新增服务、新建月份表、退役业务表时，可以沿用这些规则处理后续同步，而不是让分析侧再次解释一遍业务意图。</p>
<p><strong>第三项是能够修改具体的恢复行为。</strong> 大记录如何限制内存，兼容加列是否保留断点，退役副本是否保留，恢复操作是否影响其他 reader，都是我们可以在实现中检查、复现和修改的内容。定制空间已经在上述故障处理中发挥作用，同时也意味着这些实现的正确性由我们负责。</p>
<p><strong>第四项是运维证据集中。</strong> 平台能够关联来源目录、执行计划、阻塞原因、目标映射、重试操作和现有告警体系。排查新增表未同步时，可以沿着这些记录定位，而不只看到一个外部任务的最终状态。这是当前平台整合后的收益，并不意味着 DTS 的信息不能通过 API 接入同一平台。</p>
<table>
  <thead>
      <tr>
          <th>比较项</th>
          <th>当前自建方案</th>
          <th>DTS</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>数据传输基础</td>
          <td>Debezium 与 JDBC，团队维护运行适配</td>
          <td>云服务提供并维护传输能力</td>
      </tr>
      <tr>
          <td>资源安排</td>
          <td>多库 reader 共享预算，自行规划接管余量</td>
          <td>按产品提供的规格和计费模式选择</td>
      </tr>
      <tr>
          <td>平台整合</td>
          <td>已接入自身目录、映射、建表与恢复流程</td>
          <td>可通过 API 建设相应整合</td>
      </tr>
      <tr>
          <td>结构变化</td>
          <td>按已实现的安全差异分类处理</td>
          <td>按具体链路的 DDL 支持范围处理</td>
      </tr>
      <tr>
          <td>故障修复</td>
          <td>可以改代码，同时承担定位、回归和发布</td>
          <td>使用产品运维能力及服务支持</td>
      </tr>
      <tr>
          <td>数据验证</td>
          <td>现有类型、恢复回归和有界抽样</td>
          <td>提供专门的校验能力，需核对适用范围</td>
      </tr>
  </tbody>
</table>
<h3 id="73-成本优势需要按总成本验证">7.3. 成本优势需要按总成本验证</h3>
<p>最初希望避免逐库采购，是这次选择的重要动机，但不能把少买几条链路直接换算成总成本下降。DTS 当前也有不同计费模式，实际费用取决于所选产品和规格，不能沿用某次历史报价推导今天的价格。<a href="https://help.aliyun.com/zh/dts/product-overview/billing-overview" target="_blank" rel="noopener noreffer ">DTS 计费概述</a>列出了相应规则。</p>
<p>可以用下面这个简化模型组织对比，它不是实测账单：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">托管方案成本 ≈ DTS 实际费用 + 接入与日常运维投入
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">自建方案成本 ≈ 计算与容灾余量 + 开发维护投入 + 值守与故障处理投入
</span></span></code></pre></div><p>两种方式都还需要考虑源端读取、WAL 保留、网络和目标写入等成本。要得出可靠结论，应在相同数据规模、延迟目标、恢复要求和统计周期下比较，并记录工程时间。本文没有这组完整数据，也没有同负载的 DTS 性能对照，因此不宣称已经节省某个百分比，或同步速度超过 DTS。</p>
<p>还有一种可行组合是保留平台的目录、策略与操作记录，让 DTS 承担底层复制。平台管理能力与自建传输引擎并不需要绑定选择。我们目前选择 Debezium，主要看中共享资源和定制恢复路径的空间；已有四条 DTS 仍与新链路并存，账号、publication、slot 和目标各自独立。</p>
<h2 id="8-当前规模与仍需解决的问题">8. 当前规模与仍需解决的问题</h2>
<p>2026 年 10 月 14 日的目录核查覆盖 4 个来源实例、21 个有效 reader；当日 14:21 的目标覆盖记录包含 563 张应有目标表。这里的“有效”排除了空库和已经退出生产目录的历史恢复库。表存在、归属正确和状态处于 streaming，分别只说明对应层面的事实，不能合起来替代所有数据的完整校验。</p>
<p>10 月 13 日的完整验收记录确认 Gateway 历史补数完成，21 个 reader 在两轮采样中均有持久 LSN 推进，并完成了 69 张表的有界抽样。10 月 14 日仍然出现了前文所述的完成证明和恢复锁问题。这些记录说明链路已经承担实际同步工作，也说明一个时点的验收不能作为长期可靠性的证明。</p>
<p>接下来首先需要处理发布与容量边界。analytics 虽然是独立容器，仍与平台位于同一组 Pod，并使用同一制品发布。分块续跑减轻了重启代价，却没有消除共同发布带来的中断。让同步执行器拥有独立发布节奏，是后续架构方向，尚不能当作已完成能力。</p>
<p>同样，两个副本能够分担正常负载，不等于任意一个退出后，另一个就能接管全部 reader。容量规划需要把 JVM 堆、原生内存、大记录和接管余量一起计算，并通过故障演练验证。只看集群平均 CPU，无法回答这个问题。</p>
<p>数据语义也有明确限制。目标是分析副本，不完整复制源库的外键和次级唯一约束；多个来源之间没有共同的事务快照；部分表按小时刷新。因此它适合构建分析原始层，不能直接作为业务主库替代品，跨库关联和时间窗口仍需在分析模型中处理。</p>
<p>如果需求是少量标准数据库之间的持续复制，产品支持范围已经满足，而且团队希望减少底层维护工作，DTS 是值得优先评估的方案。当多库接入、业务建表、历史副本策略和平台恢复流程成为主要工作时，自建这一层也有实际价值。对我们而言，后续要继续验证的是：这些整合收益，能否在可接受的维护投入下，持续换来明确的数据时效、恢复时间和正确性。</p>]]></description></item></channel></rss>