云服务器价格_云数据库_云主机【优惠】最新活动-搜集站云资讯

域名注册_数据库物理设计_多少钱

小七 141 0

切碎是苦药

Percona几年前就提到过,现在仍然是真的。切分通常是个坏主意。在体式上,我们推迟了很多年,并从中获得了很多好处:我们的应用程序逻辑更简单。我们有更多的自由来改变我们的数据模型。我们不能再在那些事情上浪费时间了。延迟分片对我们来说也很容易:我们使数据库模式更加紧凑,AWS发布了更大的RDS实例。如果我们坚持的时间够长的话,我们可能会在AWS的新极光上。出于空间的考虑,我们最终不得不进行切分。我们的数据库是一个3TB的RDS实例,我们使用了一半的空间,并且增长强劲。在一个单独的例子中也是一个痛苦的来源。负载是个问题,备份要花很多时间,数据库是一个巨大的单点故障。在这篇文章中,我将谈谈我们所做的一些与切分相关的工作。我将介绍我们预期的工作以及结果如何,一些为我们带来回报的最佳实践,以及我们沿途遇到的一些惊喜。已知工作决定如何切分切分数据没有单一的"正确方法"。更确切地说,切分数据的正确方法取决于您的应用程序。在Asana,我们认为正确的切分方式是"按工作区"。我们的读写几乎总是局限于一个工作区,因此,按工作区的分片允许我们继续在大多数写操作中使用单个事务,并在大多数读取中继续使用mysql查询(而不是依赖于应用程序级别的连接)。这些特性(事务和富查询)是我们首先使用关系数据库的原因,所以保存它们对我们来说很重要。此外,按工作区进行分片意味着用户通常只与少数几个数据库交互,因此,如果一个数据库出现问题,它只会影响一小部分用户。然而,按工作区划分也有其缺点。有些数据不适合工作区,因此需要对这些数据进行特殊大小写。为此,我们创建了一个单独的"主切分"。另外,按工作区进行分片意味着我们的碎片比按对象分片时要大得多,所以如果工作区变得太大,我们就需要改变切分行为来适应它。但总的来说,按工作区划分似乎比我们的其他选择要好得多。更新应用程序切分的主要部分是读、写和在数据库之间迁移碎片的能力。其中,我们最担心的是阅读,因为我们预计会有很多交叉切分查询,我们需要重写这些查询才能在客户端工作。相比之下,我们认为写操作很简单,因为我们只能给出几个跨切分写的例子。这些期望结果是倒退的:事实上,阅读是相当直接的,而写作比预期的糟糕得多。写作很难成形。由于交叉分片写入不能以事务方式完成,所以每个交叉分片写入都需要仔细检查,以确保它是幂等的。通常,这意味着要重写应用程序的这一部分。许多重写都是冗长而混乱的,以至于需要数周的时间才能完成。我们还没有达到我们希望的状态。我们偶尔会使用嵌套事务,在这种情况下,我们在一个针对shard a的事务中对shard B执行一个操作。这使得代码很难得到正确的处理,我们只剩下代码库中不直观的部分,并且会对重构造成危险。例如,在代码块中运行IntransactionAgainsShard(用户.shard()):user.addNewEmail(电子邮件)工作区=getWorkspaceForEmailDomain(电子邮件)运行IntransactionAgainsShard(工作区.shard()):工作区.addUser(用户)假断言我们会将用户添加到工作区,但不会添加她的新电子邮件地址。迁移数据对于从主数据库迁移碎片,我们有几个目标。不要丢失任何数据。尽量减少停机时间。快速迁移数据。这些目标通常不一致。例如,当碎片离线时,快速安全地复制它就容易得多。最终,我们在拷贝碎片还活着的时候就解决了,当我们转换到使用拷贝的时候,它们短暂地不可用了。我们使用的基本方法很简单:我们启动一个进程来复制碎片,然后启动第二个进程来重新复制任何发生变化的对象。复制完成后,我们将更新shard to database记录,以指示shard现在位于新数据库中。在实践中,这个迁移过程甚至允许在几秒钟内移动最大的碎片。要做到这一点,需要在应用层进行一些簿记。首先,我们维护一个修改过的对象队列,这样我们就可以在迁移过程中重新复制任何修改过的对象。我们已经有这样一个系统,允许搜索索引,所以这不需要额外的工作。其次,我们在每个数据库上维护一个"shard locks"表。这个表保存shard_id的行,shard_是_This_数据库上的,每当我们针对一个shard启动一个事务时,我们就对这行取一个共享锁,并确认shard_is_on_This_database是真的。当我们切换哪个数据库保存实时碎片时,我们会将这个数据库上的shard_is_更新为原始数据库上的false。这解决了争用条件,即进程可以在我们从旧数据库转换后写入旧数据库。有成效的最佳实践自动化测试我们的大型自动化测试套件大大降低了切分的风险(因此更容易)。我们的测试捕捉到了很多bug,只漏掉了几个;而漏掉的bug通常出现在覆盖率较差的地方。一般来说,捕获bug的测试并不是特定的切分——它们只是执行应用程序特定行为的单元测试。狗粮Asana是一个中等规模的Asana客户,在将内部版本部署到其他客户之前,我们不断地进行内部版本的工作。类似地,当需要迁移碎片时,我们迁移的第一个非测试碎片是Asana碎片。这很好地解决了问题,因为即使在进行了大量的测试之后,第一次迁移也会导致轻微损坏的数据。问题是我们的迁移脚本复制了关联,但从未删除它们。例如,如果在迁移过程中从列表中删除了任务,则迁移完成后,该任务可能会重新出现在列表中。由于我们在dogfooding中发现了这一点,我们可以通过向同事道歉并要求他们重做少量工作来修复数据惊喜数据垃圾在分片的过程中,我们发现我们多年来积累了相当数量的数据垃圾债务,现在需要偿还。我们发现这一点是因为我们可以通过多种途径来确定一个物体的碎片,但它们并不一致。修复这类数据需要大量的修补工作。例如,当用户删除这些临时对象时,这些对象可能是不可访问的。一些数据可以通过改变它所在的碎片来保持一致。通常我们会修复一种对象,却发现这只是移动了不一致性。我们通过多种手段发现了这些数据问题。在应用程序中,我们有关于加载对象的断言。例如,如果我们查询shard A上的对象,这些断言将检查返回的每个对象是否都同意它在shard A上。一旦断言出错并且我们知道有问题,我们编写离线查询报告数据库中的每个shard不一致。查询对于完整性来说是必要的,但是断言对于首先发现问题并帮助确认我们确实解决了问题至关重要。用户发现错误与beta测试相比,在生产中启用分片更为重要,我们的用户发现了一小部分未经充分测试的代码路径,而这些代码路径我们自己都没有碰到。通常,当服务器进程在不恰当的时间崩溃并使数据处于无法自动恢复的状态时,就会出现这些错误。这些问题主要集中在用户注册或加入一个新的工作区上,这两件事我们个人并不经常做,因此在我们的beta集群中还没有经过测试。他们还要求产品在某个特定时间崩溃,然后重试一个操作—这是我们根本没有测试的。数据丢失迁移的几个星期后,我们遇到了客户数据中唯一已知的问题。调度脚本没有迁移脚本本身那么健壮,它们设法为单个域安排两个同时迁移。我们的迁移被安排为"将shardx移动到databasey",尤其是,它们总是从shard当时所在的任何数据库迁移。这意味着,一旦第一次迁移完成,第二次迁移就只能尝试将shard X从数据库Y迁移到数据库Y删除rowsonTargetDatabase()readRowsFromSourceDatabase()writeRowsToTargetDatabase()一线希望是,迁移以难以置信的速度从新数据库中删除了数据,而碎片很快就无法使用。这意味着shard上的用户不能输入新的数据,所以当我们再次切换shard以读取原始数据库时,这些用户只丢失了最后几分钟的写操作。我们联系了工作区中的用户,让他们知道发生了什么,修复了我们的脚本以拒绝从数据库迁移到数据库本身,并重新启动了迁移。燃烧率讽刺在继续迁移的过程中,我们密切关注主数据库上磁盘空间下降的速度。我们定期估计什么时候空间会用完,并在日历上标出。如果我们能以足够低的速度,在未来的10年里,我们就完蛋了。几周后,这一比率降到了开始时的一半,但后来趋于平稳。它看起来像是一条长长的尾巴