Backport cache key fix and optimizations (#10199)

* Add GetKey<T, TId>

* Update Usages of GetKey and remove GetKey<T>(object)

We shouldn't have to retain this since RepositoryCacheKeys is internal.

* Apply changes to DefaultRepositoryCachePolicy

* Add check for default/less than -1 on UserRepository PerformGet

Co-authored-by: Nikolaj <nel@umbraco.dk>
This commit is contained in:
Mole
2021-05-03 10:22:42 +02:00
committed by GitHub
parent 1a5b88525b
commit d8d4be9e8e
15 changed files with 79 additions and 50 deletions

View File

@@ -20,7 +20,7 @@ namespace Umbraco.Core.Cache
internal class DefaultRepositoryCachePolicy<TEntity, TId> : RepositoryCachePolicyBase<TEntity, TId>
where TEntity : class, IEntity
{
private static readonly TEntity[] EmptyEntities = new TEntity[0]; // const
private static readonly TEntity[] s_emptyEntities = new TEntity[0]; // const
private readonly RepositoryCachePolicyOptions _options;
public DefaultRepositoryCachePolicy(IAppPolicyCache cache, IScopeAccessor scopeAccessor, RepositoryCachePolicyOptions options)
@@ -29,16 +29,24 @@ namespace Umbraco.Core.Cache
_options = options ?? throw new ArgumentNullException(nameof(options));
}
protected string GetEntityCacheKey(object id)
protected string GetEntityCacheKey(int id) => EntityTypeCacheKey + id;
protected string GetEntityCacheKey(TId id)
{
if (id == null) throw new ArgumentNullException(nameof(id));
return GetEntityTypeCacheKey() + id;
if (EqualityComparer<TId>.Default.Equals(id, default))
{
return string.Empty;
}
if (typeof(TId).IsValueType)
{
return EntityTypeCacheKey + id;
}
return EntityTypeCacheKey + id.ToString().ToUpperInvariant();
}
protected string GetEntityTypeCacheKey()
{
return $"uRepo_{typeof (TEntity).Name}_";
}
protected string EntityTypeCacheKey { get; } = $"uRepo_{typeof(TEntity).Name}_";
protected virtual void InsertEntity(string cacheKey, TEntity entity)
{
@@ -52,7 +60,7 @@ namespace Umbraco.Core.Cache
// getting all of them, and finding nothing.
// if we can cache a zero count, cache an empty array,
// for as long as the cache is not cleared (no expiration)
Cache.Insert(GetEntityTypeCacheKey(), () => EmptyEntities);
Cache.Insert(EntityTypeCacheKey, () => s_emptyEntities);
}
else
{
@@ -81,7 +89,7 @@ namespace Umbraco.Core.Cache
}
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
Cache.Clear(GetEntityTypeCacheKey());
Cache.Clear(EntityTypeCacheKey);
}
catch
{
@@ -91,7 +99,7 @@ namespace Umbraco.Core.Cache
Cache.Clear(GetEntityCacheKey(entity.Id));
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
Cache.Clear(GetEntityTypeCacheKey());
Cache.Clear(EntityTypeCacheKey);
throw;
}
@@ -113,7 +121,7 @@ namespace Umbraco.Core.Cache
}
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
Cache.Clear(GetEntityTypeCacheKey());
Cache.Clear(EntityTypeCacheKey);
}
catch
{
@@ -123,7 +131,7 @@ namespace Umbraco.Core.Cache
Cache.Clear(GetEntityCacheKey(entity.Id));
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
Cache.Clear(GetEntityTypeCacheKey());
Cache.Clear(EntityTypeCacheKey);
throw;
}
@@ -144,7 +152,7 @@ namespace Umbraco.Core.Cache
var cacheKey = GetEntityCacheKey(entity.Id);
Cache.Clear(cacheKey);
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
Cache.Clear(GetEntityTypeCacheKey());
Cache.Clear(EntityTypeCacheKey);
}
}
@@ -195,7 +203,7 @@ namespace Umbraco.Core.Cache
else
{
// get everything we have
var entities = Cache.GetCacheItemsByKeySearch<TEntity>(GetEntityTypeCacheKey())
var entities = Cache.GetCacheItemsByKeySearch<TEntity>(EntityTypeCacheKey)
.ToArray(); // no need for null checks, we are not caching nulls
if (entities.Length > 0)
@@ -218,7 +226,7 @@ namespace Umbraco.Core.Cache
{
// if none of them were in the cache
// and we allow zero count - check for the special (empty) entry
var empty = Cache.GetCacheItem<TEntity[]>(GetEntityTypeCacheKey());
var empty = Cache.GetCacheItem<TEntity[]>(EntityTypeCacheKey);
if (empty != null) return empty;
}
}
@@ -238,7 +246,7 @@ namespace Umbraco.Core.Cache
/// <inheritdoc />
public override void ClearAll()
{
Cache.ClearByKey(GetEntityTypeCacheKey());
Cache.ClearByKey(EntityTypeCacheKey);
}
}
}

View File

@@ -86,7 +86,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
Database.Update(dto);
entity.ResetDirtyProperties();
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IConsent>(entity.Id));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IConsent, int>(entity.Id));
}
/// <inheritdoc />

View File

@@ -130,7 +130,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
foreach (var translation in dictionaryItem.Translations)
translation.Value = translation.Value.ToValidXmlString();
var dto = DictionaryItemFactory.BuildDto(dictionaryItem);
var id = Convert.ToInt32(Database.Insert(dto));
@@ -152,7 +152,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
foreach (var translation in entity.Translations)
translation.Value = translation.Value.ToValidXmlString();
var dto = DictionaryItemFactory.BuildDto(entity);
Database.Update(dto);
@@ -174,8 +174,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
entity.ResetDirtyProperties();
//Clear the cache entries that exist by uniqueid/item key
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(entity.ItemKey));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(entity.Key));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, string>(entity.ItemKey));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, Guid>(entity.Key));
}
protected override void PersistDeletedItem(IDictionaryItem entity)
@@ -186,8 +186,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
Database.Delete<DictionaryDto>("WHERE id = @Id", new { Id = entity.Key });
//Clear the cache entries that exist by uniqueid/item key
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(entity.ItemKey));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(entity.Key));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, string>(entity.ItemKey));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, Guid>(entity.Key));
entity.DeleteDate = DateTime.Now;
}
@@ -203,8 +203,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
Database.Delete<DictionaryDto>("WHERE id = @Id", new { Id = dto.UniqueId });
//Clear the cache entries that exist by uniqueid/item key
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(dto.Key));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem>(dto.UniqueId));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, string>(dto.Key));
IsolatedCache.Clear(RepositoryCacheKeys.GetKey<IDictionaryItem, Guid>(dto.UniqueId));
}
}

View File

@@ -1163,7 +1163,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (withCache)
{
// if the cache contains the (proper version of the) item, use it
var cached = IsolatedCache.GetCacheItem<IContent>(RepositoryCacheKeys.GetKey<IContent>(dto.NodeId));
var cached = IsolatedCache.GetCacheItem<IContent>(RepositoryCacheKeys.GetKey<IContent, int>(dto.NodeId));
if (cached != null && cached.VersionId == dto.DocumentVersionDto.ContentVersionDto.Id)
{
content[i] = (Content)cached;

View File

@@ -508,7 +508,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (withCache)
{
// if the cache contains the (proper version of the) item, use it
var cached = IsolatedCache.GetCacheItem<IMedia>(RepositoryCacheKeys.GetKey<IMedia>(dto.NodeId));
var cached = IsolatedCache.GetCacheItem<IMedia>(RepositoryCacheKeys.GetKey<IMedia, int>(dto.NodeId));
if (cached != null && cached.VersionId == dto.ContentVersionDto.Id)
{
content[i] = (Models.Media)cached;

View File

@@ -331,7 +331,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
}
protected override void PersistUpdatedItem(IMember entity)
{
{
// update
entity.UpdatingEntity();
@@ -534,7 +534,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var sqlSelectTemplateVersion = SqlContext.Templates.Get("Umbraco.Core.MemberRepository.SetLastLogin2", s => s
.Select<ContentVersionDto>(x => x.Id)
.From<ContentVersionDto>()
.From<ContentVersionDto>()
.InnerJoin<NodeDto>().On<NodeDto, ContentVersionDto>((l, r) => l.NodeId == r.NodeId)
.InnerJoin<MemberDto>().On<MemberDto, NodeDto>((l, r) => l.NodeId == r.NodeId)
.Where<NodeDto>(x => x.NodeObjectType == SqlTemplate.Arg<Guid>("nodeObjectType"))
@@ -606,7 +606,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (withCache)
{
// if the cache contains the (proper version of the) item, use it
var cached = IsolatedCache.GetCacheItem<IMember>(RepositoryCacheKeys.GetKey<IMember>(dto.NodeId));
var cached = IsolatedCache.GetCacheItem<IMember>(RepositoryCacheKeys.GetKey<IMember, int>(dto.NodeId));
if (cached != null && cached.VersionId == dto.ContentVersionDto.Id)
{
content[i] = (Member) cached;

View File

@@ -8,14 +8,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
/// </summary>
internal static class RepositoryCacheKeys
{
private static readonly Dictionary<Type, string> Keys = new Dictionary<Type, string>();
private static readonly Dictionary<Type, string> s_keys = new Dictionary<Type, string>();
public static string GetKey<T>()
{
var type = typeof(T);
return Keys.TryGetValue(type, out var key) ? key : (Keys[type] = "uRepo_" + type.Name + "_");
return s_keys.TryGetValue(type, out var key) ? key : (s_keys[type] = "uRepo_" + type.Name + "_");
}
public static string GetKey<T>(object id) => GetKey<T>() + id;
public static string GetKey<T, TId>(TId id)
{
if (EqualityComparer<TId>.Default.Equals(id, default))
{
return string.Empty;
}
if (typeof(TId).IsValueType)
{
return GetKey<T>() + id;
}
return GetKey<T>() + id.ToString().ToUpperInvariant();
}
}
}

View File

@@ -83,6 +83,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
protected override IUser PerformGet(int id)
{
// This will never resolve to a user, yet this is asked
// for all of the time (especially in cases of members).
// Don't issue a SQL call for this, we know it will not exist.
if (id == default || id < -1)
{
return null;
}
var sql = SqlContext.Sql()
.Select<UserDto>()
.From<UserDto>()
@@ -168,7 +176,7 @@ ORDER BY colName";
}
public Guid CreateLoginSession(int userId, string requestingIpAddress, bool cleanStaleSessions = true)
{
{
var now = DateTime.UtcNow;
var dto = new UserLoginDto
{

View File

@@ -54,9 +54,9 @@ namespace Umbraco.Web.Cache
foreach (var payload in payloads.Where(x => x.Id != default))
{
//By INT Id
isolatedCache.Clear(RepositoryCacheKeys.GetKey<IContent>(payload.Id));
isolatedCache.Clear(RepositoryCacheKeys.GetKey<IContent, int>(payload.Id));
//By GUID Key
isolatedCache.Clear(RepositoryCacheKeys.GetKey<IContent>(payload.Key));
isolatedCache.Clear(RepositoryCacheKeys.GetKey<IContent, Guid?>(payload.Key));
_idkMap.ClearCache(payload.Id);

View File

@@ -51,7 +51,7 @@ namespace Umbraco.Web.Cache
var macroRepoCache = AppCaches.IsolatedCaches.Get<IMacro>();
if (macroRepoCache)
{
macroRepoCache.Result.Clear(RepositoryCacheKeys.GetKey<IMacro>(payload.Id));
macroRepoCache.Result.Clear(RepositoryCacheKeys.GetKey<IMacro, int>(payload.Id));
}
}

View File

@@ -62,8 +62,8 @@ namespace Umbraco.Web.Cache
// repository cache
// it *was* done for each pathId but really that does not make sense
// only need to do it for the current media
mediaCache.Result.Clear(RepositoryCacheKeys.GetKey<IMedia>(payload.Id));
mediaCache.Result.Clear(RepositoryCacheKeys.GetKey<IMedia>(payload.Key));
mediaCache.Result.Clear(RepositoryCacheKeys.GetKey<IMedia, int>(payload.Id));
mediaCache.Result.Clear(RepositoryCacheKeys.GetKey<IMedia, Guid?>(payload.Key));
// remove those that are in the branch
if (payload.ChangeTypes.HasTypesAny(TreeChangeTypes.RefreshBranch | TreeChangeTypes.Remove))

View File

@@ -21,7 +21,7 @@ namespace Umbraco.Web.Cache
_legacyMemberRefresher = new LegacyMemberCacheRefresher(this, appCaches);
}
public class JsonPayload
public class JsonPayload
{
[JsonConstructor]
public JsonPayload(int id, string username)
@@ -87,11 +87,11 @@ namespace Umbraco.Web.Cache
_idkMap.ClearCache(p.Id);
if (memberCache)
{
memberCache.Result.Clear(RepositoryCacheKeys.GetKey<IMember>(p.Id));
memberCache.Result.Clear(RepositoryCacheKeys.GetKey<IMember>(p.Username));
}
memberCache.Result.Clear(RepositoryCacheKeys.GetKey<IMember, int>(p.Id));
memberCache.Result.Clear(RepositoryCacheKeys.GetKey<IMember, string>(p.Username));
}
}
}
#endregion

View File

@@ -35,7 +35,7 @@ namespace Umbraco.Web.Cache
public override void Refresh(int id)
{
var cache = AppCaches.IsolatedCaches.Get<IRelationType>();
if (cache) cache.Result.Clear(RepositoryCacheKeys.GetKey<IRelationType>(id));
if (cache) cache.Result.Clear(RepositoryCacheKeys.GetKey<IRelationType, int>(id));
base.Refresh(id);
}
@@ -48,7 +48,7 @@ namespace Umbraco.Web.Cache
public override void Remove(int id)
{
var cache = AppCaches.IsolatedCaches.Get<IRelationType>();
if (cache) cache.Result.Clear(RepositoryCacheKeys.GetKey<IRelationType>(id));
if (cache) cache.Result.Clear(RepositoryCacheKeys.GetKey<IRelationType, int>(id));
base.Remove(id);
}

View File

@@ -43,13 +43,13 @@ namespace Umbraco.Web.Cache
var userCache = AppCaches.IsolatedCaches.Get<IUser>();
if (userCache)
{
userCache.Result.Clear(RepositoryCacheKeys.GetKey<IUser>(id));
userCache.Result.Clear(RepositoryCacheKeys.GetKey<IUser, int>(id));
userCache.Result.ClearByKey(CacheKeys.UserContentStartNodePathsPrefix + id);
userCache.Result.ClearByKey(CacheKeys.UserMediaStartNodePathsPrefix + id);
userCache.Result.ClearByKey(CacheKeys.UserAllContentStartNodesPrefix + id);
userCache.Result.ClearByKey(CacheKeys.UserAllMediaStartNodesPrefix + id);
}
base.Remove(id);
}

View File

@@ -58,7 +58,7 @@ namespace Umbraco.Web.Cache
var userGroupCache = AppCaches.IsolatedCaches.Get<IUserGroup>();
if (userGroupCache)
{
userGroupCache.Result.Clear(RepositoryCacheKeys.GetKey<IUserGroup>(id));
userGroupCache.Result.Clear(RepositoryCacheKeys.GetKey<IUserGroup, int>(id));
userGroupCache.Result.ClearByKey(UserGroupRepository.GetByAliasCacheKeyPrefix);
}