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

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

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 #0 回复

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

mizuhashi #2 回复

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

hooopo #3 回复

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

mizuhashi #4 回复

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

hooopo #5 回复

这个确实是他的思路,他喜欢把 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 #6 回复

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 #7 回复

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

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

mizuhashi #8 回复

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

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

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

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

ericguo #10 回复

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

mizuhashi #11 回复

但是 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 #12 回复

我觉得有点危言耸听了

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 #13 回复

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

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

ericguo #14 回复

印象中 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 #16 回复

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

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

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

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

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

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

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

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

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

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

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