带有嵌套查询(Django + React)的 Graphql 瓶颈性能使前端应用程序无法使用。请帮忙 :'(
Posted
技术标签:
【中文标题】带有嵌套查询(Django + React)的 Graphql 瓶颈性能使前端应用程序无法使用。请帮忙 :\'(【英文标题】:Graphql bottleneck performance with nested query (Django + React ) makes frontend app unusable. Please help :'(带有嵌套查询(Django + React)的 Graphql 瓶颈性能使前端应用程序无法使用。请帮忙 :'( 【发布时间】:2021-10-09 18:30:55 【问题描述】:对于这个项目,我在后端使用 Python+Django 和 GraphQL(石墨烯),使用 mysql 作为数据库,使用 React.js 作为前端。
在前端,用户登录后,我要执行以下查询:
const GET_ORGANIZATION = gql`
query getOrganization($orgId : Int!)
organization(id:$orgId)
id
name
user
id
username
firstName
lastName
email
dateJoined
lastLogin
isActive
trainings
id
name
sessions
id
name
category
createdAt
totalSteps
completedAt
user
id
eventSet
id
category
description
object
errorSeverity
performedAt
courses
id
name
description
trainings
id
name
user
id
username
isSuperuser
isStaff
isActive
email
sessions
id
name
category
createdAt
completedAt
user
id
username
eventSet
id
category
description
object
errorSeverity
performedAt
`;
如您所见,它嵌套了多个级别。当我进入 sessions 和 events 时,问题就来了。我不是 graphQL 的超级专家,但我一直认为 GraphQL 的卖点是您可以在一个查询中使用所有这些嵌套字段。好吧,事实并非如此。以下是几张图片原因:
响应需要 30 多秒。深入研究一下我的数据库的 slow_log,我发现:
相同的查询使用相同的参数重复多次:
这会重复超过 5000 次。 通读 SO 和其他来源,这似乎是 GraphQL 的经典 N+1 问题
所以现在我面临(希望)解决方案的两条道路:
第一个,我找到了一种方法,可以让这个查询以现有的数据量可用,这就是我需要建议和帮助的地方。有办法吗?我想这样想,因为如果几级嵌套和其中一个级中的数千行足以使其无法使用,那么我想没有人会使用它。
第二种方式,这就是我今天开始做的方式,但后来停下来,哭着来创建这个帖子:我限制我的查询只到培训级别,而不是有一个大查询,我有几个较小的。这种方法的问题是我痛苦的原因是我意识到我基本上必须在前端重做我的整个反应组件,因为他们中的许多人都希望数据在那个大对象中,更不用说什么时候您将该对象或其部分传递给其他组件
你会建议哪一个?或者还有其他方法吗? 请记住,我只有非常有限的时间来进行重大更改,因为在接下来的几天里,我的最后期限即将到来,让我进行直播。
添加一些上下文: 我一个人在开发一种支持 VR 应用程序的度量应用程序。没有团队(我一加入就离开了前端和 devOps 人员),所以我必须做所有的 devOps、前端和后端工作,这是很多工作。由于这种压力,我犯了新手错误,从未花时间获取包含 VR 收集的所有数据的数据库副本,这些数据实际上是用户将在前端可视化的数据。所以我一直在用较小的虚拟数据进行测试,到目前为止一切都“完美”地工作。可能这对我自己和其他阅读此问题/寻求帮助的人来说是一个教训:确保您的环境在开发时尽可能接近生产,特别是有足够的数据
【问题讨论】:
这种可能性不是必须的... 您可以查询n级深度数据,但您不应该 ...在显示它的组件上使用查询/数据...列表视图上的查询列表,详细视图上的查询详情等...基础知识 不,基本上不是知识。如果我必须查询您所说的方式,我基本上使用的是 REST 方法,其中我有用于不同数据的端点。那么采用 GraphQL 有什么意义呢? 仍然过度获取 [如 REST] 有什么意义? 如果我真的需要将所有数据集中在一个地方,这并不过分。无论如何,不要打扰......我已经解决了我的问题:) 【参考方案1】:这不是 GraphQL 本身的问题,而是石墨烯实现的问题。正如您所发现的,没有 SQL 查询优化,并且为每个深度级别创建查询,这对于深度 GraphQL 查询来说非常糟糕。
您有一些选择:
通过为多个深度级别创建某种查询优化器来修复石墨烯的实现——根据您的分析,您可以看到进行一些改进很容易,如果您为开源项目做出贡献,社区将非常感激(但这个选项需要几个月的工作)
创建您自己的解析器来劫持查询中特别慢的部分,并用您自己的优化查询替换,其中包括所有必要的连接并返回结构化的 JSON 输出
创建不属于 Django 架构的自定义字段和/或对象类型,并如上所述编写优化的字段解析器。
(更新)寻找查询优化器(详见@Rafael 的回答)
【讨论】:
谢谢伙计。您的建议对我找到答案有很大帮助:)【参考方案2】:感谢马克的回答,我环顾四周,找到了这个包 https://github.com/tfoxy/graphene-django-optimizer 完全按照他的建议行事。
使用它很容易。例如,为了优化我的 organization_resolver,我所要做的就是:
def resolve_organization(root, info, id):
return gql_optimizer.query(Organization.objects.filter(pk=id),info).get()
查询从 50s 变为 11s 。巨大的进步!
尽管如此,速度还不够快,但我仍然可以做其他改进。
在他们的页面中,在 Advanced Usage 下,它解释了如何为更复杂的解析器做一些小技巧
希望这对同样面临同样问题的其他人有所帮助
【讨论】:
11s 听起来仍然很糟糕。 正如我所说,还有其他改进工作要做。主要瓶颈已得到解决 这充分解决了我的问题,谢谢!【参考方案3】:试试DataLoader。而不是每次graphql解析嵌套的graphql query
时都尝试访问数据库,我们可以批处理database query
然后访问数据库一次。
示例(在js中,因为我不懂python)
没有数据加载器
// the schema
type Query
user: User
users: [User]
type User
id: String!
email: String!
name: String
profile: Profile
type Profile
bio: String
age: Int
user: User
userId: String
// Resolver
export const resolvers =
Query:
user: (): null => null,
users: async (_, __, context) =>
return await context.db.user.findMany()
,
,
User:
profile: async (parent, _, context) =>
return await context.db.profile.findFirst(
where:
userId: parent.id,
,
)
,
,
Profile:
user: async (parent, _, context) =>
return await context.db.user.findFirst(
where:
id: parent.userId
)
,
,
当我尝试这个 graphql 查询时的结果
query
users
email
name
profile
bio
user
email
是this(大约一百个数据库查询或更多)
带有数据加载器
// Loader.js
async function profileBatchFunction(keys: readonly string[])
const result = await db.profile.findMany(
where:
userId:
in: keys as string[],
,
,
)
return keys.map(k => result.find(res => res.userId === k) || null)
export const profileLoader = new DataLoader(profileBatchFunction)
async function userBatchFunction(keys: readonly string[])
const result = await db.user.findMany(
where:
id:
in: keys as string[],
,
,
)
return keys.map(k => result.find(res => res.id === k) || null)
export const userLoader = new DataLoader(userBatchFunction)
// Resolver
export const resolvers =
Query:
user: (): null => null,
users: async (_, __, context) =>
return await context.db.user.findMany()
,
,
Profile:
user: async parent =>
return await userLoader.load(parent.userId)
,
,
User:
profile: async parent =>
return await profileLoader.load(parent.id)
,
,
同样graphql query
的结果是this one。好多了
【讨论】:
以上是关于带有嵌套查询(Django + React)的 Graphql 瓶颈性能使前端应用程序无法使用。请帮忙 :'(的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Apollo 和 React Router 避免嵌套路由/查询组件的请求瀑布?
React Hook Form:提交带有嵌套组件的表单或提取嵌套组件的字段提交
在带有 webpack 的 React-Router 中使用嵌套路由的问题