From 7f7af42e6db3ac4d0fb8c9fbfcccac9d82eb2de3 Mon Sep 17 00:00:00 2001 From: Patrick Molgaard Date: Sun, 1 May 2022 18:34:17 +0100 Subject: [PATCH] test: add benchmark for select querybuilder 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! --- .../entity/Eight.ts | 38 ++++++++++ .../entity/Five.ts | 38 ++++++++++ .../entity/Four.ts | 38 ++++++++++ .../entity/Nine.ts | 38 ++++++++++ .../multiple-joins-querybuilder/entity/One.ts | 73 +++++++++++++++++++ .../entity/Seven.ts | 38 ++++++++++ .../multiple-joins-querybuilder/entity/Six.ts | 38 ++++++++++ .../multiple-joins-querybuilder/entity/Ten.ts | 38 ++++++++++ .../entity/Three.ts | 38 ++++++++++ .../multiple-joins-querybuilder/entity/Two.ts | 38 ++++++++++ .../multiple-joins-querybuilder.ts | 57 +++++++++++++++ 11 files changed, 472 insertions(+) create mode 100644 test/benchmark/multiple-joins-querybuilder/entity/Eight.ts create mode 100644 test/benchmark/multiple-joins-querybuilder/entity/Five.ts create mode 100644 test/benchmark/multiple-joins-querybuilder/entity/Four.ts create mode 100644 test/benchmark/multiple-joins-querybuilder/entity/Nine.ts create mode 100644 test/benchmark/multiple-joins-querybuilder/entity/One.ts create mode 100644 test/benchmark/multiple-joins-querybuilder/entity/Seven.ts create mode 100644 test/benchmark/multiple-joins-querybuilder/entity/Six.ts create mode 100644 test/benchmark/multiple-joins-querybuilder/entity/Ten.ts create mode 100644 test/benchmark/multiple-joins-querybuilder/entity/Three.ts create mode 100644 test/benchmark/multiple-joins-querybuilder/entity/Two.ts create mode 100644 test/benchmark/multiple-joins-querybuilder/multiple-joins-querybuilder.ts diff --git a/test/benchmark/multiple-joins-querybuilder/entity/Eight.ts b/test/benchmark/multiple-joins-querybuilder/entity/Eight.ts new file mode 100644 index 0000000000..6e483ef5f7 --- /dev/null +++ b/test/benchmark/multiple-joins-querybuilder/entity/Eight.ts @@ -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 +} diff --git a/test/benchmark/multiple-joins-querybuilder/entity/Five.ts b/test/benchmark/multiple-joins-querybuilder/entity/Five.ts new file mode 100644 index 0000000000..5925ed58f8 --- /dev/null +++ b/test/benchmark/multiple-joins-querybuilder/entity/Five.ts @@ -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 +} diff --git a/test/benchmark/multiple-joins-querybuilder/entity/Four.ts b/test/benchmark/multiple-joins-querybuilder/entity/Four.ts new file mode 100644 index 0000000000..2da286c12c --- /dev/null +++ b/test/benchmark/multiple-joins-querybuilder/entity/Four.ts @@ -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 +} diff --git a/test/benchmark/multiple-joins-querybuilder/entity/Nine.ts b/test/benchmark/multiple-joins-querybuilder/entity/Nine.ts new file mode 100644 index 0000000000..8351c205f6 --- /dev/null +++ b/test/benchmark/multiple-joins-querybuilder/entity/Nine.ts @@ -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 +} diff --git a/test/benchmark/multiple-joins-querybuilder/entity/One.ts b/test/benchmark/multiple-joins-querybuilder/entity/One.ts new file mode 100644 index 0000000000..02ba4a4dbb --- /dev/null +++ b/test/benchmark/multiple-joins-querybuilder/entity/One.ts @@ -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 +} diff --git a/test/benchmark/multiple-joins-querybuilder/entity/Seven.ts b/test/benchmark/multiple-joins-querybuilder/entity/Seven.ts new file mode 100644 index 0000000000..826a3272c3 --- /dev/null +++ b/test/benchmark/multiple-joins-querybuilder/entity/Seven.ts @@ -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 +} diff --git a/test/benchmark/multiple-joins-querybuilder/entity/Six.ts b/test/benchmark/multiple-joins-querybuilder/entity/Six.ts new file mode 100644 index 0000000000..a127fec622 --- /dev/null +++ b/test/benchmark/multiple-joins-querybuilder/entity/Six.ts @@ -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 +} diff --git a/test/benchmark/multiple-joins-querybuilder/entity/Ten.ts b/test/benchmark/multiple-joins-querybuilder/entity/Ten.ts new file mode 100644 index 0000000000..a0d83205d1 --- /dev/null +++ b/test/benchmark/multiple-joins-querybuilder/entity/Ten.ts @@ -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 +} diff --git a/test/benchmark/multiple-joins-querybuilder/entity/Three.ts b/test/benchmark/multiple-joins-querybuilder/entity/Three.ts new file mode 100644 index 0000000000..2ad50d1f96 --- /dev/null +++ b/test/benchmark/multiple-joins-querybuilder/entity/Three.ts @@ -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 +} diff --git a/test/benchmark/multiple-joins-querybuilder/entity/Two.ts b/test/benchmark/multiple-joins-querybuilder/entity/Two.ts new file mode 100644 index 0000000000..a73f202346 --- /dev/null +++ b/test/benchmark/multiple-joins-querybuilder/entity/Two.ts @@ -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 +} diff --git a/test/benchmark/multiple-joins-querybuilder/multiple-joins-querybuilder.ts b/test/benchmark/multiple-joins-querybuilder/multiple-joins-querybuilder.ts new file mode 100644 index 0000000000..7c26b6ee0a --- /dev/null +++ b/test/benchmark/multiple-joins-querybuilder/multiple-joins-querybuilder.ts @@ -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 + */ + }) +})