gqlgen go,通过添加一个解析器减少数据库调用
gqlgen go, reduce db calls by adding one resolver
我在解决导致性能下降的特定情况时遇到了一些问题。
我很确定这是可以做到的,但我不知道该怎么做。
这是一个暴露问题的示例模式:
type Answer{
answerId: String!
text: String!
topic: Topic!
}
type Topic {
topicId: String!
name: String!
level: Int!
}
extend type Query {
answer(answerId: String!): Answer!
answers: [Answer!]!
}
我已经按照文档进行操作,特别是这部分 https://gqlgen.com/getting-started/#dont-eagerly-fetch-the-user
从我的架构中,它生成以下解析器:
func (r *queryResolver) Answer(ctx context.Context, answerId string) (*models.Answer, error) {
...
#Single Query which retrives single record of Answer from DB.
#Fills a model Answer with the Id and the text
#Proceeds by calling the Topic resolver
...
}
func (r *queryResolver) Answers(ctx context.Context) ([]*models.Answer, error) {
...
#Single Query which retrives list of Answers from DB
#Fills a list of model Answer with the Id and the text
-->#For each element of that list, it calls the Topic resolver
...
}
func (r *answerResolver) Topic(ctx context.Context, obj *models.Answer) (*models.Topic, error) {
...
#Single Query which retrives single record of Topic from DB
#Return a model Topic with id, name and level
...
}
当使用 answerId
参数调用 answer
查询时,answer
解析器被触发,它解析 text
属性 并调用 Topic
解析器。
Topic
解析器按预期工作,检索 Topic
并将其合并到 Answer
和 return.
中
当 answers
查询在没有 answerId
参数的情况下被调用时,answer
解析器被触发,它通过单个查询检索 answers
的列表。
然后,对于该列表的每个元素,它调用 Topic
解析器。
Topic
检索 Topic
并将其合并到单个 Answer
和 return.
中
两种情况下的结果都不错,但如果我要求很多答案,answers
查询会成为性能问题。
对于每个答案,Topic
解析器都会被触发并执行查询以检索单个记录。
例如。如果我有 2 个答案 --> 1 个 [Answer0, Answer1]
查询,然后 1 个 Topic0
查询和 1 个 Topic1
查询
例如。 10 个答案 --> [Answer0, ..., Answer9]
1 个,然后每个 TopicN
10 个
我想获得一些 topic
数组解析器,例如
func (r *answersResolver) Topics(ctx context.Context, obj *[]models.Answer) (*[]models.Topic, error) {
...
#Single Query which retrives list of Topics from DB
#Return a list of model Topic with id, name and level
...
}
而且我希望 returned 数组的每个元素都与 Answers
数组的相应元素合并。
有可能吗?我在哪里可以找到这种方法的例子?
谢谢
问题可以使用数据加载器解决(docs)
我必须为 Topics
实现以下数据源:
package dataloader
import (
"github.com/graph-gophers/dataloader"
)
type ctxKey string
const (
loadersKey = ctxKey("dataloaders")
)
type TopicReader struct {
conn *sql.DB
}
func (t *TopicReader) GetTopics(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
topicIDs := make([]string, len(keys))
for ix, key := range keys {
topicIDs[ix] = key.String()
}
res := u.db.Exec(
r.Conn,
"SELECT id, name, level
FROM topics
WHERE id IN (?" + strings.Repeat(",?", len(topicIDs-1)) + ")",
topicIDs...,
)
defer res.Close()
output := make([]*dataloader.Result, len(keys))
for index, _ := range keys {
output[index] = &dataloader.Result{Data: res[index], Error: nil}
}
return output
}
type Loaders struct {
TopicLoader *dataloader.Loader
}
func NewLoaders(conn *sql.DB) *Loaders {
topicReader := &TopicReader{conn: conn}
loaders := &Loaders{
TopicLoader: dataloader.NewBatchedLoader(t.GetTopics),
}
return loaders
}
func Middleware(loaders *Loaders, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
nextCtx := context.WithValue(r.Context(), loadersKey, loaders)
r = r.WithContext(nextCtx)
next.ServeHTTP(w, r)
})
}
func For(ctx context.Context) *Loaders {
return ctx.Value(loadersKey).(*Loaders)
}
func GetTopic(ctx context.Context, topicID string) (*model.Topic, error) {
loaders := For(ctx)
thunk := loaders.TopicLoader.Load(ctx, dataloader.StringKey(topicID))
result, err := thunk()
if err != nil {
return nil, err
}
return result.(*model.Topic), nil
}
我在解决导致性能下降的特定情况时遇到了一些问题。 我很确定这是可以做到的,但我不知道该怎么做。
这是一个暴露问题的示例模式:
type Answer{
answerId: String!
text: String!
topic: Topic!
}
type Topic {
topicId: String!
name: String!
level: Int!
}
extend type Query {
answer(answerId: String!): Answer!
answers: [Answer!]!
}
我已经按照文档进行操作,特别是这部分 https://gqlgen.com/getting-started/#dont-eagerly-fetch-the-user 从我的架构中,它生成以下解析器:
func (r *queryResolver) Answer(ctx context.Context, answerId string) (*models.Answer, error) {
...
#Single Query which retrives single record of Answer from DB.
#Fills a model Answer with the Id and the text
#Proceeds by calling the Topic resolver
...
}
func (r *queryResolver) Answers(ctx context.Context) ([]*models.Answer, error) {
...
#Single Query which retrives list of Answers from DB
#Fills a list of model Answer with the Id and the text
-->#For each element of that list, it calls the Topic resolver
...
}
func (r *answerResolver) Topic(ctx context.Context, obj *models.Answer) (*models.Topic, error) {
...
#Single Query which retrives single record of Topic from DB
#Return a model Topic with id, name and level
...
}
当使用 answerId
参数调用 answer
查询时,answer
解析器被触发,它解析 text
属性 并调用 Topic
解析器。
Topic
解析器按预期工作,检索 Topic
并将其合并到 Answer
和 return.
当 answers
查询在没有 answerId
参数的情况下被调用时,answer
解析器被触发,它通过单个查询检索 answers
的列表。
然后,对于该列表的每个元素,它调用 Topic
解析器。
Topic
检索 Topic
并将其合并到单个 Answer
和 return.
两种情况下的结果都不错,但如果我要求很多答案,answers
查询会成为性能问题。
对于每个答案,Topic
解析器都会被触发并执行查询以检索单个记录。
例如。如果我有 2 个答案 --> 1 个 [Answer0, Answer1]
查询,然后 1 个 Topic0
查询和 1 个 Topic1
例如。 10 个答案 --> [Answer0, ..., Answer9]
1 个,然后每个 TopicN
我想获得一些 topic
数组解析器,例如
func (r *answersResolver) Topics(ctx context.Context, obj *[]models.Answer) (*[]models.Topic, error) {
...
#Single Query which retrives list of Topics from DB
#Return a list of model Topic with id, name and level
...
}
而且我希望 returned 数组的每个元素都与 Answers
数组的相应元素合并。
有可能吗?我在哪里可以找到这种方法的例子? 谢谢
问题可以使用数据加载器解决(docs)
我必须为 Topics
实现以下数据源:
package dataloader
import (
"github.com/graph-gophers/dataloader"
)
type ctxKey string
const (
loadersKey = ctxKey("dataloaders")
)
type TopicReader struct {
conn *sql.DB
}
func (t *TopicReader) GetTopics(ctx context.Context, keys dataloader.Keys) []*dataloader.Result {
topicIDs := make([]string, len(keys))
for ix, key := range keys {
topicIDs[ix] = key.String()
}
res := u.db.Exec(
r.Conn,
"SELECT id, name, level
FROM topics
WHERE id IN (?" + strings.Repeat(",?", len(topicIDs-1)) + ")",
topicIDs...,
)
defer res.Close()
output := make([]*dataloader.Result, len(keys))
for index, _ := range keys {
output[index] = &dataloader.Result{Data: res[index], Error: nil}
}
return output
}
type Loaders struct {
TopicLoader *dataloader.Loader
}
func NewLoaders(conn *sql.DB) *Loaders {
topicReader := &TopicReader{conn: conn}
loaders := &Loaders{
TopicLoader: dataloader.NewBatchedLoader(t.GetTopics),
}
return loaders
}
func Middleware(loaders *Loaders, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
nextCtx := context.WithValue(r.Context(), loadersKey, loaders)
r = r.WithContext(nextCtx)
next.ServeHTTP(w, r)
})
}
func For(ctx context.Context) *Loaders {
return ctx.Value(loadersKey).(*Loaders)
}
func GetTopic(ctx context.Context, topicID string) (*model.Topic, error) {
loaders := For(ctx)
thunk := loaders.TopicLoader.Load(ctx, dataloader.StringKey(topicID))
result, err := thunk()
if err != nil {
return nil, err
}
return result.(*model.Topic), nil
}