-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Description
Bug description
We've been having trouble with bad performance with our database queries. We have spent multiple weeks on this issue on implementing tracing, checking Postgres performance, rewriting/reducing our queries, etc. but have only had marginal improvements in performance. The only piece of our stack which we haven't changed much is Prisma, so I wrote a simple benchmark script comparing Prisma's performance with the pg library.
I took one of our worst performing Graphql queries and wrote out all the raw SQL queries the corresponding resolver and nested resolvers make. There are about 300 individual SELECT queries that are executed in chunks, and each chunk is executed concurrently. With pg I am creating a connection pool of size 40. And with Prisma, I am using $queryRawUnsafe to execute the query and setting ?connection_limit=40.
The expectation would be that query performance is close to equivalent between the two, but the difference is quite spectacular with the Prisma benchmark taking twice as long as pg.
pg:
$ node benchmark_pg.mjs
Concurrency: 10
Execution time: 2.1500 seconds
All queries executed successfully.
Concurrency: 20
Execution time: 1.2770 seconds
All queries executed successfully.
Concurrency: 40
Execution time: 0.7930 seconds
All queries executed successfully.
Concurrency: 80
Execution time: 0.7440 seconds
All queries executed successfully.
Concurrency: 160
Execution time: 0.7220 seconds
All queries executed successfully.
Prisma:
$ node benchmark_prisma.mjs
Concurrency: 10
Execution time: 4.3470 seconds
All queries executed successfully.
Concurrency: 20
Execution time: 2.3690 seconds
All queries executed successfully.
Concurrency: 40
Execution time: 1.4500 seconds
All queries executed successfully.
Concurrency: 80
Execution time: 1.4120 seconds
All queries executed successfully.
Concurrency: 160
Execution time: 1.3750 seconds
All queries executed successfully.
I expect the likely issue is in how we configure Prisma, otherwise this big of a performance drop is unexpected. What is the problem here? We would like the benefits of Prisma's ORM but the performance drop is too large for us.
How to reproduce
The benchmark script looks like so:
import fs from 'fs'
import pg from 'pg'
function createPool() {
const pool = new pg.Pool({
user: 'postgres',
host: 'X.X.X.X',
database: 'stagingDb',
password: '...',
port: 5432,
max: 40,
})
return pool
}
// Function to read SQL queries from a file
function readSqlFromFile(filePath) {
return new Promise((resolve, reject) => {
fs.readFile(filePath, { encoding: 'utf-8' }, (err, data) => {
if (err) reject(err)
else resolve(data)
})
})
}
// Function to execute a chunk of queries concurrently
async function executeQueryChunk(pool, queries) {
await Promise.all(
queries.map((query) => {
if (query) {
return pool.query(query)
}
return Promise.reject()
})
)
}
// Function to execute your queries
async function executeQueries(filePath, concurrencyLimit) {
console.log('\nConcurrency:', concurrencyLimit)
const pool = createPool()
try {
const queriesString = await readSqlFromFile(filePath)
const queries = queriesString
.split(';') // Splitting queries by ';'
.map((query) => query.trim())
.filter((query) => query) // Ensure no empty queries
const start = Date.now()
for (let i = 0; i < queries.length; i += concurrencyLimit) {
// Create a chunk of queries to run concurrently
const queryChunk = queries.slice(i, i + concurrencyLimit)
await executeQueryChunk(pool, queryChunk)
}
const end = Date.now()
console.log(`Execution time: ${((end - start) / 1000).toFixed(4)} seconds`)
console.log('All queries executed successfully.')
} catch (error) {
console.error('Error executing queries:', error)
} finally {
await pool.end() // Close the pool
}
}
const concurrencyLimits = [10, 20, 40, 80, 160]
for (const limit of concurrencyLimits) {
await executeQueries('./benchmark.sql', limit)
}And the corresponding changes for the Prisma test script is:
import { PrismaClient } from '@prisma/client'
function createClient() {
const client = new PrismaClient({
datasources: {
db: {
url: 'postgresql://postgres:[email protected]:5432/stagingDb?connection_limit=40',
},
},
})
return client
}
// Function to execute a chunk of queries concurrently
async function executeQueryChunk(client, queries) {
await Promise.all(
queries.map((query) => {
if (query) {
return client.$queryRawUnsafe(query)
}
return Promise.reject()
})
)
}
// ...
const client = createClient()
await client.$connect()Expected behavior
No response
Prisma information
datasource db {
provider = "postgresql"
url = env("PRISMA_POSTGRES_URL")
}
generator client {
provider = "prisma-client-js"
previewFeatures = ["relationJoins", "tracing"]
}
// bunch of model and enum definitions ...
Environment & setup
- Database: PostgreSQL 12.17
- Node.js version: v18.12.1
Prisma Version
npx prisma -v
Environment variables loaded from .env
prisma : 5.10.2
@prisma/client : 5.10.2
Computed binaryTarget : darwin-arm64
Operating System : darwin
Architecture : arm64
Node.js : v18.12.1
Query Engine (Node-API) : libquery-engine 5a9203d0590c951969e85a7d07215503f4672eb9 (at node_modules/@prisma/engines/libquery_engine-darwin-arm64.dylib.node)
Schema Engine : schema-engine-cli 5a9203d0590c951969e85a7d07215503f4672eb9 (at node_modules/@prisma/engines/schema-engine-darwin-arm64)
Schema Wasm : @prisma/prisma-schema-wasm 5.10.0-34.5a9203d0590c951969e85a7d07215503f4672eb9
Default Engines Hash : 5a9203d0590c951969e85a7d07215503f4672eb9
Studio : 0.499.0
Preview Features : tracing, relationJoins