From 1dcd0a6de01aeabcee5e323d00d475fcbd01046d Mon Sep 17 00:00:00 2001 From: Charles Lowell Date: Tue, 7 Nov 2023 10:45:51 -0600 Subject: [PATCH] Parallelize documentation loading We were noticing very long isolate startup times for the Effection site. Upon inspection it became clear that the docs were being all loaded up front before you could even serve a single request. The following shows a performance snapshot on how long it took to load the documents. ```shellsession $ deno run -A main.ts load docs: 294.138709 www -> http://0.0.0.0:8000 ``` This changes the `loadDocs()` operation to spawn the file read and parse in parallel. In order to facilitate this, the id of the route is determined _not_ by the MDX frontmatter, but instead by the name of the file. As a result, the startup time is reduced by over 100x ```shellsession $ deno run -A main.ts load docs: 2.495749999999987 www -> http://0.0.0.0:8000 ``` --- www/deno.json | 2 +- www/deno.lock | 6 +++ www/docs/actions.mdx | 1 - www/docs/collections.mdx | 1 - www/docs/docs.ts | 81 +++++++++++++++++++++++++------------- www/docs/errors.mdx | 1 - www/docs/events.mdx | 1 - www/docs/inspector.mdx | 1 - www/docs/introduction.mdx | 1 - www/docs/operations.mdx | 1 - www/docs/processes.mdx | 1 - www/docs/resources.mdx | 1 - www/docs/scope.mdx | 1 - www/docs/spawn.mdx | 1 - www/docs/testing.mdx | 1 - www/docs/typescript.mdx | 1 - www/html/document.html.tsx | 20 +++++----- www/server.ts | 7 ++-- 18 files changed, 74 insertions(+), 55 deletions(-) diff --git a/www/deno.json b/www/deno.json index 68799f86e..25280f76f 100644 --- a/www/deno.json +++ b/www/deno.json @@ -19,7 +19,7 @@ "imports": { "effection": "../mod.ts", "freejack/": "./lib/", - "hastx/": "https://deno.land/x/hastx@0.0.5/", + "hastx/": "https://deno.land/x/hastx@0.0.8/", "hastx/jsx-runtime": "https://deno.land/x/hastx@v0.0.5/jsx-runtime.ts" } } diff --git a/www/deno.lock b/www/deno.lock index ddd2a8dd4..674bacf94 100644 --- a/www/deno.lock +++ b/www/deno.lock @@ -1651,6 +1651,12 @@ "https://deno.land/std@0.203.0/path/extname.ts": "62c4b376300795342fe1e4746c0de518b4dc9c4b0b4617bfee62a2973a9555cf", "https://deno.land/std@0.203.0/path/join.ts": "31c5419f23d91655b08ec7aec403f4e4cd1a63d39e28f6e42642ea207c2734f8", "https://deno.land/std@0.203.0/streams/read_all.ts": "3b20a50af87d1bfebefa9c2dbda49e2b214d8ab0382ffdcc8ce858af80a912be", + "https://deno.land/std@0.205.0/path/_common/assert_path.ts": "061e4d093d4ba5aebceb2c4da3318bfe3289e868570e9d3a8e327d91c2958946", + "https://deno.land/std@0.205.0/path/_common/basename.ts": "0d978ff818f339cd3b1d09dc914881f4d15617432ae519c1b8fdc09ff8d3789a", + "https://deno.land/std@0.205.0/path/_common/constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", + "https://deno.land/std@0.205.0/path/_common/strip_trailing_separators.ts": "7ffc7c287e97bdeeee31b155828686967f222cd73f9e5780bfe7dfb1b58c6c65", + "https://deno.land/std@0.205.0/path/posix/_util.ts": "ecf49560fedd7dd376c6156cc5565cad97c1abe9824f4417adebc7acc36c93e5", + "https://deno.land/std@0.205.0/path/posix/basename.ts": "a630aeb8fd8e27356b1823b9dedd505e30085015407caa3396332752f6b8406a", "https://deno.land/x/continuation@0.1.5/mod.ts": "690def2735046367b3e1b4bc6e51b5912f2ed09c41c7df7a55c060f23720ad33", "https://deno.land/x/hastx@v0.0.5/jsx-runtime.ts": "5e1db59ceea4834b8d248a018a5adfc73a09922d8dcdaa3a3bb228c884da0f6c" } diff --git a/www/docs/actions.mdx b/www/docs/actions.mdx index b8a92c2c6..3be8ef90e 100644 --- a/www/docs/actions.mdx +++ b/www/docs/actions.mdx @@ -1,5 +1,4 @@ --- -id: actions title: Actions and Suspensions --- diff --git a/www/docs/collections.mdx b/www/docs/collections.mdx index b6ac0d06b..c86c905af 100644 --- a/www/docs/collections.mdx +++ b/www/docs/collections.mdx @@ -1,5 +1,4 @@ --- -id: collections title: Streams and Subscriptions --- diff --git a/www/docs/docs.ts b/www/docs/docs.ts index d2c9607b7..b0938550e 100644 --- a/www/docs/docs.ts +++ b/www/docs/docs.ts @@ -1,6 +1,8 @@ -import { createContext, expect, type Operation } from "effection"; +import { createContext, all, call, spawn, type Operation, type Task } from "effection"; import structure from "./structure.json" assert { type: "json" }; +import { basename } from "https://deno.land/std@0.205.0/path/posix/basename.ts"; + import remarkFrontmatter from "npm:remark-frontmatter@4.0.1"; import remarkMdxFrontmatter from "npm:remark-mdx-frontmatter@3.0.0"; import remarkGfm from "npm:remark-gfm@3.0.1"; @@ -22,8 +24,8 @@ export interface DocModule { } export interface Docs { - getTopics(): Topic[]; - getDoc(id: string): Doc | undefined; + getTopics(): Operation; + getDoc(id?: string): Operation; } export interface Topic { @@ -36,24 +38,31 @@ export interface Doc { title: string; MDXContent: () => JSX.Element; filename: string; - previous?: Doc; - next?: Doc; + nextId?: string; + previousId?: string; } export function* loadDocs(): Operation { - let topics: Topic[] = []; - let docs: Record = {}; - let current: Doc | undefined; + let topics = new Map(); + + let loaders = new Map>(); + + let entries = Object.entries(structure); - for (let [topicName, files] of Object.entries(structure)) { - let topic = { name: topicName, items: [] } as Topic; + let files = entries.flatMap(([topicName, files]) => { + return files.map(filename => ({ topicName, filename, id: basename(filename, ".mdx") })); + }) - topics.push(topic); + for (let i = 0; i < files.length; i++ ) { + let file = files[i]; + let nextId = files[i + 1]?.id; + let previousId = files[i - 1]?.id; + let { topicName, filename, id } = file; + let location = new URL(filename, import.meta.url); - for (let filename of files) { - let location = new URL(filename, import.meta.url); - let source = yield* expect(Deno.readTextFile(location)); - let mod = yield* expect(evaluate(source, { + loaders.set(id, yield* spawn(function*() { + let source = yield* call(Deno.readTextFile(location)); + let mod = yield* call(evaluate(source, { jsx, jsxs, jsxDEV: jsx, @@ -68,27 +77,43 @@ export function* loadDocs(): Operation { ], })); - let { id, title } = mod.frontmatter as { id: string; title: string }; + let { title } = mod.frontmatter as { id: string; title: string }; - let previous = current; - let doc = current = { + let doc: Doc = { id, + nextId, + previousId, title, filename, MDXContent: () => mod.default({}), - previous, } as Doc; - topic.items.push(doc); - docs[id] = doc; - if (previous) { - previous.next = doc; + let topic = topics.get(topicName); + if (!topic) { + topic = { name: topicName, items: [] }; + topics.set(topicName, topic); } - } + + topic.items.push(doc); + + return doc; + })); + } - return { - getTopics: () => topics, - getDoc: (id) => docs[id], - }; + + return yield* Docs.set({ + *getTopics() { + yield* all([...loaders.values()]); + return [...topics.values()]; + }, + *getDoc(id) { + if (id) { + let task = loaders.get(id); + if (task) { + return yield* task; + } + } + }, + }); } diff --git a/www/docs/errors.mdx b/www/docs/errors.mdx index 8658d80f1..87d84607a 100644 --- a/www/docs/errors.mdx +++ b/www/docs/errors.mdx @@ -1,5 +1,4 @@ --- -id: errors title: Errors --- diff --git a/www/docs/events.mdx b/www/docs/events.mdx index a02f7c87c..6a14cf1fb 100644 --- a/www/docs/events.mdx +++ b/www/docs/events.mdx @@ -1,5 +1,4 @@ --- -id: events title: Events --- diff --git a/www/docs/inspector.mdx b/www/docs/inspector.mdx index 9d1ece4c6..2ca4e8d84 100644 --- a/www/docs/inspector.mdx +++ b/www/docs/inspector.mdx @@ -1,5 +1,4 @@ --- -id: inspector title: Inspector --- diff --git a/www/docs/introduction.mdx b/www/docs/introduction.mdx index 51ec7131a..e59f86918 100644 --- a/www/docs/introduction.mdx +++ b/www/docs/introduction.mdx @@ -1,5 +1,4 @@ --- -id: introduction title: Introduction --- diff --git a/www/docs/operations.mdx b/www/docs/operations.mdx index d71e1504b..07c22c966 100644 --- a/www/docs/operations.mdx +++ b/www/docs/operations.mdx @@ -1,5 +1,4 @@ --- -id: operations title: Operations --- diff --git a/www/docs/processes.mdx b/www/docs/processes.mdx index 8bc49d545..c2d841814 100644 --- a/www/docs/processes.mdx +++ b/www/docs/processes.mdx @@ -1,5 +1,4 @@ --- -id: processes title: Spawning processes --- diff --git a/www/docs/resources.mdx b/www/docs/resources.mdx index 92a0b38e4..85529782d 100644 --- a/www/docs/resources.mdx +++ b/www/docs/resources.mdx @@ -1,5 +1,4 @@ --- -id: resources title: Resources --- diff --git a/www/docs/scope.mdx b/www/docs/scope.mdx index 6152f2b48..7e8d45ff3 100644 --- a/www/docs/scope.mdx +++ b/www/docs/scope.mdx @@ -1,5 +1,4 @@ --- -id: scope title: Scope --- diff --git a/www/docs/spawn.mdx b/www/docs/spawn.mdx index 965470ebb..72bf30ffb 100644 --- a/www/docs/spawn.mdx +++ b/www/docs/spawn.mdx @@ -1,5 +1,4 @@ --- -id: spawn title: Spawn --- diff --git a/www/docs/testing.mdx b/www/docs/testing.mdx index cef81e5cc..47d408477 100644 --- a/www/docs/testing.mdx +++ b/www/docs/testing.mdx @@ -1,5 +1,4 @@ --- -id: testing title: Testing --- diff --git a/www/docs/typescript.mdx b/www/docs/typescript.mdx index 676e38116..e2a8e0668 100644 --- a/www/docs/typescript.mdx +++ b/www/docs/typescript.mdx @@ -1,5 +1,4 @@ --- -id: typescript title: TypeScript --- diff --git a/www/html/document.html.tsx b/www/html/document.html.tsx index c53eefb81..cc3dce7e1 100644 --- a/www/html/document.html.tsx +++ b/www/html/document.html.tsx @@ -11,7 +11,9 @@ import rehypeToc from "npm:@jsdevtools/rehype-toc@3.0.2"; export default function* (doc: Doc): Operation { let docs = yield* useDocs(); - let topics = docs.getTopics(); + let topics = yield* docs.getTopics(); + let next = yield* docs.getDoc(doc.nextId); + let prev = yield* docs.getDoc(doc.previousId); return (
@@ -114,38 +116,38 @@ export default function* (doc: Doc): Operation { > - +
); } -function NextPrevLinks({ doc }: { doc: Doc }): JSX.Element { +function NextPrevLinks({ next, prev }: { next?: Doc, prev?: Doc }): JSX.Element { return ( - {doc.previous + {prev ? (
  • Previous - {doc.previous.title} + {prev.title}
  • ) :
  • } - {doc.next + {next ? (
  • Next - {doc.next.title} + {next.title}
  • ) diff --git a/www/server.ts b/www/server.ts index 59ea1f3ed..4af1885b1 100644 --- a/www/server.ts +++ b/www/server.ts @@ -1,7 +1,7 @@ import { serve } from "freejack/server.ts"; import { html } from "freejack/html.ts"; import { render } from "freejack/view.ts"; -import { Docs, loadDocs, useDocs } from "./docs/docs.ts"; +import { Docs, loadDocs } from "./docs/docs.ts"; import { useV2Docs } from "./hooks/use-v2-docs.ts"; import { AppHtml, DocumentHtml, IndexHtml } from "./html/templates.ts"; @@ -9,7 +9,7 @@ import { AppHtml, DocumentHtml, IndexHtml } from "./html/templates.ts"; export default function* start() { let v2docs = yield* useV2Docs({ fetchEagerly: !!Deno.env.get("V2_DOCS_FETCH_EAGERLY"), - revision: Number(Deno.env.get("V2_DOCS_REVISION")) ?? 4, + revision: Number(Deno.env.get("V2_DOCS_REVISION") ?? 4), }); let docs = yield* loadDocs(); @@ -24,9 +24,8 @@ export default function* start() { }), "/docs/:id": html.get(function* ({ id }) { - let docs = yield* useDocs(); - let doc = docs.getDoc(id); + let doc = yield* docs.getDoc(id); if (!doc) { return { name: "h1", attrs: {}, children: ["Not Found"] };