当您必须在几周内迁移数亿行数据以及超过100多个服务时会发生什么,同时保持优步运行数百万骑手?这是几十名工程师帮助优步到2014年夹层的故事。
在2014年初,我们面临着一个严峻的事实,即考虑到我们的行程增长(约每月20%),存储行程的解决方案将在存储容量和存储容量方面都失去动力IOPS.到年底,如果不是。我们推出了项目夹层作为一种巨大的攻击,以征服特殊问题。目标发布日期是万圣节(10月31日),传统上是一个非常大的交通天。
背景
与大多数基于Web的服务一样,UBER后端系统已启动为具有一堆App服务器和单个数据库的“单片”软件架构。该系统主要用Python编写并使用Sqlalchemy.作为ORM- 到数据库。原始架构对于在几个城市运行相对较为谦逊的旅行时很好。截至2014年初,该架构已进化为真正的服务型架构,靠近100个服务。系统的高级图片是:

实时服务负责执行活动旅行,后端服务句柄骑手计费,驾驶员支付,欺诈检测,分析,城市管理等。上图中的大问题是我们仍然依赖于单个PostgreSQL.实例存储大多数数据。下面的饼图显示了如何在数据库中分发数据:

Trip数据占比最大,是(现在仍然是)增长最快的数据,也是IOPS最多的数据来源。我们利用行程数据来改善uberPOOL等服务,为乘客和司机提供支持,防止欺诈,并开发和测试旧金山的建议皮卡(Suggested皮卡)等新功能。因此,我们开始了项目Mezzanine来重构系统,使它看起来像这样:

新旅行商店
第一个设计决策是Trip Store的数据库的选择。我们的要求清单是:
- 操作健壮(无数据丢失,支持备份、复制到辅助数据中心,易于故障排除,可预测,具有操作专长)。
- 存储容量和IOPS水平可扩展。
- 高写下可用性。我们总是希望能够持续到稳定的存储旅行。可以根据批处理的时尚正常工作,从而进行短期读取可用性。
- 二级索引支持。用户,城市和各种方式总结旅行。
- 任何操作(扩展存储、备份、添加索引、添加数据等等)都没有停机时间。
列表中的最后一个项目正在解决一个非常立即痛点。PostgreSQL中的TRIPS表已经增长如此大,即添加新列或添加新索引所需的任何操作导致停机时间。这使得开发新功能越来越繁琐。
我们决定采用面向列的无模式方法,其中数据(JSONBlobs)以跳闸索引的网格组织 -uuid.,列名和可选的时间戳将适用于整体数据模型。该模型通过跨多个分区行自然地划分水平缩放碎片,并通过无模式支持我们的快速发展文化。可以添加新列,也可以向列添加新字段,无需重新配置。
我们评估各种NoSQL.-Style数据库具有上述特征。然而,由于我们的运营经验或产品的成熟,我们并没有觉得自己很有适合存储旅行数据。
由博客帖子的启发,例如这雷竞技到底好不好用一个Friendfeed,我们决定在上面构建我们自己的简单的分片数据存储mysql.。我们构建的系统的关键特征是:
- 分片:行被分成固定的碎片组,在设置时决定。通常,我们使用4096.每个碎片对应于MySQL表空间,并且分布在许多MySQL服务器上分发。可以在MySQL服务器之间移动碎片以进行负载平衡,并且可以在线增加容量。我们通常通过将每个MySQL服务器分为两个MySQL服务器来扩展。
- 仅附加(没有更新)数据模型:它只支持只添加的数据模型,其中单元格在写入后永远不能被修改。这对于存储事务性数据并希望防止数据损坏的系统非常有用。由于是附加的,修改是自然的幂等和换向。后者意味着我们可以以任何顺序重放更新并获得相同的结果。(我们稍后学会了唯一的申请风格也倡导Lambda建筑。)
- 缓冲写道。如果需要写入单元的分片不可用(或慢),我们将数据写入任何其他可用MySQL服务器中的挂起表。稍后,当碎片可用时,将重新播放这些内容。由于幂等和可交换数据模型,这总是安全的,不需要跨主机协调。
- 分片二级索引:可以在列中的多个字段上创建索引,并在特定密钥(例如,用户uuid)上分离。它们以MySQL表实现并在后台填写。如果我们需要更改索引(例如,添加字段),我们可以创建一个新版本,回填它,然后通过更改索引别名来切换到新版本,所有没有应用程序停机。
我们只需在其设计中致电思路的整个系统,都是用Python编写的。初始版本从想法大约需要5个月到生产部署,我们将在未来的博客帖子中描述具体的实施细节。雷竞技到底好不好用
从SQLAlchemy到Schemaless
写一个新的可扩展数据存储从头开始是一个重大的事业。从使用PostgreSQL数据库来利用面向列的数据库的重构Live System的关键部分是一个完整的Ballgame。显然,旅行数据是超级优步的后端系统代码中的一个组成部分,所以这项承诺将触及大多数工程团队。
该项目这一部分的主要里程碑是:
- 随处可随时随地更改旅行its-uuids。
- ScareAless中的柱布局(例如,新的TRIPS数据模型)。
- 将数据从PostgreSQL回填到Mefuremaless。
- 镜像写入postgreSQL和lequeraless。
- 重写所有查询以使用lequeraless。
- 验证,验证,验证和验证!
第一个任务,在我们真正开始迁移之前,是旅行-UUID迁移的旅行ID,因为原始代码依赖于自动增量PostgreSQLD B标识符。需要重写数百个SQL查询。这些SQL查询全部采用SQLALCHEMY PYTHON代码的形式,并通过模型关系组成了显式或间接查询。这些都需要重写为在新的模式查询之上工作API.,这是一个更加受限的API,不支持对PostgreSQL中的其他表进行连接。
我们最初的目标是删除SQLAlchemy旅行模型的所有使用,并直接删除Trips表上的查询。本质上,我们想要得到以下架构:

lib/tripstore公开了一个与基于模式的实现兼容的API。lib/tripstore是通过一个开关实现的,因此查询可以通过PostgreSQL或Schemaless。因此,随着代码重构的进行,我们在PostgreSQL数据模型的基础上模拟了无Schemaless API。
随着所有写入都镜像到拼凑,我们将所有查询重播到MarfuleAls并验证了背景中的结果。因此,我们几乎立即开始评估图案中的数据与PostgreSQL中的数据一致。由于验证将负载添加到数据库(已加载大量),我们使用了概率方法来控制我们对PostgreSQL数据库的额外加载。这里的一个大外带是数据建模,回填,重构和模式开发可以并行进步,并以小增量连续部署。
执行
Mezzanine项目的最后冲刺持续了6周。我们都聚集在一个“作战室”(除了几个远程人员),开始将剩余的SQLAlchemy代码转换为新的tripstore库,完成填充、设计和重建索引,并将验证工作扩展到某些人可能称之为“强迫行为”的地方。
在最后一天,2014年万圣节前一个月,我们已准备好将交换机翻转在PostgreSQL中的TRIPS表到MAFCEALESS。所有工程集团的团队成员在那天凌晨6点在SF办公室。如果出现问题,那么所有的手都可以立即在甲板上,我们将有一个整个工作日来解决它。情况谨慎乐观但紧张。对每个人的惊人都很令人惊讶,这是一个非活动。没有警报,没有恐慌。对于优步平台,它是常用的业务。这就像万圣节,这可能看起来很可怕,但只是乐趣!
得到教训
- 使用uuid:始终为所有内容使用UUID。如果您以整数ID启动并且您体验大增长,则撤消它是很多繁琐的工作。
- 保持数据层简单:必须易于调试和排除故障。性能指标尤其有价值。依靠MySQL引擎,因为低级存储层允许我们非常快速地构建一个强大的系统。
- 这是一个学习曲线:(Mental)从编写代码对键值存储的转换,具有有限的查询功能,而不是写入ORM或SQL直接需要时间才能掌握。idempotence替换事务。
- NoSQL是强大的使用分片索引的基于列的方法将数据层的性能权衡直接交给程序员。有了正确的抽象,就可以直接编写解决方案,不仅可以在应用服务器层向外扩展,还可以在数据层向外扩展。
- 完成快速:快速完成最终迁移。随着特性的开发,它总是一个移动的目标,所以您需要比其余的代码库移动得更快。
- 试验和错误不要期望第一次就能得到正确的数据模型。计划多次完整和部分回填。
- 优步!在完成这样的大型团队工作时,积极乐观的态度会让一切变得不同。
自切换以来,我们已经将旅行商店的容量增加了一倍,零停机时间,并实现了许多性能和运营改进。在此基础上,Schemaless现在也被其他一些服务所使用。
这篇文章献给整个Mezzanine团队,他们完成了这一惊人的努力!
由RenéW.Schmidt,Uber和Mezzanine项目技术领先的可扩展性工程师。
更新:有关更多信息,请参阅rene谈话Facebook于2015年9月的第二届年度@Scale会议。
标题的照片学分:“马塞马拉”经过克里斯托弗米歇尔许可cc-by 2.0。图像裁剪头部尺寸。
标题说明:年度迁移塞伦盖蒂(在肯尼亚和坦桑尼亚)被广泛认为是自然世界中最伟大的一个。






