为什么优步工程从Postgres转向MySQL

0.
为什么优步工程从Postgres转向MySQL

介绍

优步的早期架构由用在Python中编写的单片后端应用程序组成postgres.用于数据持久性。从那时起,优步的体系结构已经发生了重大变化,到了一个模型微服务和新的数据平台。具体而言,在我们以前使用过Postgres的许多情况下,我们现在使用艺术,这是一个基于MySQL的新型数据库分片层。在本文中,我们将探索Postgres的一些缺点,并解释在MySQL之上构建Schemaless和其他后端服务的决定。

Postgres的建筑

我们遇到了许多Postgres限制:

  • 低效的写体系结构
  • 低效数据复制
  • 表损坏的问题
  • 糟糕的副本MVCC支持
  • 难以升级到更新的版本

我们将通过分析Postgres对磁盘的表格和索引数据的分析来查看所有这些限制,尤其是与MySQL的方式相比,MySQL与其相同的数据InnoDB存储引擎.请注意,我们在此处提供的分析主要基于我们的经验,旧的Postgres 9.2释放系列。To our knowledge, the internal architecture that we discuss in this article has not changed significantly in newer Postgres releases, and the basic design of the on-disk representation in 9.2 hasn’t changed significantly since at least the Postgres 8.3 release (now nearly 10 years old).

磁盘格式

关系数据库必须执行以下几个关键任务:

  • 提供插入/更新/删除功能
  • 提供制定架构更改的功能
  • 实施A.多数并发控制(MVCC)机制,使不同的连接具有与其工作的数据的交易视图

考虑所有这些功能如何合作,是设计数据库如何代表磁盘上的数据的重要组成部分。

Postgres的核心设计方面之一是不可变行数据。这些不可变行在Postgres Pantance中称为“元组”。这些元组是由Postgres呼叫的唯一标识的CTID..一种CTID.概念上表示元组的磁盘位置(即物理磁盘偏移)。多ctids可以潜在地描述单个行(例如,当行存在多个版本用于MVCC目的时,或当一行的旧版本尚未被autovacuum.过程)。组织元组的集合形成了一张表。表本身具有索引,这些索引被组织为数据结构(通常是B树),即将索引字段映射到aCTID.有效载荷。

通常,这些是ctids对用户来说是透明的,但了解他们的工作方式有助于您了解Postgres表的磁盘结构。看目前CTID.对于一行,您可以添加"CTID.“到列表中的列表在哪里条款:

优步@ [本地] UBER =>选择CTID,*来自my_table限制1;- - - - - -[记录1  ]--------+------------------------------ctid |(0,1)......这里的其他领域......

要解释布局的详细信息,让我们考虑一个简单的用户表的示例。对于每个用户,我们有一个自动递增的用户标识主键,用户的名字和姓氏以及用户的诞生年份。我们还在用户的全名(名字和姓氏)上定义了复合次要索引以及用户诞生年份的另一个次要索引。这DDL要创建此类表可能是这样的:

创建表用户(id序列,第一个文字,最后文字,出生_ year整数,主键(ID));在用户创建index ix_users_first_last(首先,最后);CREATE INDEX ix_users_birth_year ON users (birth_year)

注意此定义中的三个索引:主键索引加上我们定义的两个辅助索引。

对于本文中的示例,我们将从表中的以下数据开始,这些数据由一些有影响力的历史数学家组成:

id 第一的 最后的 出生年
1 布莱斯 帕斯卡尔 1623.
2 格特弗里德 莱布尼兹 1646.
3. 艾美 no 1882年
4. 默罕默德 al-Khwārizmī 780.
5. 艾伦 图灵 1912年
6. Srinivasa. Ramanujan 1887年
7. ada. leavelace. 1815.
8. 亨利。 Poincaré. 1854年

如前所述,这些行中的每一行隐含地具有唯一的不透明CTID..因此,我们可以想到这样的表格的内部表示:

CTID. id 第一的 最后的 出生年
一种 1 布莱斯 帕斯卡尔 1623.
B. 2 格特弗里德 莱布尼兹 1646.
C 3. 艾美 no 1882年
D. 4. 默罕默德 al-Khwārizmī 780.
E. 5. 艾伦 图灵 1912年
F 6. Srinivasa. Ramanujan 1887年
G 7. ada. leavelace. 1815.
H 8. 亨利。 Poincaré. 1854年

主键索引,哪个映射ids.ctids,定义如下:

id CTID.
1 一种
2 B.
3. C
4. D.
5. E.
6. F
7. G
8. H

b -树定义在id字段,B-Tree中的每个节点都持有CTID.价值。注意,在这种情况下,由于使用自动递增,B树中的字段的顺序恰好与表中的顺序相同id,但这并不一定需要如此。

次要索引看起来相似;主要区别在于,字段以不同的顺序存储,因为B树必须被描述为词典。这 (第一的最后的)索引从名字开始,朝向字母表的顶端:

第一的 最后的 CTID.
ada. leavelace. G
艾伦 图灵 E.
布莱斯 帕斯卡尔 一种
艾美 no C
格特弗里德 莱布尼兹 B.
亨利。 Poincaré. H
默罕默德 al-Khwārizmī D.
Srinivasa. Ramanujan F

同样,这是出生年索引按升序群集,如下所示:

出生年 CTID.
780. D.
1623. 一种
1646. B.
1815. G
1854年 H
1887年 F
1882年 C
1912年 E.

如您所见,在这两种情况下CTID.相应的辅助索引中的字段不会在lexically上增加,与自动递增主键的情况不同。

假设我们需要更新此表中的记录。例如,假设我们正在更新Al-Khwārizmī诞生年份的另一个估计的诞生年条领域,770 CE。正如我们之前提到的,行元素是不可变的。因此,要更新记录,我们将一个新的元组添加到表中。这个新的元组有一个新的不透明CTID.,我们会打电话一世.Postgres需要能够区分新的活跃元组一世来自旧元组D..在内部,Postgres在每个元组中存储版本字段和指向先前元组的指针(如果有一个)。因此,表的新结构如下所示:

CTID. 上一个 id 第一的 最后的 出生年
一种 空值 1 布莱斯 帕斯卡尔 1623.
B. 空值 2 格特弗里德 莱布尼兹 1646.
C 空值 3. 艾美 no 1882年
D. 空值 4. 默罕默德 al-Khwārizmī 780.
E. 空值 5. 艾伦 图灵 1912年
F 空值 6. Srinivasa. Ramanujan 1887年
G 空值 7. ada. leavelace. 1815.
H 空值 8. 亨利。 Poincaré. 1854年
一世 D. 4. 默罕默德 al-Khwārizmī 770.

只要存在两个版本的al-khwārizmī行,索引必须包含两行的条目。对于简洁起见,我们省略了主键索引并仅显示此处的辅助索引,这看起来像这样:

第一的 最后的 CTID.
ada. leavelace. G
艾伦 图灵 E.
布莱斯 帕斯卡尔 一种
艾美 no C
格特弗里德 莱布尼兹 B.
亨利。 Poincaré. H
默罕默德 al-Khwārizmī D.
默罕默德 al-Khwārizmī 一世
Srinivasa. Ramanujan F

出生年 CTID.
770. 一世
780. D.
1623. 一种
1646. B.
1815. G
1854年 H
1887年 F
1882年 C
1912年 E.

我们以绿色的红色和新行版本代表了旧版本。在引擎盖下,Postgres使用其他持有行版本的字段确定哪个元组最近。此添加的字段允许该数据库确定要为可能不允许的事务提供服务的行元素,以查看最新的行版本。

postgres_tuple_property_
使用Postgres,主要索引和辅助索引直接到磁盘上的元组偏移。当元组位置发生变化时,必须更新所有索引。

复制

当我们将新行插入表时,如果启用流复制,Postgres需要复制它。对于崩溃恢复目的,数据库已经维护了一个注册日志(WAL)并使用它实施两阶段提交.即使未启用流式复制,数据库必须维护此WAL,因为WAL允许原子性和持久性方面

我们可以通过考虑如果数据库意外崩溃(比如突然断电)会发生什么来理解WAL。WAL代表了数据库计划对表和索引的磁盘内容所做的更改的总帐。当Postgres守护进程第一次启动时,进程会将这个分类账中的数据与磁盘上的实际数据进行比较。如果账本包含没有反映在磁盘上的数据,数据库将纠正任何元组或索引数据,以反映WAL所指示的数据。然后,它回滚任何出现在WAL中的数据,但这些数据来自一个部分应用的事务(意味着该事务从未提交)。

Postgres通过将主数据库上的WAL发送到副本来实现流复制。每个副本数据库都有效地执行崩溃恢复,不断地应用WAL更新,就像它在崩溃后启动一样。流复制和实际崩溃恢复之间的唯一区别是,在“热备”模式下的副本在应用流WAL时服务读查询,而实际上处于崩溃恢复模式的Postgres数据库通常拒绝服务任何查询,直到数据库实例完成崩溃恢复过程。

由于WAL实际设计用于崩溃恢复目的,因此它包含有关磁盘上更新的低级信息。WAL的内容在行元组及其磁盘偏移的实际磁盘表示的级别(即,行ctids)。如果在完全捕获副本时暂停Postgres Master和Replica,则副本上的实际磁盘内容完全匹配Byte主字节上的内容。因此,工具就像rsync.如果它与主人无限期,可以修复损坏的副本。

Postgres设计的后果

Postgres的设计导致了我们的效率低下和困难数据在超级

写放大

Postgres设计的第一个问题在其他情况下被称为写放大.通常,写入放大是指将数据写入SSD磁盘的问题:小逻辑更新(例如,写入几个字节)在转换为物理层时变为更大的Costlier更新。Postgres中出现了同样的问题。在我们之前的示例中,当我们为Al-Khwārizmī诞生年份进行了小的逻辑更新时,我们必须发出至少四个物理更新:

  1. 将新行元组写入表空间
  2. 更新主键索引,为新元组添加一条记录
  3. 更新(第一的最后的)为新元组添加记录的索引
  4. 更新出生年索引为新元组添加记录

事实上,这四个更新仅反映了对主要表空间所做的写作;这些写入中的每一个也需要在WAL中反映,因此磁盘上的写入总数甚至更大。

这里有什么值得注意的是更新2和3.当我们更新Al-Khwārizmī的诞生年时,我​​们实际上并没有改变他的主要密钥,也没有改变他的第一个和姓氏。但是,必须使用在数据库中创建行记录的新行元组来更新这些索引。对于具有大量二级索引的表,这些多余的步骤可能导致巨大的效率低下。例如,如果我们在其上定义了有十几个索引的表,则必须将由单个索引覆盖的字段的更新传播到所有12个索引中以反映CTID.对于新行。

复制

这个写入放大问题自然也会转化为复制层,因为复制发生在磁盘上更改的级别。而不是复制一个小的逻辑记录,例如“更改出生年份”CTID.D.现在是770,“数据库反而为我们刚才描述的所有四个写入写出了WAL条目,并且所有四个这些WAL条目都在网络上传播。因此,写入放大问题还转化为复制放大问题,并且Postgres复制数据流快速变得非常冗长,可能占据大量带宽。

在Postgres复制纯粹在单个数据中心内发生的情况下,复制带宽可能不是问题。现代网络设备和交换机可以处理大量带宽,许多托管提供商提供免费或廉价的数据内部中心带宽。但是,当数据中心之间必须发生复制时,问题可以快速升级。例如,优步最初使用了西海岸的拼放空间中的物理服务器。对于灾难恢复目的,我们在第二个东海岸的托管空间中添加了服务器。在这种设计中,我们在我们的西部数据中心和东部的一组复制品中有一个Master Postgres实例(加上副本)。

级联式复制将数据中心间的带宽要求限制在主数据中心和单个副本之间所需的复制量,即使第二个数据中心中有许多副本。但是,冗长的Postgres复制协议对于使用大量索引的数据库仍然会导致大量数据。购买非常高带宽的跨国链路是昂贵的,即使在钱不是问题的情况下,也不可能获得与本地互连相同带宽的跨国网络链路。这个带宽问题也给我们的WAL档案带来了问题。除了发送所有细胞膜的更新从西海岸到东海岸,我们存档所有犯下一个文件存储web服务,包括额外的保证,我们可以恢复数据在发生灾难,以便存档细胞膜可能带来新的副本从数据库快照。在早期的高峰流量期间,我们对存储web服务的带宽不足以跟上WALs被写入的速度。

数据损坏

在常规主数据库促销期间增加数据库容量,我们遇到了Postgres 9.2错误。跟随副本时间轴交换机不正确,导致其中一些误报了一些沃尔记录。由于此错误,版本控制机制应该被标记为非活动的一些记录并未实际标记为非活动状态。

以下查询说明了该错误如何影响我们的用户表示例:

选择*来自ID = 4的用户;

此查询将返回两个记录:原始的Al-Khwārizmī行与780年出生年份,加上新的al-khwārizmī行,其中770年出生年份。如果我们要添加CTID.到了在哪里列表,我们会看到不同的CTID.两个返回的记录的值,因为一个人会对两个不同的行元组进行期望。

出于几个原因,这个问题非常令人烦恼。要启动,我们无法轻易识别出此问题影响了多少行。从数据库返回的重复结果导致应用程序逻辑在许多情况下失败。我们最终添加了防御性编程语句,以检测已知该问题的表格的情况。由于BUG影响了所有服务器,因此在不同的副本实例上损坏的行不同,这意味着在一个副本行上X可能是坏和争吵y会很好,但在另一个副本行上X也许可以划船y可能是坏的。事实上,我们不确定损坏数据的副本数量,也不确定这个问题是否影响了主服务器。

从我们可以说的那样,问题只在每个数据库中表现出几行,但我们非常担心,因为复制发生在物理级别,我们最终可能会完全破坏我们的数据库索引。B树的重要方面是他们必须定期重新平衡,这些重新平衡操作可以完全改变树的结构,因为子树被移动到新的磁盘位置。如果移动了错误的数据,这可能导致树的大部分变为完全无效。

最后,我们能够追踪实际错误并使用它来确定新推广的主人没有任何损坏的行。我们通过将所有这些从船长的新快照重新转换为艰苦的过程来修复了副本上的腐败问题;我们一次只有足够的能力,一次从负载平衡池中采取一些复制品。

我们遇到的bug只影响了Postgres 9.2的某些版本,并且已经修复了很长一段时间。然而,我们仍然感到担忧的是,这类错误可能会发生。新版本的Postgres可以在任何时候发布,如果有这种性质的错误,而且由于复制的工作方式,这个问题有可能蔓延到复制层次结构中的所有数据库中。

副本MVCC.

Postgres不支持真正的副本MVCC。副本应用WAL更新的事实结果它们具有与主站相同的磁盘数据副本在任何给定的时间点。这种设计对优步构成了问题。

Postgres需要维护MVCC的旧行版本的副本。如果流副本副本具有打开的事务,则会阻止数据库的更新,如果它们影响交易打开的行。在这种情况下,Postgres暂停了WAL应用程序线程,直到交易结束。如果事务需要很长一段时间,这是有问题的,因为副本可能会严重落后于主人。因此,Postgres在这种情况下应用超时:如果事务阻止WAL应用程序设定时间,Postgres杀死了该交易。

这种设计意味着副本可以常规延迟延迟秒,因此很容易编写导致杀死事务的代码。应用程序开发人员编写掩盖事务启动和结束时的代码可能是显而易见的。例如,说明开发人员有一些代码必须向用户发送电子邮件收据。根据它的编写方式,代码可能隐含地具有在电子邮件完成发送后保持打开的数据库事务。尽管it’s always bad form to let your code hold open database transactions while performing unrelated blocking I/O, the reality is that most engineers are not database experts and may not always understand this problem, especially when using an ORM that obscures low-level details like open transactions.

Postgres升级

因为复制记录在物理级别工作,所以无法复制Postgres的不同通用可用性版本之间的数据。运行Postgres 9.3的主数据库无法复制到运行Postgres 9.2的副本,也不能将主运行9.2复制到运行Postgres 9.3的副本。

我们跟着这些步骤从一个Postgres升级到另一个PainGRE释放:

  • 关闭主数据库。
  • 运行一个名为pg_upgrade.在主机上,将主数据更新到位。对于大型数据库,这很容易需要多个小时,并且在此过程发生时可以从主服务器提供流量。
  • 再次启动主人。
  • 创建一个新的主人快照。此步骤完全复制来自主设备的所有数据,因此大型数据库也需要很多时间。
  • 擦除每个副本并将来自主设备的新快照恢复到副本。
  • 将每个副本带回复制层次结构。等待副本完全追赶Master在恢复副本时由主应用程序应用的所有更新。

我们始于Postgres 9.1并成功完成了升级过程以转向Postgres 9.2。但是,这个过程花了这么多小时,我们不能再做这个过程。当Postgres 9.3出来时,优步的增长大大增加了我们的数据集,因此升级甚至冗长。出于这个原因,我们的旧版Postgres实例运行Postgres 9.2至今,即使目前的Postgres GA发行量为9.5。

如果您正在运行Postgres 9.4或更高版本,您可以使用类似的东西pglogical这实现了Postgres的逻辑复制层。使用PGlogical,您可以复制不同Postgres版本中的数据,这意味着它可能会在没有导致的大量停机情况下进行升级。此功能仍然存在问题,因为它未集成到Postgres MainLine Tree中,并且Plogical仍然不是在旧的Postgres释放上运行的人员的选项。

MySQL的体系结构

除了解释一些Postgres的局限性之外,我们还解释了为什么MySQL是较新优步工程存储项目的重要工具,如艺术件。在许多情况下,我们发现MySQL对我们的用途更有利。要了解差异,我们检查了MySQL的架构以及与Postgres的造影方式。我们专门分析MySQL如何与之合作InnoDB存储引擎.我们不仅在优步使用InnoDB;这也许是最受欢迎的MySQL存储引擎。

InnoDB on-Disk的表示

与Postgres一样,InnoDB支持像MVCC和可变数据等高级功能。令人遗憾的讨论InnoDB的磁盘格式超出了本文的范围;相反,我们将专注于与Postgres的核心差异。

最重要的架构差异是,Postgres直接将索引记录映射到磁盘上的位置,而InnoDB维护一个二级结构。而不是持有指向磁盘上行位置的指针(如CTID.在Postgres中,InnoDB二级索引记录将指向主键值的指针保持指针。因此,MySQL中的辅助索引将索引键与主键相关联:

第一的 最后的 ID(主键)
ada. leavelace. 7.
艾伦 图灵 5.
布莱斯 帕斯卡尔 1
艾美 no 3.
格特弗里德 莱布尼兹 2
亨利。 Poincaré. 8.
默罕默德 al-Khwārizmī 4.
Srinivasa. Ramanujan 6.

为了对(第一个,最后一个)索引执行索引查找,我们实际上需要进行两次查找。第一个查找将搜索表并找到记录的主键。找到主键后,第二次查找将搜索主键索引,以找到该行在磁盘上的位置。

这种设计意味着InnoDB在进行次要密钥查找时对Postgres的略有缺点,因为InnoDB必须与InnoDB一起搜索两种索引,因为Postgres只有一个索引。但是,由于数据已归一化,因此行更新仅需要更新行更新实际更改的索引记录。此外,InnoDB通常会在适当位置进行行更新。如果旧交易需要引用一行,因为MVCC MySQL将旧行复制到一个名为the的特殊区域回滚段

让我们遵循更新Al-Khwārizmī的诞生年度时会发生什么。如果有空间,则排中的诞生年条id4已更新到位(实际上,此更新始终发生在位,因为诞生年份是占用固定数量的空间的整数)。出生年度指数也更新到位以反映新日期。旧行数据被复制到回滚段。主键索引不需要更新,也不需要(第一的最后的指数)的名字。如果这个表上有大量索引,我们仍然只需要更新实际上在出生年场地。所以说我们有索引索引像田野signup_date.last_login_time等等,我们不需要更新这些索引,而Postgres则必须。

这种设计也使吸尘和压实更有效。所有有资格吸尘的行都可以直接在回滚段中提供。相比之下,Postgres Autovacuum进程必须进行全表扫描以识别已删除的行。

mysql_index_property_
MySQL使用额外的间接层次:辅助索引记录指向主索引记录,主索引本身保存磁盘上行位置。如果行偏移更改,则只需更新主要索引。

复制

MySQL支持多个不同的复制模式

  • 基于语句的复制复制逻辑SQL语句(例如,它将逐字复制文字语句,如:UPDATE users SET birth_year=770 WHERE id = 4
  • 基于行的复制复制了更改的行记录
  • 混合复制混合了这两种模式

这些模式有各种各样的权衡。基于语句的复制通常是最紧凑的,但是可能需要副本应用昂贵的语句来更新少量数据。另一方面,类似于Postgres WAL复制的基于行的复制更加冗长,但会在副本上产生更可预测和更有效的更新。

在MySQL中,只有主索引有一个指向磁盘上行的偏移量的指针。当涉及到复制时,这有一个重要的后果。MySQL复制流只需要包含有关行逻辑更新的信息。复制更新的类型可以是“更改行时间戳”Xt1t2。Replicas自动推断出这些语句之后需要进行的任何索引更改。

相比之下,Postgres复制流包含物理更改,例如“在磁盘偏移量8,382,491处写入字节XYZ.“使用Postgres,对磁盘所做的每个物理更改都需要包含在WAL流中。小的逻辑变化(比如更新时间戳)需要很多磁盘上的变化:Postgres必须插入新的元组并更新所有指向该元组的索引。因此,许多变化将被放到WAL流中。这种设计差异意味着MySQL复制二进制日志明显比PostgreSQL的WAL流更紧凑。

每个复制流如何工作也对MVCC如何与副本合作的重要结果。由于MySQL Replication Stream具有逻辑更新,因此副本可以具有真正的MVCC语义;因此,刷新副本上的查询不会阻止复制流。相比之下,Postgres Wal Stream包含物理的磁盘更改,因此Postgres replicas无法应用与读取查询冲突的复制更新,因此它们无法实现MVCC。

MySQL的复制架构意味着如果错误确实导致表损坏,问题不太可能导致灾难性的失败。复制会发生在逻辑图层中,因此重新平衡一个操作b -树永远不会导致索引损坏。典型的MySQL复制问题是跳过语句的情况(或者,频繁,应用两次)。这可能导致数据丢失或无效,但它不会导致数据库中断。

最后,MySQL的Replication架构使得在不同的MySQL版本之间进行速度进行琐碎。如果复制格式更改,MySQL仅递增其版本,在各种MySQL版本之间是不寻常的。MySQL的逻辑复制格式也意味着存储引擎层中的磁盘更改不会影响复制格式。MySQL升级的典型方式是一次将更新应用于一个副本,一旦您更新所有副本,您将推广其中一个成为新的主人。这可以通过几乎零停机时间来完成,并且简化了保持MySQL的最新状态。

其他MySQL设计优势

到目前为止,我们主要关注的是Postgres和MySQL的磁盘架构。MySQL体系结构的其他一些重要方面也使它的性能明显优于Postgres。

缓冲池

首先,缓存在两个数据库中不同地工作。Postgres为内部缓存分配了一些内存,但与机器上的存储器总量相比,这些缓存通常很小。为了提高性能,Postgres允许内核通过最近访问最近访问的磁盘数据页面缓存.例如,我们最大的Postgres副本具有768 GB的内存,但实际上只有大约25 GB的内存RSS内存Postgres过程中的错误。这将留下超过700 GB的内存,即Linux页面缓存。

与访问RSS存储器相比,通过页面缓存访问数据的问题实际上略有昂贵。从磁盘查找数据,Postgres进程问题LSEEK(2)阅读(2)系统调用以找到数据。这些系统中的每一个都会引起上下文切换,该上下文切换比访问来自主存储器的数据更昂贵。事实上,Postgres在这方面甚至没有完全优化:Postgres没有利用普令(2)系统呼叫,合并寻找+将操作转换为单个系统调用。

相比之下,InnoDB存储引擎在它呼叫InnoDB的某些东西中实现了自己的LRU缓冲池.这在逻辑上类似于Linux页面缓存,但是是在用户空间中实现的。虽然比起Postgres的设计要复杂得多,InnoDB的缓冲池设计也有一些巨大的优点:

  1. 它可以实现自定义LRU设计。例如,可以检测将吹出LRU并防止它们造成太多损坏的病理访问模式。
  2. 它导致较少的上下文交换机。通过InnoDB缓冲池访问的数据不需要任何用户/内核上下文切换。最糟糕的情况是发生的情况TLB小姐,这是相对便宜的,可以通过使用最小化巨大的页面

连接处理

MySQL通过产卵每连接来实现并发连接。这相对较低的开销;每个线程都有一些用于堆栈空间的内存开销,以及在堆上分配的一些内存,用于连接特定的缓冲区。将MySQL扩展到10,000或So Concurrent Connection并不罕见,实际上我们今天对某些MySQL实例进行了接近此连接。

但是,Postgres使用每次连接过程设计。由于多种原因,这比按线/连接设计更昂贵。分叉新过程占用的内存比产卵更多的内容。此外,IPC之间的过程比线程之间的昂贵得多。Postgres 9.2用途System V IPC用于IPC而不是轻量级的原语贵妇人使用线程时。Futexes比系统v IPC速度更快,因为在Dutex无关的常见情况下,无需进行上下文切换。

除了与Postgres设计相关的内存和IPC开销之外,Postgres似乎简单地支持处理大连接数,即使有足够的内存可用。我们对Postgres进行了重大问题,超过了几百个活动连接。尽管文档不是对原因非常具体,它强烈建议采用流程的连接汇集机制来扩展到与Postgres的大连接计数。因此,使用pgbouncer与Postgres进行连接汇集一般对我们来说一直成功。但是,我们在我们的后端服务中遇到了应用程序错误,导致它们打开更多活动连接(通常是“交易”连接中的空闲),而不是经得使用的服务,并且这些错误导致我们的扩展时间延长了。

结论

在Uber的早期,Postgres为我们提供了很好的服务,但我们在增长过程中遇到了重大问题。今天,我们有一些遗留的Postgres实例,但是我们的大部分数据库是建立在MySQL之上的(通常使用我们的艺术或者,在某些特殊情况下,使用像Cassandra这样的NoSQL数据库。总的来说,我们对MySQL很满意,将来我们可能会有更多的博客文章来解释它在Uber的一些更高级的用途。雷竞技到底好不好用

Evan Klitzke是一名员工软件工程师之内Uber Engineering.核心基础设施集团。他也是一个数据库爱好者和加入优步作为2012年9月作为工程早鸟。

注释