Implemented query cache size limit in front-end and in logic, Reworked LRUCache for performance, Fixed updating entities from front-end not working
This commit is contained in:
@@ -55,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)
|
||||||
@@ -255,7 +259,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});
|
||||||
int elementCount = searchdomain_.queryCache.Count();
|
int elementCount = searchdomain_.queryCache.Count;
|
||||||
int ElementMaxCount = searchdomain_.settings.QueryCacheSize;
|
int ElementMaxCount = searchdomain_.settings.QueryCacheSize;
|
||||||
return Ok(new SearchdomainQueryCacheSizeResults() { SizeBytes = searchdomain_.GetSearchCacheSize(), ElementCount = elementCount, ElementMaxCount = ElementMaxCount, Success = true });
|
return Ok(new SearchdomainQueryCacheSizeResults() { SizeBytes = searchdomain_.GetSearchCacheSize(), ElementCount = elementCount, ElementMaxCount = ElementMaxCount, Success = true });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -887,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'
|
||||||
@@ -1073,6 +1073,8 @@
|
|||||||
let configElementCachereconciliation = document.getElementById('searchdomainConfigCacheReconciliation');
|
let configElementCachereconciliation = document.getElementById('searchdomainConfigCacheReconciliation');
|
||||||
let configElementCacheSize = document.getElementById('searchdomainConfigQueryCacheSize');
|
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());
|
||||||
|
|
||||||
@@ -1114,6 +1116,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
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;
|
configElementCacheSize.value = searchdomainConfig.Settings.QueryCacheSize;
|
||||||
@@ -1126,6 +1129,7 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
cacheUtilizationPromise.then(cacheUtilization => {
|
cacheUtilizationPromise.then(cacheUtilization => {
|
||||||
|
hideThrobber(document.querySelector('#searchdomainConfigQueryCacheSize'), true);
|
||||||
if (cacheUtilization != null && cacheUtilization.SizeBytes != null)
|
if (cacheUtilization != null && cacheUtilization.SizeBytes != null)
|
||||||
{
|
{
|
||||||
document.querySelector('#cacheUtilization').innerText =
|
document.querySelector('#cacheUtilization').innerText =
|
||||||
@@ -1309,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
|
||||||
|
|||||||
@@ -1,215 +1,240 @@
|
|||||||
namespace Shared;
|
namespace Shared;
|
||||||
|
|
||||||
|
public sealed class EnumerableLruCache<TKey, TValue> where TKey : notnull
|
||||||
public class EnumerableLruCache<TKey, TValue> where TKey : notnull
|
|
||||||
{
|
{
|
||||||
private readonly Dictionary<TKey, TValue> _cache;
|
private sealed record CacheItem(TKey Key, TValue Value);
|
||||||
private readonly LinkedList<TKey> _keys = new();
|
|
||||||
|
private readonly Dictionary<TKey, LinkedListNode<CacheItem>> _map;
|
||||||
|
private readonly LinkedList<CacheItem> _lruList;
|
||||||
|
private readonly ReaderWriterLockSlim _lock = new();
|
||||||
|
|
||||||
private int _capacity;
|
private int _capacity;
|
||||||
private ReaderWriterLockSlim _readerWriterLock;
|
|
||||||
|
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
|
public int Capacity
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
_readerWriterLock.EnterReadLock();
|
_lock.EnterReadLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return _capacity;
|
return _capacity;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_readerWriterLock.ExitReadLock();
|
_lock.ExitReadLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
_readerWriterLock.EnterWriteLock();
|
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(value);
|
||||||
|
|
||||||
|
_lock.EnterWriteLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_capacity = value;
|
_capacity = value;
|
||||||
|
TrimIfNeeded();
|
||||||
// Trim cache if new capacity is smaller than current size
|
|
||||||
while (_keys.Count > _capacity)
|
|
||||||
{
|
|
||||||
TKey last = _keys.Last!.Value;
|
|
||||||
_keys.RemoveLast();
|
|
||||||
_cache.Remove(last);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_readerWriterLock.ExitWriteLock();
|
_lock.ExitWriteLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public EnumerableLruCache(int capacity)
|
public int Count
|
||||||
{
|
{
|
||||||
_readerWriterLock = new();
|
get
|
||||||
_capacity = capacity;
|
{
|
||||||
_cache = [];
|
_lock.EnterReadLock();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return _map.Count;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitReadLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public TValue this[TKey key]
|
public TValue this[TKey key]
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
_readerWriterLock.EnterReadLock();
|
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
|
try
|
||||||
{
|
{
|
||||||
return _cache[key];
|
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
|
finally
|
||||||
{
|
{
|
||||||
_readerWriterLock.ExitReadLock();
|
_lock.ExitWriteLock();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
_readerWriterLock.EnterWriteLock();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_cache.TryGetValue(key, out _))
|
|
||||||
{
|
|
||||||
_keys.Remove(key);
|
|
||||||
}
|
|
||||||
_keys.AddFirst(key);
|
|
||||||
_cache[key] = value;
|
|
||||||
|
|
||||||
if (_keys.Count > _capacity)
|
return true;
|
||||||
{
|
|
||||||
TKey last = _keys.Last!.Value;
|
|
||||||
_keys.RemoveLast();
|
|
||||||
_cache.Remove(last);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_readerWriterLock.ExitWriteLock();
|
_lock.ExitUpgradeableReadLock();
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ContainsKey(TKey key)
|
|
||||||
{
|
|
||||||
_readerWriterLock.EnterReadLock();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return _cache.ContainsKey(key);
|
|
||||||
} finally
|
|
||||||
{
|
|
||||||
_readerWriterLock.ExitReadLock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Count()
|
|
||||||
{
|
|
||||||
_readerWriterLock.EnterReadLock();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return _cache.Count;
|
|
||||||
} finally
|
|
||||||
{
|
|
||||||
_readerWriterLock.ExitReadLock();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Set(TKey key, TValue value)
|
public void Set(TKey key, TValue value)
|
||||||
{
|
{
|
||||||
_readerWriterLock.EnterWriteLock();
|
_lock.EnterWriteLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_cache.TryGetValue(key, out _))
|
if (_map.TryGetValue(key, out var existing))
|
||||||
{
|
{
|
||||||
_keys.Remove(key);
|
// Update + nach vorne
|
||||||
|
existing.Value = existing.Value with { Value = value };
|
||||||
|
_lruList.Remove(existing);
|
||||||
|
_lruList.AddFirst(existing);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
_keys.AddFirst(key);
|
|
||||||
_cache[key] = value;
|
|
||||||
|
|
||||||
if (_keys.Count > _capacity)
|
var item = new CacheItem(key, value);
|
||||||
{
|
var node = new LinkedListNode<CacheItem>(item);
|
||||||
TKey? last = _keys.Last();
|
|
||||||
_keys.RemoveLast();
|
_lruList.AddFirst(node);
|
||||||
_cache.Remove(last);
|
_map[key] = node;
|
||||||
|
|
||||||
|
TrimIfNeeded();
|
||||||
}
|
}
|
||||||
} finally
|
finally
|
||||||
{
|
{
|
||||||
_readerWriterLock.ExitWriteLock();
|
_lock.ExitWriteLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Remove(TKey key)
|
public bool Remove(TKey key)
|
||||||
{
|
{
|
||||||
_readerWriterLock.EnterWriteLock();
|
_lock.EnterWriteLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_keys.Remove(key);
|
if (!_map.TryGetValue(key, out var node))
|
||||||
_cache.Remove(key);
|
|
||||||
} finally
|
|
||||||
{
|
|
||||||
_readerWriterLock.ExitWriteLock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryGetValue(TKey key, out TValue? value)
|
|
||||||
{
|
|
||||||
_readerWriterLock.EnterUpgradeableReadLock();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (_cache.TryGetValue(key, out value))
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
_lruList.Remove(node);
|
||||||
|
_map.Remove(key);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
_readerWriterLock.EnterWriteLock();
|
finally
|
||||||
|
{
|
||||||
|
_lock.ExitWriteLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ContainsKey(TKey key)
|
||||||
|
{
|
||||||
|
_lock.EnterReadLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_keys.Remove(key);
|
return _map.ContainsKey(key);
|
||||||
_keys.AddFirst(key);
|
|
||||||
} finally
|
|
||||||
{
|
|
||||||
_readerWriterLock.ExitWriteLock();
|
|
||||||
}
|
}
|
||||||
return true;
|
finally
|
||||||
} finally
|
|
||||||
{
|
{
|
||||||
_readerWriterLock.ExitUpgradeableReadLock();
|
_lock.ExitReadLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dictionary<TKey, TValue> AsDictionary()
|
public Dictionary<TKey, TValue> AsDictionary()
|
||||||
{
|
{
|
||||||
_readerWriterLock.EnterReadLock();
|
_lock.EnterReadLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return new Dictionary<TKey, TValue>(_cache);
|
return _map.Values.ToDictionary(
|
||||||
|
n => n.Value.Key,
|
||||||
|
n => n.Value.Value
|
||||||
|
);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_readerWriterLock.ExitReadLock();
|
_lock.ExitReadLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<KeyValuePair<TKey, TValue>> Items()
|
public IEnumerable<KeyValuePair<TKey, TValue>> Items()
|
||||||
{
|
{
|
||||||
_readerWriterLock.EnterReadLock();
|
_lock.EnterReadLock();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach (var key in _keys)
|
foreach (var item in _lruList)
|
||||||
{
|
{
|
||||||
if (_cache.TryGetValue(key, out var value))
|
yield return new KeyValuePair<TKey, TValue>(item.Key, item.Value);
|
||||||
{
|
|
||||||
yield return new KeyValuePair<TKey, TValue>(key, value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally
|
finally
|
||||||
{
|
{
|
||||||
_readerWriterLock.ExitReadLock();
|
_lock.ExitReadLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
||||||
{
|
{
|
||||||
return Items().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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user