译为什么Uber将构架从Postgr

译为什么Uber将构架从Postgr

原文:WHYUBERENGINEERINGSWITCHEDFROMPOSTGRESTOMYSQL

译者:杰微刊兼职翻译巫明瀚

简介

Uber早期的构架是一个独立的Python后端程序和负责数据持久化的Postgres数据库组成的。从那个时候开始,Uber的构架就在不停地变化,包括新的微服务和我们的数据平台。值得一提的是,当时很多由Postgres构成的服务,现在转而使用Schemaless了。Schemaless基于MySQL,是一个崭新的数据库分片层。在这篇文章里,我们会介绍更多我们在使用Postgres中碰到的问题,并且解释我们是为何要在MySQL上构建Schemaless等其他后端服务的。

Postgres的架构

我们碰到了很多Postgres的瓶颈:*构架上,写请求效率低下。*主从复制(Replication)数据性能低下*数据表损坏的问题*主从复制数据不支持MVCC(多版本并发控制)*很难更新到新版本

为了搞清楚这些瓶颈的由来,我们会分析Postgres是如何在磁盘上组织数据表和索引的,特别是跟MySQL在使用InnoDB存储引擎存储同样数据的场景下有何区别。值得一提的是,我们的分析全部都是基于一个比较老的版本——Postgres9.2系列的。根据我们的调查,我们在这里要讨论的内部构架在新的Postgres发布版中并没有大的变化。而且这些磁盘数据结构的设计至少从Postgres8.3开始就没有什么大的变化了(至今差不多十年了)。

磁盘数据结构

一个关系型数据库必须要能够完成下面几个关键的任务:

能够插入、更新、删除数据

能够更改数据结构定义

实现多版本并发控制(MVCC)机制,这样每一个链接都能在事务范围内有一致的数据表现。

一个数据库在磁盘上的数据组织方式必须同时考虑到上述所有的需求。

Postgres的一个核心设计思路就是一些行内数据必须为只读。这些只读的行在Postgres里的术语叫做“元组(Tuples)”。这些元组相互独立,并有一个独特的id,在Postgres里被称作ctid。一个ctid理论上记录了一个元组在磁盘上的位置(比如物理磁头位移)。若干个ctid有可能同时代表了一个行(当因为MVCC而存在多个版本的行时或者一个行的老版本还没有来得及被autovacuum进程回收时)。有组织的元组的集合就是数据表。数据表们有自己独立的索引,他们是通过特定的数据结构(一般是B-tree)组织的,并且将索引字段映射到特定的ctid数据块。

总的来说,这些ctids对用户是透明的,但是理解他们的工作原理能让我们更好的理解Postgres在磁盘上数据表的结构。我们可以通过添加一个“ctid”到WHERE语句的列表处来查看一个行当前的ctid:

uber

[local]uber=SELECTctid,*FROMmy_tableLIMIT1;

-[RECORD1]--------+------------------------------

ctid

(0,1)

...otherfieldshere...

为了介绍整个结构,我们先用一个简单的用户表做例子。对每个用户,我们都会用一个自增的ID作为主键,并且包含firstname,lastname,和用户的生日。我们会为用户的全名(firstname和lastname)定义一个复合的二级索引,还会为用户的生日定义一个二级索引。定义这样一个数据表的DDL如下:

CREATETABLEusers(

idSERIAL,

firstTEXT,

lastTEXT,

birth_yearINTEGER,

PRIMARYKEY(id)

);

CREATEINDEXix_users_first_lastONusers(first,last);

CREATEINDEXix_users_birth_yearONusers(birth_year);

注意,定义里面有三个索引:主键的索引和另外两个二级索引。

我们会用下面的数据建立我们的数据表格,里面都是历史上有名的数学家:

我们前面也提到过,每一行都隐含了一个唯一且不透明的ctid。因此我们可以假设内部的表实际如下:

主键的索引是从id到ctid的映射,定义如下:

B-tree是基于id字段的,B-tree中的每一个节点包含一个ctid。注意,在我们的例子中,B-tree中字段的顺序碰巧和表格中的数据一样,因为我们用的是自增的ID,但这并不是必须的。

跟二级索引类似,主要的区别是在与字段存储的顺序,因为B-tree必须按照字典顺序组织。(first,Last)索引根据firstname,从上到下根据字母表的顺序排列。

同样的,birth_year的索引是根据升序构建的,如下:

如表所示,跟自增的主键不同,上面两个例子里面ctid字段都不是根据字母表顺序升序的。

假设我们需要更新这个表里面的一条记录。比方说,我们要更新al-Khwārizmī’s的birth_year字段的记录。我们之前提到过行元组是不可修改的,因此为了更新这个记录,我们要在表上添加一个新的元组。这个新的元组有一个新的不透明字段ctid,这个字段的值是I。Postgres需要能够区分新的元组I和旧的元组D。在内部,Postgres会在旧的元组上维护一个version字段指向之前的元组(如果有的话)。那么,新的表如下所示:

只要这两个版本的al-Khwārizmī行存在,索引就必须维护这两个行。为了简化问题,我们省略主键的索引只在这里列出二级索引,如下:

我们把老版本的用红色表示,新版本的行有绿色表示。在内部,Postgres会使用一个额外的字段来存储哪个版本是最新的。这个额外的字段让数据库决定事务当中行元组应不应该被包含到最新的版本中去。

主从数据复制

当我们添加一个新的行到表格中区的时候,如果流式主从复制选项被启动了的话,Postgres就得进行主从复制操作。考虑到宕机恢复的情况,数据库必须有一个write-aheadlog(WAL写前日志)并用这个日志实现一个two-phase







































北京白癜风专科医院地址
乌鲁木齐治疗白癜风医院



转载请注明:http://www.92nongye.com/ksfc/204612614.html