Skip to main content

performance

MUST be used when fixing Flows app performance — re-renders, query patterns, pagination, unbounded fetches, LLM-over-query-results, bundles, memory leaks. Measure before and after. Triggers: performance, slow, laggy, optimize, re-render, bundle size, CDF query, virtualize, chat completions, LLM cost.

跳到安装

来源信息

仓库
cognitedata/builder-skills
最近来源活动
2026年9月1日 10:41
检测到的 SKILL.md 语言
英语
星标
6
分支
1

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
performance
description
MUST be used when fixing Flows app performance — re-renders, query patterns, pagination, unbounded fetches, LLM-over-query-results, bundles, memory leaks. Measure before and after. Triggers: performance, slow, laggy, optimize, re-render, bundle size, CDF query, virtualize, chat completions, LLM cost.
allowed-tools
Read, Glob, Grep, Shell, Write
metadata
{"argument-hint":"[file, component, or area to optimize — e.g. 'src/components/AssetTable.tsx']"}
# Performance Fix Systematically find and fix performance issues in **$ARGUMENTS** (or the whole app if no argument is given). Always measure first — never optimize blindly. --- ## Step 1 — Measure baseline before touching anything Run the production build and capture metrics before making any changes: ```bash pnpm run build pnpm run preview ``` Open the app in Chrome and capture: - **Lighthouse score** (Performance tab → Run audit) - **React Profiler** (React DevTools → Profiler → Record an interaction) - Note the components with the longest render times and highest render counts Record baseline numbers. Every fix must be measured against these. --- ## Step 2 — Find and fix unnecessary re-renders Read the component tree (start from `src/App.tsx`) and search for these patterns: ```bash grep -rn --include="*.tsx" \ -E "value=\{\{|onClick=\{\(\)" src/ ``` For each instance found, **apply the fix directly**: **Inline object/array creation in JSX → wrap with `useMemo`:** ```tsx // BAD — new object on every render causes children to re-render <Chart options={{ color: "red" }} /> // FIX — wrap with useMemo const chartOptions = useMemo(() => ({ color: "red" }), []); <Chart options={chartOptions} /> ``` **Event handlers recreated on every render → wrap with `useCallback`:** ```tsx // BAD <Button onClick={() => doSomething(id)} /> // FIX — wrap with useCallback const handleClick = useCallback(() => doSomething(id), [id]); <Button onClick={handleClick} /> ``` **Context that changes on every render → memoize the context value:** ```tsx // BAD — new object reference every render <MyContext.Provider value={{ user, sdk }}> // FIX — memoize the context value const ctxValue = useMemo(() => ({ user, sdk }), [user, sdk]); <MyContext.Provider value={ctxValue}> ``` Apply `React.memo` to pure presentational components that receive stable props. Do NOT wrap every component — only those confirmed to re-render unnecessarily via the Profiler. --- ## Step 3 — Find and fix DMS query patterns For **read-heavy** workloads, prefer APIs that hit the **search/Elasticsearch path** (`query` or `search` on instances) rather than `list` paths that stress **Postgres**. ```bash # Find all DMS instance API calls grep -rn --include="*.ts" --include="*.tsx" -E "instances\.(list|search|query|aggregate|retrieve)" src/ # Find direct SDK calls to other CDF resources grep -rn --include="*.ts" --include="*.tsx" -E "\.(assets|timeseries|events|files|sequences|relationships)\.(list|search|retrieve)" src/ ``` For each `instances.list` call in a read-heavy path (e.g. populating a table, dropdown, or search results), **rewrite it to use `instances.query`** with the equivalent filter. Preserve the existing filter logic but express it in the query API format: ```ts // BAD — instances.list hits Postgres, expensive for read-heavy UI const result = await client.instances.list({ instanceType: "node", filter: { equals: { property: ["node", "space"], value: "my-space" } }, limit: 100, }); // FIX — rewrite to instances.query which hits Elasticsearch const result = await client.instances.query({ with: { nodes: { nodes: { filter: { equals: { property: ["node", "space"], value: "my-space" } }, }, limit: 100, }, }, select: { nodes: {}, }, }); ``` | API used | When it's correct | When to rewrite | |----------|-------------------|-----------------| | `instances.query` | Read with filters that map to Elasticsearch (text, equals, range) | — | | `instances.search` | Full-text or fuzzy search | — | | `instances.list` | Writing, syncing, or need for semantics not available on query/search | Rewrite to `instances.query` if used for read-heavy UI display | | `instances.retrieve` | Fetching by known external IDs | — | | `instances.aggregate` | Counts, histograms | — | For deeper rationale on search vs relational paths, cardinality, and materialization tradeoffs, consult the `semantic-knowledge/` directory if available in the workspace. ### Hard gate — LLM over query results ```bash grep -rn --include="*.ts" --include="*.tsx" -E "chat\.completions|agents/chat|useAtlasChat|openai|anthropic" src/ ``` Do not map completions over DMS rows. Fix: one `sendAgentMessage` or agent resource (`integrate-fusion-agent`). If per-item completions remain: **5** / ceiling **50**, cache by `space:externalId:lastUpdatedTime`, user-initiated only. --- ## Step 4 — Find and fix client-side filtering (move to server-side) Filters, limits, and projections must be applied **in the API request** — not by downloading large result sets and filtering in the browser. ```bash # Find client-side filtering after data fetch (common anti-pattern) grep -rn --include="*.ts" --include="*.tsx" -B 5 "\.filter(" src/ | grep -B 5 "data\|items\|result\|response\|nodes" # Find .map() or .reduce() on full datasets that suggest client-side processing grep -rn --include="*.ts" --include="*.tsx" -E "\.(map|reduce|find|some|every)\(" src/hooks/ src/services/ src/api/ ``` For each client-side filter pattern, **move the filter logic into the SDK call's `filter` parameter and remove the `.filter()` call**: ```ts // BAD — fetches all nodes then filters client-side const result = await client.instances.query({ ... }); const activeNodes = result.items.nodes.filter(n => n.properties.status === "active"); // FIX — move filter into the API request, remove client-side .filter() const result = await client.instances.query({ with: { nodes: { nodes: { filter: { and: [ existingFilters, { equals: { property: ["mySpace", "myView/v1", "status"], value: "active" } }, ], }, }, limit: 100, }, }, select: { nodes: {} }, }); const activeNodes = result.items.nodes; // no client-side filter needed ``` | Issue | Fix | |-------|-----| | `.filter()` after SDK call on full result set | Move the filter into the API request's `filter` parameter and delete the `.filter()` | | No `properties` selection in DMS query | Add a `sources` or `properties` parameter to fetch only needed fields | | Fetching all items then rendering a subset | Add `limit` and `filter` to the API call to fetch only what's displayed | | Client-side text search on fetched array | Replace with the SDK's `search` endpoint | **Hard rule:** If the API supports a filter for the criterion being applied client-side, **move it server-side now**. Client-side filtering is acceptable only for trivial local state (e.g. filtering a cached list of 10 user preferences). If the API does not support the exact filter, add a code comment explaining why client-side filtering is necessary. --- ## Step 5 — Find and fix CDF data fetching and pagination Read all CDF SDK calls (search for `sdk.`, `client.`, `useQuery`, `useCogniteClient`). ```bash # Find pagination patterns grep -rn --include="*.ts" --include="*.tsx" -E "(nextCursor|cursor|hasNextPage|fetchNextPage|offset|skip|page)" src/ # Find "fetch all" loops grep -rn --include="*.ts" --include="*.tsx" -B 3 -A 3 "while.*cursor\|while.*hasMore\|while.*nextPage" src/ ``` For each call, find the issue and **apply the fix**: | Issue | Fix to apply | |-------|-------------| | No `limit` set | **Add `limit: 100`** (or the actual page size needed) to the SDK call | | Fetching all properties | **Add a `properties` filter** to select only required fields | | Fetching on every render | **Move inside `useQuery`/`useMemo`** with a stable dependency array | | Sequential requests that could be parallel | **Rewrite to `Promise.all`** or batched SDK methods | | Missing `limit` parameter | **Add explicit `limit`** matching the UI's page size (e.g. 25, 50, 100) | | Offset-based pagination for large datasets | **Replace with cursor-based pagination** using `nextCursor` from the response | | "Fetch all" loop (exhausts cursors up front) | **Replace with on-demand pagination** using TanStack Query's `useInfiniteQuery` | **Fixing fetch-all loops** — replace the while loop with `useInfiniteQuery`: ```ts // BAD — fetches ALL pages before rendering let allItems = []; let cursor = undefined; while (true) { const result = await client.instances.list({ limit: 1000, cursor }); allItems.push(...result.items); if (!result.nextCursor) break; cursor = result.nextCursor; } // FIX — paginate on demand with useInfiniteQuery const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({ queryKey: ["instances", filters], queryFn: ({ pageParam }) => client.instances.list({ limit: 100, cursor: pageParam, ...filters }), getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined, staleTime: 30_000, }); ``` **Fixing offset-based pagination** — switch to cursor-based: ```ts // BAD — offset pagination degrades at scale const result = await client.instances.list({ limit: 100, offset: page * 100 }); // FIX — cursor-based pagination const result = await client.instances.list({ limit: 100, cursor: nextCursor }); ``` --- ## Step 6 — Find and fix excessive API call rates ```bash # Find search/filter inputs that trigger queries grep -rn --include="*.tsx" --include="*.ts" -E "onChange|onInput|onSearch|onFilter" src/ | grep -i "search\|filter\|query" # Find debounce usage grep -rn --include="*.ts" --include="*.tsx" -i -E "debounce|useDebouncedValue|useDebounce" src/ # Find polling/interval patterns grep -rn --include="*.ts" --include="*.tsx" -E "setInterval|refetchInterval|pollingInterval|refetchOnWindowFocus" src/ # Find useQuery options that control refetch behavior grep -rn --include="*.ts" --include="*.tsx" -E "staleTime|cacheTime|gcTime|refetchOnMount|refetchOnWindowFocus" src/ ``` For each issue found, **apply the fix**: **Search inputs that fire on every keystroke → add debounce with 300ms delay:** ```tsx // BAD — fires API call on every keystroke const [search, setSearch] = useState(""); const { data } = useQuery({ queryKey: ["search", search], queryFn: () => api.search(search) }); // FIX — create or use a useDebouncedValue hook with 300ms delay function useDebouncedValue<T>(value: T, delay = 300): T { const [debounced, setDebounced] = useState(value); useEffect(() => { const timer = setTimeout(() => setDebounced(value), delay); return () => clearTimeout(timer); }, [value, delay]); return debounced; } const [search, setSearch] = useState(""); const debouncedSearch = useDebouncedValue(search, 300); const { data } = useQuery({ queryKey: ["search", debouncedSearch], queryFn: () => api.search(debouncedSearch), enabled: debouncedSearch.length > 0, }); ``` **useQuery calls without staleTime → add appropriate staleTime:** ```ts // BAD — refetches on every mount/focus useQuery({ queryKey: ["data"], queryFn: fetchData }); // FIX — add staleTime to prevent unnecessary refetches useQuery({ queryKey: ["data"], queryFn: fetchData, staleTime: 30_000 }); ``` **Duplicate parallel identical requests → lift the query to a shared hook:** ```ts // BAD — multiple components each call the same query independently // ComponentA.tsx: useQuery({ queryKey: ["assets"], queryFn: fetchAssets }); // ComponentB.tsx: useQuery({ queryKey: ["assets"], queryFn: fetchAssets }); // FIX — create a shared hook, import it from both components // hooks/useAssets.ts export function useAssets() { return useQuery({ queryKey: ["assets"], queryFn: fetchAssets, staleTime: 30_000 }); } ``` | Issue | Fix to apply |
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看