<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title>Hash Wheel - tag - 沐木</title><link>https://oldletter.cn/tags/hash-wheel/</link><description>Hash Wheel - 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/hash-wheel/" rel="self" type="application/rss+xml"/><item><title>千万级数据同步与 ETL 实战（三）：Hash Wheel、行指纹与删除识别</title><link>https://oldletter.cn/posts/data-sync-etl-03-hash-wheel-fingerprint/</link><pubDate>Sun, 19 Jul 2026 01:00:00 +0800</pubDate><author>mumu</author><guid>https://oldletter.cn/posts/data-sync-etl-03-hash-wheel-fingerprint/</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>有些源表只有主键和业务字段，没有更新时间、行版本、删除标记，也拿不到数据库日志。要发现历史记录被修改或删除，完整周期仍需检查全部记录。工程上的问题变成：怎样把一次重扫描拆开，怎样保证同一条记录总落到同一个分区，怎样避免读取中断造成误删除，以及怎样把目标写入量限制在真实变化量附近。</p>
<p>Hash Wheel 负责把全表检查平摊到时间轴，Fingerprint Index 保存上一次看到的状态，Generation 在完整扫描后识别删除。这三个组件必须共同工作。只实现“主键取模加定时查询”，无法形成可恢复的变化检测链路。</p>
<h2 id="一身份键和内容指纹分开">一、身份键和内容指纹分开</h2>
<p>每条记录先抽象为二元组：</p>
<p>$$
Entry=(IdentityKey, RowFingerprint)
$$</p>
<p><code>IdentityKey</code> 由稳定主键或业务键得到，用于确认记录身份。<code>RowFingerprint</code> 由参与同步的字段计算，用于判断内容变化。两者的判断关系如下：</p>
<table>
  <thead>
      <tr>
          <th>旧状态</th>
          <th>本轮状态</th>
          <th>结果</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>不存在</td>
          <td>存在</td>
          <td>INSERT</td>
      </tr>
      <tr>
          <td>存在</td>
          <td>指纹不同</td>
          <td>UPDATE</td>
      </tr>
      <tr>
          <td>存在</td>
          <td>指纹相同</td>
          <td>NO_CHANGE</td>
      </tr>
      <tr>
          <td>存在</td>
          <td>完整扫描后未见</td>
          <td>DELETE</td>
      </tr>
  </tbody>
</table>
<p>业务键允许修改时，它不再是稳定身份。系统只能把键变更表达成旧键删除和新键新增，除非源端另有不可变 ID 或变更日志能够关联前后值。这个限制需要进入数据集契约。</p>
<p>身份键可直接保存原主键，也可以保存带命名空间的摘要：</p>
<p>$$
IdentityKey=H(datasetId \parallel canonical(primaryKey))
$$</p>
<p>跨数据集使用同一主键时，<code>datasetId</code> 防止状态键冲突。原主键是否保留取决于审计和回查需求；只有摘要会增加碰撞处理与问题定位成本。</p>
<h2 id="二hash-前先定义-canonicalization">二、Hash 前先定义 Canonicalization</h2>
<p>指纹错误通常源于编码规则，而非 Hash 函数。以下值在某些业务语义下相同，直接转字符串却会得到不同字节：</p>
<div class="code-block code-line-numbers open" style="counter-reset: code-block 0">
    <div class="code-header language-text">
        <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-text" data-lang="text"><span class="line"><span class="cl">Decimal: 1、1.0、1.00
</span></span><span class="line"><span class="cl">Time:    2026-07-19 10:00:00、2026-07-19T02:00:00Z
</span></span><span class="line"><span class="cl">JSON:    {&#34;a&#34;:1,&#34;b&#34;:2}、{&#34;b&#34;:2,&#34;a&#34;:1}
</span></span><span class="line"><span class="cl">String:  Unicode 组合字符与预组合字符</span></span></code></pre></div></div>
<p>每次编码口径调整都要提升 <code>canonicalizationVersion</code>，旧指纹只按旧口径解释。字段规则包括：</p>]]></description></item></channel></rss>