面壓測(cè)節(jié)點(diǎn)創(chuàng)建與查詢性能)
Gatsby Markdown Table Benchmark 深度解析用海量 Markdown 表格頁(yè)面壓測(cè)節(jié)點(diǎn)創(chuàng)建與查詢性能【免費(fèi)下載鏈接】gatsbyReact-based framework with performance, scalability, and security built in.項(xiàng)目地址: https://gitcode.com/gh_mirrors/ga/gatsby本篇文章圍繞 Gatsby 官方倉(cāng)庫(kù)benchmarks/markdown_table基準(zhǔn)測(cè)試站點(diǎn)展開完整梳理它的設(shè)計(jì)初衷、目錄結(jié)構(gòu)、數(shù)據(jù)生成與頁(yè)面創(chuàng)建流程、運(yùn)行方式與調(diào)參方法并結(jié)合倉(cāng)庫(kù)源碼剖析其作為性能基準(zhǔn)測(cè)試的局限性。讀完本文你將掌握如何復(fù)現(xiàn)該基準(zhǔn)、如何通過(guò)NUM_PAGES與MAX_NUM_ROWS控制壓測(cè)規(guī)模以及為什么官方推薦改用markdown-slug與markdown-id這兩個(gè)更貼近真實(shí)場(chǎng)景的衍生基準(zhǔn)。一、基準(zhǔn)測(cè)試的定位與設(shè)計(jì)初衷在 benchmarks/markdown_table/README.md 中官方明確說(shuō)明這個(gè)基準(zhǔn)測(cè)試最初是為了**基準(zhǔn)測(cè)試 Gatsby 的節(jié)點(diǎn)創(chuàng)建node creation與查詢運(yùn)行query running**而創(chuàng)建的手段是創(chuàng)建大量由 Markdown 渲染出來(lái)的頁(yè)面。簡(jiǎn)單來(lái)說(shuō)它試圖回答這樣一個(gè)問(wèn)題當(dāng)站點(diǎn)包含數(shù)千個(gè)由 Markdown 內(nèi)容生成的頁(yè)面時(shí)Gatsby 的數(shù)據(jù)層node 創(chuàng)建、GraphQL 查詢與頁(yè)面構(gòu)建HTML 生成需要多長(zhǎng)時(shí)間、消耗多少內(nèi)存。不過(guò)README 同時(shí)也坦誠(chéng)地指出了該基準(zhǔn)存在的三個(gè)設(shè)計(jì)問(wèn)題這正是理解它價(jià)值的關(guān)鍵頁(yè)面生成發(fā)生在測(cè)試過(guò)程之中數(shù)據(jù)是構(gòu)建時(shí)在內(nèi)存里生成的sourceNodes中直接createNode這給測(cè)量本身增加了額外開銷并且會(huì)預(yù)熱warm up節(jié)點(diǎn)與內(nèi)存導(dǎo)致計(jì)時(shí)結(jié)果偏離真實(shí)場(chǎng)景重度依賴 Markdown 插件頁(yè)面內(nèi)容中包含大量 Markdown 表格表格解析由gatsby-transformer-remark處理占用了大量處理時(shí)間使基準(zhǔn)結(jié)果主要反映的是插件開銷而非 Gatsby 核心能力索引鍵選擇README 指出該基準(zhǔn)是按slug而非id進(jìn)行索引的——需要說(shuō)明的是從當(dāng)前倉(cāng)庫(kù)源碼看gatsby-node.js 中createPage的 context 實(shí)際傳入的是id: step.toString()blank.js 的查詢也以fakeMarkdown(id: { eq: $id })為鍵README 的歷史描述與當(dāng)前代碼存在出入這一點(diǎn)恰好也是官方認(rèn)為該基準(zhǔn)不夠有代表性的原因之一。正因?yàn)橐陨蠁?wèn)題README 明確寫道保留此基準(zhǔn)主要是出于歷史原因historical reasons并建議在需要更接近真實(shí)場(chǎng)景的基準(zhǔn)時(shí)選擇父級(jí)目錄中的markdown-slug與markdown-id兩個(gè)站點(diǎn)——它們做同樣的事但不包含表格且分別按slug和id索引。二、站點(diǎn)結(jié)構(gòu)與核心源碼解析benchmarks/markdown_table是一個(gè)麻雀雖小、五臟俱全的完整 Gatsby 站點(diǎn)其文件布局如下benchmarks/markdown_table/ ├── gatsby-config.js # 插件配置 ├── gatsby-node.js # 節(jié)點(diǎn)創(chuàng)建與頁(yè)面創(chuàng)建 ├── page-template.js # Markdown 內(nèi)容生成模板 ├── package.json # 腳本與依賴 ├── scripts/ │ └──>const NUM_PAGES parseInt(process.env.NUM_PAGES || 5000, 10) const template require(./page-template) exports.sourceNodes ({ actions: { createNode } }) { // Create markdown nodes for (let step 0; step NUM_PAGES; step) { createNode({ id: step.toString(), parent: null, children: [], internal: { type: FakeMarkdown, mediaType: text/markdown, content: template(step), contentDigest: step.toString(), }, }) } }注意這里讀取的默認(rèn)值5000不設(shè)置環(huán)境變量時(shí)直接執(zhí)行g(shù)atsby build會(huì)構(gòu)建 5000 個(gè)頁(yè)面詳見下文運(yùn)行方式。頁(yè)面內(nèi)容由 page-template.js 動(dòng)態(tài)生成它使用了兩個(gè)依賴faker生成隨機(jī)化的英文單詞、句子、段落gray-matter將title、slug序列化進(jìn) Markdown frontmatter。每個(gè)頁(yè)面內(nèi)容的 Markdown 結(jié)構(gòu)固定為frontmatter隨機(jī)標(biāo)題 隨機(jī) slug→## Page #index標(biāo)題 →### API小節(jié)中的一張隨機(jī)行數(shù)的表格 →### More Detail小節(jié)中的隨機(jī)段落。表格生成邏輯如下const MAX_NUM_ROWS parseInt(process.env.MAX_NUM_ROWS || 25, 10) module.exports index ${matter.stringify(, { title: faker.lorem.sentence(), slug: /${faker.helpers.slugify(faker.lorem.sentence())}, }).trim()} ## Page #${index} ### API |Name|Description|Required| |:--:|-----------|--------| ${new Array(faker.random.number(MAX_NUM_ROWS)) .fill(undefined) .map(() |${faker.lorem.word()}|${faker.lorem.sentence()}|${faker.random.boolean()}| .trim() ) .join(\n)} ### More Detail ${faker.lorem.paragraphs()} 由代碼可見MAX_NUM_ROWS默認(rèn)值為25表格實(shí)際行數(shù)在0 ~ MAX_NUM_ROWS之間隨機(jī)每行由隨機(jī)單詞Name、隨機(jī)句子Description和隨機(jī)布爾值Required組成。這樣的內(nèi)容讓gatsby-transformer-remark必須對(duì)每張表格進(jìn)行真實(shí)解析從而把 Markdown 表格解析的開銷納入基準(zhǔn)測(cè)量。2.2 頁(yè)面創(chuàng)建createPages 與模板查詢createPages同樣循環(huán)NUM_PAGES次為每個(gè)節(jié)點(diǎn)創(chuàng)建一條路徑為/path/${step}/的頁(yè)面組件指向 src/templates/blank.js并把id放入 contextexports.createPages ({ actions: { createPage } }) { let step for (step 0; step NUM_PAGES; step) { createPage({ path: /path/${step}/, component: require.resolve(./src/templates/blank.js), context: { id: step.toString(), }, }) } }頁(yè)面模板blank.js通過(guò) GraphQL 按id精確查詢對(duì)應(yīng)的fakeMarkdown節(jié)點(diǎn)并取出經(jīng)gatsby-transformer-remark轉(zhuǎn)換后的 HTML用dangerouslySetInnerHTML渲染export default ({ data }) { const markdown data.fakeMarkdown.childMarkdownRemark return ( div h1{markdown.frontmatter.title}/h1 div dangerouslySetInnerHTML{{ __html: markdown.html }} / /div ) } export const query graphql query testing($id: String!) { fakeMarkdown(id: { eq: $id }) { childMarkdownRemark { frontmatter { title } html } } } 也就是說(shuō)整個(gè)基準(zhǔn)的負(fù)載鏈條是內(nèi)存生成 N 個(gè) FakeMarkdown 節(jié)點(diǎn) → transformer-remark 將每篇 Markdown含表格解析成 HTML → 為每個(gè)節(jié)點(diǎn)創(chuàng)建頁(yè)面 → 每個(gè)頁(yè)面執(zhí)行一次按 id 的 GraphQL 查詢。任何一環(huán)的性能問(wèn)題節(jié)點(diǎn)創(chuàng)建、表格解析、查詢執(zhí)行、頁(yè)面生成都會(huì)反映到最終耗時(shí)上。首頁(yè) src/pages/index.js 只是一個(gè)輸出 Hello world! 的靜態(tài)頁(yè)面用于占位不參與壓測(cè)邏輯。2.3 插件配置與依賴清單gatsby-config.js 只啟用兩個(gè)插件module.exports { plugins: [gatsby-plugin-benchmark-reporting, gatsby-transformer-remark], }gatsby-transformer-remark把text/markdown類型的節(jié)點(diǎn)解析為 MarkdownRemark 節(jié)點(diǎn)產(chǎn)出html與 frontmatter 字段——它正是 README 中所說(shuō)表格處理耗時(shí)的承擔(dān)者gatsby-plugin-benchmark-reporting基準(zhǔn)數(shù)據(jù)的采集與上報(bào)插件詳見下文如何觀測(cè)基準(zhǔn)結(jié)果。package.json 中可以看到該基準(zhǔn)所依賴的環(huán)境gatsby ^2.19.5、gatsby-transformer-remark ^2.6.48、faker ^4.1.0、gray-matter ^4.0.2、react ^16.12.0、react-dom ^16.12.0以及用于運(yùn)行scripts/data-update.ts當(dāng)前內(nèi)容為空占位 noop見>NUM_PAGES25000 MAX_NUM_ROWS100 gatsby build即構(gòu)建一個(gè)包含 2.5 萬(wàn)頁(yè)、每頁(yè)表格最多 100 行的站點(diǎn)。調(diào)大這兩個(gè)參數(shù)可以放大節(jié)點(diǎn)創(chuàng)建、表格解析與查詢運(yùn)行的開銷形成更強(qiáng)的壓力調(diào)小則可以快速驗(yàn)證鏈路是否跑通。3.2 三種啟動(dòng)方式README 提供了兩種執(zhí)行路徑其一是先安裝依賴再構(gòu)建# 首次運(yùn)行只需安裝一次依賴 yarn yarn build其二是使用封裝好的 bench 腳本yarn bench查看 package.json 中的 scripts 定義yarn bench實(shí)際執(zhí)行的是set -x; gatsby clean; NUM_PAGES${NUM_PAGES:-2000} gatsby build這里有兩點(diǎn)值得注意gatsby clean每次 bench 都會(huì)先清空.cache與public目錄確保從零開始構(gòu)建避免增量構(gòu)建緩存干擾測(cè)量結(jié)果默認(rèn)值差異gatsby build直接運(yùn)行時(shí)NUM_PAGES未設(shè)置則取gatsby-node.js中的默認(rèn)值5000而yarn bench腳本自身又兜底了一個(gè)默認(rèn)值2000即未設(shè)置NUM_PAGES時(shí)以 2000 構(gòu)建。也就是說(shuō)README 所說(shuō)的默認(rèn) 5k 頁(yè)面針對(duì)的是直接gatsby build的情形而yarn bench的默認(rèn)規(guī)模是 2000 頁(yè)兩者并不沖突但容易混淆建議始終顯式傳入NUM_PAGES以保證結(jié)果可復(fù)現(xiàn)。其余腳本clean、develop、serve與普通 Gatsby 站點(diǎn)一致分別用于清緩存、開發(fā)模式和預(yù)覽構(gòu)建產(chǎn)物。3.3 大頁(yè)面規(guī)模下的內(nèi)存提示在姊妹基準(zhǔn) markdown_slug/README.md 與 markdown_id/README.md 中官方給出了一個(gè)大規(guī)模構(gòu)建時(shí)的實(shí)用建議當(dāng)頁(yè)面數(shù)量很大時(shí)可以通過(guò)node --max_old_space_size提高 Node.js 老生代內(nèi)存上限例如node --max_old_space_size2000 node_modules/.bin/gatsby build頁(yè)面量級(jí)越大節(jié)點(diǎn)圖、查詢結(jié)果與 HTML 產(chǎn)物占用的內(nèi)存越多這一參數(shù)對(duì)markdown_table這種默認(rèn) 5000 頁(yè)、可擴(kuò)展到 25000 頁(yè)的基準(zhǔn)同樣適用。四、如何觀測(cè)基準(zhǔn)結(jié)果benchmark-reporting 插件的工作原理基準(zhǔn)跑完后結(jié)果從哪來(lái)答案就在gatsby-plugin-benchmark-reporting。它的核心實(shí)現(xiàn)位于 packages/gatsby-plugin-benchmark-reporting/src/gatsby-node.js通過(guò)掛載onPreInit、onPreBootstrap、onPreBuild、onPostBuild四個(gè)生命周期鉤子來(lái)采集數(shù)據(jù)。從源碼可以看到它主要記錄以下指標(biāo)時(shí)間戳序列bootstrapTime插件加載時(shí)刻→benchmarkStart→preInit→preBootstrap→preBuild→postBuild→benchmarkEnd完整覆蓋一次構(gòu)建的各個(gè)階段用于分析時(shí)間花在了哪個(gè)環(huán)節(jié)內(nèi)存快照process.memoryUsage()的rss、heapTotal、heapUsed、external四項(xiàng)環(huán)境與版本Node.js 版本、Gatsby 版本、Gatsby CLI 版本、sharp與webpack版本、當(dāng)前 git commit 哈希與提交時(shí)間、CI 標(biāo)識(shí)產(chǎn)物統(tǒng)計(jì)public目錄下 JS 總大小以及構(gòu)建產(chǎn)物中 jpg/png/gif 等其他資源的數(shù)量構(gòu)建規(guī)模counts.pages直接讀取process.env.NUM_PAGES與本文基準(zhǔn)的壓測(cè)規(guī)模對(duì)應(yīng)。數(shù)據(jù)去向有兩種模式若設(shè)置了BENCHMARK_REPORTING_URL環(huán)境變量插件會(huì)把 JSON 數(shù)據(jù) POST 到該地址并攜帶BENCHMARK_REPORTING_SECRET作為鑒權(quán)頭若未設(shè)置或設(shè)為cli插件會(huì)在終端打印Gathered data: json后直接結(jié)束——本地復(fù)現(xiàn)基準(zhǔn)時(shí)使用這種模式即可拿到全部測(cè)量結(jié)果。從源碼還可以推斷gatsby clean后全新構(gòu)建會(huì)得到initial類型的構(gòu)建而BENCHMARK_BUILD_TYPE_NEXT可指定后續(xù)構(gòu)建類型如DATA_UPDATE這一機(jī)制服務(wù)于持續(xù)基準(zhǔn)測(cè)試CI場(chǎng)景。五、為什么要換一個(gè)基準(zhǔn)與 markdown_slug / markdown_id 的對(duì)比README 反復(fù)強(qiáng)調(diào)該基準(zhǔn)的歷史屬性并指向兩個(gè)替代者理解其中的對(duì)比有助于你選擇正確的壓測(cè)方案維度markdown_tablemarkdown_slug / markdown_id內(nèi)容形態(tài)包含大量隨機(jī) Markdown 表格普通 Markdown 頁(yè)面無(wú)表格數(shù)據(jù)來(lái)源構(gòu)建時(shí)在sourceNodes中內(nèi)存生成FakeMarkdown節(jié)點(diǎn)構(gòu)建前由腳本預(yù)生成.md文件存放于根目錄markdown-pages經(jīng)gatsby-source-filesystem讀取查詢鍵按idREADME 歷史描述為slug一個(gè)按slug索引一個(gè)按id索引站點(diǎn)基礎(chǔ)自定義最小站點(diǎn)基于gatsby-starter-blog定位歷史遺留供對(duì)照官方推薦的代表性基準(zhǔn)從 markdown_slug/gatsby-node.js 與 markdown_id/gatsby-node.js 的實(shí)現(xiàn)差異可以直觀看到兩者的關(guān)鍵區(qū)別markdown_slug在createPages中僅把slug放進(jìn) context而markdown_id額外傳入了id。兩個(gè)基準(zhǔn)的 README 明確指出按slug查詢?cè)趯懽鳟?dāng)時(shí)慢于按id查詢——這正是把二者分開建站、分別測(cè)量的原因也是markdown_table被認(rèn)為按 slug 索引不夠有代表性的背景。對(duì)比之下markdown_table的劣勢(shì)一目了然它把內(nèi)容生成和內(nèi)容處理混在一次構(gòu)建中計(jì)時(shí)被內(nèi)容生成開銷污染表格解析又讓 transformer 插件成為瓶頸主導(dǎo)項(xiàng)掩蓋了 Gatsby 核心節(jié)點(diǎn)/查詢管線本身的真實(shí)表現(xiàn)。六、總結(jié)與使用建議benchmarks/markdown_table是一個(gè)值得收藏的歷史樣本它演示了在 Gatsby 中純內(nèi)存生成海量 Markdown 節(jié)點(diǎn) → transformer 解析 → 批量建頁(yè) → 按鍵查詢的完整壓測(cè)鏈路其NUM_PAGES/MAX_NUM_ROWS雙參數(shù)調(diào)壓模型、gatsby clean冷啟動(dòng)測(cè)量、以及配套的gatsby-plugin-benchmark-reporting指標(biāo)采集方案在今天仍有借鑒意義。但如果你需要的是能代表真實(shí)站點(diǎn)、結(jié)果可解釋的 Markdown 類基準(zhǔn)請(qǐng)按 README 的建議直接選擇想測(cè)按slug索引的查詢性能 → 參考 markdown_slug/README.md想測(cè)按id索引的查詢性能 → 參考 markdown_id/README.md想對(duì)比兩種索引鍵的性能差異 → 兩個(gè)站點(diǎn)都跑一遍對(duì)比同一NUM_PAGES下的構(gòu)建耗時(shí)與內(nèi)存數(shù)據(jù)。無(wú)論選擇哪一個(gè)都建議固定NUM_PAGES、先gatsby clean、并在本地關(guān)閉BENCHMARK_REPORTING_URL或設(shè)為cli來(lái)直接讀取終端輸出的 JSON 指標(biāo)保證每次測(cè)量可比?!久赓M(fèi)下載鏈接】gatsbyReact-based framework with performance, scalability, and security built in.項(xiàng)目地址: https://gitcode.com/gh_mirrors/ga/gatsby創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考