Skip to content

Commit 0691aaa

Browse files
authored
ENG-1557: Optimize getAllDiscourseNodesSince to single query (#910)
* ENG-1557: Optimize getAllDiscourseNodesSince to single query Consolidate 24 per-node-type Datalog queries into 1 combined-regex query. Roam serializes backend.q calls (~800ms each), so 24 types took ~20s blocking all other queries. Single query: ~2s. Block queries now take pre-filtered pageUids via collection binding instead of re-scanning with regex. * Fix block-backed discourse node sync
1 parent 3008b6b commit 0691aaa

File tree

1 file changed

+74
-59
lines changed

1 file changed

+74
-59
lines changed

apps/roam/src/utils/getAllDiscourseNodesSince.ts

Lines changed: 74 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { type DiscourseNode } from "./getDiscourseNodes";
22
import getDiscourseNodeFormatExpression from "./getDiscourseNodeFormatExpression";
3-
import { extractRef } from "roamjs-components/util";
3+
import extractRef from "roamjs-components/util/extractRef";
44

55
type ISODateString = string;
66

@@ -17,22 +17,21 @@ export type RoamDiscourseNodeData = {
1717
};
1818
/* eslint-enable @typescript-eslint/naming-convention */
1919

20-
export type DiscourseNodesSinceResult = {
21-
pageNodes: RoamDiscourseNodeData[];
22-
blockNodes: RoamDiscourseNodeData[];
23-
};
24-
2520
export const getDiscourseNodeTypeWithSettingsBlockNodes = async (
2621
node: DiscourseNode,
2722
sinceMs: number,
2823
): Promise<RoamDiscourseNodeData[]> => {
24+
const firstChildUid = extractRef(node.embeddingRef || "");
25+
if (!firstChildUid) {
26+
return [];
27+
}
28+
2929
const regex = getDiscourseNodeFormatExpression(node.format);
3030
const regexPattern = regex.source.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
31-
const firstChildUid = extractRef(node.embeddingRef);
3231
const queryBlock = `[
33-
:find ?childString ?nodeUid ?nodeCreateTime ?nodeEditTime ?author_local_id ?type ?author_name ?node-title
34-
:keys text source_local_id created last_modified author_local_id type author_name node_title
35-
:in $ ?firstChildUid ?type ?since
32+
:find ?childString ?nodeUid ?nodeCreateTime ?nodeEditTime ?author_local_id ?author_name ?node-title
33+
:keys text source_local_id created last_modified author_local_id author_name node_title
34+
:in $ ?firstChildUid ?since
3635
:where
3736
[(re-pattern "${regexPattern}") ?title-regex]
3837
[?node :node/title ?node-title]
@@ -57,75 +56,91 @@ export const getDiscourseNodeTypeWithSettingsBlockNodes = async (
5756
[(> ?nodeEditTime ?since)]]
5857
]`;
5958

60-
const blockNode = (await window.roamAlphaAPI.data.backend.q(
59+
return (await window.roamAlphaAPI.data.backend.q(
6160
queryBlock,
6261
String(firstChildUid),
63-
String(node.type),
6462
sinceMs,
6563
)) as unknown[] as RoamDiscourseNodeData[];
66-
return blockNode;
6764
};
6865

6966
export const getAllDiscourseNodesSince = async (
7067
since: ISODateString,
7168
nodeTypes: DiscourseNode[],
7269
): Promise<RoamDiscourseNodeData[]> => {
7370
const sinceMs = new Date(since).getTime();
71+
if (!nodeTypes.length) {
72+
return [];
73+
}
74+
75+
const typeMatchers = nodeTypes.map((node) => ({
76+
node,
77+
regex: getDiscourseNodeFormatExpression(node.format),
78+
}));
79+
const regexPattern = typeMatchers
80+
.map(({ regex }) => `(?:${regex.source})`)
81+
.join("|")
82+
.replace(/\\/g, "\\\\")
83+
.replace(/"/g, '\\"');
84+
85+
const query = `[
86+
:find ?node-title ?uid ?nodeCreateTime ?nodeEditTime ?author_local_id ?author_name
87+
:keys text source_local_id created last_modified author_local_id author_name
88+
:in $ ?since
89+
:where
90+
[(re-pattern "${regexPattern}") ?title-regex]
91+
[?node :node/title ?node-title]
92+
[(re-find ?title-regex ?node-title)]
93+
[?node :block/uid ?uid]
94+
[?node :create/time ?nodeCreateTime]
95+
[?node :create/user ?user-eid]
96+
[?user-eid :user/uid ?author_local_id]
97+
[(get-else $ ?user-eid :user/display-name "Anonymous User") ?author_name]
98+
[(get-else $ ?node :edit/time ?nodeCreateTime) ?nodeEditTime]
99+
[(> ?nodeEditTime ?since)]
100+
]`;
101+
102+
const allPages = (await window.roamAlphaAPI.data.backend.q(
103+
query,
104+
sinceMs,
105+
)) as unknown[] as RoamDiscourseNodeData[];
106+
74107
const resultMap = new Map<string, RoamDiscourseNodeData>();
108+
const blockBackedNodeTypes = nodeTypes.filter((node) =>
109+
Boolean(extractRef(node.embeddingRef || "")),
110+
);
111+
112+
for (const page of allPages) {
113+
for (const { node, regex } of typeMatchers) {
114+
if (regex.test(page.text)) {
115+
if (page.source_local_id) {
116+
resultMap.set(page.source_local_id, {
117+
...page,
118+
type: node.type,
119+
});
120+
}
121+
break;
122+
}
123+
}
124+
}
75125

76126
await Promise.all(
77-
nodeTypes.map(async (node) => {
78-
const regex = getDiscourseNodeFormatExpression(node.format);
79-
const regexPattern = regex.source
80-
.replace(/\\/g, "\\\\")
81-
.replace(/"/g, '\\"');
82-
83-
const query = `[
84-
:find ?node-title ?uid ?nodeCreateTime ?nodeEditTime ?author_local_id ?author_name ?type
85-
:keys text source_local_id created last_modified author_local_id author_name type
86-
:in $ ?since ?type
87-
:where
88-
[(re-pattern "${regexPattern}") ?title-regex]
89-
[?node :node/title ?node-title]
90-
[(re-find ?title-regex ?node-title)]
91-
[?node :block/uid ?uid]
92-
[?node :create/time ?nodeCreateTime]
93-
[?node :create/user ?user-eid]
94-
[?user-eid :user/uid ?author_local_id]
95-
[(get-else $ ?user-eid :user/display-name "Anonymous User") ?author_name]
96-
[(get-else $ ?node :edit/time ?nodeCreateTime) ?nodeEditTime]
97-
[(> ?nodeEditTime ?since)]
98-
]`;
99-
100-
const nodesOfType = (await window.roamAlphaAPI.data.backend.q(
101-
query,
127+
blockBackedNodeTypes.map(async (node) => {
128+
const blockNodes = await getDiscourseNodeTypeWithSettingsBlockNodes(
129+
node,
102130
sinceMs,
103-
String(node.type),
104-
)) as unknown[] as RoamDiscourseNodeData[];
105-
106-
nodesOfType.forEach((n) => {
107-
if (n.source_local_id) {
108-
resultMap.set(n.source_local_id, n);
109-
}
110-
});
131+
);
111132

112-
const hasBlockSettings =
113-
node.embeddingRef && extractRef(node.embeddingRef);
114-
if (hasBlockSettings) {
115-
const blockNodes = await getDiscourseNodeTypeWithSettingsBlockNodes(
116-
node,
117-
sinceMs,
118-
);
119-
if (blockNodes) {
120-
blockNodes.forEach((blockNode) => {
121-
if (blockNode.source_local_id) {
122-
resultMap.set(blockNode.source_local_id, blockNode);
123-
}
133+
blockNodes.forEach((blockNode) => {
134+
if (blockNode.source_local_id) {
135+
resultMap.set(blockNode.source_local_id, {
136+
...blockNode,
137+
type: node.type,
124138
});
125139
}
126-
}
140+
});
127141
}),
128142
);
143+
129144
return Array.from(resultMap.values());
130145
};
131146

0 commit comments

Comments
 (0)