Skip to content

Commit a5242af

Browse files
author
Jakob Schlanstedt
committed
git(feat): improve search ranking performance using fuzzysort
1 parent e61d51e commit a5242af

File tree

4 files changed

+64
-36
lines changed

4 files changed

+64
-36
lines changed

apps/server/src/becca/becca_loader.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ function load() {
7171
}
7272

7373
becca.loaded = true;
74+
75+
log.info(`Becca (note cache) load took ${Date.now() - start}ms`);
7476
}
7577

7678
function reload(reason: string) {

apps/server/src/services/search/services/search.ts

Lines changed: 52 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -324,41 +324,44 @@ function performSearch(
324324
}
325325

326326
// Main filtering
327-
const filteredNotes = noteSet.notes.filter(note => {
328-
if (!searchContext.includeArchivedNotes && note.isArchived) return false;
329-
if (!searchContext.includeHiddenNotes && note.isInHiddenSubtree()) return false;
330-
return true;
331-
});
332-
333-
// ✅ replaced per-note computeScore loop with rankSearchResults
334-
const notePaths = filteredNotes.map(note => {
335-
const notePathArray =
336-
executionContext.noteIdToNotePath[note.noteId] || note.getBestNotePath();
337-
if (!notePathArray) {
338-
throw new Error(
339-
`Can't find note path for note ${JSON.stringify(note.getPojo())}`
340-
);
341-
}
342-
return notePathArray;
343-
});
327+
const searchResults = noteSet.notes
328+
.filter(note => {
329+
if (!searchContext.includeArchivedNotes && note.isArchived) return false;
330+
if (!searchContext.includeHiddenNotes && note.isInHiddenSubtree()) return false;
331+
return true;
332+
})
333+
.map(note => {
334+
const notePathArray =
335+
executionContext.noteIdToNotePath[note.noteId] || note.getBestNotePath();
336+
337+
if (!notePathArray) {
338+
throw new Error(
339+
`Can't find note path for note ${JSON.stringify(note.getPojo())}`
340+
);
341+
}
344342

345-
const searchResults = rankSearchResults(
346-
notePaths,
347-
searchContext.fulltextQuery,
348-
searchContext.highlightedTokens,
349-
enableFuzzyMatching
350-
);
343+
return new SearchResult(notePathArray);
344+
});
351345

352-
// Preserve fine-tuning logic
346+
// Compute scores
353347
for (const res of searchResults) {
348+
res.computeScore(
349+
searchContext.fulltextQuery,
350+
searchContext.highlightedTokens,
351+
enableFuzzyMatching
352+
);
353+
354+
// Optional fine-tuning: disable fuzzy for title if fuzzyAttributeSearch only
354355
if (!enableFuzzyMatching && searchContext.fuzzyAttributeSearch) {
356+
// Re-score attributes only, skip fuzzy on title
355357
res.computeScore(
356358
searchContext.fulltextQuery,
357359
searchContext.highlightedTokens,
358360
true // fuzzy for attributes
359361
);
360362
}
361363

364+
// If ignoring internal attributes, reduce their weight
362365
if (searchContext.ignoreInternalAttributes) {
363366
const note = becca.notes[res.noteId];
364367
const internalAttrs = note.getAttributes()?.filter(a => a.name.startsWith("_")) || [];
@@ -374,6 +377,7 @@ function performSearch(
374377

375378
// Optional fast-search mode (e.g. autocomplete)
376379
if (searchContext.fastSearch) {
380+
// Skip fuzzy rescoring & heavy sorting logic
377381
return searchResults
378382
.filter(r => r.score > 0)
379383
.sort((a, b) => b.score - a.score)
@@ -549,15 +553,32 @@ function findResultsWithQueryIncremental(
549553
.filter(Boolean);
550554
const candidateSet = new NoteSet(candidateNotes);
551555

552-
const execCtx = { noteIdToNotePath: {} };
553-
const noteSet = expression.execute(candidateSet, execCtx, searchContext);
556+
const executionContext = { noteIdToNotePath: {} };
557+
const noteSet = expression.execute(candidateSet, executionContext, searchContext);
558+
559+
const filteredNotes = noteSet.notes.filter(note => {
560+
if (!searchContext.includeArchivedNotes && note.isArchived) return false;
561+
if (!searchContext.includeHiddenNotes && note.isInHiddenSubtree()) return false;
562+
return true;
563+
});
554564

555-
results = noteSet.notes.map(note => {
556-
const path = execCtx.noteIdToNotePath[note.noteId] || note.getBestNotePath();
557-
return new SearchResult(path);
565+
const notePaths = filteredNotes.map(note => {
566+
const notePathArray =
567+
executionContext.noteIdToNotePath[note.noteId] ||
568+
becca.notes[note.noteId]?.getBestNotePath() ||
569+
note.getBestNotePath();
570+
if (!notePathArray) {
571+
throw new Error(`Can't find note path for note ${note.noteId}`);
572+
}
573+
return notePathArray;
558574
});
559575

560-
results.sort((a, b) => a.notePathTitle.localeCompare(b.notePathTitle));
576+
results = rankSearchResults(
577+
notePaths,
578+
searchContext.fulltextQuery,
579+
searchContext.highlightedTokens,
580+
searchContext.enableFuzzyMatching
581+
);
561582
} else {
562583
results = findResultsWithExpression(expression, searchContext);
563584
}
@@ -915,7 +936,7 @@ export default {
915936
searchFromNote,
916937
searchNotesForAutocomplete,
917938
findResultsWithQuery,
918-
findResultsWithQueryIncremental, // ✅ new export
939+
findResultsWithQueryIncremental,
919940
findFirstNoteWithQuery,
920941
searchNotes
921942
};

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,6 @@
122122
]
123123
},
124124
"dependencies": {
125-
"bloom-filters": "3.0.4",
126125
"fuzzysort": "3.1.0"
127126
}
128127
}

pnpm-lock.yaml

Lines changed: 10 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)