每个用户一个连接

时间:2019-05-03 15:49:53

标签: node.js postgresql pg-promise

我知道已经问过这个问题,但是似乎还有一些事情需要澄清。 :)

数据库的设计方式是,每个用户都具有读取文档的适当特权,因此连接池需要与其他用户建立连接,这超出了连接池的概念。由于优化和性能,我需要调用所谓的“用户准备”,其中包括设置会话变量,在缓存中计算和缓存值等,然后执行查询。

目前,我有两种解决方案。在第一个解决方案中,我首先检查是否已为用户准备好一切,然后执行一个或多个查询。如果没有准备好,那么我需要调用“用户准备”,然后执行一个或多个查询。有了这种解决方案,我失去了很多性能,因为每次都要进行检查,因此我决定使用另一种解决方案。

第二种解决方案包括“数据库池”,其中每个池都供一个用户使用。仅在第一个连接时useCount === 0(我不使用{direct:true})我称为“用户准备”(它是设置一些会话变量并准备缓存的存储过程),然后执行sql查询。

我已经在initOptions参数内的connect事件中完成了用于初始化pgPromise的用户准备工作。我使用了pg-promise-demo,所以不需要解释其余的代码。

使用数据库池包装器进行pgp初始化的代码如下:

import * as promise from "bluebird";

import pgPromise from "pg-promise";

import { IDatabase, IMain, IOptions } from "pg-promise";
import { IExtensions, ProductsRepository, UsersRepository, Session, getUserFromJWT } from "../db/repos";
import { dbConfig } from "../server/config";

// pg-promise initialization options:
export const initOptions: IOptions<IExtensions> = {
  promiseLib: promise,

  async connect(client: any, dc: any, useCount: number) {
    if (useCount === 0) {
      try {
        await client.query(pgp.as.format("select prepareUser($1)", [getUserFromJWT(session.JWT)]));
      } catch(error) {
        console.error(error);
      }
    }
  },

  extend(obj: IExtensions, dc: any) {
    obj.users = new UsersRepository(obj);
    obj.products = new ProductsRepository(obj);
  }
};

type DB = IDatabase<IExtensions>&IExtensions;

const pgp: IMain = pgPromise(initOptions);

class DBPool {
  private pool = new Map();

  public get = (ct: any): DB => {
    const checkConfig = {...dbConfig, ...ct};
    const {host, port, database, user} = checkConfig;
    const dbKey = JSON.stringify({host, port, database, user})
    let db: DB = this.pool.get(dbKey) as DB;
    if (!db) {
      // const pgp: IMain = pgPromise(initOptions);
      db = pgp(checkConfig) as DB;
      this.pool.set(dbKey, db);
    }
    return db;
  }
}
export const dbPool = new DBPool();

import diagnostics = require("./diagnostics");
diagnostics.init(initOptions);

Web API如下:

GET("/api/getuser/:id", (req: Request) => {
  const user = getUserFromJWT(session.JWT);
  const db = dbPool.get({ user });
  return db.users.findById(req.params.id);
});

我对源代码是否正确地实例化pgp或应该在get方法中的if块内对其进行实例化感兴趣(该行已注释)?

我已经看到pg-promise使用从dbPool.js导出的DatabasePool单例,这与我的DBPool类相似,但是目的是给出“警告:为同一连接创建重复的数据库对象”。是否可以使用DatabasePool单例而不是dbPool单例?

在我看来,dbContext(pgp初始化中的第二个参数)可以解决我的问题,但前提是它可以作为函数而不是作为值或对象转发。访问数据库对象时,dbContext是否是错误的?或者是动态的?

我想知道是否还有第三种(更好的)解决方案?或其他任何建议。

1 个答案:

答案 0 :(得分:0)

如果您对此警告感到困扰:

  

警告:为相同的连接创建重复的数据库对象

但是您的目的是为每个用户维护一个单独的池,您可以通过为连接提供任何唯一参数来表明这一点。例如,您可以在用户名中包含自定义属性:

const cn = {
    database: 'my-db',
    port: 12345,
    user: 'my-login-user',
    password: 'my-login-password'

    ....

    my_dynamic_user: 'john-doe'
}

这足以使库看到您的连接中有唯一的东西,该东西与其他连接不匹配,因此不会产生该警告。

这同样适用于连接字符串。


请注意,只有当连接总数完全超过用户数量时,您要实现的目标才能正常工作。例如,如果您最多可以使用100个连接,最多可以有10个用户。然后,您可以分配10个池,每个池中最多包含10个连接。否则,系统的可伸缩性将受到影响,因为连接总数是非常有限的资源,通常您永远不会超过100个连接,因为这会在同时运行这么多物理连接的CPU上产生过多的负载。这就是为什么共享单个连接池可扩展性更好的原因。