Skip to content

Commit dab65f3

Browse files
committed
fix: Configure Qdrant to use on-disk storage for memory efficiency
- Add memory optimization configuration to QdrantVectorStore - Configure vectors and HNSW indexes to use on-disk storage by default - Add memory-mapped file support for segments larger than 50k vectors - Reduce HNSW search parameter (ef) from 128 to 64 for memory efficiency - Add configuration options for memory optimization settings - Update type definitions to include new configuration options - Update all affected tests to handle new constructor parameters Fixes #6262
1 parent 91c21a1 commit dab65f3

File tree

8 files changed

+309
-9
lines changed

8 files changed

+309
-9
lines changed

packages/types/src/codebase-index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ export const codebaseIndexConfigSchema = z.object({
3434
// OpenAI Compatible specific fields
3535
codebaseIndexOpenAiCompatibleBaseUrl: z.string().optional(),
3636
codebaseIndexOpenAiCompatibleModelDimension: z.number().optional(),
37+
// Memory optimization settings
38+
codebaseIndexUseOnDiskStorage: z.boolean().optional(),
39+
codebaseIndexMemoryMapThreshold: z.number().optional(),
40+
codebaseIndexHnswEfSearch: z.number().optional(),
3741
})
3842

3943
export type CodebaseIndexConfig = z.infer<typeof codebaseIndexConfigSchema>

src/services/code-index/__tests__/config-manager.spec.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,18 @@ describe("CodeIndexConfigManager", () => {
104104
isConfigured: false,
105105
embedderProvider: "openai",
106106
modelId: undefined,
107+
modelDimension: undefined,
107108
openAiOptions: { openAiNativeApiKey: "" },
108109
ollamaOptions: { ollamaBaseUrl: "" },
110+
openAiCompatibleOptions: undefined,
111+
geminiOptions: undefined,
112+
mistralOptions: undefined,
109113
qdrantUrl: "http://localhost:6333",
110114
qdrantApiKey: "",
111115
searchMinScore: 0.4,
116+
useOnDiskStorage: true,
117+
memoryMapThreshold: 50000,
118+
hnswEfSearch: 64,
112119
})
113120
expect(result.requiresRestart).toBe(false)
114121
})
@@ -135,11 +142,18 @@ describe("CodeIndexConfigManager", () => {
135142
isConfigured: true,
136143
embedderProvider: "openai",
137144
modelId: "text-embedding-3-large",
145+
modelDimension: undefined,
138146
openAiOptions: { openAiNativeApiKey: "test-openai-key" },
139147
ollamaOptions: { ollamaBaseUrl: "" },
148+
openAiCompatibleOptions: undefined,
149+
geminiOptions: undefined,
150+
mistralOptions: undefined,
140151
qdrantUrl: "http://qdrant.local",
141152
qdrantApiKey: "test-qdrant-key",
142153
searchMinScore: 0.4,
154+
useOnDiskStorage: true,
155+
memoryMapThreshold: 50000,
156+
hnswEfSearch: 64,
143157
})
144158
})
145159

@@ -168,15 +182,21 @@ describe("CodeIndexConfigManager", () => {
168182
isConfigured: true,
169183
embedderProvider: "openai-compatible",
170184
modelId: "text-embedding-3-large",
185+
modelDimension: undefined,
171186
openAiOptions: { openAiNativeApiKey: "" },
172187
ollamaOptions: { ollamaBaseUrl: "" },
173188
openAiCompatibleOptions: {
174189
baseUrl: "https://api.example.com/v1",
175190
apiKey: "test-openai-compatible-key",
176191
},
192+
geminiOptions: undefined,
193+
mistralOptions: undefined,
177194
qdrantUrl: "http://qdrant.local",
178195
qdrantApiKey: "test-qdrant-key",
179196
searchMinScore: 0.4,
197+
useOnDiskStorage: true,
198+
memoryMapThreshold: 50000,
199+
hnswEfSearch: 64,
180200
})
181201
})
182202

@@ -212,9 +232,14 @@ describe("CodeIndexConfigManager", () => {
212232
baseUrl: "https://api.example.com/v1",
213233
apiKey: "test-openai-compatible-key",
214234
},
235+
geminiOptions: undefined,
236+
mistralOptions: undefined,
215237
qdrantUrl: "http://qdrant.local",
216238
qdrantApiKey: "test-qdrant-key",
217239
searchMinScore: 0.4,
240+
useOnDiskStorage: true,
241+
memoryMapThreshold: 50000,
242+
hnswEfSearch: 64,
218243
})
219244
})
220245

@@ -243,16 +268,22 @@ describe("CodeIndexConfigManager", () => {
243268
isConfigured: true,
244269
embedderProvider: "openai-compatible",
245270
modelId: "custom-model",
271+
modelDimension: undefined,
246272
openAiOptions: { openAiNativeApiKey: "" },
247273
ollamaOptions: { ollamaBaseUrl: "" },
248274
openAiCompatibleOptions: {
249275
baseUrl: "https://api.example.com/v1",
250276
apiKey: "test-openai-compatible-key",
251277
// modelDimension is undefined when not set
252278
},
279+
geminiOptions: undefined,
280+
mistralOptions: undefined,
253281
qdrantUrl: "http://qdrant.local",
254282
qdrantApiKey: "test-qdrant-key",
255283
searchMinScore: 0.4,
284+
useOnDiskStorage: true,
285+
memoryMapThreshold: 50000,
286+
hnswEfSearch: 64,
256287
})
257288
})
258289

@@ -289,9 +320,13 @@ describe("CodeIndexConfigManager", () => {
289320
apiKey: "test-openai-compatible-key",
290321
},
291322
geminiOptions: undefined,
323+
mistralOptions: undefined,
292324
qdrantUrl: "http://qdrant.local",
293325
qdrantApiKey: "test-qdrant-key",
294326
searchMinScore: 0.4,
327+
useOnDiskStorage: true,
328+
memoryMapThreshold: 50000,
329+
hnswEfSearch: 64,
295330
})
296331
})
297332

@@ -1292,14 +1327,19 @@ describe("CodeIndexConfigManager", () => {
12921327
isConfigured: true,
12931328
embedderProvider: "openai",
12941329
modelId: "text-embedding-3-large",
1330+
modelDimension: undefined,
12951331
openAiOptions: { openAiNativeApiKey: "test-openai-key" },
12961332
ollamaOptions: { ollamaBaseUrl: undefined },
12971333
geminiOptions: undefined,
12981334
openAiCompatibleOptions: undefined,
1335+
mistralOptions: undefined,
12991336
qdrantUrl: "http://qdrant.local",
13001337
qdrantApiKey: "test-qdrant-key",
13011338
searchMinScore: 0.4,
13021339
searchMaxResults: 50,
1340+
useOnDiskStorage: true,
1341+
memoryMapThreshold: 50000,
1342+
hnswEfSearch: 64,
13031343
})
13041344
})
13051345

src/services/code-index/__tests__/service-factory.spec.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ describe("CodeIndexServiceFactory", () => {
4949

5050
mockConfigManager = {
5151
getConfig: vitest.fn(),
52+
memoryOptimizationConfig: {
53+
useOnDiskStorage: true,
54+
memoryMapThreshold: 50000,
55+
hnswEfSearch: 64,
56+
},
5257
}
5358

5459
mockCacheManager = {}
@@ -367,6 +372,11 @@ describe("CodeIndexServiceFactory", () => {
367372
"http://localhost:6333",
368373
3072,
369374
"test-key",
375+
{
376+
useOnDiskStorage: true,
377+
memoryMapThreshold: 50000,
378+
hnswEfSearch: 64,
379+
},
370380
)
371381
})
372382

@@ -392,6 +402,11 @@ describe("CodeIndexServiceFactory", () => {
392402
"http://localhost:6333",
393403
768,
394404
"test-key",
405+
{
406+
useOnDiskStorage: true,
407+
memoryMapThreshold: 50000,
408+
hnswEfSearch: 64,
409+
},
395410
)
396411
})
397412

@@ -417,6 +432,11 @@ describe("CodeIndexServiceFactory", () => {
417432
"http://localhost:6333",
418433
3072,
419434
"test-key",
435+
{
436+
useOnDiskStorage: true,
437+
memoryMapThreshold: 50000,
438+
hnswEfSearch: 64,
439+
},
420440
)
421441
})
422442

@@ -449,6 +469,11 @@ describe("CodeIndexServiceFactory", () => {
449469
"http://localhost:6333",
450470
modelDimension, // Should use model's built-in dimension, not manual
451471
"test-key",
472+
{
473+
useOnDiskStorage: true,
474+
memoryMapThreshold: 50000,
475+
hnswEfSearch: 64,
476+
},
452477
)
453478
})
454479

@@ -480,6 +505,11 @@ describe("CodeIndexServiceFactory", () => {
480505
"http://localhost:6333",
481506
manualDimension, // Should use manual dimension as fallback
482507
"test-key",
508+
{
509+
useOnDiskStorage: true,
510+
memoryMapThreshold: 50000,
511+
hnswEfSearch: 64,
512+
},
483513
)
484514
})
485515

@@ -509,6 +539,11 @@ describe("CodeIndexServiceFactory", () => {
509539
"http://localhost:6333",
510540
768,
511541
"test-key",
542+
{
543+
useOnDiskStorage: true,
544+
memoryMapThreshold: 50000,
545+
hnswEfSearch: 64,
546+
},
512547
)
513548
})
514549

@@ -578,6 +613,11 @@ describe("CodeIndexServiceFactory", () => {
578613
"http://localhost:6333",
579614
3072,
580615
"test-key",
616+
{
617+
useOnDiskStorage: true,
618+
memoryMapThreshold: 50000,
619+
hnswEfSearch: 64,
620+
},
581621
)
582622
})
583623

@@ -603,6 +643,11 @@ describe("CodeIndexServiceFactory", () => {
603643
"http://localhost:6333",
604644
3072,
605645
"test-key",
646+
{
647+
useOnDiskStorage: true,
648+
memoryMapThreshold: 50000,
649+
hnswEfSearch: 64,
650+
},
606651
)
607652
})
608653

@@ -627,6 +672,11 @@ describe("CodeIndexServiceFactory", () => {
627672
"http://localhost:6333",
628673
1536,
629674
"test-key",
675+
{
676+
useOnDiskStorage: true,
677+
memoryMapThreshold: 50000,
678+
hnswEfSearch: 64,
679+
},
630680
)
631681
})
632682

src/services/code-index/config-manager.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ export class CodeIndexConfigManager {
2323
private qdrantApiKey?: string
2424
private searchMinScore?: number
2525
private searchMaxResults?: number
26+
// Memory optimization settings
27+
private useOnDiskStorage?: boolean
28+
private memoryMapThreshold?: number
29+
private hnswEfSearch?: number
2630

2731
constructor(private readonly contextProxy: ContextProxy) {
2832
// Initialize with current configuration to avoid false restart triggers
@@ -50,6 +54,9 @@ export class CodeIndexConfigManager {
5054
codebaseIndexEmbedderModelId: "",
5155
codebaseIndexSearchMinScore: undefined,
5256
codebaseIndexSearchMaxResults: undefined,
57+
codebaseIndexUseOnDiskStorage: true, // Default to true for memory efficiency
58+
codebaseIndexMemoryMapThreshold: 50000, // Default 50k vectors
59+
codebaseIndexHnswEfSearch: 64, // Default 64 for balance
5360
}
5461

5562
const {
@@ -60,6 +67,9 @@ export class CodeIndexConfigManager {
6067
codebaseIndexEmbedderModelId,
6168
codebaseIndexSearchMinScore,
6269
codebaseIndexSearchMaxResults,
70+
codebaseIndexUseOnDiskStorage,
71+
codebaseIndexMemoryMapThreshold,
72+
codebaseIndexHnswEfSearch,
6373
} = codebaseIndexConfig
6474

6575
const openAiKey = this.contextProxy?.getSecret("codeIndexOpenAiKey") ?? ""
@@ -76,6 +86,9 @@ export class CodeIndexConfigManager {
7686
this.qdrantApiKey = qdrantApiKey ?? ""
7787
this.searchMinScore = codebaseIndexSearchMinScore
7888
this.searchMaxResults = codebaseIndexSearchMaxResults
89+
this.useOnDiskStorage = codebaseIndexUseOnDiskStorage ?? true
90+
this.memoryMapThreshold = codebaseIndexMemoryMapThreshold ?? 50000
91+
this.hnswEfSearch = codebaseIndexHnswEfSearch ?? 64
7992

8093
// Validate and set model dimension
8194
const rawDimension = codebaseIndexConfig.codebaseIndexEmbedderModelDimension
@@ -144,6 +157,9 @@ export class CodeIndexConfigManager {
144157
qdrantUrl?: string
145158
qdrantApiKey?: string
146159
searchMinScore?: number
160+
useOnDiskStorage?: boolean
161+
memoryMapThreshold?: number
162+
hnswEfSearch?: number
147163
}
148164
requiresRestart: boolean
149165
}> {
@@ -187,6 +203,9 @@ export class CodeIndexConfigManager {
187203
qdrantUrl: this.qdrantUrl,
188204
qdrantApiKey: this.qdrantApiKey,
189205
searchMinScore: this.currentSearchMinScore,
206+
useOnDiskStorage: this.useOnDiskStorage,
207+
memoryMapThreshold: this.memoryMapThreshold,
208+
hnswEfSearch: this.hnswEfSearch,
190209
},
191210
requiresRestart,
192211
}
@@ -379,6 +398,9 @@ export class CodeIndexConfigManager {
379398
qdrantApiKey: this.qdrantApiKey,
380399
searchMinScore: this.currentSearchMinScore,
381400
searchMaxResults: this.currentSearchMaxResults,
401+
useOnDiskStorage: this.useOnDiskStorage,
402+
memoryMapThreshold: this.memoryMapThreshold,
403+
hnswEfSearch: this.hnswEfSearch,
382404
}
383405
}
384406

@@ -413,6 +435,21 @@ export class CodeIndexConfigManager {
413435
}
414436
}
415437

438+
/**
439+
* Gets the memory optimization settings
440+
*/
441+
public get memoryOptimizationConfig(): {
442+
useOnDiskStorage?: boolean
443+
memoryMapThreshold?: number
444+
hnswEfSearch?: number
445+
} {
446+
return {
447+
useOnDiskStorage: this.useOnDiskStorage,
448+
memoryMapThreshold: this.memoryMapThreshold,
449+
hnswEfSearch: this.hnswEfSearch,
450+
}
451+
}
452+
416453
/**
417454
* Gets the current model ID being used for embeddings.
418455
*/

src/services/code-index/interfaces/config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ export interface CodeIndexConfig {
1818
qdrantApiKey?: string
1919
searchMinScore?: number
2020
searchMaxResults?: number
21+
// Memory optimization settings
22+
useOnDiskStorage?: boolean // Enable on-disk storage for vectors and indexes
23+
memoryMapThreshold?: number // Number of vectors before using memory-mapped files
24+
hnswEfSearch?: number // HNSW search parameter (lower = less memory, faster)
2125
}
2226

2327
/**
@@ -37,4 +41,8 @@ export type PreviousConfigSnapshot = {
3741
mistralApiKey?: string
3842
qdrantUrl?: string
3943
qdrantApiKey?: string
44+
// Memory optimization settings
45+
useOnDiskStorage?: boolean
46+
memoryMapThreshold?: number
47+
hnswEfSearch?: number
4048
}

src/services/code-index/service-factory.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,17 @@ export class CodeIndexServiceFactory {
136136
throw new Error(t("embeddings:serviceFactory.qdrantUrlMissing"))
137137
}
138138

139-
// Assuming constructor is updated: new QdrantVectorStore(workspacePath, url, vectorSize, apiKey?)
140-
return new QdrantVectorStore(this.workspacePath, config.qdrantUrl, vectorSize, config.qdrantApiKey)
139+
// Get memory optimization config from config manager
140+
const memoryOptimization = this.configManager.memoryOptimizationConfig
141+
142+
// Create QdrantVectorStore with memory optimization settings
143+
return new QdrantVectorStore(
144+
this.workspacePath,
145+
config.qdrantUrl,
146+
vectorSize,
147+
config.qdrantApiKey,
148+
memoryOptimization,
149+
)
141150
}
142151

143152
/**

0 commit comments

Comments
 (0)