TiDB4PG 之兼容 Gitlab

【是否原创】是
【首发渠道】知乎
【首发渠道链接】https://zhuanlan.zhihu.com/p/448122284
【正文】

前言

在数据库流行度排名上,PostgreSQL 和 MySQL 都是名列前茅的存在,他们如此受欢迎,说明他们的优势非常强悍。虽然 PostgreSQL 和 MySQL 各有千秋,但是不免经常被用户拿来比较,想知道两者中谁更厉害,或者谁的缺点更不能被用户接受。就在最近,有一个人就爆出了对 MySQL 的不满。Oracle 前 MySQL 优化器团队成员 Steinar Gunderson,从 MySQL 团队离职时,强烈推荐用户考虑使用 PostgreSQL。这一举动在行业中炸开了锅, 用户对自己拥戴的产品各种维护,讨论尤为激烈。
Steinar Gunderson 在博客中的原句:

MySQL is a pretty poor database, and you should strongly consider using Postgres instead.
MySQL 是一款非常糟糕的数据库,强烈建议大家认真考虑使用 PostgreSQL。

Steinar H. Gunderson 博客

而且 Steinar Gunderson 指出:

那些坚持认为一切都很好的人(似乎大多数MySQL用户和开发人员并没有真正使用其他数据库)

没错,始终使用某一款数据库,不去尝试使用其他的数据库,就不会有对比,也就不会知道,可能有一款更适合自己的数据库产品,正在等着自己。所以在自己的认知中,对某一个数据库的崇拜,可能只是自己的无知或者是偏见。所以,不能先见为主,要保持开放的心态,更多的接纳新鲜事务,了解更多的数据库,才能更合理的挑选数据库产品。

而且,对数据库的选择,一定是要结合自身业务、结合实际场景来做选择的,能够最大程度发挥数据库的能力,扬长避短。没有完美的数据库,只有最适合自己的数据库。

最新数据库流行程度排行榜(如下图),我们可以看到,在开源数据库领域,虽然 MySQL 名列前茅, PostgreSQL 却是紧跟其后,稳稳的坐在了第四名的位置上。

毫无疑问,MySQL 还是当下最流行的开源数据库。但是,PostgreSQL 却是以极快的速度追向 MySQL,其增长之势迅猛无比。

Gitlab的抉择

其实早在2019年,PostgreSQL 与 MySQL 就已经发生过剧烈碰撞了。

在2019年6月27日,Gitlab官方发布公告:要在 Gitlab 12.1以后的版本中,终止对MySQL 数据库的支持,并且解释了这么做的原因。

Why we’re ending support for MySQL in 12.1 | GitLab

在这里说明一下,在 Gitlab 12.1之前的版本,是能够同时支持两款数据库的,分别是 MySQL 和 PostgreSQL 。Gitlab 12.1以后的版本只支持 PostgreSQL ,放弃了 MySQL,这一做法就像是个炸弹,在当时的数据库行业中爆炸。

那么,Gitlab为什么要这么做呢,我们一起来看一下。Gitlab 官方列举了几个 MySQL 不能满足 Gitlab 要求的地方:

当然,终止支持 MySQL 的举动,不仅仅是因为这几项 MySQL 的缺点,还因为Gitlab 创建了许多专门针对 MySQL 的代码,同时支持 MySQL 和 PostgreSQL 两个数据库,让开发团队工作量倍增,苦不堪言。虽然理由很充分,但是单纯从这个举动来看,可以发现,在数据库领域中,MySQL在某些特定的场景恐怕已经被 PostgreSQL 超越了。

而且更为重要的是,公告中说到:

最近只有不到1200个GitLab实例报告其使用MySQL的情况。当时有110,000个实例使用PostgreSQL

这是一个惊人的数字,在当时,Gitlab实例中,使用 PostgreSQL 数据库的占比已经高达98.92%,足以说明PostgreSQL 在用户心中的地位。

国产分布式数据库的崛起-TiDB

TiDB 是 PingCAP 公司设计的开源分布式 HTAP (Hybrid Transactional and Analytical Processing) 数据库,结合了传统的 RDBMS 和 NoSQL 的最佳特性。TiDB 兼容 MySQL,支持无限的水平扩展,具备强一致性和高可用性。TiDB 的目标是为 OLTP (Online Transactional Processing) 和 OLAP (Online Analytical Processing) 场景提供一站式的解决方案。

TiDB是国产分布式数据库,也是数据库领域国际范围内的顶级开源项目。现在,TiDB在国产数据库排行榜上稳坐榜首的位置。

TiDB到底有什么优势,可以夺得榜首呢?

官网给出了 TiDB 的五大核心特性,这也就奠定了TiDB数据库强大的基础:

  • 一键水平扩容或者缩容

  • 金融级高可用

  • 实时 HTAP

  • 云原生的分布式数据库

  • 兼容 MySQL 5.7 协议和 MySQL 生态

基于这些特性,TiDB 目前在互联网、游戏、金融、大型企业等多个领域,拥有大量可靠的用户群体。而且,在 TiDB Lightning、Dumpling、TiDB Binlog、TICDC 等多种 TiDB 生态工具的加持下,让用户能够更加方便快捷的迁移数据,并快速上手使用 TiDB。

想要了解更多关于 TiDB 的信息,可以进入官网:TiDB 简介 | PingCAP Docs

TiDB For PostgreSQL 的诞生

神州数码在2017年开始接触TiDB,基于多年对 TiDB 的了解,深刻的体会到 TiDB 的过人之处。在实践中,TiDB也没有让我们失望,特别是高可用和无限水平扩展的能力,让我们刮目相看。

但是,TiDB 只支持 MySQL 协议,而我们公司内部许多历史系统都是以 PostgreSQL 作为数据库的。针对日益增加的数据,PostgreSQL 数据库早已成为了瓶颈。当我们想用分布式数据库进行优化的时候,却无从选择。于是,我们想到在 TiDB 源码的基础上进行重构,使其兼容 PostgreSQL ,为一些想要从 PostgreSQL 迁移到分布式数据库的系统提供便利,增加 TiDB 的业务范围,所以诞生了 TiDB For PostgreSQL。

更详细的 TiDB For PostgreSQL 起源、我们已经做过的兼容,可以查看下面这篇文章:

TiDB for PostgreSQL—牛刀小试

TiDB For PostgreSQL 与 Gitlab 的兼容

我们已经在 TiDB4PG 中做了许多的工作,包括实现基本的PostgreSQL 通信协议、PostgreSQL 部分关键字和语法、PostgreSQL 系统表和系统函数等等。基本的 PostgreSQL 已经可以正常兼容,现在就缺少一个兼容对象了。

根据我们的调研,Gitlab 是一个非常理想的兼容对象:Gitlab 是一个开源项目,我们可以轻易的从代码层面着手,判断兼容问题;公司内部已经有本地搭建的 Gitlab 环境。根据调研,发现 Gitlab 使用的 SQL 中,大部分我们已经可以兼容。于是,我们正式开始使用 TiDB4PG 作为 Gitlab 的数据库的尝试。

使用 TiDB4PG 兼容 Gitlab,我们的初步计划是先搭建 Gitlab 12.1以前的版本(既可以使用 MySQL,又可以使用 PostgreSQL ),利用抓包工具把Gitlab 与 数据库通信的 SQL 语句统计出来。根据这些 SQL 语句,我们能轻易的判断,哪些语句可以兼容,哪些语句中的关键字或者是系统表等等是需要我们继续兼容的。等到完全兼容所有的SQL 语句的时候,我们应该可以初步兼容 Gitlab 了。

方案定制完成,接下来开始实施。想着方案实施应该会非常的顺利的,没想到第一步就遇到了问题。

一、Gitlab 源码搭建

本来用源码搭建 Gitlab 环境还是很顺利的。在官网能够找到部署流程,按照流程做就可以部署成功。可是好巧不巧的,在拉取依赖的时候出现了一个报错:

image-20211215103412534

这是什么情况?于是我去查阅了相关信息,发现这是因为 mimemagic 中的许可证冲突了,所以作者下架了冲突版本的 mimemagic,并更新了修复之后的 mimemagic。Ruby on rails 是根据 gemfile 文件来安装依赖的,所以我在 gemfile中 修改 mimemagic 的版本号,尝试使用更新版本的 mimemagic 来代替旧版,可是,因为新版依赖与旧版差别较大,导致更新版本后,其他依赖都需要去同步升级,牵扯很大,所以这个办法无法进行下去。

通过升级版本无法解决,就需要换个思维。我想到 gemfile 中,都是以 "gem <名称>,<版本号>"来安装依赖的,但是 gem 可以写成 “gem <名称>, github: ‘<github中项目地址>’, branch: ‘分支名’” 来直接编译源码进行安装。所以我只需要找到github中的相关源码就行。于是我在 github 中检索到0.3.2的源码项目了:‘LaSoftRepo/mimemagic-0.3.2’,替换到gemfile中:

果然,可以正常拉取依赖了。之后的步骤都非常顺利,配置好各种参数后,就搭建好了 Gitlab 环境,并且可以在浏览器正常访问和使用了。

二、SQL 抓取

部署好了 Gitlab 之后,我们就要开始抓取 SQL 语句了。我们修改了 Gitlab 的配置,让 Gitlab 连接到了另一个机器的 PostgreSQL 数据库上,然后开始抓包。首先抓包的场景是 Gitlab 对于数据库的初始化,包括库中的各种表结构和初始数据。接着我们又抓取了几个场景的SQL语句:用户登录、查看项目、查看分支、创建分支、删除分支和用户退出。在这些场景中,我们抓到了大量SQL语句:

# 部分SQL语句
SET client_encoding TO 'UTF8';
SET client_encoding TO 'unicode';
SELECT t.oid,t.typname FROM pg_type AS t WHERE t.typname IN ( 'int2', 'int4', 'int8', 'oid', 'float4', 'float8', 'bool' );
SHOW TIME ZONE;
 
SELECT  c.relname FROM  pg_class c  LEFT JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = ANY (current_schemas ( FALSE )) AND c.relkind IN ( 'r', 'v', 'm', 'p', 'f' );
 
SELECT
    a.attname,
    format_type ( a.atttypid, a.atttypmod ),
    pg_get_expr ( d.adbin, d.adrelid ),
    a.attnotnull,
    a.atttypid,
    a.atttypmod,
    c.collname,
    col_description ( a.attrelid, a.attnum ) AS COMMENT
FROM
    pg_attribute a
    LEFT JOIN pg_attrdef d ON a.attrelid = d.adrelid
    AND a.attnum = d.adnum
    LEFT JOIN pg_type t ON a.atttypid = t.oid
    LEFT JOIN pg_collation c ON a.attcollation = c.oid
    AND a.attcollation <> t.typcollation
WHERE
    a.attrelid = '"geo_nodes"' :: regclass
    AND a.attnum > 0
    AND NOT a.attisdropped
ORDER BY
    a.attnum;
 
SHOW max_identifier_length;
 
SELECT  1 AS one FROM   "geo_nodes" LIMIT 1;
 
SELECT  "shards"."name" FROM    "shards";

SELECT  1 AS one FROM (SELECT "namespaces".* FROM "namespaces" INNER JOIN "members" ON "namespaces"."id" = "members"."source_id" WHERE "members"."type" IN ('GroupMember') AND "members"."source_type" = 'Namespace' AND "namespaces"."type" IN ('Group') AND "members"."user_id" = 2 AND "members"."requested_at" IS NULL AND "members"."access_level" = 50
UNION
SELECT "namespaces".* FROM "namespaces" INNER JOIN "members" ON "namespaces"."id" = "members"."source_id" WHERE "members"."type" IN ('GroupMember') AND "members"."source_type" = 'Namespace' AND "namespaces"."type" IN ('Group') AND "members"."user_id" = 2 AND "members"."requested_at" IS NULL AND "members"."access_level" = 40
UNION
SELECT "namespaces".* FROM "namespaces" WHERE "namespaces"."type" IN ('Group') AND "namespaces"."id" IN (WITH RECURSIVE "base_and_descendants" AS (SELECT "namespaces".* FROM "namespaces" INNER JOIN "members" ON "namespaces"."id" = "members"."source_id" WHERE "members"."type" IN ('GroupMember') AND "members"."source_type" = 'Namespace' AND "namespaces"."type" IN ('Group') AND "members"."user_id" = 2 AND "members"."requested_at" IS NULL AND "members"."access_level" = 30
UNION
SELECT "namespaces".* FROM "namespaces", "base_and_descendants" WHERE "namespaces"."type" IN ('Group') AND "namespaces"."parent_id" = "base_and_descendants"."id") SELECT "id" FROM "base_and_descendants" AS "namespaces") AND ("namespaces"."project_creation_level" = 2 OR "namespaces"."project_creation_level" IS NULL)) namespaces WHERE "namespaces"."type" IN ('Group') LIMIT $1;
 
SELECT  "namespaces".* FROM "namespaces" WHERE "namespaces"."owner_id" = $1 AND "namespaces"."type" IS NULL LIMIT $2;
 
SELECT "feature_gates".* FROM "feature_gates" WHERE "feature_gates"."feature_key" = $1;
 
SELECT  "user_statuses".* FROM "user_statuses" WHERE "user_statuses"."user_id" = $1 LIMIT $2;

三、兼容性判断

我们把抓出来的大量 SQL 语句进行整理,找出还未与 TiDB4PG 兼容的部分,单独记录,大致分为一下几类:

  1. TiDB4PG 的飘号与 PostgreSQL 的双引号

    在 TiDB4PG 中,表名和字段名都是使用飘号括起来的,而 PostgreSQL 中,是用双引号括起来的。

  2. :: 符号,类型转换符号

    在 PostgreSQL 中,大量使用了:: 符号,而 TiDB4PG 中并没有这种写法。

    ::regprocedure
    ::regclass
    ::regclass::text
    ::integer
    ::regtype
    ::oidvector
    
  3. 系统表(包括数据),系统函数,系统变量

    PostgreSQL 中的一些系统表、系统函数、系统变量,在 TiDB4PG 中并不存在或不够完整,比如:

    #系统变量
    standard_conforming_strings
    timezone
    client_encoding
    client_min_messages
    max_identifier_length
    search_path
    WITH
    RETURNING
    
    #系统函数
    pg_get_expr
    generate_subscripts
    current_schemas
    format_type
    col_description
    array_in
     
    #系统表
    pg_type
    pg_class
    pg_index
    pg_namespace
    pg_attribute
    pg_attrdef
    pg_range
    

四、成功兼容 Gitlab

TiDB4PG 不支持的部分内容已经被找到,接下来要做的就是去兼容它们。

我们通过继续重构 TiDB 的源码,使 TiDB4PG 能够更好的兼容 Gitlab。其中,我们也使用了临时表来代替了某些功能的实现,以便更快的看到 TiDB4PG 兼容 Gitlab 的效果。

在小伙伴们没日没夜的不懈努力中,我们成功的使用 TiDB4PG 作为 Gitlab 的数据库,启动了 Gitlab 程序,并且可以正常访问 Gitlab 主页。

能够登录到 Gitlab 主页之后,我们都非常开心,自己的努力有了一个初步的结果。于是我们乘胜追击,继续兼容 TiDB4PG 不支持的部分内容。

截止到目前,我们已经可以使用 TiDB4PG 兼容 Gitlab 的几个场景了:用户登录、查看项目、查看分支、创建分支、删除分支和用户退出。

展望未来

这是 TiDB4PG 兼容的第一个项目,非常的有意义。就目前而言,TiDB4PG 还是有很多不完善的地方,还是有很多的路要走。但是 TiDB4PG 已经开源,希望大家对 TiDB4PG 有信心,并且能够和我们一起,让 TiDB4PG 更加完善,更加强大。

我们接下来的要努力实现的方向:

  • PostgreSQL 关键字和语法
  • PostgreSQL 数据库结构
  • PostgreSQL 系统表和系统函数
  • PostgreSQL 权限管理
  • 部署运维相关的工具

下面是我们的邮箱以及github链接,感兴趣的同学可以与我们联系,交流自己的想法和建议。

dc.opensource@yungoal.com

TiDB-for-PostgreSQL

DCParser

7赞

前排围观~:eyes:

3赞

强的一p

3赞

没太看懂,问个问题,如果想用,TiDB4PG是把tidb server替换了吧?

4赞

是的,我们的想法就是做一个兼容pg协议的tidb-server,未来的设想就是一个tidb集群上面,有兼容各种协议的tidb-server

5赞

跟官方的tidb的优化怎么保持一直呢?是不是官方的tidb server发版本之后,您那边会跟一个版本?

3赞

现在我们这个项目还处在初期阶段,后面我们肯定会根据官方的优化持续跟进

5赞

希望早日走上正轨,我们也有类似的需求,有么有什么交流群啥的,我们参与一下?

3赞

其实我有个比较好的想法,就是把pg这一层抽象起来,和tidb的处理尽量隔离开,这样的话,TiDB的版本变更对pg层支持的影响会尽可能的小

3赞

image
TiDB for PostgreSQL的交流群,项目上有进展我们会及时同步

4赞

这也是我最初的理解,仔细看了一下之后发现不是,感觉还是这样分离的能好一些,不过这涉及到tidb server的组件化问题,不是很简单。

4赞

虽然看不懂,但是觉得很厉害~

3赞

这一块如果抽出来,那么就是单纯的兼容PGSQL协议,很难说支持PG一些特别功能,所以现在要想兼容一些PG语法和功能,还是只能在 TiDB Server 上面动刀了,看看这个项目以后会走哪条路吧

3赞