'Why does batching with a dataloader not work in a test?
Problem
I am trying to test the performance of the following query:
query {
classes {
teachers {
user_id
}
}
}
When I start up my server and run the query through the graphQL playground, the dataloader works as expected and batches the queries: teachersForClasses
only runs once.
When I run the test to benchmark the query's performance, teachersForClasses
runs once for each unique class returned by getClasses
.
Why do the behaviours differ? In both cases an HTTP request is being sent to an already running server.
Background
I am using the dataloader library for node.js on a project where I create a graphQL API with Apollo Server.
Code
function getClasses(args, ctx) {
/* returns a list of Class objects */
}
const resolvers = {
Class: {
teachers: (parent: Class, _args, ctx: Context) =>
ctx.loaders.class.teachers.load(parent.class_id)
}
Query: {
class: (_parent, args, ctx): Promise<Class[]> => getClasses(args, ctx)
}
}
The loader is defined like this:
function teachersForClasses(classIds: readonly string[]) {
console.log(classIds) // added for debugging
/* returns an array of User objects for each class ID */
}
export const loader = {
class: {
teachers: new Dataloader<string, User[]>>(teachersForClasses)
}
}
Tests
A server is already running at http://localhost:8080 before this runs.
const url = 'http://localhost:8080'
const request = supertest(url)
async function runQuery(token: string) {
return request
.post('/user')
.set({
ContentType: 'application/json',
Authorization: token
})
.send({
query: `
{
classes {
teachers {
user_id
}
}
}`
})
}
describe('benchmarks', () => {
it('getTeachersForClasses', () => {
/*populates the database with data*/
...
for (let i=0; i < 10; i++) {
console.time('query')
const start = Date.now()
await runQuery(userToken)
const end = Date.now()
console.timeEnd('query')
}
})
})
Solution 1:[1]
I found the problem: there was an async directive (permission check) for 'teachers' which meant that each time the dataloader was called with .load()
, it was part of a different event loop 'tick' (explained here) & thus the calls weren't batched as expected.
This wasn't an issue in the graphQL playground because I was making the call with an admin user, so the directive resolved immediately.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | raphael-p |