n8n/packages
Garrit Franke d24fa54996
perf(editor): harden node search data layer with index, EXISTS sharing and keyset scan
Ports the measured DB-layer hardening from #30294 onto the node content
search, and closes two correctness gaps:

1. Sharing as EXISTS, not `workflow.id IN (subquery)`. The IN form makes
   SQLite drive from shared_workflow and sort every LIKE match in a temp
   B-tree, discarding the updatedAt index (measured on a 20k corpus in
   #30294; plan-asserted there in node-search-variants.perf.ts).

2. Index on workflow_entity.updatedAt (migration
   AddUpdatedAtIndexToWorkflowEntity1786525332822 + entity mirror). The
   query orders by updatedAt and stops at the limit; without the index
   every keystroke sorts the whole table (149ms -> 0.9ms at 20k). The
   migration was already registered in the generated migration index on
   this branch but the file itself was never committed.

3. Keyset scan instead of a fixed 500-candidate cap. The SQL pre-filter
   matches the raw JSON blob - node ids, webhook ids, positions - which
   the in-memory matcher deliberately skips, so a fixed cap let blob-only
   false positives displace real hits and silently return nothing. The
   service now batches 100 candidates on an (updatedAt, id) cursor (id
   tiebreak: updatedAt is not unique) until the result list fills,
   candidates run out, or a 2000-workflow safety rail stops pathological
   queries. The cursor is a row-value comparison, NOT the equivalent
   'a < x OR (a = x AND b < y)': the OR form makes SQLite pick a
   MULTI-INDEX OR plan that re-materialises per batch (measured 3.5s vs
   96ms per search at 20k in #30294).

   Credential names are matched too (new 'credentials' field, ranked
   last): they appear in the serialized workflow, so the pre-filter
   matches them, and without an in-memory counterpart a credential-name
   query only ever produced false positives - the most realistic trigger
   for the empty-results bug.

Also ports the Postgres planner hint (SET LOCAL enable_seqscan = off)
from #30294: Postgres cannot estimate '%...%' selectivity and otherwise
seq-scans + top-N sorts instead of walking the index (75ms -> 1ms).

Deliberately not ported from #30294 (yet): the pLimit(1) concurrency
gate + per-user rate limiter, and the performance benchmark suite.

Verified: matcher 40/40, service unit 19/19, integration 22/22 on both
SQLite and Postgres 16; typecheck and lint clean across db, cli,
workflow, api-types and editor-ui; editor-ui composable tests 24/24.
2026-08-12 16:24:01 +02:00
..
@n8n perf(editor): harden node search data layer with index, EXISTS sharing and keyset scan 2026-08-12 16:24:01 +02:00
cli perf(editor): harden node search data layer with index, EXISTS sharing and keyset scan 2026-08-12 16:24:01 +02:00
core perf(core): Resolve webhook description fields via declared native resolvers (#35404) 2026-08-06 11:44:14 +00:00
extensions/insights 🚀 Release 2.34.0 (#35487) 2026-08-04 07:14:55 +00:00
frontend perf(editor): harden node search data layer with index, EXISTS sharing and keyset scan 2026-08-12 16:24:01 +02:00
node-dev chore: Set up repository for TypeScript 7 migration (#33890) 2026-07-09 13:21:16 +03:00
nodes-base feat(Kafka Trigger Node): Add v2 node class and register version 2 (no-changelog) (#35715) 2026-08-10 15:00:11 +00:00
testing test: Pin Instance AI off for the legacy AI Assistant e2e suites (#35928) 2026-08-10 11:37:17 +00:00
workflow perf(editor): harden node search data layer with index, EXISTS sharing and keyset scan 2026-08-12 16:24:01 +02:00