N
Neon2y ago
harsh-harlequin

Switched to pooled connection, but still seeing `prepared statement XXXX` does not exist.

Hi, I am using the pooled connection string combined with Drizzle. I am using the latest Neon packages and Drizzle packages. Sentry is reporting some calls are 400'ing to https://api.us-east-1.aws.neon.tech/sql Weirdly, the TRPC responses that initiate the downstream Neon calls are coming back as 200 on the front-end, but intermediate batched queries appear to snag a 400 sometimes. Is this intended with the new PGBouncer change? Worth noting I am not explicitly using .prepare statements, but I believe Postgres.js (which Drizzle might use under the hood...?) will auto convert certain statements to prepare.
5 Replies
eastern-cyan
eastern-cyan2y ago
@minty sorry to hear you're having trouble! Can you share any of the error messages associated with the 400 responses from api.us-east-1.aws.neon.tech/sql Did this only start happening after a certain date and time?
harsh-harlequin
harsh-harlequinOP2y ago
This happened after we switched back to pooled connection strings this afternoon I will try to get some logs, one sec some other info: prepared statement "s7971" does not exist is one instance of the error Is there a way you recommending debugging the error messages? Since it is far down in the stack I am not entirely sure where to look Sentry shows 400's within spans on requests to Neon, but I cannot see the actual response here is my relevant DB code, if it's helpful:
import * as dotenv from 'dotenv';

// Only load the local enviornment file if we are running locally
// VERCEL_ENV is 'development' when running the Next.js dev server
// VERCEL_ENV is undefined when running one-off scripts locally (such as migrate.ts)
if (process.env.VERCEL_ENV === 'development' || process.env.VERCEL_ENV === undefined) {
dotenv.config({ path: '.env.local' });
}

import { neon, neonConfig, Client } from '@neondatabase/serverless';
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
import { drizzle as drizzleHttp, type NeonHttpDatabase } from 'drizzle-orm/neon-http';
import { drizzle as drizzleServerless } from 'drizzle-orm/neon-serverless';

import * as schema from '@/server/schema';

const DATABASE_URL = process.env.DATABASE_URL || '';

export type NonTransactionalDatabase = NeonHttpDatabase<typeof schema>;
export type TransactionalDatabase = NodePgDatabase<typeof schema>;
export type Database = NonTransactionalDatabase | TransactionalDatabase;

neonConfig.fetchConnectionCache = true;
const neonClient = neon(DATABASE_URL);

/**
* Non-transactional database client using Drizzle ORM and Neon. This client does not need to be closed and can be imported globally.
*/
export const NonTransactionalDbClient: NonTransactionalDatabase = drizzleHttp(neonClient, { schema, logger: true });

/**
* Creates a transactional database instance. THIS MUST BE CLOSED AFTER USE WITH `pgClient.end()`.
*
* @returns A Promise resolving to an object containing both the Drizzle ORM client and the PostgreSQL client.
*/
export const getTransactionalDbInstance = async (): Promise<{
drizzleClient: TransactionalDatabase;
pgClient: Client;
}> => {
const pgClient = new Client({ connectionString: DATABASE_URL });
await pgClient.connect();
const drizzleClient = drizzleServerless(pgClient, { schema, logger: true });

return { drizzleClient, pgClient };
};

/**
* Runs a transactional query using Drizzle ORM.
*
* @param action A function taking a transactional database and returning a Promise.
* @returns A Promise of the result of the transactional action.
*/
export const runTransactionalQuery = async <T>(action: (db: TransactionalDatabase) => Promise<T>): Promise<T> => {
let client;
try {
const { drizzleClient, pgClient } = await getTransactionalDbInstance();
client = pgClient;
const results = await action(drizzleClient); // pass the `db` instance to the action
return results;
} finally {
if (client) {
await client.end(); // always close the client, even if the action fails
}
}
};
import * as dotenv from 'dotenv';

// Only load the local enviornment file if we are running locally
// VERCEL_ENV is 'development' when running the Next.js dev server
// VERCEL_ENV is undefined when running one-off scripts locally (such as migrate.ts)
if (process.env.VERCEL_ENV === 'development' || process.env.VERCEL_ENV === undefined) {
dotenv.config({ path: '.env.local' });
}

import { neon, neonConfig, Client } from '@neondatabase/serverless';
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
import { drizzle as drizzleHttp, type NeonHttpDatabase } from 'drizzle-orm/neon-http';
import { drizzle as drizzleServerless } from 'drizzle-orm/neon-serverless';

import * as schema from '@/server/schema';

const DATABASE_URL = process.env.DATABASE_URL || '';

export type NonTransactionalDatabase = NeonHttpDatabase<typeof schema>;
export type TransactionalDatabase = NodePgDatabase<typeof schema>;
export type Database = NonTransactionalDatabase | TransactionalDatabase;

neonConfig.fetchConnectionCache = true;
const neonClient = neon(DATABASE_URL);

/**
* Non-transactional database client using Drizzle ORM and Neon. This client does not need to be closed and can be imported globally.
*/
export const NonTransactionalDbClient: NonTransactionalDatabase = drizzleHttp(neonClient, { schema, logger: true });

/**
* Creates a transactional database instance. THIS MUST BE CLOSED AFTER USE WITH `pgClient.end()`.
*
* @returns A Promise resolving to an object containing both the Drizzle ORM client and the PostgreSQL client.
*/
export const getTransactionalDbInstance = async (): Promise<{
drizzleClient: TransactionalDatabase;
pgClient: Client;
}> => {
const pgClient = new Client({ connectionString: DATABASE_URL });
await pgClient.connect();
const drizzleClient = drizzleServerless(pgClient, { schema, logger: true });

return { drizzleClient, pgClient };
};

/**
* Runs a transactional query using Drizzle ORM.
*
* @param action A function taking a transactional database and returning a Promise.
* @returns A Promise of the result of the transactional action.
*/
export const runTransactionalQuery = async <T>(action: (db: TransactionalDatabase) => Promise<T>): Promise<T> => {
let client;
try {
const { drizzleClient, pgClient } = await getTransactionalDbInstance();
client = pgClient;
const results = await action(drizzleClient); // pass the `db` instance to the action
return results;
} finally {
if (client) {
await client.end(); // always close the client, even if the action fails
}
}
};
DATABASE_URL is a -pooler string We pass both TransactionalDb and NonTransactionalDb instances into our TRPC context (deferring to TransactionalDb unless we need transactions)
eastern-cyan
eastern-cyan2y ago
Is one of these instances generating the error, or are both? We recently rolled out improvements to PgBouncer to support prepared statements, so prepared statements should be working with the -pooler URL. I wonder if @Conrad Ludgate or @jawj have some theories... Actually, we just rolled out the prepared statements update everywhere today @minty. Can you keep an eye on your logs are let me know if the situation has improved
harsh-harlequin
harsh-harlequinOP2y ago
Will do, thanks!
eastern-cyan
eastern-cyan2y ago
@minty all good now?

Did you find this page helpful?