<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>数据库 &#8211; mylogs.cn</title>
	<atom:link href="https://mylogs.cn/tag/%e6%95%b0%e6%8d%ae%e5%ba%93/feed/" rel="self" type="application/rss+xml" />
	<link>https://mylogs.cn</link>
	<description>发现、记录、分享</description>
	<lastBuildDate>Mon, 10 Aug 2026 08:50:33 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	
	<item>
		<title>Postgres 数据复制终于不用折腾了：Snowflake 把 CDC 塞进了数据库内核</title>
		<link>https://mylogs.cn/snowflake-postgres-cdc-data-mirroring/</link>
		
		<dc:creator><![CDATA[steve, zhang]]></dc:creator>
		<pubDate>Mon, 10 Aug 2026 08:49:25 +0000</pubDate>
				<category><![CDATA[科技]]></category>
		<category><![CDATA[CDC]]></category>
		<category><![CDATA[Iceberg]]></category>
		<category><![CDATA[Postgres]]></category>
		<category><![CDATA[Snowflake]]></category>
		<category><![CDATA[数据复制]]></category>
		<category><![CDATA[数据库]]></category>
		<category><![CDATA[架构]]></category>
		<guid isPermaLink="false">https://mylogs.cn/snowflake-postgres-cdc-data-mirroring/</guid>

					<description><![CDATA[将事务数据库的数据实时同步到分析平台，是现代数据架构中最基础也最头疼的问题之一。传统方案依赖外部 CDC 工具 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">将事务数据库的数据实时同步到分析平台，是现代数据架构中最基础也最头疼的问题之一。传统方案依赖外部 CDC 工具，架构脆弱、运维复杂、成本高企。Snowflake 工程师 Marco Slot 日前撰文详解了该公司如何从根本上重新设计了 Postgres 复制机制——通过一个数据库扩展，把变更数据捕获直接推入对象存储。</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6a7bc22c06baf&quot;}" data-wp-interactive="core/image" data-wp-key="6a7bc22c06baf" class="wp-block-image size-large aligncenter wp-lightbox-container"><img fetchpriority="high" decoding="async" width="960" height="540" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://mylogs.cn/wp-content/uploads/2026/08/c96b6a6f921a47c8635dc88f39388a5d_header.webp" alt="Postgres 数据复制终于不用折腾了：Snowflake 把 CDC 塞进了数据库内核" class="wp-image-4296" style="max-width:100%;height:auto;" srcset="https://mylogs.cn/wp-content/uploads/2026/08/c96b6a6f921a47c8635dc88f39388a5d_header.webp 960w, https://mylogs.cn/wp-content/uploads/2026/08/c96b6a6f921a47c8635dc88f39388a5d_header-300x169.webp 300w, https://mylogs.cn/wp-content/uploads/2026/08/c96b6a6f921a47c8635dc88f39388a5d_header-768x432.webp 768w" sizes="(max-width: 960px) 100vw, 960px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">图片来源：Snowflake 工程博客</figcaption></figure>





<h2 class="wp-block-heading">从「拉」到「推」：CDC 的范式转变</h2>



<p class="wp-block-paragraph">Postgres 原生的逻辑解码（logical decoding）只负责把 WAL 日志翻译成行级变更流，剩下的重担全部甩给客户端：回填、Schema 变更处理、表创建与删除、快照对齐、故障重启……这些步骤中的任何一个出问题，整条管道就会断裂。</p>



<p class="wp-block-paragraph">Snowflake 的方案名为 <strong>Data Mirroring</strong>（数据镜像），核心理念极其简单：不再让外部系统来「拉」数据，而是从 Postgres 内部主动「推」。具体实现是一个名为 <code>snowflake_cdc</code> 的 Postgres 扩展，它在后台持续将变更批量写入每张表的变更日志（change log）和一个元日志（meta log），底层存储格式为 Apache Iceberg（压缩 Parquet 文件）。</p>



<p class="wp-block-paragraph">为什么选对象存储？因为 Amazon S3 这类服务本身具有高可扩展性和高可靠性，Postgres 备份早已在使用。作为 CDC 数据的目的地，它比任何自定义中间件都更稳。</p>



<h2 class="wp-block-heading">四阶段时间线解耦</h2>



<p class="wp-block-paragraph">Data Mirroring 将一次写入拆解为四个连续但解耦的阶段：</p>



<p class="wp-block-paragraph">1. <strong>写入（Write）</strong>：数据修改表并产生 WAL 记录</p>



<p class="wp-block-paragraph">2. <strong>解码（Decode）</strong>：历史 WAL 被翻译成行级变更，利用 Postgres 的「历史快照」功能读取写入时刻的目录表状态</p>



<p class="wp-block-paragraph">3. <strong>捕获（Capture）</strong>：行级变更被分批收集，定期追加到 Iceberg 变更日志</p>



<p class="wp-block-paragraph">4. <strong>应用（Apply）</strong>：Snowflake 端以有限状态机方式执行元日志指令，将变更批次合并到目标表</p>



<p class="wp-block-paragraph">这种设计的精妙之处在于：即使某张表在解码时已被修改甚至删除，历史快照仍能保证 WAL 记录被正确理解。Schema 变更走的是同一条 Write→Decode→Capture 路径，自然地嵌入变更流中，不会出现时序错乱。</p>



<h2 class="wp-block-heading">交易边界：用分布式系统的最简原语解决最难的问题</h2>



<p class="wp-block-paragraph">数据库系统用一个简单原语隐藏了海量复杂性：<strong>事务（transaction）</strong>。事务失败就重试，成功就保证恰好执行一次——这正是 ETL/CDC 最缺乏的保证。</p>



<p class="wp-block-paragraph">Snowflake 此前发布的 <strong>pg_lake</strong>（已正式可用）让 Postgres 能够跨 Postgres 表和 Iceberg 表执行事务。Data Mirroring 在此基础上更进一步：Postgres 端在一个事务内把数据和 Schema 变更批量推入多个 Iceberg 变更日志，Snowflake 端在一个事务内合并多个批次。这意味着所有 Snowflake 表精确推进到某个 Postgres 事务边界，外键和联接的正确性得到保证。</p>



<h2 class="wp-block-heading">不做 Upsert：插入密集型场景的性能飞跃</h2>



<p class="wp-block-paragraph">传统复制方案普遍采用 upsert（插入或更新）策略，原因是很难解决快照与变更的一致性问题。但 upsert 在列式存储上有严重缺陷：每次插入都要匹配目标表已有行，代价极高；且无法高效地将近期变更与已有数据合并。</p>



<p class="wp-block-paragraph">由于 Data Mirroring 是受控的事务性过程，它可以生成完美的删除流和插入流，<strong>精确执行一次</strong>，不存在重复应用的风险。结果是：插入密集型工作负载（通常是最大的表）的复制速度极快且成本低廉，因为插入只是追加，永远不做 upsert。</p>



<h2 class="wp-block-heading">Live Views：低频应用也能获得低延迟</h2>



<p class="wp-block-paragraph">基于上述能力，Snowflake 推出了 <strong>Live Views</strong>（实时视图）功能：将未应用的变更日志与目标表数据结合，查询时直接将过滤条件下推到存储层，同时扫描 Parquet 变更日志和基表。即使变更应用频率不高，Live Views 的延迟仍能控制在 1 分钟以内，且查询性能几乎不受影响。</p>



<p class="wp-block-paragraph">Slot 总结道：「你设置一次，它永远运行。」没有外部连接器会落后，没有快照冲突，没有随表增长而减速的 upsert——只有一个 Postgres 扩展往对象存储推批次，一个 Snowflake 端事务性地应用它们，两边独立运行，永不停歇。</p>



<p class="has-small-font-size wp-block-paragraph">来源：Snowflake 工程博客 / Marco Slot</p>

]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>不用 Redis 也能扛住：Shopify 用 MySQL 重写库存预留</title>
		<link>https://mylogs.cn/shopify-mysql-replace-redis-inventory-reservations/</link>
					<comments>https://mylogs.cn/shopify-mysql-replace-redis-inventory-reservations/#respond</comments>
		
		<dc:creator><![CDATA[steve, zhang]]></dc:creator>
		<pubDate>Sun, 09 Aug 2026 13:41:52 +0000</pubDate>
				<category><![CDATA[科技]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Redis]]></category>
		<category><![CDATA[Shopify]]></category>
		<category><![CDATA[SKIP LOCKED]]></category>
		<category><![CDATA[数据库]]></category>
		<category><![CDATA[电商架构]]></category>
		<category><![CDATA[高并发]]></category>
		<guid isPermaLink="false">https://mylogs.cn/shopify-mysql-replace-redis-inventory-reservations/</guid>

					<description><![CDATA[你以为高并发的库存系统离不开 Redis？Shopify 工程团队用一次「反向迁移」给出了另一种答案：他们把库 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">你以为高并发的库存系统离不开 Redis？Shopify 工程团队用一次「反向迁移」给出了另一种答案：他们把库存预留系统从 Redis 搬回 MySQL，并在 2025 年黑色星期五扛住了峰值——当时平台每分钟销售额达到 510 万美元。Shopify 支撑着美国超过 14% 的电商交易。</p>



<figure data-wp-context="{&quot;imageId&quot;:&quot;6a7bc22c07884&quot;}" data-wp-interactive="core/image" data-wp-key="6a7bc22c07884" class="wp-block-image size-large aligncenter wp-lightbox-container"><img decoding="async" width="1200" height="669" data-wp-class--hide="state.isContentHidden" data-wp-class--show="state.isContentVisible" data-wp-init="callbacks.setButtonStyles" data-wp-on--click="actions.showLightbox" data-wp-on--load="callbacks.setButtonStyles" data-wp-on--pointerdown="actions.preloadImage" data-wp-on--pointerenter="actions.preloadImageWithDelay" data-wp-on--pointerleave="actions.cancelPreload" data-wp-on-window--resize="callbacks.setButtonStyles" src="https://mylogs.cn/wp-content/uploads/2026/08/c17aba3d4098b3bffac2723514f2b28a_header.webp" alt="不用 Redis 也能扛住：Shopify 用 MySQL 重写库存预留" class="wp-image-4200" style="max-width:100%;height:auto;" srcset="https://mylogs.cn/wp-content/uploads/2026/08/c17aba3d4098b3bffac2723514f2b28a_header.webp 1200w, https://mylogs.cn/wp-content/uploads/2026/08/c17aba3d4098b3bffac2723514f2b28a_header-300x167.webp 300w, https://mylogs.cn/wp-content/uploads/2026/08/c17aba3d4098b3bffac2723514f2b28a_header-1024x571.webp 1024w, https://mylogs.cn/wp-content/uploads/2026/08/c17aba3d4098b3bffac2723514f2b28a_header-768x428.webp 768w" sizes="(max-width: 1200px) 100vw, 1200px" /><button
			class="lightbox-trigger"
			type="button"
			aria-haspopup="dialog"
			data-wp-bind--aria-label="state.thisImage.triggerButtonAriaLabel"
			data-wp-init="callbacks.initTriggerButton"
			data-wp-on--click="actions.showLightbox"
			data-wp-style--right="state.thisImage.buttonRight"
			data-wp-style--top="state.thisImage.buttonTop"
		>
			<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 12 12">
				<path fill="#fff" d="M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z" />
			</svg>
		</button><figcaption class="wp-element-caption">图片来源：Shopify</figcaption></figure>





<p class="wp-block-paragraph">旧方案的问题出在「两套账」。库存计数存在 Redis 里（占用就减、释放就加），但真正的库存总账在 MySQL。两步没法打包成一个原子操作，一旦顺序出错，就会出现超卖（东西卖了却没从总账扣掉）或幽灵库存（扣了却还显示占用）。Redis 也不感知多仓库库存，还得单独养一个集群。</p>



<p class="wp-block-paragraph">新方案靠的是 MySQL 8 的 SKIP LOCKED 特性。核心思路是「每个可售单元存一行」，而不是「每个商品存一行数量」。一件有 10 件库存的商品就对应 10 行，预留 3 件就是一次性锁 3 行、挪进预留表。为避免行数爆炸，每个商品/仓库最多维持 1000 行可用池，不够了再由补充进程从总账补齐。SKIP LOCKED 让并发请求跳过被锁的行、直接拿别的可用行，不用排队。</p>



<p class="wp-block-paragraph">几个工程细节值得记：把主键改成复合主键（店铺、商品、分组、序号），每行锁从两把降到一把；把事务隔离级别从默认的「可重复读」降到「读已提交」，避开空表补充时的间隙锁死锁；统一「先删后插」的锁顺序，消除预留与扣减路径间的循环等待；再用 UNION ALL 把一单多件的查询合并成一次往返。</p>



<p class="wp-block-paragraph">最有意思的是真瓶颈不在数据库。调完表结构，吞吐量还是卡在目标线下，CPU 也没跑满。团队给每条 SQL 打上业务标签（例如「conn_tag:checkout_completion」），在数据库代理层按调用方统计连接占用时长，才发现是结账链路上的其他环节长时间霸占连接。清理掉这些长事务后，主库读操作减少 50%、事务数减少 33%。</p>



<p class="wp-block-paragraph">迁移采用影子模式：Redis 和 MySQL 双写一段时间，确认无误再逐步把「真相源」切到 MySQL，并保留一键回滚。</p>



<p class="wp-block-paragraph">说白了，很多「必须上 Redis」的判断，只是因为当年 MySQL 还没有 SKIP LOCKED 这类能力；如今你手上的数据库往往就够用。真正的瓶颈常常藏在代码里的连接管理，而不是引擎本身。下次想引入新中间件之前，不妨先看看现有数据库的新特性和连接画像。</p>



<p class="has-small-font-size wp-block-paragraph">来源：Shopify 工程博客（作者 Emilie Noel，2026-05-12）</p>

]]></content:encoded>
					
					<wfw:commentRss>https://mylogs.cn/shopify-mysql-replace-redis-inventory-reservations/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
