Skip to content

Commit bc360ee

Browse files
[Manager] Cache Algolia searches and limit suggestions queries (#3876)
1 parent a52cc0e commit bc360ee

File tree

2 files changed

+96
-27
lines changed

2 files changed

+96
-27
lines changed

src/composables/useRegistrySearch.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { watchDebounced } from '@vueuse/core'
22
import type { Hit } from 'algoliasearch/dist/lite/browser'
33
import { memoize, orderBy } from 'lodash'
4-
import { computed, ref, watch } from 'vue'
4+
import { computed, onUnmounted, ref, watch } from 'vue'
55

66
import {
77
AlgoliaNodePack,
@@ -11,10 +11,10 @@ import {
1111
import type { NodesIndexSuggestion } from '@/services/algoliaSearchService'
1212
import { SortableAlgoliaField } from '@/types/comfyManagerTypes'
1313

14-
const SEARCH_DEBOUNCE_TIME = 256
14+
const SEARCH_DEBOUNCE_TIME = 320
1515
const DEFAULT_PAGE_SIZE = 64
1616
const DEFAULT_SORT_FIELD = SortableAlgoliaField.Downloads // Set in the index configuration
17-
17+
const DEFAULT_MAX_CACHE_SIZE = 64
1818
const SORT_DIRECTIONS: Record<SortableAlgoliaField, 'asc' | 'desc'> = {
1919
[SortableAlgoliaField.Downloads]: 'desc',
2020
[SortableAlgoliaField.Created]: 'desc',
@@ -30,7 +30,12 @@ const isDateField = (field: SortableAlgoliaField): boolean =>
3030
/**
3131
* Composable for managing UI state of Comfy Node Registry search.
3232
*/
33-
export function useRegistrySearch() {
33+
export function useRegistrySearch(
34+
options: {
35+
maxCacheSize?: number
36+
} = {}
37+
) {
38+
const { maxCacheSize = DEFAULT_MAX_CACHE_SIZE } = options
3439
const isLoading = ref(false)
3540
const sortField = ref<SortableAlgoliaField>(SortableAlgoliaField.Downloads)
3641
const searchMode = ref<'nodes' | 'packs'>('packs')
@@ -56,7 +61,10 @@ export function useRegistrySearch() {
5661
: []
5762
)
5863

59-
const { searchPacks, toRegistryPack } = useAlgoliaSearchService()
64+
const { searchPacksCached, toRegistryPack, clearSearchPacksCache } =
65+
useAlgoliaSearchService({
66+
maxCacheSize
67+
})
6068

6169
const algoliaToRegistry = memoize(
6270
toRegistryPack,
@@ -77,7 +85,7 @@ export function useRegistrySearch() {
7785
if (!options.append) {
7886
pageNumber.value = 0
7987
}
80-
const { nodePacks, querySuggestions } = await searchPacks(
88+
const { nodePacks, querySuggestions } = await searchPacksCached(
8189
searchQuery.value,
8290
{
8391
pageSize: pageSize.value,
@@ -116,6 +124,8 @@ export function useRegistrySearch() {
116124
immediate: true
117125
})
118126

127+
onUnmounted(clearSearchPacksCache)
128+
119129
return {
120130
isLoading,
121131
pageNumber,

src/services/algoliaSearchService.ts

Lines changed: 80 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1+
import QuickLRU from '@alloc/quick-lru'
12
import type {
23
BaseSearchParamsWithoutQuery,
34
Hit,
5+
SearchQuery,
46
SearchResponse
57
} from 'algoliasearch/dist/lite/browser'
68
import { liteClient as algoliasearch } from 'algoliasearch/dist/lite/builds/browser'
79
import { omit } from 'lodash'
810

911
import { components } from '@/types/comfyRegistryTypes'
12+
import { paramsToCacheKey } from '@/utils/formatUtil'
13+
14+
const DEFAULT_MAX_CACHE_SIZE = 64
15+
const DEFAULT_MIN_CHARS_FOR_SUGGESTIONS = 2
1016

1117
type SafeNestedProperty<
1218
T,
@@ -15,6 +21,10 @@ type SafeNestedProperty<
1521
> = T[K1] extends undefined | null ? undefined : NonNullable<T[K1]>[K2]
1622

1723
type RegistryNodePack = components['schemas']['Node']
24+
type SearchPacksResult = {
25+
nodePacks: Hit<AlgoliaNodePack>[]
26+
querySuggestions: Hit<NodesIndexSuggestion>[]
27+
}
1828

1929
export interface AlgoliaNodePack {
2030
objectID: RegistryNodePack['id']
@@ -91,8 +101,33 @@ type SearchNodePacksParams = BaseSearchParamsWithoutQuery & {
91101
restrictSearchableAttributes: SearchAttribute[]
92102
}
93103

94-
export const useAlgoliaSearchService = () => {
104+
interface AlgoliaSearchServiceOptions {
105+
/**
106+
* Maximum number of search results to store in the cache.
107+
* The cache is automatically cleared when the component is unmounted.
108+
* @default 64
109+
*/
110+
maxCacheSize?: number
111+
/**
112+
* Minimum number of characters for suggestions. An additional query
113+
* will be made to the suggestions/completions index for queries that
114+
* are this length or longer.
115+
* @default 3
116+
*/
117+
minCharsForSuggestions?: number
118+
}
119+
120+
export const useAlgoliaSearchService = (
121+
options: AlgoliaSearchServiceOptions = {}
122+
) => {
123+
const {
124+
maxCacheSize = DEFAULT_MAX_CACHE_SIZE,
125+
minCharsForSuggestions = DEFAULT_MIN_CHARS_FOR_SUGGESTIONS
126+
} = options
95127
const searchClient = algoliasearch(__ALGOLIA_APP_ID__, __ALGOLIA_API_KEY__)
128+
const searchPacksCache = new QuickLRU<string, SearchPacksResult>({
129+
maxSize: maxCacheSize
130+
})
96131

97132
const toRegistryLatestVersion = (
98133
algoliaNode: AlgoliaNodePack
@@ -141,34 +176,39 @@ export const useAlgoliaSearchService = () => {
141176
const searchPacks = async (
142177
query: string,
143178
params: SearchNodePacksParams
144-
): Promise<{
145-
nodePacks: Hit<AlgoliaNodePack>[]
146-
querySuggestions: Hit<NodesIndexSuggestion>[]
147-
}> => {
179+
): Promise<SearchPacksResult> => {
148180
const { pageSize, pageNumber } = params
149181
const rest = omit(params, ['pageSize', 'pageNumber'])
150182

183+
const requests: SearchQuery[] = [
184+
{
185+
query,
186+
indexName: 'nodes_index',
187+
attributesToRetrieve: RETRIEVE_ATTRIBUTES,
188+
...rest,
189+
hitsPerPage: pageSize,
190+
page: pageNumber
191+
}
192+
]
193+
194+
const shouldQuerySuggestions = query.length >= minCharsForSuggestions
195+
196+
// If the query is long enough, also query the suggestions index
197+
if (shouldQuerySuggestions) {
198+
requests.push({
199+
indexName: 'nodes_index_query_suggestions',
200+
query
201+
})
202+
}
203+
151204
const { results } = await searchClient.search<
152205
AlgoliaNodePack | NodesIndexSuggestion
153206
>({
154-
requests: [
155-
{
156-
query,
157-
indexName: 'nodes_index',
158-
attributesToRetrieve: RETRIEVE_ATTRIBUTES,
159-
...rest,
160-
hitsPerPage: pageSize,
161-
page: pageNumber
162-
},
163-
{
164-
indexName: 'nodes_index_query_suggestions',
165-
query
166-
}
167-
],
207+
requests,
168208
strategy: 'none'
169209
})
170210

171-
const [nodePacks, querySuggestions] = results as [
211+
const [nodePacks, querySuggestions = { hits: [] }] = results as [
172212
SearchResponse<AlgoliaNodePack>,
173213
SearchResponse<NodesIndexSuggestion>
174214
]
@@ -179,8 +219,27 @@ export const useAlgoliaSearchService = () => {
179219
}
180220
}
181221

222+
const searchPacksCached = async (
223+
query: string,
224+
params: SearchNodePacksParams
225+
): Promise<SearchPacksResult> => {
226+
const cacheKey = paramsToCacheKey({ query, ...params })
227+
const cachedResult = searchPacksCache.get(cacheKey)
228+
if (cachedResult !== undefined) return cachedResult
229+
230+
const result = await searchPacks(query, params)
231+
searchPacksCache.set(cacheKey, result)
232+
return result
233+
}
234+
235+
const clearSearchPacksCache = () => {
236+
searchPacksCache.clear()
237+
}
238+
182239
return {
183240
searchPacks,
184-
toRegistryPack
241+
searchPacksCached,
242+
toRegistryPack,
243+
clearSearchPacksCache
185244
}
186245
}

0 commit comments

Comments
 (0)