如果您对分布式系统以及构建可靠的共享服务以支持所有Coinbase感兴趣,那么Platform团队正在招聘!
在2019年,Coinbase着手加强其产品构建的基础设施,并为支持当前和未来的产品线奠定坚实的基础。作为这项工作的一部分,我们决定采用AWS RDS PostgreSQL作为关系工作负载的首选数据库,并采用AWS DynamoDB作为键值存储。
我们决定重点关注的第一个领域是如何跟踪余额和转移资金。我们的产品各自设计了自己的解决方案,并且某些遗留系统也受到技术债务和性能问题的困扰。快速准确地处理交易的能力是Coinbase建立面向全球的开放财务系统的使命的核心。
我们设计并构建了一种新的分类帐服务,以快速,准确地满足产品中所有当前和将来的需求,并进行了迄今为止最大的迁移,将超过10亿行公司和客户交易以及余额信息从MongoDB移动到了新的基于PostgreSQL的解决方案,无需定期维护,也不会对用户产生明显影响。
使过程变得平稳—通过设计流程使其运行而不会中断正常的业务运营。
准确性和正确性:由于我们在处理资金问题,因此我们知道这将是非常敏感的迁移,因此,我们会采取一切预防措施,确保将每一个最后的麻烦都考虑在内。
可重复性:此外,新系统与旧系统的数据形状完全不同。此外,我们不得不处理整体应用程序中随着时间推移而积累的技术债务和杂项。我们知道我们需要考虑一次无法正确解决所有问题的可能性,因此我们想设计一个可重复的过程,我们可以反复进行此过程,直到正确为止。
无需维护停机时间:我们希望Coinbase上的每笔交易都能在执行的同时执行。我们不想进行任何计划的维护,也不想花任何停机时间进行过渡。
我们可以将迁移分解为两个独立的问题:切换实时写入和读取新服务,以及将所有历史数据迁移到新服务中。
对于迁移,我们决定采用双重写入/双重读取阶段方法。第1阶段是迁移之前的阶段,在该阶段中,我们只有旧系统。在阶段2中,我们介绍了新系统。我们将从两者读取的读取路径双重写入旧系统和新系统,然后记录差异并从旧系统返回结果。在第3阶段,我们增强了对新设置的信心,因此在返回结果时会偏爱它。我们仍然有旧系统,可以根据需要切换回旧系统。最后,我们逐步淘汰未使用的代码以完成迁移(第4阶段)。
我们不会详细介绍双重写入的实现方式,因为业界博客文章以前已经涵盖了总体思路。
有趣的是,在第2阶段和第3阶段之间发生了一些事情,即将所有客户数据回填到我们的新系统中,以便我们可以实现奇偶校验。
我们考虑了多种方法来回填十亿多行,这些行代表了从一开始就在Coinbase上进行的所有交易,各有利弊。
最直接的解决方案是在应用程序级别上完成所有工作,利用我们为双重写入而设置的分类帐客户端实现。它的优点是可以执行与实时写入相同的代码路径-从旧到新只有一个映射才能维护。但是,我们将不得不修改服务接口以允许回填,并且在发生故障的情况下,我们必须建立长时间运行的进程以及检查点机制。我们还对该解决方案进行了基准测试,发现它太慢了,无法满足我们对快速迭代的要求。
我们最终决定采用基于ETL的解决方案。从高层次上讲,这需要从ETL版本的源数据库中生成回填数据,并将其转储到S3中,然后直接将其加载到目标Postgres数据库中。这种方法的一个主要优点是使用SQL进行数据转换既快速又容易。我们可以在大约20分钟内运行整个数据生成步骤,检查输出,验证内部一致性,并直接在输出上进行数据质量检查,而不必运行整个回填管道。
我们的数据平台提供商提供了用于程序访问的各种连接器和驱动程序。这意味着我们可以使用我们的标准软件开发工具和生命周期-我们为数据转换编写的代码已经过测试,审查和检入存储库。
它还具有一流的支持,可以将数据卸载到S3中,这使我们在配置适当的资源后可以轻松导出数据。
另一端,AWS提供了aws_s3 postgres扩展,该扩展允许从S3存储桶将数据批量导入数据库。但是,直接将其导入实时生产表存在问题,因为将数亿行插入索引表很慢,并且还影响了实时写入的延迟。
现在,导入受到I / O的限制,这成为了瓶颈。通过将数据拆分为多个文件,并在顺序导入之间添加了较短的睡眠间隔,我们最终降低了速度。
接下来,在表上重新创建索引。幸运的是,Postgres通过使用CONCURRENT关键字,可以在不写锁定表的情况下创建索引。这允许表在创建索引时继续进行写操作。
到现在为止还挺好。但是,真正的复杂性来自我们对迁移的要求,该迁移不涉及计划的维护或停止事务处理。我们希望目标数据库能够维持实时写入而不会丢失单个写入,并且我们希望回填的数据无缝连接到实时数据。每个事务都存储有关所涉及的所有帐户的累计余额的信息,这使情况变得更加复杂-这使我们可以轻松地评估和维护数据完整性,并在任何时间戳下查找任何帐户的时间点余额。
为此,我们使用触发器将活动表的插入,更新,删除复制到回填表中,从而解决了这一问题。通过并发索引生成,我们可以在创建索引时写入这些表。
索引完成后,在单个事务中,我们翻转了回填表和活动表,删除了触发器,并删除了现在不需要的表。实时写操作继续进行,好像什么也没发生。
在此过程的最后,我们运行另一个脚本,该脚本遍历所有数据并通过重新创建累积余额和顺序事务之间的链接来恢复数据完整性。
最后但并非最不重要的一点是,我们针对旧数据存储库进行了另一轮完整性检查和比较,以确保数据正确且完整。
等待将双重写入数据加载到ETL中,以便实时写入数据和回填数据之间存在重叠。
该过程需要4到6个小时才能运行,并且大部分是自动化的。我们在解决数据质量问题和修复错误时一遍又一遍地做。
我们最后的迁移和回填并不令人难忘。我们没有“作战室”,没有待命的值班工程师,只是我们流程的另一轮运行,之后我们决定是时候进行切换了。公司中的大多数人都不高兴。平稳的一天。
我们使用分类帐服务已经快一年了。 与以前的系统相比,我们有能力维持每秒更多数量级的事务,并且在延迟方面有严格的限制。 现有和新功能(例如Coinbase卡)都依靠分类帐服务来快速准确地进行余额和交易。