Merge pull request #88 from LD-Reborn/66-add-query-cache-size-limit
66 add query cache size limit
This commit is contained in:
@@ -121,13 +121,13 @@ public class Client
|
|||||||
}), new StringContent(settings, Encoding.UTF8, "application/json"));
|
}), new StringContent(settings, Encoding.UTF8, "application/json"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SearchdomainSearchesResults> SearchdomainGetQueriesAsync(string searchdomain)
|
public async Task<SearchdomainQueriesResults> SearchdomainGetQueriesAsync(string searchdomain)
|
||||||
{
|
{
|
||||||
Dictionary<string, string> parameters = new()
|
Dictionary<string, string> parameters = new()
|
||||||
{
|
{
|
||||||
{"searchdomain", searchdomain}
|
{"searchdomain", searchdomain}
|
||||||
};
|
};
|
||||||
return await FetchUrlAndProcessJson<SearchdomainSearchesResults>(HttpMethod.Get, GetUrl($"{baseUri}/Searchdomain", "Queries", parameters));
|
return await FetchUrlAndProcessJson<SearchdomainQueriesResults>(HttpMethod.Get, GetUrl($"{baseUri}/Searchdomain", "Queries", parameters));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EntityQueryResults> SearchdomainQueryAsync(string query)
|
public async Task<EntityQueryResults> SearchdomainQueryAsync(string query)
|
||||||
@@ -190,13 +190,13 @@ public class Client
|
|||||||
return await FetchUrlAndProcessJson<SearchdomainUpdateResults>(HttpMethod.Put, GetUrl($"{baseUri}/Searchdomain", "Settings", parameters), content);
|
return await FetchUrlAndProcessJson<SearchdomainUpdateResults>(HttpMethod.Put, GetUrl($"{baseUri}/Searchdomain", "Settings", parameters), content);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SearchdomainSearchCacheSizeResults> SearchdomainGetQueryCacheSizeAsync(string searchdomain)
|
public async Task<SearchdomainQueryCacheSizeResults> SearchdomainGetQueryCacheSizeAsync(string searchdomain)
|
||||||
{
|
{
|
||||||
Dictionary<string, string> parameters = new()
|
Dictionary<string, string> parameters = new()
|
||||||
{
|
{
|
||||||
{"searchdomain", searchdomain}
|
{"searchdomain", searchdomain}
|
||||||
};
|
};
|
||||||
return await FetchUrlAndProcessJson<SearchdomainSearchCacheSizeResults>(HttpMethod.Get, GetUrl($"{baseUri}/Searchdomain/QueryCache", "Size", parameters));
|
return await FetchUrlAndProcessJson<SearchdomainQueryCacheSizeResults>(HttpMethod.Get, GetUrl($"{baseUri}/Searchdomain/QueryCache", "Size", parameters));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<SearchdomainInvalidateCacheResults> SearchdomainClearQueryCache(string searchdomain)
|
public async Task<SearchdomainInvalidateCacheResults> SearchdomainClearQueryCache(string searchdomain)
|
||||||
@@ -222,9 +222,9 @@ public class Client
|
|||||||
return await FetchUrlAndProcessJson<ServerGetModelsResult>(HttpMethod.Get, GetUrl($"{baseUri}/Server", "Models", []));
|
return await FetchUrlAndProcessJson<ServerGetModelsResult>(HttpMethod.Get, GetUrl($"{baseUri}/Server", "Models", []));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ServerGetEmbeddingCacheSizeResult> ServerGetEmbeddingCacheSizeAsync()
|
public async Task<ServerGetStatsResult> ServerGetStatsAsync()
|
||||||
{
|
{
|
||||||
return await FetchUrlAndProcessJson<ServerGetEmbeddingCacheSizeResult>(HttpMethod.Get, GetUrl($"{baseUri}/Server/EmbeddingCache", "Size", []));
|
return await FetchUrlAndProcessJson<ServerGetStatsResult>(HttpMethod.Get, GetUrl($"{baseUri}/Server/Stats", "Size", []));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<T> FetchUrlAndProcessJson<T>(HttpMethod httpMethod, string url, HttpContent? content = null)
|
private async Task<T> FetchUrlAndProcessJson<T>(HttpMethod httpMethod, string url, HttpContent? content = null)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Http.HttpResults;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Server.Exceptions;
|
using Server.Exceptions;
|
||||||
using Server.Helper;
|
using Server.Helper;
|
||||||
|
using Shared;
|
||||||
using Shared.Models;
|
using Shared.Models;
|
||||||
|
|
||||||
namespace Server.Controllers;
|
namespace Server.Controllers;
|
||||||
@@ -54,6 +55,10 @@ public class SearchdomainController : ControllerBase
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (settings.QueryCacheSize <= 0)
|
||||||
|
{
|
||||||
|
settings.QueryCacheSize = 1_000_000; // TODO get rid of this magic number
|
||||||
|
}
|
||||||
int id = _domainManager.CreateSearchdomain(searchdomain, settings);
|
int id = _domainManager.CreateSearchdomain(searchdomain, settings);
|
||||||
return Ok(new SearchdomainCreateResults(){Id = id, Success = true});
|
return Ok(new SearchdomainCreateResults(){Id = id, Success = true});
|
||||||
} catch (Exception)
|
} catch (Exception)
|
||||||
@@ -134,13 +139,13 @@ public class SearchdomainController : ControllerBase
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="searchdomain">Name of the searchdomain</param>
|
/// <param name="searchdomain">Name of the searchdomain</param>
|
||||||
[HttpGet("Queries")]
|
[HttpGet("Queries")]
|
||||||
public ActionResult<SearchdomainSearchesResults> GetQueries([Required]string searchdomain)
|
public ActionResult<SearchdomainQueriesResults> GetQueries([Required]string searchdomain)
|
||||||
{
|
{
|
||||||
(Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
|
(Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
|
||||||
if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
|
if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
|
||||||
Dictionary<string, DateTimedSearchResult> searchCache = searchdomain_.searchCache;
|
Dictionary<string, DateTimedSearchResult> searchCache = searchdomain_.queryCache.AsDictionary();
|
||||||
|
|
||||||
return Ok(new SearchdomainSearchesResults() { Searches = searchCache, Success = true });
|
return Ok(new SearchdomainQueriesResults() { Searches = searchCache, Success = true });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -175,7 +180,7 @@ public class SearchdomainController : ControllerBase
|
|||||||
{
|
{
|
||||||
(Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
|
(Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
|
||||||
if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
|
if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
|
||||||
Dictionary<string, DateTimedSearchResult> searchCache = searchdomain_.searchCache;
|
EnumerableLruCache<string, DateTimedSearchResult> searchCache = searchdomain_.queryCache;
|
||||||
bool containsKey = searchCache.ContainsKey(query);
|
bool containsKey = searchCache.ContainsKey(query);
|
||||||
if (containsKey)
|
if (containsKey)
|
||||||
{
|
{
|
||||||
@@ -196,7 +201,7 @@ public class SearchdomainController : ControllerBase
|
|||||||
{
|
{
|
||||||
(Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
|
(Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
|
||||||
if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
|
if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
|
||||||
Dictionary<string, DateTimedSearchResult> searchCache = searchdomain_.searchCache;
|
EnumerableLruCache<string, DateTimedSearchResult> searchCache = searchdomain_.queryCache;
|
||||||
bool containsKey = searchCache.ContainsKey(query);
|
bool containsKey = searchCache.ContainsKey(query);
|
||||||
if (containsKey)
|
if (containsKey)
|
||||||
{
|
{
|
||||||
@@ -237,6 +242,7 @@ public class SearchdomainController : ControllerBase
|
|||||||
};
|
};
|
||||||
searchdomain_.helper.ExecuteSQLNonQuery("UPDATE searchdomain set settings = @settings WHERE id = @id", parameters);
|
searchdomain_.helper.ExecuteSQLNonQuery("UPDATE searchdomain set settings = @settings WHERE id = @id", parameters);
|
||||||
searchdomain_.settings = request;
|
searchdomain_.settings = request;
|
||||||
|
searchdomain_.queryCache.Capacity = request.QueryCacheSize;
|
||||||
return Ok(new SearchdomainUpdateResults(){Success = true});
|
return Ok(new SearchdomainUpdateResults(){Success = true});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,15 +251,17 @@ public class SearchdomainController : ControllerBase
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="searchdomain">Name of the searchdomain</param>
|
/// <param name="searchdomain">Name of the searchdomain</param>
|
||||||
[HttpGet("QueryCache/Size")]
|
[HttpGet("QueryCache/Size")]
|
||||||
public ActionResult<SearchdomainSearchCacheSizeResults> GetSearchCacheSize([Required]string searchdomain)
|
public ActionResult<SearchdomainQueryCacheSizeResults> GetQueryCacheSize([Required]string searchdomain)
|
||||||
{
|
{
|
||||||
if (!SearchdomainHelper.IsSearchdomainLoaded(_domainManager, searchdomain))
|
if (!SearchdomainHelper.IsSearchdomainLoaded(_domainManager, searchdomain))
|
||||||
{
|
{
|
||||||
return Ok(new SearchdomainSearchCacheSizeResults() { QueryCacheSizeBytes = 0, Success = true });
|
return Ok(new SearchdomainQueryCacheSizeResults() { SizeBytes = 0, ElementCount = 0, ElementMaxCount = 0, Success = true });
|
||||||
}
|
}
|
||||||
(Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
|
(Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
|
||||||
if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
|
if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
|
||||||
return Ok(new SearchdomainSearchCacheSizeResults() { QueryCacheSizeBytes = searchdomain_.GetSearchCacheSize(), Success = true });
|
int elementCount = searchdomain_.queryCache.Count;
|
||||||
|
int ElementMaxCount = searchdomain_.settings.QueryCacheSize;
|
||||||
|
return Ok(new SearchdomainQueryCacheSizeResults() { SizeBytes = searchdomain_.GetSearchCacheSize(), ElementCount = elementCount, ElementMaxCount = ElementMaxCount, Success = true });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -91,4 +91,10 @@ public static class DatabaseMigrations
|
|||||||
helper.ExecuteSQLNonQuery("ALTER TABLE datapoint ADD COLUMN similaritymethod VARCHAR(512) NULL DEFAULT 'Cosine' AFTER probmethod_embedding", []);
|
helper.ExecuteSQLNonQuery("ALTER TABLE datapoint ADD COLUMN similaritymethod VARCHAR(512) NULL DEFAULT 'Cosine' AFTER probmethod_embedding", []);
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int UpdateFrom4(SQLHelper helper)
|
||||||
|
{
|
||||||
|
helper.ExecuteSQLNonQuery("UPDATE searchdomain SET settings = JSON_SET(settings, '$.QueryCacheSize', 1000000) WHERE JSON_EXTRACT(settings, '$.QueryCacheSize') is NULL;", []); // Set QueryCacheSize to a default of 1000000
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ using System.Text.Json;
|
|||||||
using ElmahCore.Mvc.Logger;
|
using ElmahCore.Mvc.Logger;
|
||||||
using MySql.Data.MySqlClient;
|
using MySql.Data.MySqlClient;
|
||||||
using Server.Helper;
|
using Server.Helper;
|
||||||
|
using Shared;
|
||||||
using Shared.Models;
|
using Shared.Models;
|
||||||
using AdaptiveExpressions;
|
using AdaptiveExpressions;
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ public class Searchdomain
|
|||||||
public string searchdomain;
|
public string searchdomain;
|
||||||
public int id;
|
public int id;
|
||||||
public SearchdomainSettings settings;
|
public SearchdomainSettings settings;
|
||||||
public Dictionary<string, DateTimedSearchResult> searchCache; // Key: query, Value: Search results for that query (with timestamp)
|
public EnumerableLruCache<string, DateTimedSearchResult> queryCache; // Key: query, Value: Search results for that query (with timestamp)
|
||||||
public List<Entity> entityCache;
|
public List<Entity> entityCache;
|
||||||
public List<string> modelsInUse;
|
public List<string> modelsInUse;
|
||||||
public LRUCache<string, Dictionary<string, float[]>> embeddingCache;
|
public LRUCache<string, Dictionary<string, float[]>> embeddingCache;
|
||||||
@@ -33,12 +34,12 @@ public class Searchdomain
|
|||||||
this.aIProvider = aIProvider;
|
this.aIProvider = aIProvider;
|
||||||
this.embeddingCache = embeddingCache;
|
this.embeddingCache = embeddingCache;
|
||||||
this._logger = logger;
|
this._logger = logger;
|
||||||
searchCache = [];
|
|
||||||
entityCache = [];
|
entityCache = [];
|
||||||
connection = new MySqlConnection(connectionString);
|
connection = new MySqlConnection(connectionString);
|
||||||
connection.Open();
|
connection.Open();
|
||||||
helper = new SQLHelper(connection, connectionString);
|
helper = new SQLHelper(connection, connectionString);
|
||||||
settings = GetSettings();
|
settings = GetSettings();
|
||||||
|
queryCache = new(settings.QueryCacheSize);
|
||||||
modelsInUse = []; // To make the compiler shut up - it is set in UpdateSearchDomain() don't worry // yeah, about that...
|
modelsInUse = []; // To make the compiler shut up - it is set in UpdateSearchDomain() don't worry // yeah, about that...
|
||||||
if (!runEmpty)
|
if (!runEmpty)
|
||||||
{
|
{
|
||||||
@@ -163,7 +164,7 @@ public class Searchdomain
|
|||||||
|
|
||||||
public List<(float, string)> Search(string query, int? topN = null)
|
public List<(float, string)> Search(string query, int? topN = null)
|
||||||
{
|
{
|
||||||
if (searchCache.TryGetValue(query, out DateTimedSearchResult cachedResult))
|
if (queryCache.TryGetValue(query, out DateTimedSearchResult cachedResult))
|
||||||
{
|
{
|
||||||
cachedResult.AccessDateTimes.Add(DateTime.Now);
|
cachedResult.AccessDateTimes.Add(DateTime.Now);
|
||||||
return [.. cachedResult.Results.Select(r => (r.Score, r.Name))];
|
return [.. cachedResult.Results.Select(r => (r.Score, r.Name))];
|
||||||
@@ -187,7 +188,7 @@ public class Searchdomain
|
|||||||
[.. sortedResults.Select(r =>
|
[.. sortedResults.Select(r =>
|
||||||
new ResultItem(r.Item1, r.Item2 ))]
|
new ResultItem(r.Item1, r.Item2 ))]
|
||||||
);
|
);
|
||||||
searchCache[query] = new DateTimedSearchResult(DateTime.Now, searchResult);
|
queryCache.Set(query, new DateTimedSearchResult(DateTime.Now, searchResult));
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,7 +293,7 @@ public class Searchdomain
|
|||||||
{
|
{
|
||||||
if (settings.CacheReconciliation)
|
if (settings.CacheReconciliation)
|
||||||
{
|
{
|
||||||
foreach (KeyValuePair<string, DateTimedSearchResult> element in searchCache)
|
foreach (var element in queryCache)
|
||||||
{
|
{
|
||||||
string query = element.Key;
|
string query = element.Key;
|
||||||
DateTimedSearchResult searchResult = element.Value;
|
DateTimedSearchResult searchResult = element.Value;
|
||||||
@@ -322,7 +323,7 @@ public class Searchdomain
|
|||||||
{
|
{
|
||||||
if (settings.CacheReconciliation)
|
if (settings.CacheReconciliation)
|
||||||
{
|
{
|
||||||
foreach (KeyValuePair<string, DateTimedSearchResult> element in searchCache)
|
foreach (KeyValuePair<string, DateTimedSearchResult> element in queryCache)
|
||||||
{
|
{
|
||||||
string query = element.Key;
|
string query = element.Key;
|
||||||
DateTimedSearchResult searchResult = element.Value;
|
DateTimedSearchResult searchResult = element.Value;
|
||||||
@@ -337,13 +338,13 @@ public class Searchdomain
|
|||||||
|
|
||||||
public void InvalidateSearchCache()
|
public void InvalidateSearchCache()
|
||||||
{
|
{
|
||||||
searchCache = [];
|
queryCache = new(settings.QueryCacheSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long GetSearchCacheSize()
|
public long GetSearchCacheSize()
|
||||||
{
|
{
|
||||||
long sizeInBytes = 0;
|
long sizeInBytes = 0;
|
||||||
foreach (var entry in searchCache)
|
foreach (var entry in queryCache)
|
||||||
{
|
{
|
||||||
sizeInBytes += sizeof(int); // string length prefix
|
sizeInBytes += sizeof(int); // string length prefix
|
||||||
sizeInBytes += entry.Key.Length * sizeof(char); // string characters
|
sizeInBytes += entry.Key.Length * sizeof(char); // string characters
|
||||||
|
|||||||
@@ -62,11 +62,17 @@
|
|||||||
<!-- Settings -->
|
<!-- Settings -->
|
||||||
<div class="row align-items-center mb-3">
|
<div class="row align-items-center mb-3">
|
||||||
<h3>@T["Settings"]</h3>
|
<h3>@T["Settings"]</h3>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-check-label" for="searchdomainConfigQueryCacheSize">@T["Query cache size"]:</label>
|
||||||
|
<input type="number" class="form-control" id="searchdomainConfigQueryCacheSize" />
|
||||||
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<input type="checkbox" class="form-check-input" id="searchdomainConfigCacheReconciliation" />
|
<input type="checkbox" class="form-check-input" id="searchdomainConfigCacheReconciliation" />
|
||||||
<label class="form-check-label" for="searchdomainConfigCacheReconciliation">@T["Cache reconciliation"]</label>
|
<label class="form-check-label" for="searchdomainConfigCacheReconciliation">@T["Cache reconciliation"]</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2 mt-3 mt-md-0">
|
</div>
|
||||||
|
<div class="row align-items-center mb-3">
|
||||||
|
<div class="col-md-2 mt-md-0">
|
||||||
<button class="btn btn-warning w-100" id="searchdomainConfigUpdate">@T["Update"]</button>
|
<button class="btn btn-warning w-100" id="searchdomainConfigUpdate">@T["Update"]</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -347,11 +353,21 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
<div class="row align-items-center mb-3">
|
||||||
|
<div class="col-md-12">
|
||||||
<label for="createSearchdomainName" class="form-label">@T["Searchdomain name"]</label>
|
<label for="createSearchdomainName" class="form-label">@T["Searchdomain name"]</label>
|
||||||
<input type="text" class="form-control mb-3" id="createSearchdomainName" placeholder="@T["Searchdomain name"]" />
|
<input type="text" class="form-control mb-3" id="createSearchdomainName" placeholder="@T["Searchdomain name"]" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-5">
|
||||||
|
<label class="form-check-label mb-2" for="createSearchdomainQueryCacheSize">@T["Query cache size"]:</label>
|
||||||
|
<input type="number" class="form-control" id="createSearchdomainQueryCacheSize" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-7">
|
||||||
<input type="checkbox" class="form-check-input" id="createSearchdomainWithCacheReconciliation" />
|
<input type="checkbox" class="form-check-input" id="createSearchdomainWithCacheReconciliation" />
|
||||||
<label class="form-check-label" for="createSearchdomainWithCacheReconciliation">@T["Enable cache reconciliation"]</label>
|
<label class="form-check-label" for="createSearchdomainWithCacheReconciliation">@T["Enable cache reconciliation"]</label>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" id="searchdomainConfirmCreate" class="btn btn-primary" data-bs-dismiss="modal">
|
<button type="button" id="searchdomainConfirmCreate" class="btn btn-primary" data-bs-dismiss="modal">
|
||||||
@@ -694,7 +710,8 @@
|
|||||||
.addEventListener('click', () => {
|
.addEventListener('click', () => {
|
||||||
const domainKey = getSelectedDomainKey();
|
const domainKey = getSelectedDomainKey();
|
||||||
const cacheReconciliation = document.getElementById('searchdomainConfigCacheReconciliation').checked;
|
const cacheReconciliation = document.getElementById('searchdomainConfigCacheReconciliation').checked;
|
||||||
updateSearchdomainConfig(domainKey, { CacheReconciliation: cacheReconciliation});
|
const queryCacheSize = document.getElementById('searchdomainConfigQueryCacheSize').value;
|
||||||
|
updateSearchdomainConfig(domainKey, { CacheReconciliation: cacheReconciliation, QueryCacheSize: queryCacheSize});
|
||||||
});
|
});
|
||||||
|
|
||||||
document
|
document
|
||||||
@@ -775,8 +792,9 @@
|
|||||||
document.getElementById('createSearchdomainModal')
|
document.getElementById('createSearchdomainModal')
|
||||||
);
|
);
|
||||||
const name = document.getElementById('createSearchdomainName').value;
|
const name = document.getElementById('createSearchdomainName').value;
|
||||||
|
const queryCacheSize = document.getElementById('createSearchdomainQueryCacheSize').value;
|
||||||
const cacheReconciliation = document.getElementById('createSearchdomainWithCacheReconciliation').checked;
|
const cacheReconciliation = document.getElementById('createSearchdomainWithCacheReconciliation').checked;
|
||||||
const settings = { CacheReconciliation: cacheReconciliation };
|
const settings = { CacheReconciliation: cacheReconciliation, QueryCacheSize: queryCacheSize };
|
||||||
// Implement create logic here
|
// Implement create logic here
|
||||||
fetch(`/Searchdomain?searchdomain=${encodeURIComponent(name)}`, {
|
fetch(`/Searchdomain?searchdomain=${encodeURIComponent(name)}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -869,12 +887,12 @@
|
|||||||
var data = [{
|
var data = [{
|
||||||
"name": name,
|
"name": name,
|
||||||
"probmethod": probMethod,
|
"probmethod": probMethod,
|
||||||
"searchdomain": encodeURIComponent(domains[getSelectedDomainKey()]),
|
"searchdomain": domains[getSelectedDomainKey()],
|
||||||
"attributes": attributes,
|
"attributes": attributes,
|
||||||
"datapoints": datapoints
|
"datapoints": datapoints
|
||||||
}];
|
}];
|
||||||
showToast("@T["Updating entity"]", "primary");
|
showToast("@T["Updating entity"]", "primary");
|
||||||
fetch(`/Entity`, {
|
fetch(`/Entities`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@@ -1053,7 +1071,10 @@
|
|||||||
|
|
||||||
let searchdomainConfigPromise = getSearchdomainConfig(getSelectedDomainKey());
|
let searchdomainConfigPromise = getSearchdomainConfig(getSelectedDomainKey());
|
||||||
let configElementCachereconciliation = document.getElementById('searchdomainConfigCacheReconciliation');
|
let configElementCachereconciliation = document.getElementById('searchdomainConfigCacheReconciliation');
|
||||||
|
let configElementCacheSize = document.getElementById('searchdomainConfigQueryCacheSize');
|
||||||
|
|
||||||
|
showThrobber(document.querySelector('#searchdomainConfigQueryCacheSize'), true);
|
||||||
|
showThrobber(document.querySelector('#searchdomainConfigCacheReconciliation'), true);
|
||||||
let cacheUtilizationPromise = getSearchdomainCacheUtilization(getSelectedDomainKey());
|
let cacheUtilizationPromise = getSearchdomainCacheUtilization(getSelectedDomainKey());
|
||||||
let databaseUtilizationPromise = getSearchdomainDatabaseUtilization(getSelectedDomainKey());
|
let databaseUtilizationPromise = getSearchdomainDatabaseUtilization(getSelectedDomainKey());
|
||||||
|
|
||||||
@@ -1095,8 +1116,10 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
searchdomainConfigPromise.then(searchdomainConfig => {
|
searchdomainConfigPromise.then(searchdomainConfig => {
|
||||||
|
hideThrobber(document.querySelector('#searchdomainConfigCacheReconciliation'), true);
|
||||||
if (searchdomainConfig != null && searchdomainConfig.Settings != null)
|
if (searchdomainConfig != null && searchdomainConfig.Settings != null)
|
||||||
{
|
{
|
||||||
|
configElementCacheSize.value = searchdomainConfig.Settings.QueryCacheSize;
|
||||||
configElementCachereconciliation.checked = searchdomainConfig.Settings.CacheReconciliation;
|
configElementCachereconciliation.checked = searchdomainConfig.Settings.CacheReconciliation;
|
||||||
configElementCachereconciliation.disabled = false;
|
configElementCachereconciliation.disabled = false;
|
||||||
} else {
|
} else {
|
||||||
@@ -1106,10 +1129,11 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
cacheUtilizationPromise.then(cacheUtilization => {
|
cacheUtilizationPromise.then(cacheUtilization => {
|
||||||
if (cacheUtilization != null && cacheUtilization.QueryCacheSizeBytes != null)
|
hideThrobber(document.querySelector('#searchdomainConfigQueryCacheSize'), true);
|
||||||
|
if (cacheUtilization != null && cacheUtilization.SizeBytes != null)
|
||||||
{
|
{
|
||||||
document.querySelector('#cacheUtilization').innerText =
|
document.querySelector('#cacheUtilization').innerText =
|
||||||
`${NumberOfBytesAsHumanReadable(cacheUtilization.QueryCacheSizeBytes)}`;
|
`${NumberOfBytesAsHumanReadable(cacheUtilization.SizeBytes)}`;
|
||||||
} else {
|
} else {
|
||||||
showToast("@T["Unable to fetch searchdomain cache utilization"]", "danger");
|
showToast("@T["Unable to fetch searchdomain cache utilization"]", "danger");
|
||||||
console.error('Failed to fetch searchdomain cache utilization');
|
console.error('Failed to fetch searchdomain cache utilization');
|
||||||
@@ -1289,15 +1313,31 @@
|
|||||||
domainItem.classList.add('list-group-item-danger');
|
domainItem.classList.add('list-group-item-danger');
|
||||||
}
|
}
|
||||||
|
|
||||||
function showThrobber(element = null) {
|
function showThrobber(element = null, direct = false) {
|
||||||
if (element == null) element = document;
|
if (element == null) element = document;
|
||||||
|
if (direct) {
|
||||||
|
let spinner = document.createElement('div');
|
||||||
|
spinner.classList.add('spinner');
|
||||||
|
spinner.style.position = "absolute";
|
||||||
|
spinner.style.marginTop = "0.5rem";
|
||||||
|
spinner.style.marginLeft = "0.5rem";
|
||||||
|
element.style.opacity = "0.25";
|
||||||
|
element.parentElement.insertBefore(spinner, element);
|
||||||
|
} else {
|
||||||
element.querySelector('.spinner').classList.remove('d-none');
|
element.querySelector('.spinner').classList.remove('d-none');
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideThrobber(element = null) {
|
}
|
||||||
|
|
||||||
|
function hideThrobber(element = null, direct = false) {
|
||||||
if (element == null) element = document;
|
if (element == null) element = document;
|
||||||
|
if (direct) {
|
||||||
|
element.previousElementSibling.remove()
|
||||||
|
element.style.opacity = "1";
|
||||||
|
} else {
|
||||||
element.querySelector('.spinner').classList.add('d-none');
|
element.querySelector('.spinner').classList.add('d-none');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function showEntityDetails(entity) {
|
function showEntityDetails(entity) {
|
||||||
// Title
|
// Title
|
||||||
|
|||||||
240
src/Shared/LRUCache.cs
Normal file
240
src/Shared/LRUCache.cs
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
namespace Shared;
|
||||||
|
|
||||||
|
public sealed class EnumerableLruCache<TKey, TValue> where TKey : notnull
|
||||||
|
{
|
||||||
|
private sealed record CacheItem(TKey Key, TValue Value);
|
||||||
|
|
||||||
|
private readonly Dictionary<TKey, LinkedListNode<CacheItem>> _map;
|
||||||
|
private readonly LinkedList<CacheItem> _lruList;
|
||||||
|
private readonly ReaderWriterLockSlim _lock = new();
|
||||||
|
|
||||||
|
private int _capacity;
|
||||||
|
|
||||||
|
public EnumerableLruCache(int capacity)
|
||||||
|
{
|
||||||
|
if (capacity <= 0)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(capacity));
|
||||||
|
|
||||||
|
_capacity = capacity;
|
||||||
|
_map = new Dictionary<TKey, LinkedListNode<CacheItem>>(capacity);
|
||||||
|
_lruList = new LinkedList<CacheItem>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Capacity
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
_lock.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _capacity;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value);
|
||||||
|
|
||||||
|
_lock.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_capacity = value;
|
||||||
|
TrimIfNeeded();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
_lock.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _map.Count;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TValue this[TKey key]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!TryGetValue(key, out var value))
|
||||||
|
throw new KeyNotFoundException();
|
||||||
|
|
||||||
|
return value!;
|
||||||
|
}
|
||||||
|
set => Set(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetValue(TKey key, out TValue? value)
|
||||||
|
{
|
||||||
|
_lock.EnterUpgradeableReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_map.TryGetValue(key, out var node))
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = node.Value.Value;
|
||||||
|
|
||||||
|
// LRU aktualisieren
|
||||||
|
_lock.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_lruList.Remove(node);
|
||||||
|
_lruList.AddFirst(node);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitWriteLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitUpgradeableReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Set(TKey key, TValue value)
|
||||||
|
{
|
||||||
|
_lock.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_map.TryGetValue(key, out var existing))
|
||||||
|
{
|
||||||
|
// Update + nach vorne
|
||||||
|
existing.Value = existing.Value with { Value = value };
|
||||||
|
_lruList.Remove(existing);
|
||||||
|
_lruList.AddFirst(existing);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = new CacheItem(key, value);
|
||||||
|
var node = new LinkedListNode<CacheItem>(item);
|
||||||
|
|
||||||
|
_lruList.AddFirst(node);
|
||||||
|
_map[key] = node;
|
||||||
|
|
||||||
|
TrimIfNeeded();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(TKey key)
|
||||||
|
{
|
||||||
|
_lock.EnterWriteLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_map.TryGetValue(key, out var node))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
_lruList.Remove(node);
|
||||||
|
_map.Remove(key);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsKey(TKey key)
|
||||||
|
{
|
||||||
|
_lock.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _map.ContainsKey(key);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<TKey, TValue> AsDictionary()
|
||||||
|
{
|
||||||
|
_lock.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _map.Values.ToDictionary(
|
||||||
|
n => n.Value.Key,
|
||||||
|
n => n.Value.Value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<KeyValuePair<TKey, TValue>> Items()
|
||||||
|
{
|
||||||
|
_lock.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var item in _lruList)
|
||||||
|
{
|
||||||
|
yield return new KeyValuePair<TKey, TValue>(item.Key, item.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
||||||
|
{
|
||||||
|
List<KeyValuePair<TKey, TValue>> snapshot;
|
||||||
|
|
||||||
|
_lock.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
snapshot = new List<KeyValuePair<TKey, TValue>>(_map.Count);
|
||||||
|
|
||||||
|
foreach (var item in _lruList)
|
||||||
|
{
|
||||||
|
snapshot.Add(new KeyValuePair<TKey, TValue>(
|
||||||
|
item.Key,
|
||||||
|
item.Value
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitReadLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
return snapshot.GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TrimIfNeeded()
|
||||||
|
{
|
||||||
|
while (_map.Count > _capacity)
|
||||||
|
{
|
||||||
|
var lruNode = _lruList.Last!;
|
||||||
|
_lruList.RemoveLast();
|
||||||
|
_map.Remove(lruNode.Value.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -95,10 +95,12 @@ public struct DateTimedSearchResult(DateTime dateTime, List<ResultItem> results)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct SearchdomainSettings(bool cacheReconciliation = false)
|
public struct SearchdomainSettings(bool cacheReconciliation = false, int queryCacheSize = 1_000_000)
|
||||||
{
|
{
|
||||||
[JsonPropertyName("CacheReconciliation")]
|
[JsonPropertyName("CacheReconciliation")]
|
||||||
public bool CacheReconciliation { get; set; } = cacheReconciliation;
|
public bool CacheReconciliation { get; set; } = cacheReconciliation;
|
||||||
|
[JsonPropertyName("QueryCacheSize")]
|
||||||
|
public int QueryCacheSize { get; set; } = queryCacheSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class MemorySizes
|
public static class MemorySizes
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
using Shared;
|
||||||
|
|
||||||
namespace Shared.Models;
|
namespace Shared.Models;
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ public class SearchdomainDeleteResults : SuccesMessageBaseModel
|
|||||||
public required int DeletedEntities { get; set; }
|
public required int DeletedEntities { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SearchdomainSearchesResults : SuccesMessageBaseModel
|
public class SearchdomainQueriesResults : SuccesMessageBaseModel
|
||||||
{
|
{
|
||||||
[JsonPropertyName("Searches")]
|
[JsonPropertyName("Searches")]
|
||||||
public required Dictionary<string, DateTimedSearchResult> Searches { get; set; }
|
public required Dictionary<string, DateTimedSearchResult> Searches { get; set; }
|
||||||
@@ -41,10 +42,14 @@ public class SearchdomainSettingsResults : SuccesMessageBaseModel
|
|||||||
public required SearchdomainSettings? Settings { get; set; }
|
public required SearchdomainSettings? Settings { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SearchdomainSearchCacheSizeResults : SuccesMessageBaseModel
|
public class SearchdomainQueryCacheSizeResults : SuccesMessageBaseModel
|
||||||
{
|
{
|
||||||
[JsonPropertyName("QueryCacheSizeBytes")]
|
[JsonPropertyName("ElementCount")]
|
||||||
public required long? QueryCacheSizeBytes { get; set; }
|
public required int? ElementCount { get; set; }
|
||||||
|
[JsonPropertyName("ElementMaxCount")]
|
||||||
|
public required int? ElementMaxCount { get; set; }
|
||||||
|
[JsonPropertyName("SizeBytes")]
|
||||||
|
public required long? SizeBytes { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SearchdomainInvalidateCacheResults : SuccesMessageBaseModel {}
|
public class SearchdomainInvalidateCacheResults : SuccesMessageBaseModel {}
|
||||||
|
|||||||
Reference in New Issue
Block a user