
后端API网关【免费下载链接】crystal Graphiles Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!项目地址https://gitcode.com/gh_mirrors/cry/crystal点击查看免费下载本文以 PostGraphile v5 官方调试文档 为骨架结合当前仓库中grafserv、dataplan/pg、graphileCLI 的源码实现系统梳理应用出问题时的排查路径。你将掌握如何确认 GraphQL 请求真实内容、如何解除错误掩码并安全地暴露错误详情、如何查看 PostGraphile 生成的 SQL 与 EXPLAIN 结果、如何定位 Schema 中多出来/缺了的内容、以及如何针对 RLS 与过度获取做性能排查最后还能用 Chrome DevTools 直接调试 PostGraphile 进程本身。当应用行为与预期不符时第一步不是急着改代码而是判断你遇到的到底属于哪一类问题——是 GraphQL 请求层面的问题、Schema 内容的问题、性能问题还是 PostGraphile 内部实现的问题。不同类型的问题有不同的排查工具和手段用错了方向往往会浪费大量时间。下面按文档的官方分类逐层展开。一、GraphQL 请求出了问题1.1 先用 Chrome Network 面板确认你请求的就是你以为的很多bug其实源于客户端代码并没有发送你以为的请求。在动手排查服务端之前先用浏览器开发者工具确认网络层的真实情况在 Chrome 中打开你的网站右键选择检查Inspect在开发者工具中选择Network标签页在过滤框中输入/graphql或你实际配置的 API 路径确保过滤框右侧选中的是All触发你的 GraphQL 请求刷新页面或点击页面上相关元素检查到达的请求是否符合预期——变量是否意外为null、请求头中的访问令牌是否正确携带等。这一步能立刻排除掉大量客户端写错的情况把焦点收敛到服务端。1.2 在 Ruru或 GraphiQL中复现同一个查询有时候用另一种方式做同一件事更容易发现问题。把出问题的查询原样拿到 RuruPostGraphile v5 自带的 GraphiQL 变体里执行一次看是否复现同样的问题。注意Ruru 底部输入变量的位置有一个Headers标签页可以在那里设置请求头例如Authorization从而模拟客户端携带的认证信息。1.3 解除错误掩码用preset.grafserv.maskError输出错误细节PostGraphile 默认会对 GraphQL 错误进行掩码mask详细的错误信息只在服务端被记录返回给客户端的是被裁剪过的安全内容。若你关闭了错误掩码错误会被直接透传给客户端、不再在服务端记录——这种情况下可以用preset.grafserv.maskError在服务端输出错误详情并在返回客户端前对错误进行自定义加工。文档给出的完整示例配置如下graphile.config.mjsimport { GraphQLError } from postgraphile/graphql; import { isSafeError } from postgraphile/grafast; import { createHash } from node:crypto; const sha1 (text) createHash(sha1).update(text).digest(base64url); export default { //... grafserv: { maskError(error) { console.error(maskError was called with the following error:); console.error(error); console.error(which had an originalError of:); console.error(error.originalError); // 生产环境不建议直接返回原始 error因为结果会发送给客户端 // 可能泄露你不想公开的实现细节。 // // return error; // 下面是一个更谨慎的实现 if (error.originalError instanceof GraphQLError) { return error; } else if ( error.originalError ! null isSafeError(error.originalError) ) { return new GraphQLError( error.originalError.message, error.nodes, error.source, error.positions, error.path, error.originalError, error.originalError.extensions ?? null, ); } else { // 用哈希值便于对相似错误分组 const hash sha1(String(error)); console.error(Masked GraphQL error (hash: ${hash}), error); return new GraphQLError( An error occurred (logged with hash: ${hash}), error.nodes, error.source, error.positions, error.path, error.originalError, // 刻意清空 extensions {}, ); } }, }, };这段配置的实际行为在源码中可以得到印证在 grafserv 的 options.ts 中maskError是 grafserv 的一个动态选项HTTP/WebSocket 响应中的payload.errors都会经过payload.errors.map(maskError)处理在 grafserv 的 utils.ts 中GraphQL over WebSocket 的onError与onNext回调同样会把错误逐条送入maskError。也就是说无论请求走 HTTP 还是 WebSocket订阅/实时查询你自定义的maskError都会生效。示例中有三个分支值得理解其设计意图error.originalError instanceof GraphQLError原始错误本身就是 GraphQL 错误例如校验失败这类错误是设计内的错误直接原样返回isSafeError(error.originalError)grafast 提供了SafeError类型见 grafast 的 error.ts用于标记可以安全展示给客户端的错误如 HTTP 状态码相关的错误。这类错误返回其原始 message其余情况这是真正不该给客户端看的内部错误。示例用 SHA-1 哈希给错误分组把哈希值同时写进服务端日志和返回给客户端的消息中这样客户端上报问题时你能通过哈希快速在日志里定位到同一批错误同时又不会泄露堆栈、SQL 等内部细节。⚠️安全警告maskError的默认实现会出于安全考虑裁剪掉大量细节。一旦你替换它就必须谨慎对待输出给潜在攻击者的内容——例如完整的堆栈、SQL、内部路径等都可能成为攻击者探测系统内部结构的线索。调试完成后务必恢复为默认掩码行为。善用originalError属性GraphQLError实例上带有error.originalError属性可用于取回底层错误。与 GraphQL 错误本身相比它通常包含更多可操作的信息例如 PostgreSQL 驱动抛出的原始错误、带 SQLSTATE 的数据库错误等。上面的配置示例正是围绕它展开的。1.4 查看 PostGraphile 生成的 SQL如果错误来自数据库层你需要看到 PostGraphile 实际生成并执行了哪些 SQL 语句。文档提供了两种途径。方式一Ruru 的 Explain 功能首先在配置中开启 explainexport default { // ... grafast: { explain: true, }, };开启后访问 Ruru默认地址为http://localhost:5678/graphiql在左侧打开Explain标签页图标是一个放大镜 。你会看到已执行的查询以及与之对应的 Grafast操作计划operation plan通过下拉框可以逐个查看各个 SQL 查询及其 EXPLAIN 结果。⚠️生产环境务必关闭 ExplainExplain 会泄露你 Schema 的内部结构信息这些信息对攻击者是有价值的因此生产环境必须保持explain为false。ℹ️已知限制目前 SQLEXPLAIN只能通过DEBUG环境变量启用即下文方式二Ruru Explain 中暂时还无法直接触发 SQL 层 EXPLAIN。这是官方文档明确标注的已知问题仓库中相关 TODO 见 debugging.md。方式二DEBUG环境变量在启动 PostGraphile 之前设置对应的 DEBUG 环境变量该机制基于广为人知的debug包命名空间约定命名空间形如dataplan/pg:PgExecutor:explain# Bash (Linux, macOS 等) export DEBUGdataplan/pg:PgExecutor:explain postgraphile # Windows 命令提示符 set DEBUGdataplan/pg:PgExecutor:explain postgraphile # Windows PowerShell $env:DEBUGdataplan/pg:PgExecutor:explain; postgraphile 上面的示例默认你当前目录下已有graphile.config.*配置文件。如果没有请改用 CLI 参数传入连接信息与 preset。源码层面的印证在 dataplan/pg 的 executor.ts 中可以看到PgExecutor内部通过debugFactory(dataplan/pg:PgExecutor)创建了基础命名空间再通过debug.extend(explain)派生出dataplan/pg:PgExecutor:explain子命名空间。当 explain 调试开启时executor.ts 第 228-248 行每次 SQL 执行后还会追加一次EXPLAIN查询其参数为COSTS, VERBOSE, BUFFERS, SETTINGS如果被解释的语句是SELECT且当前不是 mutation 执行还会自动加上ANALYZE注意 mutation 不会加ANALYZE这是为了避免真实的写副作用。输出内容相当完整包含SQL 查询文本经过格式化见 formatSQLForDebugging.ts绑定参数placeholders查询结果超过 10 行时只展示首尾各 3 行并截断中间内容避免刷屏数据库 NOTICE 信息执行耗时# DURATIONEXPLAIN 结果。如果开启了 explain 但某条语句没有输出 EXPLAIN日志里会给出提示例如(Explain disabled due to error)或(Use DEBUGdataplan/pg:PgExecutor:explain to enable explain)——这正好对应文档中当前 SQL EXPLAIN 只能通过 DEBUG 启用的限制。二、Schema 里出现了不该有的东西这类问题的根源通常有四类按文档给出的顺序逐一排查。2.1 过滤数据库 Schema确认你的配置中只列出了想要暴露的数据库 schema。默认情况下只暴露public。如果配置里写了多个 schema或某个扩展自带 schema 被一并引入就会多出内容。2.2 隐藏 PostgreSQL 扩展带来的资源默认情况下PgRemoveExtensionResourcesPlugin会从你的 Schema 中移除来自 PostgreSQL 扩展extension的资源与 codec。用下面的命令确认它是否处于启用状态npx graphile config print plugins该命令会输出你的解析后配置resolved preset中实际生效的插件列表——它的实现位于 graphile CLI 的 config/print 命令。如果该插件被意外禁用扩展资源就会出现在 Schema 中。2.3 用权限隐藏PgRBACPluginPgRBACPlugin会把 Schema 内容限制为被 introspection 的用户visitor role有权限访问的部分。如果你禁用了这个插件这种自动省略就不会发生。同样用npx graphile config print plugins检查解析后的配置里到底有哪些插件。常见的权限配置误区不要用超级用户或数据库属主连接——这样的用户拥有一切权限PgRBACPlugin自然什么都隐藏不了。应使用权限最小化的连接用户连接串里应使用authenticator角色关于角色创建的详细说明见 required-knowledge.md#creating-roles 一节如果怀疑授权没配对可以在psql里用\dp查看表的完整权限列表。2.4 用 smart tags 隐藏omit与behavioromit只对 V4 preset 生效只使用 Amber preset 的用户应改用behavior。当你添加一个 behavior包括 V4 preset 把omit转换成的那些 behavior后可以用下一节的npx graphile behavior debug验证它是否按预期生效。2.5 用 behaviors 隐藏npx graphile behavior debug详解这是排查 behavior 类问题最核心的命令。运行npx graphile behavior debug它会要求你选择一个scope实体类型文档列出的可选 scope 如下pgResource—— 可以SELECT数据的地方表、函数、视图、物化视图等pgResourceUnique—— 表/物化视图上的唯一约束pgCodec—— 表示标量、范围、枚举、域和复合类型没有存储pgCodecAttrbute—— 复合类型上的属性列pgCodecRelation—— codec 与 resource 之间的关系pgRefDefinition—— 某个ref的定义pgCodecRef—— 一个被应用的ref。接着带上 scope 再运行一次例如npx graphile behavior debug pgResource会列出该类型下的所有实体再带上实体标识例如npx graphile behavior debug pgResource users就会输出该实体最终的 behavior 字符串及其推导过程——即哪些插件添加/移除了哪些 behavior。关于输出的解读文档给出两点提示形如__ApplyBehaviors_*__的条目是新增 behavior 系统的临时机制它代表把defaultBehaviors与可用 behaviors 做相乘的结果形如PgBasicsPlugin.schema.entityBehavior.*.override的条目通常来自你的 smart tagsomit、behavior等所产生的 override。命令的底层实现可以在 graphile CLI 的 behavior/debug 命令 中看到它会加载并解析你的配置构建 inflection 与 build 上下文枚举所有 behavior 实体类型再根据你传入的entityType与entityIdentifier逐级给出实体列表与最终的 behavior 推导信息。命令设计为逐级交互不给类型 → 列出类型给了类型不给实体 → 列出该类型下的实体两者都给 → 输出该实体的 behavior 详情。三、Schema 里缺了本该有的东西这是上一节的镜像问题。文档给出的检查清单如下returns table(...)的函数这种返回类型无法用注释和 smart tags 扩充也无法注释/调整其属性返回类型会被暴露为一个自动生成名字的 GraphQL 类型。尽量避免这种写法应优先使用显式命名类型returns setof my_type。如果确实遇到需要给函数派生类型添加字段的场景见 customization-overview.md#adding-a-field-to-a-function-derived-type计算列computed columns必须遵循 computed-columns 中的命名与签名规则记得函数要是STABLE并且必须与它们所作用的表位于同一个 PostgreSQL schema 中视图views视图没有外键约束因此无法推断关系需要按 views.md 和 relations.md 的描述给视图添加foreignKeysmart tags。如果引用的是视图还要确保视图上有primaryKey数据库 schema 列表检查配置中列出的 schema 是否正确来自扩展的资源如果资源来自 PostgreSQL 扩展要么禁用PgRemoveExtensionResourcesPlugin要么覆盖相关资源上的 behaviorsPgRBACPlugin默认启用检查你是否给相关 visitor role 授予了权限并且数据库连接角色authenticator 角色已被授予该 visitor role即使noinherit也成立smart tags 与 behaviors用上文npx graphile behavior debug检查高级或非核心功能检查对应插件是否启用例如高级过滤需要postgraphile-plugin-connection-filter用npx graphile config print plugins查看当前插件列表accessor根级 finder 字段确认你使用的是约束而非索引见下文缺少约束关系relations检查是否缺少约束或索引见下文。3.1 缺少约束Constraints默认情况下PostGraphile只根据数据库约束来添加关系relations和 accessor根级 finder 字段。对 accessor 而言唯一索引是不够的——索引只是优化手段而约束是你对数据将持续成立的声明。例如要为userByUsername: User这种字段创建基础应添加唯一约束而非索引alter table users add constraint uniq_users_username unique (username);对关系而言仅靠列名的命名约定是不够的我们可能推断出错误的东西必须显式添加约束来表达关系alter table posts add constraint fk_posts_author foreign key (author_id) references users (id); create index on posts (author_id);3.2 缺少索引Indexes默认情况下PostGraphile不会添加反向关系除非存在匹配的索引——原因是没有索引时 PostgreSQL 可能需要对表做全表扫描来查找匹配记录代价极其高昂。解决办法是创建一个与关系匹配的索引包括保证列的顺序一致例如alter table posts add constraint fk_posts_author foreign key (organization_id, author_id) references users (organization_id, id); create index on posts (organization_id, author_id);四、性能问题排查如果数据库 Schema 设计良好PostGraphile 的性能表现通常很出色但如果对数据库设计不够熟悉性能问题可能来自很多方面。先用上文查看生成的 SQL一节拿到实际执行的 SQL再对照以下清单逐项排查RLS 性能如果同样的查询直接执行比经过 PostGraphile 快得多那么极有可能是 RLS 策略性能不佳。这是 PostGraphile 用户遇到的最常见的性能问题但也是最容易修复的。具体写法见 required-knowledge.md#writing-performant-rls-policies只取所需fetch only what you render见下文索引函数见 functions.md#understanding-function-performance视图物化视图插件复杂过滤器。4.1 RLS 性能详见 required-knowledge.md#writing-performant-rls-policies。4.2 只取所需GraphQL 不该过度获取GraphQL 的设计理念是要什么取什么——不多也不少。过度获取Overfetching你取回的每一份数据都应该几乎立即渲染给用户。如果某份数据要等用户交互点击下一页、展开、详情按钮才展示就应该放到后续请求中再取。当前视图需要的数据要在一次往返内全部取回但只取你需要的而不是你猜想几秒后可能需要的。过度获取最常见的两个原因缺少分页fragment 的错误使用。分页如果你让 GraphQL 取回全部邮件却只渲染前 25 封那就是没有告诉 GraphQL 你到底需要多少。每一个列表查询都应该包含分页限制。渲染第二页时不要再次执行同一个查询它可能会重新取回你不需要的附属数据而应该用一个复用了相同 fragment 的新查询# 用这个查询渲染主页面 query MainPage { notifications { count } me { avatarUrl name } feedItems(first: 5) { ...FeedItems } } # 当用户点击第二页或无限滚动下拉时 # 只取下一批 feed items而不要重新取回附属数据 query MainPageMoreFeedItems($cursor: Cursor!) { feedItems(first: 5, after: $cursor) { ...FeedItems } } # feed items 的数据需求被两个查询共享 fragment FeedItems on FeedItemsConnection { nodes { title description author { name avatar } } pageInfo { hasNextPage endCursor } }Fragment 的使用fragment 的目的不是消除重复代码而是把数据需求与消费这些数据的应用元素函数、React 组件等对应起来。被渲染组件的 fragment 应该被合并进一个能发给服务器的单一查询中确保组件所需的全部数据且仅这些数据被取回。五、更多DEBUG环境变量系统内部大量使用 DEBUG 命名空间以下是文档整理出的几个常用项graphile-build:warn—— schema 构建期间发生的可恢复错误的详情通常包含如何修复问题的提示graphile-build:SchemaBuilder—— 用于理解 hook 的执行顺序以及 hook 调用如何嵌套——对刚接触 graphile-build 插件开发的人很有价值dataplan/pg:PgExecutor—— 正在执行的 SQL 查询、其输入和结果的详情dataplan/pg:PgExecutor:explain—— 同上一项但额外包含 EXPLAIN 结果。多个命名空间用逗号连接后设置到DEBUG环境变量再在同一终端启动 PostGraphile或你的 Node.js 服务# Bash (Linux, macOS 等) export DEBUGgraphile-build:warn,dataplan/pg:* postgraphile # Windows 命令提示符 set DEBUGgraphile-build:warn,dataplan/pg:* postgraphile # Windows PowerShell $env:DEBUG graphile-build:warn,dataplan/pg:*; postgraphile注意dataplan/pg:*是通配写法会同时开启dataplan/pg:PgExecutor与dataplan/pg:PgExecutor:explain等所有子命名空间。文档同时提示PgExecutor:explain的输出内容在 executor.ts 中被完整格式化包括 SQL 文本、占位符、结果、NOTICE、耗时与 EXPLAIN调试 SQL 时非常直观。六、直接调试 PostGraphile 进程如果你是插件作者、怀疑发现了 PostGraphile 的 bug或者只是想看看内部到底怎么运转可以用 Chrome 的 Node 调试工具直接调试进程——加断点、异常断下、单步执行都可以在 Chrome 中访问chrome://inspect出于安全原因这里不能提供可点击的超链接选择Open dedicated DevTools for Node会打开一个新的 DevTools 窗口——不要关掉它用 Node.js 以--inspect模式直接启动你的服务或 PostGraphile例如# 全局安装的 PostGraphile node --inspect which postgraphile -c postgres://... # 或本地安装的 PostGraphile node --inspect node_modules/.bin/postgraphile # 或者如果你有自己的 Node.js 应用server.js node --inspect server.js连接成功后你就可以在源码上打断点、观察变量、跟踪 Grafast的执行流程了。仓库中 Grafserv、Grafast、dataplan/pg的源码都在grafast/目录下例如 grafast/grafserv/src、grafast/grafast/src可以直接作为调试时的参考实现。小结调试决策路线图最后把文档的核心排查思路浓缩成一张决策清单方便遇到问题时按图索骥GraphQL 请求不对→ Chrome Network 面板确认请求 → Ruru 复现 →maskError输出错误细节错误来自数据库→ Ruru Explain需grafast.explain: true或DEBUGdataplan/pg:PgExecutor:explain查看 SQL 与 EXPLAINSchema 多了东西→ 检查pg_schemas配置 →npx graphile config print plugins确认PgRemoveExtensionResourcesPlugin/PgRBACPlugin是否启用 → 检查连接角色权限\dp→npx graphile behavior debug验证 smart tags/behaviorsSchema 缺了东西→ 对照缺失清单 → 补唯一约束/外键约束 → 补匹配索引性能差→ 查看生成 SQL → 排查 RLS 策略 → 检查分页与 fragment 使用 → 检查索引/函数/视图/物化视图/插件/复杂过滤器怀疑 PostGraphile 自身问题→node --inspectchrome://inspect断点调试。相关参考文档除本文档外还可进一步阅读 required-knowledge.md角色创建与 RLS 性能、computed-columns、views.md 与 relations.md。赞分享后端API网关【免费下载链接】crystal Graphiles Crystal Monorepo; home to Grafast, PostGraphile, pg-introspection, pg-sql2 and much more!项目地址https://gitcode.com/gh_mirrors/cry/crystal点击查看免费下载相关推荐PostGraphile 调试完全指南从 GraphQL 请求到 SQL 执行的全链路排障PostGraphile 调试完全指南从 GraphQL 请求到 SQL 执行的全链路排障 导读 当应用出问题时PostGraphile 提供了一整套由浅入后端API网关PostGraphile v4 调试完全指南从网络请求到 SQL 与 Node 源码的逐层排查PostGraphile v4 调试完全指南从网络请求到 SQL 与 Node 源码的逐层排查 导读 本文围绕 PostGraphile v4 的官方调试文档后端API网关PostGraphile 数据库函数完全指南从性能陷阱到 SQL 内联与 GraphQL 暴露PostGraphile 数据库函数完全指南从性能陷阱到 SQL 内联与 GraphQL 暴露 导读 在 PostGraphileCrystal Mono后端API网关创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考