gitnexus 1.6.10-rc.61 → 1.6.10-rc.63

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -61,6 +61,16 @@ export declare function resolveNativeSafeStorageDir(storagePath: string, subdir:
61
61
  */
62
62
  export declare const LBUG_MAX_DB_SIZE: number;
63
63
  export declare const parseWalCheckpointThreshold: (raw: string | undefined) => number | undefined;
64
+ /**
65
+ * Size the buffer pool to an estimated graph size (node + relationship count),
66
+ * clamped to [ADAPTIVE_POOL_FLOOR, defaultBufferPoolSize()]. The estimate can
67
+ * only *shrink* the pool from the default — never above the 2 GiB / 80%-RAM cap,
68
+ * never below the COPY-safety floor — so no repo is under-sized or gets more
69
+ * than the default it would have today.
70
+ */
71
+ export declare const estimateBufferPool: (graphElementCount: number) => number;
72
+ /** Set (bytes) or clear (`undefined`) the per-run buffer-pool size hint. */
73
+ export declare const setBufferPoolSizeHint: (bytes: number | undefined) => void;
64
74
  export declare const WAL_RECOVERY_SUGGESTION = "WAL corruption detected. Run `gitnexus analyze --force` to rebuild the index.";
65
75
  export declare function isWalCorruptionError(err: unknown): boolean;
66
76
  /**
@@ -307,6 +307,15 @@ const resolveCheckpointThreshold = () => {
307
307
  */
308
308
  const DEFAULT_BUFFER_POOL_CAP = 2 * 1024 * 1024 * 1024;
309
309
  const BUFFER_POOL_FLOOR = 64 * 1024 * 1024;
310
+ // COPY-safety floor for the adaptive hint (below). LadybugDB's bulk COPY needs
311
+ // working buffer-pool memory that scales with the repo: a 64 MiB pool fails
312
+ // ("buffer pool is full and no memory could be freed") on any non-trivial repo,
313
+ // and even the ~1800-file GitNexus checkout needs ≥256 MiB. So the adaptive
314
+ // size never drops a repo below this — a distinct, higher floor than
315
+ // BUFFER_POOL_FLOOR, which only guards defaultBufferPoolSize on tiny-RAM
316
+ // machines. It is still clamped up to defaultBufferPoolSize, so a machine whose
317
+ // default is below this floor keeps its default rather than over-committing.
318
+ const ADAPTIVE_POOL_FLOOR = 256 * 1024 * 1024;
310
319
  const parseBufferPoolSize = (raw) => {
311
320
  if (raw === undefined)
312
321
  return undefined;
@@ -319,17 +328,66 @@ const parseBufferPoolSize = (raw) => {
319
328
  return Math.floor(parsed);
320
329
  };
321
330
  const defaultBufferPoolSize = () => Math.min(DEFAULT_BUFFER_POOL_CAP, Math.max(BUFFER_POOL_FLOOR, Math.floor(os.totalmem() * 0.8)));
331
+ /**
332
+ * Clamp an adaptive pool request to [ADAPTIVE_POOL_FLOOR, default]. The lower
333
+ * bound keeps LadybugDB's COPY viable; the upper bound (defaultBufferPoolSize)
334
+ * means the hint can only shrink the pool from today's default and can never
335
+ * exceed the 2 GiB / 80%-RAM cap — and on a machine whose default is below the
336
+ * COPY floor, the default wins, so the pool is never over-committed.
337
+ */
338
+ const clampBufferPool = (bytes) => Math.min(defaultBufferPoolSize(), Math.max(ADAPTIVE_POOL_FLOOR, Math.floor(bytes)));
339
+ /**
340
+ * Buffer-pool bytes to provision per graph element (node + relationship).
341
+ *
342
+ * The fixed 2 GiB default is far larger than most repos' working set, and
343
+ * LadybugDB eagerly commits the pool at DB open — measured: a full
344
+ * `analyze --force` of the GitNexus checkout takes ~51 s with the 2 GiB pool
345
+ * vs ~35 s with the ~414 MiB this factor yields (31% faster; the oversized
346
+ * pool's commit dominates). The pool is a page cache over the on-disk index,
347
+ * which scales with node/edge count, so a per-element budget sizes it to the
348
+ * repo. Kept generous so the whole index stays resident (no COPY thrash) and
349
+ * always clamped to at least ADAPTIVE_POOL_FLOOR; tuned by timing a real
350
+ * large-repo `analyze --force` at this factor vs a forced 2 GiB pool (the pool
351
+ * is a native eager allocation, measured with a real analyze, not a build-free
352
+ * bench — see the emit-path COPY timing note in bench/emit-persistence).
353
+ */
354
+ const POOL_BYTES_PER_ELEMENT = 4 * 1024;
355
+ /**
356
+ * Size the buffer pool to an estimated graph size (node + relationship count),
357
+ * clamped to [ADAPTIVE_POOL_FLOOR, defaultBufferPoolSize()]. The estimate can
358
+ * only *shrink* the pool from the default — never above the 2 GiB / 80%-RAM cap,
359
+ * never below the COPY-safety floor — so no repo is under-sized or gets more
360
+ * than the default it would have today.
361
+ */
362
+ export const estimateBufferPool = (graphElementCount) => clampBufferPool(graphElementCount * POOL_BYTES_PER_ELEMENT);
363
+ /**
364
+ * Optional per-run buffer-pool size hint (bytes). The analyze orchestrator sets
365
+ * it once the graph size is known (after the pipeline, before the DB open) so
366
+ * the pool is sized to the repo instead of the fixed 2 GiB default, and clears
367
+ * it at run end. Non-analyze opens (MCP serve, `native-check` `:memory:`) never
368
+ * set it and keep the default.
369
+ */
370
+ let bufferPoolSizeHint;
371
+ /** Set (bytes) or clear (`undefined`) the per-run buffer-pool size hint. */
372
+ export const setBufferPoolSizeHint = (bytes) => {
373
+ bufferPoolSizeHint = bytes;
374
+ };
322
375
  /**
323
376
  * Resolve the `bufferManagerSize` passed to every `new lbug.Database(...)`.
324
- * `GITNEXUS_LBUG_BUFFER_POOL_SIZE` (bytes) overrides the default; `0` is a
377
+ * `GITNEXUS_LBUG_BUFFER_POOL_SIZE` (bytes) overrides everything; `0` is a
325
378
  * deliberate escape hatch that restores LadybugDB's native unbounded
326
- * 80%-of-RAM default. Resolved at call time (not module load) so tests can
327
- * stub the env var and `os.totalmem`.
379
+ * 80%-of-RAM default. With no env override, a per-run `setBufferPoolSizeHint`
380
+ * (clamped to [floor, default]) sizes the pool to the repo; otherwise the
381
+ * default. Resolved at call time (not module load) so tests can stub the env
382
+ * var, the hint, and `os.totalmem`.
328
383
  */
329
384
  const resolveBufferManagerSize = () => {
330
385
  const raw = process.env.GITNEXUS_LBUG_BUFFER_POOL_SIZE;
331
- if (raw === undefined)
332
- return defaultBufferPoolSize();
386
+ if (raw === undefined) {
387
+ return bufferPoolSizeHint !== undefined
388
+ ? clampBufferPool(bufferPoolSizeHint)
389
+ : defaultBufferPoolSize();
390
+ }
333
391
  const parsed = parseBufferPoolSize(raw);
334
392
  if (parsed !== undefined)
335
393
  return parsed;
@@ -13,6 +13,7 @@ import fs from 'fs/promises';
13
13
  import { runPipelineFromRepo } from './ingestion/pipeline.js';
14
14
  import { resetDegradedParseCounter } from './tree-sitter/safe-parse.js';
15
15
  import { initLbug, loadGraphToLbug, getLbugStats, executeQuery, executeWithReusedStatement, closeLbug, closeLbugBeforeExit, loadCachedEmbeddings, deleteNodesForFiles, deleteAllCommunitiesAndProcesses, deleteAllInterprocTaintPaths, deleteAllCallSummaries, deleteAllInjects, queryImportersBatch, loadFTSExtension, wipeLbugDbFiles, LbugWipeError, DELETE_FILES_CHUNK_SIZE, } from './lbug/lbug-adapter.js';
16
+ import { estimateBufferPool, setBufferPoolSizeHint } from './lbug/lbug-config.js';
16
17
  import { escapeCypherString } from './lbug/cypher-escape.js';
17
18
  import { buildSearchIndexesOrDegrade, createSearchFTSIndexes, initialiseSearchFTSStemmer, verifySearchFTSIndexes, } from './search/fts-indexes.js';
18
19
  import { cjkSegmentationModeMismatch, getSearchFTSCjkSegmentation, initialiseSearchFTSCjkSegmentation, } from './search/cjk-segmentation.js';
@@ -312,6 +313,10 @@ export async function runFullAnalysis(repoPath, options, callbacks, runnerIdenti
312
313
  // (parse-cache, parsedfile-store) and the kuzu-migration cleanup live there
313
314
  // and are shared across branches (#2106 KTD7).
314
315
  const { storagePath } = getStoragePaths(repoPath);
316
+ // Start each analyze with a clean buffer-pool hint: any pre-pipeline DB open
317
+ // (e.g. the embeddings-cache open) falls back to the default until the hint is
318
+ // set from the built graph below, so a prior run's size can't leak in.
319
+ setBufferPoolSizeHint(undefined);
315
320
  // Clean up stale KuzuDB files from before the LadybugDB migration.
316
321
  const kuzuResult = await cleanupOldKuzuFiles(storagePath);
317
322
  if (kuzuResult.found && kuzuResult.needsReindex) {
@@ -944,6 +949,13 @@ export async function runFullAnalysis(repoPath, options, callbacks, runnerIdenti
944
949
  // a still-populated DB this run believes it wiped.
945
950
  await wipeLbugDbFiles(lbugPath);
946
951
  }
952
+ // Size the buffer pool to the graph just built by the pipeline (a page cache
953
+ // over the on-disk index, which scales with node/edge count) instead of the
954
+ // fixed 2 GiB default, whose eager commit dominates large-repo analyze. The
955
+ // size is clamped to [COPY-safety floor, default], so it only ever shrinks
956
+ // the pool; env override / no-hint paths are unchanged. See
957
+ // resolveBufferManagerSize / estimateBufferPool.
958
+ setBufferPoolSizeHint(estimateBufferPool(pipelineResult.graph.nodeCount + pipelineResult.graph.relationshipCount));
947
959
  await initLbug(lbugPath);
948
960
  // Manual WAL checkpoint driver (#1741): periodically drain the WAL
949
961
  // from JS so the un-retriable native auto-checkpoint almost never
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.10-rc.61",
3
+ "version": "1.6.10-rc.63",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",