<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>故障恢复 - tag - 沐木</title><link>https://oldletter.cn/tags/%E6%95%85%E9%9A%9C%E6%81%A2%E5%A4%8D/</link><description>故障恢复 - tag - 沐木</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Sun, 19 Jul 2026 01:00:00 +0800</lastBuildDate><atom:link href="https://oldletter.cn/tags/%E6%95%85%E9%9A%9C%E6%81%A2%E5%A4%8D/" rel="self" type="application/rss+xml"/><item><title>千万级数据同步与 ETL 实战（四）：Micro Batch、幂等写入与故障恢复</title><link>https://oldletter.cn/posts/data-sync-etl-04-micro-batch-correctness/</link><pubDate>Sun, 19 Jul 2026 01:00:00 +0800</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/data-sync-etl-04-micro-batch-correctness/</guid><description><![CDATA[<blockquote>
<p>系列导航：<a href="/posts/data-sync-etl-01-boundaries-and-architecture/" rel="">1</a> | <a href="/posts/data-sync-etl-02-snapshot-watermark/" rel="">2</a> | <a href="/posts/data-sync-etl-03-hash-wheel-fingerprint/" rel="">3</a> | <a href="/posts/data-sync-etl-04-micro-batch-correctness/" rel="">4</a> | <a href="/posts/data-sync-etl-05-merkle-iblt/" rel="">5</a> | <a href="/posts/data-sync-etl-06-true-streaming/" rel="">6</a></p></blockquote>
<p>增量检测得到一组 INSERT、UPDATE 和 DELETE 后，数据仍未安全到达目标端。读取进程可能宕机，网络可能在响应返回前断开，目标库可能提交成功却没有被调用方确认，两个 Worker 也可能短时间内同时持有同一分片。系统能否恢复，取决于批次是否可重放、写入是否幂等、Checkpoint 何时推进，以及旧租约持有者能否继续提交。</p>
<p>Micro Batch 的作用不只是凑够若干行再发送。它是抽取结果的持久化边界，也是源端读取和目标端提交之间的恢复凭证。</p>
<h2 id="一batch-创建后保持不可变">一、Batch 创建后保持不可变</h2>
<p>一个批次由数据文件和 Manifest 组成。数据文件可使用 Arrow、Parquet 或有明确 Schema 的二进制格式；Manifest 描述这批数据从哪里来、覆盖哪个游标区间、包含多少变化，以及如何验证内容。</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-json">
        <span class="code-title"><i class="arrow fas fa-angle-right" aria-hidden="true"></i></span>
        <span class="ellipses"><i class="fas fa-ellipsis-h" aria-hidden="true"></i></span>
        <span class="copy" title=""><i class="far fa-copy" aria-hidden="true"></i></span>
    </div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;batchId&#34;</span><span class="p">:</span> <span class="s2">&#34;01J2SYNTHETIC8TQ5F6X8R3&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;taskId&#34;</span><span class="p">:</span> <span class="s2">&#34;customer-sync&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;taskVersion&#34;</span><span class="p">:</span> <span class="mi">12</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;partitionId&#34;</span><span class="p">:</span> <span class="s2">&#34;bucket-0037&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;epoch&#34;</span><span class="p">:</span> <span class="mi">42</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;schemaId&#34;</span><span class="p">:</span> <span class="s2">&#34;customer-v7&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;cursorFrom&#34;</span><span class="p">:</span> <span class="s2">&#34;2026-07-19T10:00:00.000Z|80000&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;cursorTo&#34;</span><span class="p">:</span> <span class="s2">&#34;2026-07-19T10:01:00.000Z|84321&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;recordCount&#34;</span><span class="p">:</span> <span class="mi">5000</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;insertCount&#34;</span><span class="p">:</span> <span class="mi">120</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;updateCount&#34;</span><span class="p">:</span> <span class="mi">47</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;deleteCount&#34;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;dataUri&#34;</span><span class="p">:</span> <span class="s2">&#34;batch://customer-sync/42/bucket-0037/part-0001.parquet&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;checksum&#34;</span><span class="p">:</span> <span class="s2">&#34;sha256:...&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;createdAt&#34;</span><span class="p">:</span> <span class="s2">&#34;2026-07-19T10:01:12Z&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p><code>batchId</code> 一经生成就对应固定字节。重试只能重复发送该批次，不能复用同一个 ID 重新查询源端并覆盖内容。源数据可能在两次查询之间变化，同 ID 不同内容会破坏去重和审计。</p>
<p>批次写入可采用临时对象加原子完成标记：先写数据文件，计算长度和校验和，再写 Manifest，随后将状态改为 <code>READY</code>。本地文件系统可使用同卷原子重命名；对象存储通常没有目录重命名语义，可以用不可变对象 Key 和独立状态记录。看到 <code>READY</code> 前，Sink Worker 不得消费。</p>]]></description></item></channel></rss>