From 141a567927dbb5455c693376d0ab007661caf404 Mon Sep 17 00:00:00 2001 From: LD-Reborn Date: Mon, 19 Jan 2026 02:18:00 +0100 Subject: [PATCH] Added entity index embeddings prefetching, fixed zero-searchdomain front-end bug --- src/Server/Helper/SearchdomainHelper.cs | 24 ++++++++++++--- src/Server/Resources/SharedResources.de.resx | 9 ++++++ src/Server/Resources/SharedResources.en.resx | 9 ++++++ src/Server/Views/Home/Index.cshtml | 7 ----- src/Server/Views/Home/Searchdomains.cshtml | 32 ++++++++++++++++---- src/Server/Views/Shared/_Layout.cshtml | 1 + src/Server/wwwroot/js/site.js | 12 +++++++- src/Shared/Models/SearchdomainModels.cs | 4 ++- 8 files changed, 78 insertions(+), 20 deletions(-) diff --git a/src/Server/Helper/SearchdomainHelper.cs b/src/Server/Helper/SearchdomainHelper.cs index 7e62bda..b422aa1 100644 --- a/src/Server/Helper/SearchdomainHelper.cs +++ b/src/Server/Helper/SearchdomainHelper.cs @@ -58,28 +58,42 @@ public class SearchdomainHelper(ILogger logger, DatabaseHelp return null; } - // toBeCached: model -> [datapoint.text * n] + // Prefetch embeddings Dictionary> toBeCached = []; + Dictionary> toBeCachedParallel = []; foreach (JSONEntity jSONEntity in jsonEntities) { + Dictionary> targetDictionary = toBeCached; + if (searchdomainManager.GetSearchdomain(jSONEntity.Searchdomain).settings.ParallelEmbeddingsPrefetch) + { + targetDictionary = toBeCachedParallel; + } foreach (JSONDatapoint datapoint in jSONEntity.Datapoints) { foreach (string model in datapoint.Model) { - if (!toBeCached.ContainsKey(model)) + if (!targetDictionary.ContainsKey(model)) { - toBeCached[model] = []; + targetDictionary[model] = []; } - toBeCached[model].Add(datapoint.Text); + targetDictionary[model].Add(datapoint.Text); } } } + foreach (var toBeCachedKV in toBeCached) { string model = toBeCachedKV.Key; List uniqueStrings = [.. toBeCachedKV.Value.Distinct()]; Datapoint.GetEmbeddings([.. uniqueStrings], [model], aIProvider, embeddingCache); - } + } + Parallel.ForEach(toBeCachedParallel, toBeCachedParallelKV => + { + string model = toBeCachedParallelKV.Key; + List uniqueStrings = [.. toBeCachedParallelKV.Value.Distinct()]; + Datapoint.GetEmbeddings([.. uniqueStrings], [model], aIProvider, embeddingCache); + }); + // Index/parse the entities ConcurrentQueue retVal = []; ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = 16 }; // <-- This is needed! Otherwise if we try to index 100+ entities at once, it spawns 100 threads, exploding the SQL pool Parallel.ForEach(jsonEntities, parallelOptions, jSONEntity => diff --git a/src/Server/Resources/SharedResources.de.resx b/src/Server/Resources/SharedResources.de.resx index 633a8a9..d781f35 100644 --- a/src/Server/Resources/SharedResources.de.resx +++ b/src/Server/Resources/SharedResources.de.resx @@ -315,4 +315,13 @@ Anzahl der Einträge, die insgesamt in den Query-Cache der geladenen Searchdomains passen. + + Query Cache Größe + + + Embeddings parallel prefetchen + + + Wenn diese Einstellung aktiv ist, wird das Abrufen von Embeddings beim Indizieren von Entities parallelisiert. Deaktiviere diese Einstellung, falls Model-unloading ein Problem ist. + \ No newline at end of file diff --git a/src/Server/Resources/SharedResources.en.resx b/src/Server/Resources/SharedResources.en.resx index f19d653..56ba0f1 100644 --- a/src/Server/Resources/SharedResources.en.resx +++ b/src/Server/Resources/SharedResources.en.resx @@ -315,4 +315,13 @@ Number of query cache entries that can be stored in the query cache of all loaded searchdomains. + + Query Cache size + + + Embeddings parallel prefetching + + + With this setting activated the embeddings retrieval will be parallelized when indexing entities. Disable this setting if model unloading is an issue. + \ No newline at end of file diff --git a/src/Server/Views/Home/Index.cshtml b/src/Server/Views/Home/Index.cshtml index ea70424..56b5143 100644 --- a/src/Server/Views/Home/Index.cshtml +++ b/src/Server/Views/Home/Index.cshtml @@ -152,13 +152,6 @@ var searchdomains = null; document.addEventListener('DOMContentLoaded', async () => { - // Initialize all tooltips - var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')) - var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { - return new bootstrap.Tooltip(tooltipTriggerEl) - }); - - let searchdomainCount = document.getElementById("searchdomainCount"); showThrobber(searchdomainCount); let searchdomainEntityCount = document.getElementById("searchdomainEntityCount"); diff --git a/src/Server/Views/Home/Searchdomains.cshtml b/src/Server/Views/Home/Searchdomains.cshtml index bbe933b..2eccf90 100644 --- a/src/Server/Views/Home/Searchdomains.cshtml +++ b/src/Server/Views/Home/Searchdomains.cshtml @@ -66,10 +66,17 @@ -
+
+
+ + + +
@@ -362,10 +369,14 @@
-
+
+
+ + +
@@ -672,7 +683,10 @@ queriesFilter.addEventListener('input', () => { populateQueriesTable(queriesFilter.value); }); - selectDomain(0); + try + { + selectDomain(0); + } catch (error) {} document .getElementById('searchdomainRename') @@ -711,7 +725,8 @@ const domainKey = getSelectedDomainKey(); const cacheReconciliation = document.getElementById('searchdomainConfigCacheReconciliation').checked; const queryCacheSize = document.getElementById('searchdomainConfigQueryCacheSize').value; - updateSearchdomainConfig(domainKey, { CacheReconciliation: cacheReconciliation, QueryCacheSize: queryCacheSize}); + const parallelEmbeddingsPrefetch = document.getElementById('searchdomainConfigParallelEmbeddingsPrefetch').checked; + updateSearchdomainConfig(domainKey, { CacheReconciliation: cacheReconciliation, QueryCacheSize: queryCacheSize, ParallelEmbeddingsPrefetch: parallelEmbeddingsPrefetch}); }); document @@ -794,8 +809,8 @@ const name = document.getElementById('createSearchdomainName').value; const queryCacheSize = document.getElementById('createSearchdomainQueryCacheSize').value; const cacheReconciliation = document.getElementById('createSearchdomainWithCacheReconciliation').checked; - const settings = { CacheReconciliation: cacheReconciliation, QueryCacheSize: queryCacheSize }; - // Implement create logic here + const parallelEmbeddingsPrefetch = document.getElementById('createSearchdomainConfigParallelEmbeddingsPrefetch').checked; + const settings = { CacheReconciliation: cacheReconciliation, QueryCacheSize: queryCacheSize, ParallelEmbeddingsPrefetch: parallelEmbeddingsPrefetch }; fetch(`/Searchdomain?searchdomain=${encodeURIComponent(name)}`, { method: 'POST', headers: { @@ -1072,9 +1087,11 @@ let searchdomainConfigPromise = getSearchdomainConfig(getSelectedDomainKey()); let configElementCachereconciliation = document.getElementById('searchdomainConfigCacheReconciliation'); let configElementCacheSize = document.getElementById('searchdomainConfigQueryCacheSize'); + let configElementParallelEmbeddingsPrefetch = document.getElementById('searchdomainConfigParallelEmbeddingsPrefetch'); showThrobber(document.querySelector('#searchdomainConfigQueryCacheSize'), true); showThrobber(document.querySelector('#searchdomainConfigCacheReconciliation'), true); + showThrobber(document.querySelector('#searchdomainConfigParallelEmbeddingsPrefetch'), true); let cacheUtilizationPromise = getSearchdomainCacheUtilization(getSelectedDomainKey()); let databaseUtilizationPromise = getSearchdomainDatabaseUtilization(getSelectedDomainKey()); @@ -1117,11 +1134,14 @@ searchdomainConfigPromise.then(searchdomainConfig => { hideThrobber(document.querySelector('#searchdomainConfigCacheReconciliation'), true); + hideThrobber(document.querySelector('#searchdomainConfigParallelEmbeddingsPrefetch'), true); + if (searchdomainConfig != null && searchdomainConfig.Settings != null) { configElementCacheSize.value = searchdomainConfig.Settings.QueryCacheSize; configElementCachereconciliation.checked = searchdomainConfig.Settings.CacheReconciliation; configElementCachereconciliation.disabled = false; + configElementParallelEmbeddingsPrefetch.checked = searchdomainConfig.Settings.ParallelEmbeddingsPrefetch; } else { configElementCachereconciliation.disabled = true; showToast("@T["Unable to fetch searchdomain config"]", "danger"); diff --git a/src/Server/Views/Shared/_Layout.cshtml b/src/Server/Views/Shared/_Layout.cshtml index 9904cf3..c6bd290 100644 --- a/src/Server/Views/Shared/_Layout.cshtml +++ b/src/Server/Views/Shared/_Layout.cshtml @@ -12,6 +12,7 @@ @ViewData["Title"] - embeddingsearch + @if (!Context.Request.Query.ContainsKey("renderRaw") && !Context.Request.Query.ContainsKey("noCriticalCSS")) { diff --git a/src/Server/wwwroot/js/site.js b/src/Server/wwwroot/js/site.js index 12425ec..6fd2adc 100644 --- a/src/Server/wwwroot/js/site.js +++ b/src/Server/wwwroot/js/site.js @@ -48,4 +48,14 @@ function showToast(message, type) { const bsToast = new bootstrap.Toast(toast, { delay: 10000 }); bsToast.show(); toast.addEventListener('hidden.bs.toast', () => toast.remove()); -} \ No newline at end of file +} + +document.addEventListener('DOMContentLoaded', async () => { + // Initialize all tooltips + var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')) + var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) { + let retVal = new bootstrap.Tooltip(tooltipTriggerEl); + tooltipTriggerEl.role = "tooltip"; + return retVal; + }); +}); \ No newline at end of file diff --git a/src/Shared/Models/SearchdomainModels.cs b/src/Shared/Models/SearchdomainModels.cs index dccb5e8..27c1ec8 100644 --- a/src/Shared/Models/SearchdomainModels.cs +++ b/src/Shared/Models/SearchdomainModels.cs @@ -95,12 +95,14 @@ public struct DateTimedSearchResult(DateTime dateTime, List results) } } -public struct SearchdomainSettings(bool cacheReconciliation = false, int queryCacheSize = 1_000_000) +public struct SearchdomainSettings(bool cacheReconciliation = false, int queryCacheSize = 1_000_000, bool parallelEmbeddingsPrefetch = false) { [JsonPropertyName("CacheReconciliation")] public bool CacheReconciliation { get; set; } = cacheReconciliation; [JsonPropertyName("QueryCacheSize")] public int QueryCacheSize { get; set; } = queryCacheSize; + [JsonPropertyName("ParallelEmbeddingsPrefetch")] + public bool ParallelEmbeddingsPrefetch { get; set; } = parallelEmbeddingsPrefetch; } public static class MemorySizes