perf(web): prepare statement once per label pair, not per sub-batch

Hoists conn.prepare(cypher) outside the sub-batch loop so it's called
once per (fromLabel, toLabel) pair instead of ceil(N/4) times. The
statement is reused for all rows in the group, then closed in finally.

Yields to event loop every 500 relations instead of every sub-batch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
jreakin 2026-03-23 09:19:00 -05:00
parent 32be69c8ee
commit 9c88c41cd6

View file

@ -226,7 +226,7 @@ export const loadGraphToLbug = async (
}
// Execute batched prepared statements per label pair
const SUB_BATCH_SIZE = 4;
// Prepare once per (fromLabel, toLabel) pair and reuse across all rows
for (const [key, rels] of relsByLabelPair) {
const [fromLabel, toLabel] = key.split(':');
const cypher = `
@ -235,34 +235,38 @@ export const loadGraphToLbug = async (
CREATE (a)-[:${REL_TABLE_NAME} {type: $relType, confidence: $confidence, reason: $reason, step: $step}]->(b)
`;
for (let i = 0; i < rels.length; i += SUB_BATCH_SIZE) {
const subBatch = rels.slice(i, i + SUB_BATCH_SIZE);
const stmt = await conn.prepare(cypher);
if (!stmt.isSuccess()) {
const errMsg = await stmt.getErrorMessage();
if (import.meta.env.DEV) console.warn(`Prepare failed for ${key}: ${errMsg}`);
skippedRels += subBatch.length;
await stmt.close();
continue;
}
const stmt = await conn.prepare(cypher);
if (!stmt.isSuccess()) {
const errMsg = await stmt.getErrorMessage();
if (import.meta.env.DEV) console.warn(`Prepare failed for ${key}: ${errMsg}`);
skippedRels += rels.length;
await stmt.close();
continue;
}
try {
for (const r of subBatch) {
try {
await conn.execute(stmt, r);
insertedRels++;
} catch (err) {
skippedRels++;
const statKey = `${r.relType}:${fromLabel}->${toLabel}`;
skippedRelStats.set(statKey, (skippedRelStats.get(statKey) || 0) + 1);
if (import.meta.env.DEV) {
console.warn(`⚠️ Skipped: ${statKey} | "${r.fromId}" → "${r.toId}" | ${err instanceof Error ? err.message : String(err)}`);
}
try {
for (let i = 0; i < rels.length; i++) {
try {
await conn.execute(stmt, rels[i]);
insertedRels++;
} catch (err) {
skippedRels++;
const r = rels[i];
const statKey = `${r.relType}:${fromLabel}->${toLabel}`;
skippedRelStats.set(statKey, (skippedRelStats.get(statKey) || 0) + 1);
if (import.meta.env.DEV) {
console.warn(`⚠️ Skipped: ${statKey} | "${r.fromId}" → "${r.toId}" | ${err instanceof Error ? err.message : String(err)}`);
}
}
} finally {
await stmt.close();
// Yield to event loop every 500 relations
if (i > 0 && i % 500 === 0) {
await new Promise(r => setTimeout(r, 0));
}
}
} finally {
await stmt.close();
}
// Yield to event loop between sub-batches
if (i + SUB_BATCH_SIZE < rels.length) {