vercel / next.js

The React Framework
https://nextjs.org
MIT License
122.91k stars 26.28k forks source link

Next.js 13 app dir: Fast Refresh (hot reloading) causes database connection exhaustion #45483

Open Eprince-hub opened 1 year ago

Eprince-hub commented 1 year ago

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 22.1.0: Sun Oct  9 20:14:30 PDT 2022; root:xnu-8792.41.9~2/RELEASE_ARM64_T8103
Binaries:
  Node: 19.4.0
  npm: 9.2.0
  Yarn: 1.22.19
  pnpm: 7.25.1
Relevant packages:
  next: 13.1.6
  eslint-config-next: N/A
  react: 18.2.0
  react-dom: 18.2.0

Which area(s) of Next.js are affected? (leave empty if unsure)

Link to the code that reproduces this issue

https://codesandbox.io/p/sandbox/autumn-mountain-0efmhn

To Reproduce

Run the project in CodeSandbox

https://codesandbox.io/p/sandbox/autumn-mountain-0efmhn

Start editing and saving any of these files => database/connect.ts / app/page.tsx

Check the console and watch the database connection count increase as the changes are saved

Describe the Bug

With Fast Refresh (hot reloading) during the development mode, a new database connection is initiated on every refresh, Thereby causing a database connection slot error like remaining connection slots are reserved for non-replication superuser connections in PostgreSQL

In the CodeSandbox linked above, a database connection is made similarly to how the docs section Sharing data between Server Components suggests:

export const db = new DatabaseConnection(...);

Screenshot of the error on the browser Screenshot 2023-02-01 at 10 38 04

Screenshot of the error in CLI Screenshot 2023-02-01 at 11 16 44

When a file that imports a database code is edited and saved, the Fast Refresh makes a new connection to the database on every refresh, rapidly increasing the database connection count. The database connection slot error is triggered when the count is above 90

The videos below show the process that triggers the error and the database connection count increment

https://user-images.githubusercontent.com/74430629/216009538-f99ec951-4cc3-4df8-83b3-8db9c2e08146.mp4

I also left a comment on another similar discussion, but this discussion is not about the app directory: https://github.com/vercel/next.js/discussions/26427

Expected Behavior

It is expected that the development mode Fast Refresh (hot reloading) doesn't attempt reconnecting to the database but instead uses the previous connection.

Workaround

Using the globalThis singleton workaround described here https://github.com/vercel/next.js/discussions/26427#discussioncomment-898067, I was able to prevent this behavior.

import postgres from 'postgres';

declare module globalThis {
  let postgresSqlClient: ReturnType<typeof postgres> | undefined;
}

// Connect only once to the database
// https://github.com/vercel/next.js/discussions/26427#discussioncomment-898067
function connectOnceToDatabase() {
  if (!globalThis.postgresSqlClient) globalThis.postgresSqlClient = postgres();
  return globalThis.postgresSqlClient;
}

export const sql = connectOnceToDatabase();

The video below shows that the database connection count is not increasing above 13-14 counts regardless of how many reloads happen

https://user-images.githubusercontent.com/74430629/216012463-c5f7aacd-840a-4d51-ad7c-42b90f5e18e0.MOV

Which browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

abuinitski commented 1 year ago

Pretty important issue, +1 on it. Super important for any non-trivial apps which use any global context or services (e.g. a Message Queue consumer, or gRPC clients / Protobuf parsers).

Few extra observations:

  1. Workaround has to be implemented exactly as proposed, any attempt to hide it behind any tooling breaks it. E.g. "singleton" utility that maintains a collection of singleton instances in global context and provide convenience methods to get-or-instantiate an instance - doesn't work, it's like globalContext is also getting reinstantiated based on some funny rules. Every singleton has to define its own extension of globalContext and export its own getSingletonInstance()-kind-of-a-function.
  2. A side-effect of that - instance of operator doesn't work at least in dev mode. If you created a custom exception class, for example, you cannot use x instance of MyException, since multiple versions of the same class may be present.
  3. A singleton hidden behind such workaround cannot use any standard next methods for server functions (e.g. cookies etc). Next is not able to find it's async context.
sangauit commented 1 year ago

In AWS has the proxy feature, actually that is the connection pooling, and in the .NET core I can implement something like that. So I believe you can implement something like that or NextJs have feature that support connection pooling in a case using server action to query data in db

eashish93 commented 11 months ago

Related to : https://github.com/vercel/next.js/issues/52165

nanzm commented 7 months ago

Is there any update?

alexeigs commented 5 months ago

Solution when working with supabase and drizzle: https://github.com/drizzle-team/drizzle-orm/issues/928#issuecomment-1739105895

chimung commented 2 weeks ago

The workaround solution does not work anymore