Redian新闻
>
如何做到高性能渲染十万条数据不卡顿?

如何做到高性能渲染十万条数据不卡顿?

公众号新闻

作者 / 琉易

编辑 / 阿晗


在日常工作中,有时会遇到一次性往页面中插入大量数据的场景,在数栈的离线开发(以下简称离线)产品中,就有类似的场景。本文将通过分享一个实际场景中的前端开发思路,介绍当遇到大量数据时,如何实现高效的数据渲染,以达到提升页面性能和用户体验的目的。


渲染大数据量时遇到的问题


在离线的数据开发模块,用户可以在 SQL 编辑器中编写 SQL,再通过整段运行/分段运行来执行 SQL。在点击整段运行后,从运行成功日志打印后到展示结果的过程中,有一段时间页面会很卡顿,主要表现为编辑器编写卡顿。


我们是在解决 SQL 最大运行行数问题时,发现了上述需要进行性能优化的场景。


先来梳理下当前代码的设计逻辑:


· 前端将选中的 SQL 传递给服务端,服务端返回一个调度运行的 jobId

· 前端接着以该 jobId 轮询服务端,查询任务的执行状态

· 当轮询到任务已完成时,选中的 SQL 中如果有查询语句,服务端则会按 select 语句的顺序返回一个 sqlId 的数组集合

· 前端基于n个 sqlId 的集合,并发 n个 selectData 的请求

· 所有的 selectData 请求完成后渲染数据


为了保证结果最终的展示顺序和 select 语句顺序一致,我们为单纯的 sqlIdList 循环方法加上了 Promise.allsettled 的方法,使得n个 selectData 的请求顺序和 select 语句顺序一致。


由上述逻辑可以看出,问题可能出现在如果选中的 SQL 中有大量 select 语句的话,会在「整段运行」完成后大批量请求 selectData 接口,再等待所有 selectData 请求完成后,集中进行渲染。此时,就会出现一次性往页面中插入大量数据的场景,导致卡顿。那么,我们怎么解决上述问题呢?


解决思路


可以看出,上述逻辑主要有两个问题:大批量请求 selectData 接口和集中性数据渲染。我们通过如下所示的解决思路去处理这些问题。


任务分组

依旧通过 Promise.allsettled 拿到所有 selectData 接口返回的结果,将原先集中渲染看作是一个大任务,我们将任务拆分成单个的 selectData 结果渲染任务。再根据实际情况,对单个任务进行分组,比如两个一组,渲染完一组再渲染下一组。


拆分完任务,就涉及到了任务的优先级问题,优先级决定了哪个任务先执行。这里采用最原始的“抢占式轮转”,按 sqlIdList 的顺序保留编辑器中的 SQL 顺序。

Promise.allSettled(promiseList).then((results = []) => {    const renderOnce = 2; // 每组渲染的结果 tab 数量    const loop = (idx) => {        if (promiseList.length <= idx) return;        results.slice(idx, idx + renderOnce).forEach((item, idx) => {            if (item.status === 'fulfilled') {                handleResultData(item?.value || {}, sqlIdList[idx]?.sqlId);            } else {                console.error(                    'selectExecResultDataList Promise.allSettled rejected',                    item.reason                );            }        });        setTimeout(() => {            loop(idx + renderOnce);        }, 100);    };    loop(0);});


请求分组 + 任务分组

问题中的大批量请求 selectData 接口,也是一个突破点。我们可以将请求进行分组,每次以固定数量的 sqlId 去请求 selectData 接口,比如每组请求 6 个 sqlId 的结果,当前组的请求全部结束后再进行渲染。为了保证效果最优,这里也引入任务分组的思路。

const requestOnce = 6; // 每组请求的数量// 将一维数组转换成二维数组const sqlIdList2D = convertTo2DArray(sqlIdList, requestOnce);const idx2D = 0; // sqlIdList2D 的索引
const requestLoop = (index) => { if (!sqlIdList2D[index]) return; const promiseList = sqlIdList2D[index].map((item) => selectExecResultData(item?.sqlId) ); Promise.allSettled(promiseList) .then((results = []) => { const renderOnce = 2; // 每组渲染的结果 tab 数量
const loop = (idx) => { if (promiseList.length <= idx) return; results.slice(idx, idx + renderOnce).forEach((item, idx) => { if (item.status === 'fulfilled') { handleResultData(item?.value || {}, sqlIdList[idx]?.sqlId); } else { console.error( 'selectExecResultDataList Promise.allSettled rejected', item.reason ); } }); setTimeout(() => { loop(idx + renderOnce); }, 100); }; loop(0); }) .finally(() => { requestLoop(index + 1); });};requestLoop(idx2D);


请求分组

上一种方案的代码相对来说又些难以理解,属于上午写,下午忘的逻辑,注释也不好写,不利于维护。基于实际情况,我们尝试下仅对请求作分组处理,看看效果。

const requestOnce = 3; // 每组请求的数量// 将一维数组转换成二维数组const sqlIdList2D = convertTo2DArray(sqlIdList, requestOnce);const idx2D = 0; // sqlIdList2D 的索引
const requestLoop = (index) => { if (!sqlIdList2D[index]) return; const promiseList = sqlIdList2D[index].map((item) => selectExecResultData(item?.sqlId) ); Promise.allSettled(promiseList) .then((results = []) => { results.forEach((item, idx) => { if (item.status === 'fulfilled') { handleResultData(item?.value || {}, sqlIdList[idx]?.sqlId); } else { console.error( 'selectExecResultDataList Promise.allSettled rejected', item.reason ); } }); }) .finally(() => { requestLoop(index + 1); });};requestLoop(idx2D);


解决思路解析


· 解决大数据量渲染的问题,常见方法有:时间分片、虚拟列表等

· 解决同步阻塞的问题,常见方法有:任务分解、异步等

· 如果某个任务执行时间较长的话,从优化的角度,我们通常会考虑将该任务分解成一系列的子任务


在任务分组一节,我们将 setTimeout 的时间间隔设置为 100ms,也就是我认为最快在 100ms 内能完成渲染。但假设不到 100ms 就完成了渲染,那么就需要白白等待一段时间,这是没有必要的,这时可以考虑 window.requestAnimationFrame 方法。

- setTimeout(() => {+ window.requestAnimationFrame(() => {      loop(idx + renderOnce);- }, 100);+ });


第三节的请求分组,实际上已经达到了渲染任务分组的效果。本文更多的是提供一个解决思路,上述方式也是基于对时间分片的理解实践。


在软件开发中,性能优化是一个重要的方面,但并不是唯一追求,往往还需要考虑多个因素,包括功能需求、可维护性、安全性等等。根据具体情况,综合使用多种技术和策略,找到最佳的解决方案,才是最终目的。


往期推荐



深度deepin-IDE正式亮相,号称 “真正自主研发”
Linus又发飙,什么TM的叫"GenPD"
微软不讲武德,用 “恶意弹窗” 提示用户弃用Google



这里有最新开源资讯、软件更新、技术干货等内容

点这里 ↓↓↓ 记得 关注✔ 标星⭐ 哦


微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
AI大模型训练背后,一条数据产业链正在形成如果经济统计数据不错,为什么美国人感觉这么糟糕?训练大模型之前,你要了解下向量数据库、算力集群、高性能网络技术底座吗?谈谈李玟与抑郁症的治疗孕期出轨产业链曝光,精准推送,涉黄短信不堪入目:大数据不是让你这么用的!听完这一场,你的Essay就不卡了| Essay Mini Workshop 第二场在线研讨会预告!主讲 NVIDIA RTX™ A5500 显卡加速工业 CAD 图像渲染与性能测评13 秒插入 30 万条数据,这才是批量插入正确的姿势!自动驾驶数据不用愁!港中文等发布MagicDrive:日夜、雨晴、多视角全覆盖,人、物位置随意变更0资源如何做到高级合伙人——她们怎样取得让自己都意外的成绩?【国际】惠誉回应下调美国信用评级:债务堆积如山 数据不言自明社区大学学费情况以及升学率如何?UC是否有扩招?以及具体情况如何?孩子领导力不足,家长该如何做?大学规划几年级开始?该如何做?刨根|最新研究证明“老年人吃猪油可能有益心血管健康”?科学数据不能这么解读3000多条数据里选出200条效果反而更好,MiniGPT-4被配置相同的模型超越了京东值得买|娇兰、雅诗兰黛、哈曼卡顿、水星家纺……超多大牌来这儿买尊嘟便宜!卡顿不止?专家揭示设备设置,提升你的Wi-Fi信号体验五部门联合发布!剑指货币经纪公司,三类数据不得提供,"9+68"名单出炉有的律师,为什么能做到高溢价?两个数据,一个趋势,看爱奇艺如何做到「淡季不淡」?3年个人戒烟残酷史 回头一看都是认知问题?iPhone 15开卖抢疯了!果粉爆推挤踩踏吼叫!“混战现场”曝光!新机还爆雷卡顿!这个“不卡背景”的高薪跨学科专业,终于在艺术圈火了!从数据不及格、改到2200万下载,这个爆款小游戏如何起死回生的?“产品数据不好,就是运营人员的问题”,怎么反击?气垫中的“爱马仕”!1s磨皮,上脸水润不卡粉,超长持妆!!「单张图像重建3D网格」告别卡顿,从30分钟提速到45秒!浙大、加州大学等联合发布One-2-3-45:在线Demo可试玩数据不算差华为新机:脖子卡不卡,谁疼谁知道八声甘州 时事这个“最不卡背景”的高薪专业,终于火了!经济数据不佳!赶紧释放重大利好!能否对冲周一开盘的影响?调查 I 如果经济数据不错,为什么美国人感觉这么糟糕?森林大火见闻iPhone15中国首销数据不及14,库克来了造访新开张的温哥华唐人街的华裔博物馆
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。