简单来说

我正在开发一个游戏(梦想),我的后端堆栈是具有Knex的Node.js和Postgresql(9.6).我在这里持有所有玩家的数据,我需要频繁请求.
其中一个请求需要进行10个简单的选择,这将提取数据,这是问题开始的地方:这些查询速度相当快(〜1ms),如果服务器只同时提供1个请求.但是如果服务器服务器多个并行请求(100-400),则查询执行时间会非常恶化(每个查询最多可能需要几秒钟)

细节

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

关于系统

我在数字海洋上运行节点代码4cpu / 8gb的液滴和Postgres在同一个conf(2个不同的液滴,相同的配置)

关于请求

它需要做一些游戏操作,他从DB中选择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,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,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),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');

代码

第一次查询

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',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);

三,

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',enemyId)
        .andWhere(enemyQ)
        .limit(MAX_CARDS_IN_DECK)
        .asCallback(cb)
],callbackFn);

第四个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',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',enemyCharacterUniqueId)
        .whereNotNull('slot_num')
        .limit(runeslotsMax)
        .asCallback(cb)
],callbackFn);

EXPLAIN(分析)

只有索引扫描和< 1ms规划和执行时间.如果需要可以发布(没有发布以节省空间) 时间本身 (总数为请求数,最小/最大/平均/中位数为响应时间)
> 4并发请求:{“total”:300,“avg”:1.81,“median”:2,“min”:1,“max”:6}
> 400个并发请求:

> {“total”:300,“avg”:209.57666666666665,“median”:176,“min”:9,“max”:1683} – 首先选择
> {“total”:300,“avg”:2105.9,“median”:2005,“min”:1563,“max”:4074} – 最后选择

我试图将执行超过100ms的缓慢查询放入日志中 – 没有.还试图将连接池大小增加到并行请求的数量 – 也没有.

解决方法

我可以看到三个潜在的问题:

> 400个并发请求实际上是相当多的,你的机器规格是没有什么可以兴奋的.也许这更与我的MSsql背景有关,但我想象这是一个可能需要加强硬件的情况.
>两台服务器之间的通信应该很快,但可能会占用您所看到的一些延迟.一个强大的服务器可能是一个更好的解决方案.
>我假设你有合理的数据量(400个并发连接应该有很多存储).也许发布一些实际生成的sql可能是有用的.很多都取决于sql Knex的出现,并且可能会有可用的优化.可以想到索引,但是肯定会看到sql.

您的测试似乎不包括客户端的网络延迟,因此这可能是您尚未考虑的其他问题.

javascript – Knex与PostgreSQL选择查询在多个并行请求上性能退化极差的更多相关文章

  1. AVPlayer不会在iOS9中播放来自网址的视频

    我试图在UIView中嵌入一个AVPlayer并从网址播放mp4视频文件.问题是我只收到一个黑色的空白视图(见截图)在以前的iOS版本中,它对我有用,但自从升级到iOS9后我遇到了这个问题.我的.h文件如下所示:而在我的实现文件中,我有以下内容:我错过了什么吗?

  2. ios – 在Swift Playground中使用AVPlayer播放视频?

    我在使用AVPlayer在快速游乐场内播放视频时遇到问题.这是我的代码.有什么建议?代码完全没有任何作用.我的期望是’v’变量应该显示视频.当我将avplayerlayer连接到故事板的视图时,它似乎在操场外工作.解决方法如果将它放入操场,以下情况应该有效.确保换出路径:

  3. 视频 – MPMoviePlayerController在完成播放后不会自动关闭电影(ios 6)

    我可能没有写出我的标题很好,也许更正确的说,我的NSNotification不会在播放后解散我的电影的观点.我发现其他人有这个问题,但没有解决方案,似乎这可能是iOS6的问题,这是我正在运行.播放视频后,您需要按“完成”才能关闭,但是我希望自动关闭,因为一旦我将其整理出来,我将使用MPMovieControlStyleNone.这是我的代码与未使用的部分被剥离:`解决方法也有这个问题要修复movi

  4. ios – 如何存档和取消归档Swift中的自定义对象?或者如何将自定义对象保存到Swift中的NSUserDefaults?

    我有一个班我想要序列化并保存到用户默认值.首先我不知道如何正确编写编码器和解码器.所以对于init我写了两个方法.当我尝试执行这个代码:应用程式崩溃,我收到这则讯息:我做错了什么?

  5. ios – 如何检查AVPlayer的状态?

    我以为我可以通过属性“rate”来检查AVPlayer的状态.这就是我创建一个播放器实例的方法:稍后我会做这样的事情这是我发现的:>在模拟器中,如果播放器没有运行,我得到“0.0”,如果它正在运行,我得到“1.0”.>如果我启动播放器但中断互联网连接,它会将值从1更改为0.>但是,在我的iPhone上,即使进入飞行模式,该属性仍保持值1?您是否知道为什么会发生这种情况以及如何检查流情况呢?

  6. swift之反初始化

    反初始化在一个类的实例被释放之前,反初始化函数被立即调用。用关键字deinit来标示反初始化函数,类似于初始化函数用init来标示。不允许主动调用自己的反初始化函数。vendCoins方法在bank分发硬币之前检查是否有足够的硬币。这通过player的coinsInPurse属性来体现:每个Player实例都由一个指定数目硬币组成的启动额度初始化,这些硬币在bank初始化的过程中得到。在这发生前一步,其反初始化函数被自动调用,其硬币被返回到bank。

  7. [翻译]Swift编程语言——析构

    使用deinit关键字定义析构方法,和使用init定义构造方法类似。析构方法只对类类型有效。析构如何工作当实例不再被需要,Swift会自动释放它们,来释放资源。析构方便不需要带任何参数也不需要写圆括号了:析构方法会被自动调用,在实例被释放之前。不允许手工调用析构方法。超类的析构方法总会被调用,尽管子类没有提供它自己的析构方法。在这些发生前,这个实例的析构方法被调用,实例中的硬币被返还给了银行。

  8. 用Swift做个游戏Lecture02 —— Player的诞生

    点击运行程序,Player出现在场景之中。将该方法添加至update方法中的最下面。意味着每隔33.3毫秒左右就要更新一次Player的位置。因此,添加一个方法到GameScene类中,用于每次用户点击屏幕时调用,作用是让Player获得向上的速度!

  9. 用Swift做个游戏Lecture10 —— 优化游戏(终结篇)

    FlappyBird整个项目临近尾声,要做的只是对游戏体验的优化,本文首先解决两个,分别是:实现Player静态时的动画,修改早前掉落时直上直下的问题。Player撞击障碍物时,给出一个shake摇晃动画。现在请将该方法添加到switchToMainMenu()以及switchToTutorial()方法中的最后,点击运行,看看Player是否挥动翅膀了。那么什么时候去真正更新Player的状态呢?Shake动画先前说到Player撞击障碍物后要有一个摇晃的动画以及闪烁的小锅,那样显得更有真实感不是吗,这

  10. The Swift Programming Language学习笔记十六——析构过程

    析构过程析构过程原理析构器实践析构过程析构器只适用于类类型,当一个类的实例被释放之前,析构器会被立即调用。析构器用关键字deinit来标示,类似于构造器要用init来标示。在类的定义中,每个类最多只能有一个析构器,而且析构器不带任何参数,也不能加上()。析构器是在实例释放发生前被自动调用。即使子类没有提供自己的析构器,父类的析构器也同样会被调用。析构器实践在上面的代码中,使用可选类型可以追踪玩家当前是否在游戏中。

随机推荐

  1. js中‘!.’是什么意思

  2. Vue如何指定不编译的文件夹和favicon.ico

    这篇文章主要介绍了Vue如何指定不编译的文件夹和favicon.ico,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  3. 基于JavaScript编写一个图片转PDF转换器

    本文为大家介绍了一个简单的 JavaScript 项目,可以将图片转换为 PDF 文件。你可以从本地选择任何一张图片,只需点击一下即可将其转换为 PDF 文件,感兴趣的可以动手尝试一下

  4. jquery点赞功能实现代码 点个赞吧!

    点赞功能很多地方都会出现,如何实现爱心点赞功能,这篇文章主要为大家详细介绍了jquery点赞功能实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  5. AngularJs上传前预览图片的实例代码

    使用AngularJs进行开发,在项目中,经常会遇到上传图片后,需在一旁预览图片内容,怎么实现这样的功能呢?今天小编给大家分享AugularJs上传前预览图片的实现代码,需要的朋友参考下吧

  6. JavaScript面向对象编程入门教程

    这篇文章主要介绍了JavaScript面向对象编程的相关概念,例如类、对象、属性、方法等面向对象的术语,并以实例讲解各种术语的使用,非常好的一篇面向对象入门教程,其它语言也可以参考哦

  7. jQuery中的通配符选择器使用总结

    通配符在控制input标签时相当好用,这里简单进行了jQuery中的通配符选择器使用总结,需要的朋友可以参考下

  8. javascript 动态调整图片尺寸实现代码

    在自己的网站上更新文章时一个比较常见的问题是:文章插图太宽,使整个网页都变形了。如果对每个插图都先进行缩放再插入的话,太麻烦了。

  9. jquery ajaxfileupload异步上传插件

    这篇文章主要为大家详细介绍了jquery ajaxfileupload异步上传插件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  10. React学习之受控组件与数据共享实例分析

    这篇文章主要介绍了React学习之受控组件与数据共享,结合实例形式分析了React受控组件与组件间数据共享相关原理与使用技巧,需要的朋友可以参考下

返回
顶部