分享 PostGraphile:将 Postgres 数据库变成全功能 GraphQL 后端

mizuhashi · 2020年08月26日 · 最后由 ericguo 回复于 2020年08月30日 · 2981 次阅读

https://www.graphile.org/

这个工具可以直接把一个 postgres 数据库生成为一个 graphql 后端,内置了对每个表格的 crud query 及 mutation。

为什么说是全功能的呢?因为仅仅通过 graphile 和 pg 可以做到传统后端需要的任何事,而且所有需要写的代码和配置都在 postgres 里,迁移的时候直接导出 postgres 数据库,就能备份整个后端。

除了生成的 crud,用户可以用 pl/sql 写业务逻辑的函数,一个有副作用(cud)的函数会直接挂载为 graphql 的 mutation,无副作用的则会挂载为 query。

graphile 对数据库的更新鉴权是使用 pg 内置的 role 和 row level security 实现的。一般应用需要两个 role:登陆前的 anonymous 和登陆后的 user。我们可以通过 grant 来对不同 role 配置他们能对表进行的操作,而 row level security 则允许我们配置一个 session 对行的权限,在 cud 行的时候,可以执行一些 check,例如检查当前用户 id 是不是和要修改的数据一样。

和 hasura 对比的好处:

  • graphile 以 postgres 为中心,没有自己的 metadata。hasura 把关联的配置保存为一份自己的配置,这样在迁移的时候除了导出数据库,还需要导出 hasura 的配置。而 graphile 的关联信息是完全通过 pg 内的外键关联推导出来的。
  • hasura 实现登陆鉴权功能的时候需要一个外部服务器。graphile 不需要一个额外的服务器,凭借自身就可以实现登陆注册,签署 jwt。
  • graphile 使用 pg 内置的 row level security 来处理权限,而 hasura 是用一套自己的鉴权机制来处理的。

期待使用 graphile 做出的纯 pg 后端的应用。

要不要 clone 一个?

mizuhashi Rails UJS + Stimulusjs + Turbolinks 5 = ❤️ 提及了此话题。 08月26日 18:26
hooopo 回复

clone 是指啥?如果是指 clone 一个项目结构啥的,graphile 是没有的,一个命令行启动服务器就够了。

mizuhashi 回复

我是说造轮子 实现一个 db to graphql 的服务

hooopo 回复

在 ruby 里造么?我感觉 graphile 就挺好了 lol

mizuhashi 回复

好久没关注 graphile 了 但觉得他的思路一直在 db 层面跑到底 这个受众太窄了 能写 pg function 的开发者太少了 rsl 其实没有 hasura 的 dsl 灵活 metadata 独立我觉得是优点 hasura 也同样导出 db 就可以迁移 他的 metadata 也就是普通的 table

hooopo 回复

这个确实是他的思路,他喜欢把 postgres 用尽,尽量不加任何额外的东西,我倒是非常喜欢,但是这样也确实有些坏处就是有些东西你不用 plsql 是做不了的,比如 transaction,需要写成 pl 函数。未来大概 plv8 也会发展起来吧,虽然没有很觉得 plsql 很复杂。

rsl 和 dsl 指的是哪个?metadata 我觉得还是放在数据库里好,我没有深入用过 hasura,但是按他们官网的说法:All the actions performed on the console, like tracking tables/views/functions, creating relationships, configuring permissions, creating event triggers and remote schemas, etc. can be exported as a JSON/yaml metadata file which can be version controlled. 这里面的东西还挺多的,我觉得都放在数据库里反而更好,但是 graphile 看起来还缺少一个类似 migration 的东西,hasura 的 metadata 可以看出实际做了什么,但是 graphile 就分散融入到 db 里了。

mizuhashi 回复

hasura 那个 migration 你可以理解为就是一个 online 的 rails migration,导出的 JSON/YAML 其实是冗余,他的这些信息都在 db 里存的。当然我觉得最佳的情况是,可以对每个 schema 修改做记录,像 rails 那样方便 rollback 某个迁移,这个 hasura 界面上我是没看到,要自己手动改回去。

rls 和 dsl 就是 graphile 和 hasura 做权限控制和可见控制的两个实现。

https://github.com/dosco/super-graph 这个我觉得和 rails 集成更友好的一个,我其实想做一个类似的,以 rails engine 方式来用,对于 API 项目开发效率会有很大提升。不一定拿来做 BaaS 服务。

hooopo 回复

居然还有这么多同类项目,刚刚看到 graphile 的 migrate https://github.com/graphile/migrate ,解决了 migration 的问题,方不方便就不好说了...

我之前也想过,不过我想的不是 graphql,是渐进式地生成 restful crud api,有默认的,也可以自己改 controller action,但是魔改 rails 的 routes 很麻烦,有些 autoload 的问题不知道怎么搞

mizuhashi 回复

额 我前段时间一直调研这类,太多了 还有这个:https://github.com/supabase/supabase

最早的鼻祖算是 postgrest 吧,大部分技术原型都来自那个项目...

他喜欢把 postgres 用尽,尽量不加任何额外的东西,我倒是非常喜欢

除非 pg 彻底解决横向扩展问题,否则第一个 hit 到性能瓶颈的肯定是这样用的 pg,到时候你写的应用就会很尴尬,不像一般的 Rails 应用,至少可以通过加机器再撑一把。

ericguo 回复

问题是 rails 起到了什么作用,如果只是 crud 的话那看不出来为什么 pg 会跪,因为 rails 处理一个请求对 sql 的请求,比起 graphql 不会有减少,如果不只是 crud,这个方案也一样可以扩展多一层用来做 cpu 计算

mizuhashi 回复

但是 Rails 只会发出 SQL,SQL 的执行计划可以很容易的缓存,最终实际上 pg 数据库的 CPU 需要做的工作很少,而PostGraphile 用了 computed-columns,看起来也很鼓励用户在数据库层上加更多的应用逻辑。

Performance note: we inline these function calls into the original SELECT statement, so there's no N+1 issues - it's very efficient.

这会花费数据库更多的 CPU 时间,我的担心是:应用服务器(哪怕 Rails 效率略低)比较容易扩展,但是数据库服务器非常难扩展,所以如果用这套 graphql,很可能过早的碰到 pg 数据库服务器 CPU 跑满的问题,而数据库,你是很难横向扩展的。

ericguo 回复

我觉得有点危言耸听了

computed columns 的例子其实只是说明 graphile 有这种能力处理,实际上如果你是一个 BaaS 服务,直接接前端,这个是可以前端自己处理的;如果是作为纯后端 API,中间层的 Rails/PHP 是可以处理的...

如果是排序场景,还可以用 pg 12+ 的 stored column

graphile 这类服务并没有鼓励在 DB 里做应用逻辑,一般都是 EventSourcing 架构,记录的更新抛给 serverless 服务,逻辑在 serverless 里处理。DB 里做的只是简单的 query 和 mutation,这个用 Rails 也要执行的,并且可能生成的 sql 更低效

和 Rails 这种比,其实省掉了 App Server 的费用,当然 graphile 之类服务也是有场景限制的,场景适合的话一定比传统架构吞吐量高出几十倍

数据库很难横向扩展也是有条件的,比如大数据和写入密集型,普通的 web 应用主从 + 读写分离就解决了 80% 的问题,然后还有 citusdb、coackroch,yugabyte。我觉得 DB 不能横向扩展已经是伪命题了

hooopo 回复

是有点说的重,我的意思是在选某个方案的时候,了解一下其他声音也好,无论如何,数据库的横向扩展肯定比应用服务器的横向扩展难多了,否则 Rails 6.1 也不会出一堆支持多服务器连接的多数据表的功能了。

如果是 GraphQL 的方案,我其实还是更站redwood.js选的Apollo,没有其他理由,就是觉得阿波罗这个名字听上去很有力量(比如阿波罗男子医院🐶)

ericguo 回复

印象中 apollo 只是一个 graphql 客户端工具集和中间件吧 和 hasura 这种 instant graphql server 还是没有太大可比性

「DB 不容易横向扩展」和「应用服务器容易横向扩展」这个前提有道理,但我观察到的现象就是当这个前提被无限放大之后,很多做法就变味了,比如 sql 的 sum 不用,改用应用里 select 之后再 sum;join 不用,改用应用程序自己实现 join;

然而真正对 DB 伤害大的我发现主要是这三个:

  • 表结构设计不合理,查询索引设计不合理
  • ORM 使用不当,生成效率低的 SQL 和 N+1
  • 架构不合理,OLTP 和 OLAP 混用

之前那些例子本质上都对 DB 没什么伤害,而后面的例子才是 DB 的性能杀手,大概就是「以大多数人的努力程度之低,根本轮不到拼天赋」的意思。

sql 的 sum 不用,改用应用里 select 之后再 sum;join 不用,改用应用程序自己实现 join;

说的正是在下。。其实这两点还是值得一说的,比如在应用里面,有明细和总和,既然已经从数据库拿到了明细,为啥不在显示的时候累加一下,然后直接显示总和呢?

至于为啥要应用程序自己实现 join,因为这样快,比如在报表里面经常需要将部门 code 翻译为部门名字,更变态的是部门的名字还会时不时的改一下,假设部门名字一个月一改的话,如下代码就远远比自己写 SQL join 要好的多了。

@data_dept_short_names = data_deptcode.collect { |d| Bi::OrgReportDeptOrder.department_names(data_last_available_date).fetch(d, Bi::PkCodeName.mapping2deptcode.fetch(d, d)) }

module Bi
  class OrgReportDeptOrder < BiLocalTimeRecord
    self.table_name = 'ORG_REPORT_DEPT_ORDER'

    def self.department_names(available_date)
      Rails.cache.fetch("#{available_date.to_s(:short_month)}/department_names", expires_in: 1.hour) do
        where("是否显示 = '1'")
          .where('开始时间 <= ?', available_date)
          .where('结束时间 IS NULL OR 结束时间 >= ?', available_date)
          .reduce({}) do |h, s|
          h[s.编号] = s.部门
          h
        end
      end
    end
  end
end

其实应用的复杂度是一个总值,schema 设计的让 OLTP 写起来很舒服,往往 OLAP 就很痛苦,当然还可以使用 ETL 工具重新抽取转换,但是,总是少了实时性。

我倒是觉得以 Rails 的表达能力,在做 schema 设计的时候,可以适当对 OLAP 倾斜,这样也就没有 ETL,读写分离啥事了,当然,这样也就变成了扁鹊的大哥,名不出于家了。。。

ericguo 回复

并不是针对你...这种做法挺流行的我知道

你这个累加例子量太小,分页都不需要,当然可以这么做了,稍微正常一点的报表(数量>50w)这么做还是明显感觉到慢的

第二个 join 的例子,直接把部门代码和部门名称都入库就没这问题了

第三个例子,经常见到的一个状况就是这种混合查询拖垮数据库,也不一定需要 ETL 那么重,配置个从库,统计类查询使用从库就可以减轻主库的负担,阿里云上点几下就可以的事

join 的例子,直接把部门代码和部门名称都入库就没这问题了

哪有那么容易,业务部门在 8 月告诉你,从 6 月起,某个部门名字换掉,难道你还能全部更新之前跑出来的报表数据不成?

统计类查询使用从库就可以减轻主库的负担,阿里云上点几下就可以的事

没错,但是如果适当针对 OLAP 倾斜,是不需要点这几下的,而且点了这几下也要钱啊!

其实软件开发之所以困难,是因为预测未来的需求是困难的,如果知道未来的需求,设计反倒容易了,这也是我比较喜欢 Rails 的原因,至少人家写的少,未来改起来比较容易,PG 这几年不错,但是 MySQL 8.0.22 以后,查询优化也是做的越来越接近于 Oracle。

回到这个主题上,PostGraphile 可能的确很好,但是我觉得这方案最大问题在于采用后,会减少未来的方案选择余地,我说的那个 Apollo 在这点好很多,它甚至允许你聚合后端的 RestAPI,更不要说多种数据库连接了。

需要 登录 后方可回复, 如果你还没有账号请 注册新账号