Knex 与 PostgreSQL 选择查询在多个并行请求上性能极度下降

2024-06-28

In brief

我正在开发一个(梦想中的)游戏,我的后端堆栈是 Node.js 和带有 Knex 的 PostgreSQL(9.6)。我在这里保存所有玩家数据,我需要经常请求它。 其中一个请求需要进行 10 个简单的选择来提取数据,这就是问题开始的地方:如果服务器同时只处理 1 个请求,这些查询非常快(~1 毫秒)。但是,如果服务器并行处理许多请求(100-400),则查询执行时间会大大降低(每个查询可能长达几秒)

Details

为了更加客观,我将描述服务器的请求目标、选择查询和我收到的结果。

关于系统

我在 Digital Ocean 4cpu/8gb Droplet 上运行节点代码,并在同一配置上运行 Postgres(2 个不同的 Droplet,相同的配置)

关于请求

它需要执行一些游戏操作,为此他从数据库中选择 2 个玩家的数据

DDL

玩家数据由5张表表示:

CREATE TABLE public.player_profile(
    id integer NOT NULL DEFAULT nextval('player_profile_id_seq'::regclass),
    public_data integer NOT NULL,
    private_data integer NOT NULL,
    current_active_deck_num smallint NOT NULL DEFAULT '0'::smallint,
    created_at bigint NOT NULL DEFAULT '0'::bigint,
    CONSTRAINT player_profile_pkey PRIMARY KEY (id),
    CONSTRAINT player_profile_private_data_foreign FOREIGN KEY (private_data)
        REFERENCES public.profile_private_data (id) MATCH SIMPLE
        ON UPDATE NO ACTION
        ON DELETE NO ACTION,
    CONSTRAINT player_profile_public_data_foreign FOREIGN KEY (public_data)
        REFERENCES public.profile_public_data (id) MATCH SIMPLE
        ON UPDATE NO ACTION
        ON DELETE NO ACTION
);

CREATE TABLE public.player_character_data(
    id integer NOT NULL DEFAULT nextval('player_character_data_id_seq'::regclass),
    owner_player integer NOT NULL,
    character_id integer NOT NULL,
    experience_counter integer NOT NULL,
    level_counter integer NOT NULL,
    character_name character varying(255) COLLATE pg_catalog."default" NOT NULL,
    created_at bigint NOT NULL DEFAULT '0'::bigint,
    CONSTRAINT player_character_data_pkey PRIMARY KEY (id),
    CONSTRAINT player_character_data_owner_player_foreign FOREIGN KEY (owner_player)
        REFERENCES public.player_profile (id) MATCH SIMPLE
        ON UPDATE NO ACTION
        ON DELETE NO ACTION
);

CREATE TABLE public.player_cards(
    id integer NOT NULL DEFAULT nextval('player_cards_id_seq'::regclass),
    card_id integer NOT NULL,
    owner_player integer NOT NULL,
    card_level integer NOT NULL,
    first_deck boolean NOT NULL,
    consumables integer NOT NULL,
    second_deck boolean NOT NULL DEFAULT false,
    third_deck boolean NOT NULL DEFAULT false,
    quality character varying(10) COLLATE pg_catalog."default" NOT NULL DEFAULT 'none'::character varying,
    CONSTRAINT player_cards_pkey PRIMARY KEY (id),
    CONSTRAINT player_cards_owner_player_foreign FOREIGN KEY (owner_player)
        REFERENCES public.player_profile (id) MATCH SIMPLE
        ON UPDATE NO ACTION
        ON DELETE NO ACTION
);

CREATE TABLE public.player_character_equipment(
    id integer NOT NULL DEFAULT nextval('player_character_equipment_id_seq'::regclass),
    owner_character integer NOT NULL,
    item_id integer NOT NULL,
    item_level integer NOT NULL,
    item_type character varying(20) COLLATE pg_catalog."default" NOT NULL,
    is_equipped boolean NOT NULL,
    slot_num integer,
    CONSTRAINT player_character_equipment_pkey PRIMARY KEY (id),
    CONSTRAINT player_character_equipment_owner_character_foreign FOREIGN KEY (owner_character)
        REFERENCES public.player_character_data (id) MATCH SIMPLE
        ON UPDATE NO ACTION
        ON DELETE NO ACTION
);

CREATE TABLE public.player_character_runes(
    id integer NOT NULL DEFAULT nextval('player_character_runes_id_seq'::regclass),
    owner_character integer NOT NULL,
    item_id integer NOT NULL,
    slot_num integer,
    decay_start_timestamp bigint,
    CONSTRAINT player_character_runes_pkey PRIMARY KEY (id),
    CONSTRAINT player_character_runes_owner_character_foreign FOREIGN KEY (owner_character)
        REFERENCES public.player_character_data (id) MATCH SIMPLE
        ON UPDATE NO ACTION
        ON DELETE NO ACTION
);

带索引

knex.raw('create index "player_cards_owner_player_first_deck_index" on "player_cards"("owner_player") WHERE first_deck = TRUE');
knex.raw('create index "player_cards_owner_player_second_deck_index" on "player_cards"("owner_player") WHERE second_deck = TRUE');
knex.raw('create index "player_cards_owner_player_third_deck_index" on "player_cards"("owner_player") WHERE third_deck = TRUE');
knex.raw('create index "player_character_equipment_owner_character_is_equipped_index" on "player_character_equipment" ("owner_character") WHERE is_equipped = TRUE');
knex.raw('create index "player_character_runes_owner_character_slot_num_not_null_index" on "player_character_runes" ("owner_character") WHERE slot_num IS NOT NULL');

The code

第一个查询

async.parallel([
    cb => tx('player_character_data')
        .select('character_id', 'id')
        .where('owner_player', playerId)
        .limit(1)
        .asCallback(cb),
    cb => tx('player_character_data')
        .select('character_id', 'id')
        .where('owner_player', enemyId)
        .limit(1)
        .asCallback(cb)
], callbackFn);

第二次查询

async.parallel([
    cb => tx('player_profile')
        .select('current_active_deck_num')
        .where('id', playerId)
        .asCallback(cb),
    cb => tx('player_profile')
        .select('current_active_deck_num')
        .where('id', enemyId)
        .asCallback(cb)
], callbackFn);

Third q

playerQ = { first_deck: true }
enemyQ = { first_deck: true }
MAX_CARDS_IN_DECK = 5
async.parallel([
    cb => tx('player_cards')
        .select('card_id', 'card_level')
        .where('owner_player', playerId)
        .andWhere(playerQ)
        .limit(MAX_CARDS_IN_DECK)
        .asCallback(cb),
    cb => tx('player_cards')
        .select('card_id', 'card_level')
        .where('owner_player', enemyId)
        .andWhere(enemyQ)
        .limit(MAX_CARDS_IN_DECK)
        .asCallback(cb)
], callbackFn);

Fourth q

MAX_EQUIPPED_ITEMS = 3
async.parallel([
    cb => tx('player_character_equipment')
        .select('item_id', 'item_level')
        .where('owner_character', playerCharacterUniqueId)
        .andWhere('is_equipped', true)
        .limit(MAX_EQUIPPED_ITEMS)
        .asCallback(cb),
    cb => tx('player_character_equipment')
        .select('item_id', 'item_level')
        .where('owner_character', enemyCharacterUniqueId)
        .andWhere('is_equipped', true)
        .limit(MAX_EQUIPPED_ITEMS)
        .asCallback(cb)
], callbackFn);

第五个

runeSlotsMax = 3
async.parallel([
    cb => tx('player_character_runes')
        .select('item_id', 'decay_start_timestamp')
        .where('owner_character', playerCharacterUniqueId)
        .whereNotNull('slot_num')
        .limit(runeSlotsMax)
        .asCallback(cb),
    cb => tx('player_character_runes')
        .select('item_id', 'decay_start_timestamp')
        .where('owner_character', enemyCharacterUniqueId)
        .whereNotNull('slot_num')
        .limit(runeSlotsMax)
        .asCallback(cb)
], callbackFn);

解释(分析)

仅索引扫描,规划和执行时间

时间本身

(total是请求数,min/max/avg/median用于响应时间)

  • 4个并发请求:{ "total": 300, "avg": 1.81, "median": 2, "min": 1, "max": 6 }
  • 400 concurrent requests:
    • { "total": 300, "avg": 209.57666666666665, "median": 176, "min": 9, "max": 1683 }- 首先选择
    • { "total": 300, "avg": 2105.9, "median": 2005, "min": 1563, "max": 4074 }- 最后选择

我尝试将执行时间超过 100 毫秒的慢速查询放入日志中 - 什么也没有。还尝试将连接池大小增加到并行请求数 - 也没有。


很快找到了解决方案,但忘记在这里回复(很忙,抱歉)。

缓慢的查询没有什么魔力,只有节点的事件循环性质:

  • 所有 silimar 请求都是并行发出的;
  • 我有一个执行时间非常慢的代码块(~150-200ms);
  • 如果您有约 800 个并行请求,则 150 毫秒的代码块会转换为约 10000 毫秒的事件循环滞后;
  • 您将看到的只是缓慢请求的可见性,但这只是回调函数的滞后,而不是数据库的滞后;

结论: use pgBadger检测缓慢的查询和isBusy检测事件循环滞后的模块

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Knex 与 PostgreSQL 选择查询在多个并行请求上性能极度下降 的相关文章

  • jade模板条件类nodejsexpressjs

    我的标题有一个 jade 模板文件 并使用引导标记 根据用户所在的页面 导航栏需要将 active 类添加到该导航项 避免这样的长代码的最佳方法是什么 header jade if nav home li active a href Hom
  • 切换 Ag-Grid 中的浮动过滤器?

    我试图通过开关或按钮单击来确定浮动过滤器的显示 看起来很简单 我应该能够在 true 和 false 之间切换 将该值提供给网格选项中的浮动过滤器 然后刷新标题 对吗 不幸的是 网格似乎总是落后一步 当我第一次点击时 什么也没有发生 当我将
  • Karma Webpack - 错误:找不到模块“./test/utilities.js”

    我正在使用 Karma Webpack 进行项目的单元测试 当我跑步时karma start 我有这个错误 Error Cannot find module test utilities js at myproject test campa
  • 将 Babel 与单个输出文件和 ES6 模块一起使用

    这是我的 gulp 任务 将 ES6 代码编译成单个 ES5 文件 我使用类和模块 import export 在 ES6 中 gulp src paths scripts pipe sourcemaps init pipe babel p
  • JavaScript 回调的效率

    我只是想证实我的一个怀疑 我偶然发现了一篇文章 建议按以下方式使用 Socket io var app require express createServer var io require socket io listen app app
  • 两个sql查询的性能差异?

    我的表中有一个具有文本数据类型的字段 以下两个sql查询的性能是否有差异 select from tablename where fieldname xyz select from tablename where fieldname zyx
  • Puppeteer 登录 Instagram

    我正在尝试使用 Puppeteer 登录 Instagram 但不知何故无法登录 你能帮助我吗 这是我正在使用的链接 https www instagram com accounts login https www instagram co
  • 导出数据库的 CREATE 脚本

    假设我已经在 pgAdmin 中创建了一个数据库 但我想导出一个 CREATE sql 文件 我将如何生成转储 以下是如何使用 pgAdmin 创建一个架构脚本 该脚本可以与 PostgreSql 数据库架构比较工具 例如apgdiff h
  • 按周将对象数组拆分为组

    我有一个包含这样的数据的对象数组 date 01 01 2017 00 00 00 dataField1 dataField2 date 01 02 2017 00 00 00 dataField1 dataField2 date 01 1
  • Array.from 的时间复杂度

    时间复杂度是多少Array from 例如 const set new Set set add car set add cat set add dog console log Array from set time complexity o
  • pointdown 与 onclick:有什么区别?

    两者有什么区别onpointerdown and onclick事件处理程序 有任何实际差异吗 事件在 DOM 树上传播的方式不一样吗 是否有一些设备仅响应这些事件之一 我最初以为这只是pointerdown在触摸设备或笔中触发 但是onc
  • 如何在Javascript中保存zip文件的二进制数据?

    我从 AJAX 响应中收到以下响应 这是 zip 文件的响应 请让我知道如何在 Javascript 中保存此 filename zip ZIP 里面有 PDF 文件 我的代码是这样的 ajax url baseURLDownload se
  • 如何在 Electron 中使窗口大小响应。 (打开应用程序时)

    我最近开始在 Electron 上制作一个应用程序 我想让窗口具有响应能力 例如 如果我在不同的屏幕上打开应用程序 它应该根据屏幕尺寸以全尺寸打开 我的代码 app on ready gt const htmlPath path join
  • Chrome 扩展程序导致 DOM 闪烁,该扩展程序会在页面加载之前删除 DOM 元素

    我正在编写一个 chrome 扩展 我想在页面加载之前删除内容 例如this http tech blog tomchambers me 2016 01 13 How to write a simple page rewriting Chr
  • 使用 _.extend() 进行 JavaScript 继承

    有什么区别 Employee prototype Object create Person prototype and extend Employee prototype Person prototype 两者都给出相似的结果 输出 但下划
  • 删除移动设备上的 adsense

    我正在研究响应式设计 但在使用 adsense 时遇到了问题 我有一个广告应该显示在桌面设计上 但不能显示在移动设计上 因此 只有在桌面上查看网站时 才应将广告代码放置在 html 中 css 可以使用 display none 但这违反了
  • getCompatedStyle 类似于 IE8 的 javascript 函数

    我正在尝试在 Java GWT 代码中编写一个 Javascript 函数 该函数获取以下样式的值 direction fontFamily fontSize fontSizeAdjust fontStyle fontWeight lett
  • 如何捕获 google 地图的无效 API 密钥

    我有这个代码 如果密钥无效 则会弹出警报 但我想在这种情况下执行一些操作 但我不知道如何连接它 有任何想法吗 Google 不提供检查 Google 地图 API 密钥的外部方法 因此 您无法使用例如查询某些服务 此代码有效吗abcde12
  • 依靠条件连接大表的速度很慢

    当表很小时 这个查询有合理的时间 我试图找出瓶颈是什么 但我不知道如何分析EXPLAIN结果 SELECT COUNT FROM performance analyses INNER JOIN total sales ON total sa
  • 如何防止外部 CSS 添加和覆盖 ReactJS 组件样式

    我有一个自定义的 ReactJS 组件 我想以某种方式设置样式 并将其作为插件提供给许多不同的网站 但是 当网站使用全局样式 Twitter bootstrap 或其他 css 框架 时 它会添加并覆盖我的组件的样式 例如 全局 css l

随机推荐