Plan: Fix SearchIndexService OOM — Rebuild Guard + Memory Cap#
Summary#
SearchIndexService.buildIndex() can run concurrently when rapid file changes trigger overlapping rebuilds (each allocating a full 24K-document index Map). Add an AbortController-based cancellation guard so only one rebuild runs at a time, and add a --max-old-space-size cap as a safety net. The 500 ms debounce on FILE_CHANGE_EVENT already exists in FileWatcherService; no debounce changes needed.
Files#
| File | Action | Description |
|---|---|---|
src/modules/search/search-index.service.ts | modify | Add rebuild guard with AbortController cancellation and rebuildPending flag |
ecosystem.config.js | create | PM2 config with --max-old-space-size=512 for production |
services/start-backend.sh | modify | Add --max-old-space-size=512 to node exec line |
src/modules/search/search-index.service.spec.ts | modify | Add tests for concurrent rebuild guard and cancellation |
Steps#
- Add rebuild guard to
SearchIndexService— Add private fields:abortController: AbortController | null,rebuildPending: boolean. InhandleFileChange()(line 62): if a rebuild is in progress, abort it viaabortController.abort(), setrebuildPending = true, and return. InbuildIndex(): create a newAbortController, checksignal.abortedbetween file iterations (inside thefor (const task of scanResult.tasks)loop at line 276), and bail early if aborted. AfterbuildIndex()completes (or aborts), checkrebuildPending— if true, reset the flag and callbuildIndex()again. - Skip incremental updates during pending full rebuild — In
handleFileChangeDetail()(line 83): ifrebuildPendingis true, skip the incremental update since the upcoming full rebuild will cover it. - Create
ecosystem.config.js— Add PM2 ecosystem config at repo root with app nameviewerv2-backend,node_args: '--max-old-space-size=512', scriptdist/main, andmax_memory_restart: '512M'. - Add
--max-old-space-size=512tostart-backend.sh— Change line 40 fromexec node dist/maintoexec node --max-old-space-size=512 dist/main. - Update unit tests — Add spec cases for: (a) concurrent
buildIndex()calls result in only one active rebuild, (b) abort signal cancels in-flight rebuild, (c)rebuildPendingtriggers a follow-up rebuild after the current one completes.
Verification#
- Run
npm run test— all existing + new tests pass - Run
npm run lint— zero warnings - Manual verification: confirm
buildIndex()logs show single active rebuild under rapid event simulation
Risks#
- AbortController check granularity: Checking
signal.abortedper-task (not per-file) means a rebuild processes up to one full task's files before aborting. Acceptable tradeoff — per-file checks add overhead for negligible gain with ~24K small markdown files spread across many tasks.