快手、孩子王、华为等专家分享大模型在电商运营、母婴消费、翻译等行业场景的实际应用 了解详情
写点什么

一次几乎不可能的数据库迁移

Brad Fitzpatrick、David Crawshaw

  • 2021-03-07
  • 本文字数:3693 字

    阅读完需:约 12 分钟

一次几乎不可能的数据库迁移

最初,我们把一个文件当作数据库,将数据转化为 JSON 大对象写入进去,后来,它的速度越来越慢,我们决定进行数据库的迁移,这个过程中我们遇到了一些问题和障碍,但最终我们成功完成了这一次不太可能的数据库迁移。

Brad 加入一家初创公司

大约一年前,当我刚加入 Tailscale(https://tailscale.com/)时,我问 Crawshaw(https://github.com/crawshaw)的第一件事是:“嗯……你们使用的是什么数据库呢?MySQL、PostgreSQL、SQLite?“我知道他喜欢 SQLite。

 

“一个文本文件,”他回答道。

“嗯?”

“是的,我们将一个大型 JSON 对象写入一个文本文件。”

“怎么写?什么时候写?写什么?”

“嗯,无论什么时候,只要有什么东西一变,我们都会在我们的单进程中获取一个锁,然后重写这个文件!”他高兴地笑着说。


听起来很疯狂。其实就是很疯狂。的确,它很容易测试,但是,不能扩展。这些,我们都知道。但是,当时它还行得通。

 

后来,它不行了。


即使使用高速 NVMe 驱动器,并且将数据库分成两部分(重要数据和 tmpfs 上的可能丢失的临时数据),有些事情也会变得越来越慢。我们知道这一天终将到来。文件达到了 150MB 的峰值,我们正在以磁盘 I/O 允许的最快速度写入它。这已经到了极限。

 

那么,迁移到 MySQL 或 PostgreSQL,如何?或者是 SQLite?


不,Crawshaw 另有主意。

聊聊大卫的背景故事

Tailscale 的协调服务器(我们的“控制面板”)以控制 CONTROL(https://getsmart.fandom.com/wiki/CONTROL闻名遐迩。它目前是单个 VM 上的单个 Go 进程。它最早的原型使用的是 SQLite。我们最初的设计与最终的设计有非常大的差异,包括同步到客户端机器上的配置数据库,以及所有我们最终不再需要的其他概念。在这个过程中,我们每周都要对 SQL 数据模型进行非常大规模的重组,这需要大量的键盘输入工作。SQL 已经得到了广泛使用,它持久、有效,但将其引入到任何编程语言中几乎都需要做大量的粘合。(通常大家都试图用 ORM 来避免这种情况,用令人生厌的大量魔法字和效率损失来取代那些同样令人生厌的键盘输入工作。)


一天,我厌倦了重构,就把它彻底丢在一边,建了一个内存数据模型进行实验。这样,迭代速度更快了。几周后,一位客户想要试用一下。我还没有做好提交数据模型和用 SQL 来完成它的准备,所以我选择了一条捷径:将持有所有数据的对象包装在一个 sync.Mutex 中。所有访问都要经过它,在编辑时,将整个结构传递给 json.Marshal,然后写入磁盘。这就是我们用大约 20 行 Go 实现的数据模型持久层。


我们本来计划要迁移为别的语言的,但忙着忙着就给忘记了。

JSONMutexDB 的后面是什么?

下一步显然是迁移到 SQL。我最喜欢的仍然是 SQLite,但是我不能说服自己把一个快速增长的服务迁移到它上面。它当然是可行,尤其是我们的控制面板的设计并不需要典型的 web 服务高可用性:短时间停机的无非就是使新节点无法登录而已,正在工作的网络可以保持正常工作。


其后是 MySQL(或 PostgreSQL)。我对 1998 年以后的 MySQL 不是特别熟悉,但我确信它是可以的。不过,开源数据库的 HA 情况有些令人惊讶:您可以使用传统的滞后副本,也可以提交到具有令人非常惊讶的事务语义的无主副本集群。我对试图在这些语义之上设计一个稳定的 API 或良好的网络图计算并不感兴趣。CockroachDBhttps://github.com/cockroachdb/cockroach#what-is-cockroachdb 曾经看起来很有前途,而实际上现在仍然很有前途!但对于一个数据库来说,它还是相对比较新的,我不太放心把一些特性附着在一个新的 DBMS 上,因为如果我们需要将这些特性中迁移出来时,可能很难做得到。


让我们的控制服务器依赖于 MySQL 或 PostgreSQL 还意味着我们对控制服务器的测试将变得缓慢和丑陋。Brad 与 Perkeephttps://perkeep.org/)曾就此有过争论,他之前写过perkeep.org/pkg/test/dockertest,它的确可行,但我们不想要求未来的员工都这么做。它需要在你的机器上部署 Docker 环境,速度不是特别快。


后来有一天我们看到一份 Jepsen 写的 etcd 报告(https://jepsen.io/analyses/etcd-3.4.3)。这篇报告不似 Jepsen 之前那种满篇吐槽的风格,里面还指出了一些 etcd(https://etcd.io/)的优点。结合 Dave Anderson(https://github.com/danderson)的一些正面体验,我们开始考虑是否可以直接使用 etcd。由于是它用 Go 编写的,我们可以直接将它连接到我们的测试中,并直接使用它。无需 Docker,无需 mock,就可以测试我们在生产环境中实际使用的东西。


事实上,我们写入到磁盘的核心数据模型严格遵循了以下模式:

type AllTheData struct {	BigLock    sync.Mutex	Somethings map[string]Something	Widgets    map[string]Widget	Gadgets    map[string]Gadget}
复制代码

这很好地映射到了 KV-store 上。因此,我们将 etcd 作为一个“最小可行的数据库”。它做了我们当前所需要的最关键的事情,那就是 1)将 BigLock 拆解成更类似于 sync.RWMutex 的东西。2)减少 I/O,只写改变的数据,而不是整体都写。


(我们会谨慎避免使用任何难以映射到 CockroachDB 的 etcd 特性。)


这样做的缺点是,etcd 虽然在 Kubernetes 中很流行,但是数据库系统的用户相对较少。作为一家公司,Tailscale 正致力于在其上打造一款创新代币(https://mcfunley.com/choose-boring-technology)。但这款数据库从概念上讲非常小,以致于我们不必把它当作一个黑盒。当我们在 etcd 3.4 中遇到一个异常缓慢的主键分页的极端情况时,我能够阅读它的源代码并在一个小时内编写出一个修复程序。(后来,我发现 etcd 的下一个版本也已经做了一样的修复(https://github.com/etcd-io/etcd/commit/26c930f27d46776da5fedae69267ba0b69c31185),所以我们将其反向移植了过来。)

我们的 etcd 客户端包装器

我们用于 etcd 的客户端是开放源码的,网址是 github.com/tailscale/tailetc(https://github.com/tailscale/tailetc)。它围绕了两个概念:1)DB 中的总数据量足够小,可以放入服务器的内存中;2)读比写更常见。鉴于这一点,我们希望降低读取成本。

我们的方法是对 etcd 注册一个监控。每次更改都被发送到这个客户端,这个客户端在一个 sync.RWMutex 后面维护一个庞大的缓存 map[string] interface{}。当你创建一个 Tx 并且做一次 Get 时,这个值从这个缓存中读出(这个缓存可能在 etcd 之后,但是通过跟踪 modrev 来保持事务一致性:即一个全局递增的 ID, etcd 使用它来界定键-值对的修订)。为了避免缓存中的混叠错误,我们将对象复制出来,但是通过对缓存中的对象实现更有效的克隆调用,避免了每次 Get 时的 JSON 解码。


最终结果是,从 etcd 获取一个值不需要任何网络流量。


当我在设计一个包时,我感受到了编写 Go 时它的类型系统的局限性,这样的感受并不多,它是其中之一。如果我使用的是一种具有各种花哨功能的语言,那么我可以在离开缓存的对象上放置某种 const 限定符,从而避免对内存进行克隆。即便如此,在我们的服务器上执行的性能分析却表明,复制并不是一个性能问题,所以该例可能说明,我实际上并不需要那些心心念念的更复杂的类型系统。通常情况下,假设很可能并不正确,性能分析才更具启发意义。

一个障碍:索引

选择最小可行的“nosql”的最大问题是缺乏每个标准 SQL DBMS 所提供的出色的索引系统。我们要么在 etcd 中存储索引,要么在客户端的内存中管理索引。

我们使用 JSONMutexDB 在内存中生成它们,因为更改数据模型要容易得多。使用 etcd 的一个简单做法是将它们写入数据库,但这将产生非常复杂的数据模型。不幸的是,如果我们想要同时运行多个控制进程以实现高可用性和更好的发布管理,就意味着我们不再只有一个管理数据的进程,因此我们的索引需要支持事务(以及回滚)。因此,我们投入了大约两到三周的工程时间来设计事务一致的内存索引。这一点描述起来有些复杂,所以笔者将在后续的博客文章中专题解释,敬请期待。

迁移

而迁徙本身却没什么特别值得注意的,这其实件好事。我们这两个系统并行运行了一段时间,并在某个时间点停止了旧系统的使用。最令人兴奋的是,当我们关闭 JSON 写入时,提交延迟降低了很多。在管理面板中编辑网络时这一点尤为明显。我们有漂亮的 Grafana 图表,在切换之前我们就调整了 Prometheus 配置以保持更多的历史纪录。不论在哪种情况下,写操作都能从几乎一秒(有时更糟!)的时间缩短到毫秒级。刚开始的时候,写入并不是我们的第二目标。永远不要低估“临时”起意会产生多么长久的影响!

未来

在这项工作中,除了确保 Tailscale 控制面板可以在可预见的未来扩展外,最令人兴奋的事情是我们发布过程的改进。我们可以轻松地将多个控制面板实例附加到一个一致的数据库中,这意味着我们可以切换为蓝绿部署(https://en.wikipedia.org/wiki/Blue-green_deployment)。这将让 Tailscale 的工程师们有信心去尝试部署特性,因为变更所能造成的最差结果是有限的。我们的目标是将开发速度保持在接近 JSONMutexDB 早期的水平,当时我们可以在不到一秒的时间内重新编译并在本地运行,每天部署上 10 几次。


原文链接:An unlikely database migration


译者简介:冬雨,小小技术宅一枚,从事研发过程改进及质量改进方面的工作,关注编程、软件工程、敏捷、DevOps、云计算等领域,非常乐意将国外新鲜的 IT 资讯和深度技术文章翻译分享给大家,已翻译出版《深入敏捷测试》、《持续交付实战》。

2021-03-07 12:003594

评论 1 条评论

发布
用户头像
原型数据库和生产数据库的选型要慎重
2021-03-15 08:34
回复
没有更多了
发现更多内容

AndroidStudio最新版(2021.1.21)编译C++代码生成so文件

中国好公民st

c++ Android; 10月月更

上岸稳了!GitHub标星115k+的阿里内部Java学习教程限时开源

Geek_0c76c3

Java 数据库 程序员 架构 开发

web3 chainlink 预言机喂价、VRF

1_bit

智能合约 web3 chanlink

STM32L051测试 (二、开始添加需要的代码)

矜辰所致

stm32 STM32CubeMX 10月月更

前端培训学习的就业前景是什么样的

小谷哥

打破“双十定律”,华为云AI推动超级抗菌药Drug X研发加速

华为云开发者联盟

AI 华为云 药物研发 盘古大模型 企业号十月 PK 榜

两数之和

掘金安东尼

算法 10月月更

算数、赋值、比较、逻辑、三元运算符

共饮一杯无

Java 运算符 10月月更

MobPush iOS端常见问题

MobTech袤博科技

ios

年底前端面试题总结(上)

loveX001

JavaScript

vivo互联网机器学习平台的建设与实践

vivo互联网技术

人工智能 机器学习 推荐系统

如何用AR Engine环境Mesh能力实现虚实遮挡

HarmonyOS SDK

AR

全网首发“Java面试考点大全”,25+专题梳理:JVM+多线程+Spring全家桶+MySQL+Redis等

Geek_0c76c3

Java 数据库 程序员 架构 面试

谈谈前端性能优化-面试版

loveX001

JavaScript

LinkedList源码分析(四)

知识浅谈

linkedlist 10月月更

Qt | 深入了解Qt的委托类

YOLO.

qt 10月月更 C++

Java数据类型转换

共饮一杯无

Java 类型转换 10月月更

如何在 SAP BTP 平台上重用另一个已经开发好的 service

汪子熙

云原生 SaaS 云平台 SAP 10月月更

初学大数据培训学习入门

小谷哥

小程序运营怎么做?

源字节1号

软件开发 前端开发 后端开发 小程序开发

大数据学习培训机构怎么去选择

小谷哥

SeaTunnel连接器V1到V2的架构演进与探究

Apache SeaTunnel

API 数据集成 连接器 Apache SeaTunnel 数据集成平台

搜索中常见数据结构与算法探究(一)

京东科技开发者

数据结构 ES 哈希 数据结构算法 搜索算法

数字化转型:营销数字化

Taylor

数字化 营销数字化 客户数据平台 CDP 营销数据中台

前端线下面授培训机构该怎么选择?

小谷哥

研发效能领域的“百科全书”重磅来袭!

博文视点Broadview

Qt | Qt的动画框架和类

YOLO.

qt 10月月更 C++

大数据ELK(二十一):Logstash简介和安装

Lansonli

Logstash 10月月更

Python进阶(十)Python 编程规范

No Silver Bullet

Python 编程规范 10月月更

又一里程碑!阿里首推Java面试通关手册,必须人手一份!

Geek_0c76c3

Java 数据库 程序员 架构 面试

web前端技术培训学习好点

小谷哥

一次几乎不可能的数据库迁移_语言 & 开发_InfoQ精选文章