在Node.js中重用PostgreSQL连接池

5

我有以下在Node.js中的PostgreSQL连接文件:

// postgresql.js

"use strict";

const { Pool } = require('pg');

module.exports = new Promise((resolve, reject) => {
  const pool = new Pool({
    host: '127.0.0.1',
    port: 5432,
    user: 'postgres',
    password: 'postgres',
    database: 'postgres',
    connectionTimeoutMillis: 0,
    idleTimeoutMillis: 0,
    min: 10,
    max: 20,
  });

  resolve({ pool });
});

我正在使用 Promise,因为稍后我将开始使用 Google Cloud Secret Manager。这些秘密是异步获取的,因此当服务器启动时无法建立数据库连接。

在我的控制器文件中,我像这样使用它:

// apicontroller.js

"use strict";

const postgresql = require('./postgresql');

app.get('/api/test', async (req, res, next) => {
// resolve the postgresql promise
const { pool } = await postgresql;

// resolve the pool.connect() promise
const client = await pool.connect();

// use the client object here
// eg. await client.query(...)
});

问题并不在于它不能正常工作,相反,它的工作非常好!但我有疑问:我是在重复使用连接池,还是每次请求该路由时都创建了新的连接(池)?
这是否是仅重用数据库连接一次的正确方法?
编辑:我只包含相关的代码部分。
2个回答

5
node-postgres的文档中所述,建议使用pool.query而不是直接操作client,以避免资源泄漏。

单个查询: 如果您不需要事务或只需运行单个查询,则池具有方便的方法在池中的任何可用客户端上运行查询。 如果可能的话,这是使用 node-postgres 查询的首选方式,因为它消除了泄漏客户端的风险。

因此,我的代码应该像这样:

postgresql.js

let mainPool = null;

functiona createPool(){
  const pool = new Pool({
    host: '127.0.0.1',
    port: 5432,
    user: 'postgres',
    password: 'postgres',
    database: 'postgres',
    connectionTimeoutMillis: 0,
    idleTimeoutMillis: 0,
    min: 10,
    max: 20,
  });
  return pool;
}

function getPool(){
  if(!mainPool){
    mainPool = createPool();
  }
  return mailPool;
}

export default { getPool };

controller.js

const { getPool } = require('./postgresql');

app.get('/api/test', async (req, res, next) => {
  getPool().query('SELECT * FROM users', [], (err, res) => {
    if (err) {
       return res.status(500).send(err);
    }
    return res.status(500).send(res);
  });
});

这既没有回答问题,也不能在 OP 的情况下工作。无论如何使用池(单个查询或托管客户端),问题都在于如何异步获取池。 - Bergi
@Bergi请先阅读问题!这是重复使用数据库连接的正确方法吗?正如您在OP的问题中所看到的。而且,该问题中的示例还提供了异步调用的解决方案。 - hurricane
我认为你误解了这个问题(我承认它可能需要改进)。它是关于正确地重复使用池,而不是每个请求只查询一次。 - Bergi
@Bergi,我觉得你误解了我的回答。我给出了一个帮助OP重用池的答案。你能告诉我回答中有什么问题吗?难道我的回答中的功能不能重用池吗? - hurricane
也许我被关于池顶部单查询使用的无关引用分散了注意力。如果您在getPool中引用单例模式,是的,那可能是我忽略的答案。而且,createPool应该像OP代码中一样返回一个Promise。 - Bergi
这个答案应该被标记为被接受的答案。 - undefined

0

是的,您正在重复使用存储为导出 Promise 结果的同一池对象。只有一个池对象,没有任何代码会创建第二个实例,无论您多少次 require 它,模块仅被计算一次。

但是,错误处理存在一个小问题 - 如果无法创建数据库池(例如,密码加载失败),则需要在某个地方处理此问题(并可能使应用程序崩溃),而不是等待 unhandledrejection 事件。

虽然您的代码没问题,但请考虑另一种方法,即在获取池及其连接详细信息之前不启动 HTTP 服务器,然后将池(而不是 Promise)传递给所有路由。这还确保了如果它的依赖项未能初始化,则根本不会启动 HTTP 服务器。


我该如何延迟服务器的启动并将池对象传递给所有控制器?我需要使用app.set吗?还是将所有控制器包装在父函数中,并将池对象作为参数传递?编辑:顺便说一句,谢谢你的回答! - Sam Leurs
@yesterday 是的,可以选择其中任意一个,然后将 createServer 调用移动到 postgresql.then(…) 回调函数内部。 - Bergi
问题是:我有多个承诺(也包括一个Redis连接)。是否可以使用Promise.all()选项,并将创建服务器放在其中的“then”部分中? - Sam Leurs
@yesterday 是的,没错,Promise.all 对此非常完美。 - Bergi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接