From 346196f2538220c332ae2032d0b491bd33c832f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Fern=C3=A1ndez?= Date: Tue, 13 Feb 2024 12:12:11 +0100 Subject: [PATCH] wasm-benchmarks: Fix potential collision of sql in recordings (#4689) * Fix JSON serialization of bigints * wasm-benchmarks: apply lateral joins when possible, and return rel fields in results (#4691) * Store recordings if requested and use joins * More traversal and serialization * Create recordings for all the different engines as they might be using different SQL queries. * Remove stale comment --- .../executor/bench/queries.json | 38 +++++++++++++-- .../executor/bench/schema.prisma | 2 +- .../driver-adapters/executor/src/bench.ts | 47 ++++++++++++++----- .../driver-adapters/executor/src/recording.ts | 28 ++++++----- 4 files changed, 85 insertions(+), 30 deletions(-) diff --git a/query-engine/driver-adapters/executor/bench/queries.json b/query-engine/driver-adapters/executor/bench/queries.json index e143da135acc..5410f162be11 100644 --- a/query-engine/driver-adapters/executor/bench/queries.json +++ b/query-engine/driver-adapters/executor/bench/queries.json @@ -1,6 +1,6 @@ [ { - "description": "movies.findMany() (all - 25000)", + "description": "movies.findMany() (all - ~50K)", "query": { "action": "findMany", "modelName": "Movie", @@ -59,7 +59,12 @@ "cast": true }, "selection": { - "$scalars": true + "$scalars": true, + "cast": { + "selection": { + "$scalars": true + } + } } } } @@ -82,7 +87,12 @@ "cast": true }, "selection": { - "$scalars": true + "$scalars": true, + "cast": { + "selection": { + "$scalars": true + } + } } } } @@ -104,7 +114,16 @@ } }, "selection": { - "$scalars": true + "$scalars": true, + "cast": { + "selection": { + "person": { + "selection": { + "$scalars": true + } + } + } + } } } } @@ -131,7 +150,16 @@ } }, "selection": { - "$scalars": true + "$scalars": true, + "cast": { + "selection": { + "person": { + "selection": { + "$scalars": true + } + } + } + } } } } diff --git a/query-engine/driver-adapters/executor/bench/schema.prisma b/query-engine/driver-adapters/executor/bench/schema.prisma index a45c1e62b4cc..6346afed158d 100644 --- a/query-engine/driver-adapters/executor/bench/schema.prisma +++ b/query-engine/driver-adapters/executor/bench/schema.prisma @@ -5,7 +5,7 @@ datasource db { generator foo { provider = "prisma-client-js" - previewFeatures = ["driverAdapters"] + previewFeatures = ["driverAdapters", "relationJoins"] } model Movie { diff --git a/query-engine/driver-adapters/executor/src/bench.ts b/query-engine/driver-adapters/executor/src/bench.ts index edd1cb6530ae..e168e95a9cab 100644 --- a/query-engine/driver-adapters/executor/src/bench.ts +++ b/query-engine/driver-adapters/executor/src/bench.ts @@ -45,11 +45,18 @@ async function main(): Promise { const withErrorCapturing = bindAdapter(pg); // We build two decorators for recording and replaying db queries. - const { recorder, replayer } = recording(withErrorCapturing); + const { recorder, replayer, recordings } = recording(withErrorCapturing); // We exercise the queries recording them await recordQueries(recorder, datamodel, prismaQueries); + // Dump recordings if requested + if (process.env.BENCH_RECORDINGS_FILE != null) { + const recordingsJson = JSON.stringify(recordings.data(), null, 2); + await fs.writeFile(process.env.BENCH_RECORDINGS_FILE, recordingsJson); + debug(`Recordings written to ${process.env.BENCH_RECORDINGS_FILE}`); + } + // Then we benchmark the execution of the queries but instead of hitting the DB // we fetch results from the recordings, thus isolating the performance // of the engine + driver adapter code from that of the DB IO. @@ -61,23 +68,37 @@ async function recordQueries( datamodel: string, prismaQueries: any ): Promise { - const qe = await initQeWasmBaseLine(adapter, datamodel); - await qe.connect(""); + // Different engines might have made different SQL queries to complete the same Prisma Query, + // so we record the results of all engines for the benchmarking phase. + const napi = await initQeNapiCurrent(adapter, datamodel); + await napi.connect(""); + const wasmCurrent = await initQeWasmCurrent(adapter, datamodel); + await wasmCurrent.connect(""); + const wasmBaseline = await initQeWasmBaseLine(adapter, datamodel); + await wasmBaseline.connect(""); + const wasmLatest = await initQeWasmLatest(adapter, datamodel); + await wasmLatest.connect(""); try { - for (const prismaQuery of prismaQueries) { - const { description, query } = prismaQuery; - const res = await qe.query(JSON.stringify(query), "", undefined); - - const errors = JSON.parse(res).errors; - if (errors != null && errors.length > 0) { - throw new Error( - `Query failed for ${description}: ${JSON.stringify(res)}` - ); + for (const qe of [napi, wasmCurrent, wasmBaseline, wasmLatest]) { + for (const prismaQuery of prismaQueries) { + const { description, query } = prismaQuery; + const res = await qe.query(JSON.stringify(query), "", undefined); + console.log(res[9]); + + const errors = JSON.parse(res).errors; + if (errors != null) { + throw new Error( + `Query failed for ${description}: ${JSON.stringify(res)}` + ); + } } } } finally { - await qe.disconnect(""); + await napi.disconnect(""); + await wasmCurrent.disconnect(""); + await wasmBaseline.disconnect(""); + await wasmLatest.disconnect(""); } } diff --git a/query-engine/driver-adapters/executor/src/recording.ts b/query-engine/driver-adapters/executor/src/recording.ts index a4152488e985..0602cb69dc4e 100644 --- a/query-engine/driver-adapters/executor/src/recording.ts +++ b/query-engine/driver-adapters/executor/src/recording.ts @@ -13,6 +13,7 @@ export function recording(adapter: DriverAdapter) { return { recorder: recorder(adapter, recordings), replayer: replayer(adapter, recordings), + recordings: recordings, }; } @@ -31,9 +32,7 @@ function recorder(adapter: DriverAdapter, recordings: Recordings) { return result; }, executeRaw: async (params) => { - const result = await adapter.executeRaw(params); - recordings.addCommandResults(params, result); - return result; + throw new Error("Not implemented"); }, }; } @@ -61,18 +60,25 @@ function createInMemoryRecordings() { const queryResults: Map> = new Map(); const commandResults: Map> = new Map(); - // Recording is currently only used in benchmarks. Before we used to serialize the whole query - // (sql + args) but since bigints are not serialized by JSON.stringify, and we didn’t really need - // (sql + args) but since bigints are not serialized by JSON.stringify, and we didn't really need - // args for benchmarks, we just serialize the sql part. - // - // If this ever changes (we reuse query recording in tests) we need to make sure to serialize the - // args as well. const queryToKey = (params: Query) => { - return JSON.stringify(params.sql); + var sql = params.sql; + params.args.forEach((arg: any, i) => { + sql = sql.replace("$" + (i + 1), arg.toString()); + }); + return sql; }; return { + data: (): Map => { + const map = new Map(); + for (const [key, value] of queryResults.entries()) { + value.map((resultSet) => { + map[key] = resultSet; + }); + } + return map; + }, + addQueryResults: (params: Query, result: Result) => { const key = queryToKey(params); queryResults.set(key, result);