test: add benchmark for select querybuilder (#8955)

Motivation: the query builder (and within it, replacePropertyNames and
associated functions) is pretty CPU intensive. For our workload, it's
one of the hottest functions in our entire stack.

While improved in https://github.com/typeorm/typeorm/pull/4760,
There are still outstanding issues relating to perf e.g. https://github.com/typeorm/typeorm/issues/3857

As we all know though, the first step in optimization is to measure
systematically ;)

https://wiki.c2.com/?ProfileBeforeOptimizing

On my machine, this benchmark runs in ~3500ms or about 0.35ms/query.
This tells us there's a way to go - on my stack, that's about 1/3 of a
typical query's latency!
This commit is contained in:
Patrick Molgaard 2022-05-20 12:51:43 +01:00 committed by GitHub
parent ea176b27d4
commit 22570f51f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 472 additions and 0 deletions

View File

@ -0,0 +1,38 @@
import { Entity } from "../../../../src/decorator/entity/Entity"
import { PrimaryGeneratedColumn } from "../../../../src/decorator/columns/PrimaryGeneratedColumn"
import { Column } from "../../../../src/decorator/columns/Column"
import { One } from "./One"
import { ManyToOne } from "../../../../src"
@Entity()
export class Eight {
@PrimaryGeneratedColumn()
id: number
@ManyToOne((type) => One)
one: One
@Column({ type: "text" })
aaaaa: string
@Column({ type: "text" })
bbbbb: string
@Column({ type: "text" })
ccccc: string
@Column({ type: "text" })
ddddd: string
@Column({ type: "text" })
eeeee: string
@Column({ type: "text" })
fffff: string
@Column({ type: "text" })
ggggg: string
@Column({ type: "text" })
hhhhh: string
}

View File

@ -0,0 +1,38 @@
import { Entity } from "../../../../src/decorator/entity/Entity"
import { PrimaryGeneratedColumn } from "../../../../src/decorator/columns/PrimaryGeneratedColumn"
import { Column } from "../../../../src/decorator/columns/Column"
import { One } from "./One"
import { ManyToOne } from "../../../../src"
@Entity()
export class Five {
@PrimaryGeneratedColumn()
id: number
@ManyToOne((type) => One)
one: One
@Column({ type: "text" })
aaaaa: string
@Column({ type: "text" })
bbbbb: string
@Column({ type: "text" })
ccccc: string
@Column({ type: "text" })
ddddd: string
@Column({ type: "text" })
eeeee: string
@Column({ type: "text" })
fffff: string
@Column({ type: "text" })
ggggg: string
@Column({ type: "text" })
hhhhh: string
}

View File

@ -0,0 +1,38 @@
import { Entity } from "../../../../src/decorator/entity/Entity"
import { PrimaryGeneratedColumn } from "../../../../src/decorator/columns/PrimaryGeneratedColumn"
import { Column } from "../../../../src/decorator/columns/Column"
import { One } from "./One"
import { ManyToOne } from "../../../../src"
@Entity()
export class Four {
@PrimaryGeneratedColumn()
id: number
@ManyToOne((type) => One)
one: One
@Column({ type: "text" })
aaaaa: string
@Column({ type: "text" })
bbbbb: string
@Column({ type: "text" })
ccccc: string
@Column({ type: "text" })
ddddd: string
@Column({ type: "text" })
eeeee: string
@Column({ type: "text" })
fffff: string
@Column({ type: "text" })
ggggg: string
@Column({ type: "text" })
hhhhh: string
}

View File

@ -0,0 +1,38 @@
import { Entity } from "../../../../src/decorator/entity/Entity"
import { PrimaryGeneratedColumn } from "../../../../src/decorator/columns/PrimaryGeneratedColumn"
import { Column } from "../../../../src/decorator/columns/Column"
import { One } from "./One"
import { ManyToOne } from "../../../../src"
@Entity()
export class Nine {
@PrimaryGeneratedColumn()
id: number
@ManyToOne((type) => One)
one: One
@Column({ type: "text" })
aaaaa: string
@Column({ type: "text" })
bbbbb: string
@Column({ type: "text" })
ccccc: string
@Column({ type: "text" })
ddddd: string
@Column({ type: "text" })
eeeee: string
@Column({ type: "text" })
fffff: string
@Column({ type: "text" })
ggggg: string
@Column({ type: "text" })
hhhhh: string
}

View File

@ -0,0 +1,73 @@
import { Entity } from "../../../../src/decorator/entity/Entity"
import { PrimaryGeneratedColumn } from "../../../../src/decorator/columns/PrimaryGeneratedColumn"
import { Column } from "../../../../src/decorator/columns/Column"
import { OneToOne } from "../../../../src"
import { Two } from "./Two"
import { Three } from "./Three"
import { Four } from "./Four"
import { Five } from "./Five"
import { Six } from "./Six"
import { Seven } from "./Seven"
import { Eight } from "./Eight"
import { Nine } from "./Nine"
import { Ten } from "./Ten"
@Entity()
export class One {
@PrimaryGeneratedColumn()
id: number
@OneToOne((type) => Two, (two) => two.one)
two: Two
@OneToOne((type) => Three, (three) => three.one)
three: Three
@OneToOne((type) => Four, (four) => four.one)
four: Four
@OneToOne((type) => Five, (five) => five.one)
five: Five
@OneToOne((type) => Six, (six) => six.one)
six: Six
@OneToOne((type) => Seven, (seven) => seven.one)
seven: Seven
@OneToOne((type) => Eight, (eight) => eight.one)
eight: Eight
@OneToOne((type) => Nine, (nine) => nine.one)
nine: Nine
@OneToOne((type) => Ten, (ten) => ten.one)
ten: Ten
@Column({ type: "text" })
aaaaa: string
@Column({ type: "text" })
bbbbb: string
@Column({ type: "text" })
ccccc: string
@Column({ type: "text" })
ddddd: string
@Column({ type: "text" })
eeeee: string
@Column({ type: "text" })
fffff: string
@Column({ type: "text" })
ggggg: string
@Column({ type: "text" })
hhhhh: string
@Column({ type: "text" })
iiiii: string
}

View File

@ -0,0 +1,38 @@
import { Entity } from "../../../../src/decorator/entity/Entity"
import { PrimaryGeneratedColumn } from "../../../../src/decorator/columns/PrimaryGeneratedColumn"
import { Column } from "../../../../src/decorator/columns/Column"
import { One } from "./One"
import { ManyToOne } from "../../../../src"
@Entity()
export class Seven {
@PrimaryGeneratedColumn()
id: number
@ManyToOne((type) => One)
one: One
@Column({ type: "text" })
aaaaa: string
@Column({ type: "text" })
bbbbb: string
@Column({ type: "text" })
ccccc: string
@Column({ type: "text" })
ddddd: string
@Column({ type: "text" })
eeeee: string
@Column({ type: "text" })
fffff: string
@Column({ type: "text" })
ggggg: string
@Column({ type: "text" })
hhhhh: string
}

View File

@ -0,0 +1,38 @@
import { Entity } from "../../../../src/decorator/entity/Entity"
import { PrimaryGeneratedColumn } from "../../../../src/decorator/columns/PrimaryGeneratedColumn"
import { Column } from "../../../../src/decorator/columns/Column"
import { One } from "./One"
import { ManyToOne } from "../../../../src"
@Entity()
export class Six {
@PrimaryGeneratedColumn()
id: number
@ManyToOne((type) => One)
one: One
@Column({ type: "text" })
aaaaa: string
@Column({ type: "text" })
bbbbb: string
@Column({ type: "text" })
ccccc: string
@Column({ type: "text" })
ddddd: string
@Column({ type: "text" })
eeeee: string
@Column({ type: "text" })
fffff: string
@Column({ type: "text" })
ggggg: string
@Column({ type: "text" })
hhhhh: string
}

View File

@ -0,0 +1,38 @@
import { Entity } from "../../../../src/decorator/entity/Entity"
import { PrimaryGeneratedColumn } from "../../../../src/decorator/columns/PrimaryGeneratedColumn"
import { Column } from "../../../../src/decorator/columns/Column"
import { One } from "./One"
import { ManyToOne } from "../../../../src"
@Entity()
export class Ten {
@PrimaryGeneratedColumn()
id: number
@ManyToOne((type) => One)
one: One
@Column({ type: "text" })
aaaaa: string
@Column({ type: "text" })
bbbbb: string
@Column({ type: "text" })
ccccc: string
@Column({ type: "text" })
ddddd: string
@Column({ type: "text" })
eeeee: string
@Column({ type: "text" })
fffff: string
@Column({ type: "text" })
ggggg: string
@Column({ type: "text" })
hhhhh: string
}

View File

@ -0,0 +1,38 @@
import { Entity } from "../../../../src/decorator/entity/Entity"
import { PrimaryGeneratedColumn } from "../../../../src/decorator/columns/PrimaryGeneratedColumn"
import { Column } from "../../../../src/decorator/columns/Column"
import { One } from "./One"
import { ManyToOne } from "../../../../src"
@Entity()
export class Three {
@PrimaryGeneratedColumn()
id: number
@ManyToOne((type) => One)
one: One
@Column({ type: "text" })
aaaaa: string
@Column({ type: "text" })
bbbbb: string
@Column({ type: "text" })
ccccc: string
@Column({ type: "text" })
ddddd: string
@Column({ type: "text" })
eeeee: string
@Column({ type: "text" })
fffff: string
@Column({ type: "text" })
ggggg: string
@Column({ type: "text" })
hhhhh: string
}

View File

@ -0,0 +1,38 @@
import { Entity } from "../../../../src/decorator/entity/Entity"
import { PrimaryGeneratedColumn } from "../../../../src/decorator/columns/PrimaryGeneratedColumn"
import { Column } from "../../../../src/decorator/columns/Column"
import { One } from "./One"
import { ManyToOne } from "../../../../src"
@Entity()
export class Two {
@PrimaryGeneratedColumn()
id: number
@ManyToOne((type) => One)
one: One
@Column({ type: "text" })
aaaaa: string
@Column({ type: "text" })
bbbbb: string
@Column({ type: "text" })
ccccc: string
@Column({ type: "text" })
ddddd: string
@Column({ type: "text" })
eeeee: string
@Column({ type: "text" })
fffff: string
@Column({ type: "text" })
ggggg: string
@Column({ type: "text" })
hhhhh: string
}

View File

@ -0,0 +1,57 @@
import "reflect-metadata"
import { DataSource } from "../../../src/data-source/DataSource"
import {
closeTestingConnections,
createTestingConnections,
} from "../../utils/test-utils"
import { One } from "./entity/One"
/**
* This test attempts to benchmark the raw CPU usage/latency of the query builder's
* SQL string generation. We intentionally don't migrate the database or perform
* any actual queries.
*/
describe("benchmark > QueryBuilder > wide join", () => {
let connections: DataSource[]
before(
async () =>
(connections = await createTestingConnections({
__dirname,
enabledDrivers: ["postgres"],
})),
)
after(() => closeTestingConnections(connections))
it("testing query builder with join to 10 relations with 10 columns each", () => {
for (let i = 1; i <= 10_000; i++) {
connections.map((connection) =>
connection.manager
.createQueryBuilder(One, "ones")
.setFindOptions({
where: { id: 1 },
relations: [
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",
"nine",
"ten",
],
})
.getQuery(),
)
}
/**
* On a M1 macbook air, 5 runs:
* 3501ms
* 3574ms
* 3575ms
* 3563ms
* 3567ms
*/
})
})