Open Closed

abp permissons slow #9823


User avatar
0
heshengli created

https://abp.io/support/questions/9767/The-abp-permission-cache-PermissionGrants-loads-very-slowly-during-the-first-login-but-very-quickly-during-the-second-login-When-the-permission-cache-is-manually-cleared-it-slows-down-again# 通过修改源码,添加耗时日志,发现权限在慢主要是这个函数 PermissionStore.SetCacheItemsAsync


30 Answer(s)
  • User Avatar
    0
    AI-Bot created
    Support Team ABP Agent

    The slow performance of ABP permission cache (PermissionGrants) during the first login is a known behavior, especially when there are a large number of permissions or roles. The method PermissionStore.SetCacheItemsAsync is responsible for loading and caching all granted permissions for the first time, which can be time-consuming if the dataset is large. Subsequent logins are fast because the data is served from the cache.

    Key points and recommendations:

    • The first load after a cache clear (or on a cold start) will query all permission grants from the database and set them in the cache, which can take several seconds if you have thousands of permissions or roles.
    • After the initial load, subsequent requests are fast due to caching.
    • To improve the first-load performance:
      • Reduce the number of permission grants if possible.
      • Optimize your database (indexes, hardware, etc.).
      • Consider pre-warming the cache after deployment or application restart.
      • If using distributed cache (e.g., Redis), ensure the cache server is performant and network latency is low.

    For more details and discussion, see the official support thread:

    • https://abp.io/support/questions/9767/The-abp-permission-cache-PermissionGrants-loads-very-slowly-during-the-first-login-but-very-quickly-during-the-second-login-When-the-permission-cache-is-manually-cleared-it-slows-down-again

    This response was generated by AI. If it didn’t solve your problem, feel free to post a new answer — our support team will jump in to help you soon.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    你可以继续覆盖内置的服务, 看看具体哪个方法影响了性能

    https://github.com/abpframework/abp/blob/dev/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionStore.cs#L65-L104

  • User Avatar
    0
    heshengli created

    用最新版替换了,老版的代码没有DisableTracking(), using (PermissionGrantRepository.DisableTracking()){} 都手都改了查询实现

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    具体是哪几行代码会缓慢?

  • User Avatar
    0
    heshengli created

    这个是权限多语言,主要是这个

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    这只是一个查询语句, 返回一个列表数据, 并也禁用了追踪, 几千个数据不会特别慢.

    你使用StopWatch计算一下耗时? 顺便复制生成的查询sql 收到执行看看耗时

  • User Avatar
    0
    heshengli created

    改造了下代码 日志

  • User Avatar
    0
    heshengli created

    权限notCacheKeys,反向匹配权限Name,性能问题

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    手动执行相同的查询Sql 也很慢吗? 数据库是远程访问吗?

  • User Avatar
    0
    heshengli created

    跟查询数据库没关系了,主要是 pn:R,pk:admin,n:AbpIdentity.Roles ,转Name 匹配 var permissions = dbpermissions.Where(x => notCacheKeys.Any(k => GetPermissionNameFormCacheKeyOrNull(k) == x.Name)).ToList(); 也就是 没修改源码之前的 var permissions = (await PermissionDefinitionManager.GetPermissionsAsync()) .Where(x => notCacheKeys.Any(k => GetPermissionNameFormCacheKeyOrNull(k) == x.Name)).ToList();

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    我没有看懂, 你修改后问题解决了?

  • User Avatar
    0
    heshengli created

    PermissionStore.GetPermissionNameFormCacheKeyOrNull 存在性能问题

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    它会尝试匹配字符串格式.

     public static string GetPermissionNameFormCacheKeyOrNull(string cacheKey)
    {
        var result = FormattedStringValueExtracter.Extract(cacheKey, CacheKeyFormat, true);
        return result.IsMatch ? result.Matches.Last().Value : null;
    }
    

    你修改或者替换后是否解决了问题?

  • User Avatar
    0
    heshengli created

    可以解决,方法比较简单 var notCacheKeysPermissionNames = notCacheKeys.Select(t=>t.Split(',').Last().Substring(2)).ToList(); 没有考虑其他特殊情况,但是很快 根据缓存权限格式 private const string CacheKeyFormat = "pn:{0},pk:{1},n:{2}"; 耗时从4989ms到96ms GetPermissionNameFormCacheKeyOrNull 这个函数的找个方法,需要特殊处理什么字符串么,有些函数没有使用过 var result = FormattedStringValueExtracter.Extract(cacheKey, CacheKeyFormat, true); FormattedStringValueExtracter这个类。 ` using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq;

    namespace Volo.Abp.Text.Formatting;

    ///

    /// This class is used to extract dynamic values from a formatted string. /// It works as reverse of /// public class FormattedStringValueExtracter { /// /// Extracts dynamic values from a formatted string. /// /// String including dynamic values /// Format of the string /// True, to search case-insensitive. public static ExtractionResult Extract(string str, string format, bool ignoreCase = false) { var stringComparison = ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;

        if (str == format)
        {
            return new ExtractionResult(true);
        }
    
        var formatTokens = new FormatStringTokenizer().Tokenize(format);
        if (formatTokens.IsNullOrEmpty())
        {
            return new ExtractionResult(str == "");
        }
    
        var result = new ExtractionResult(true);
    
        for (var i = 0; i < formatTokens.Count; i++)
        {
            var currentToken = formatTokens[i];
            var previousToken = i > 0 ? formatTokens[i - 1] : null;
    
            if (currentToken.Type == FormatStringTokenType.ConstantText)
            {
                if (i == 0)
                {
                    if (!str.StartsWith(currentToken.Text, stringComparison))
                    {
                        result.IsMatch = false;
                        return result;
                    }
    
                    str = str.Substring(currentToken.Text.Length);
                }
                else
                {
                    var matchIndex = str.IndexOf(currentToken.Text, stringComparison);
                    if (matchIndex < 0)
                    {
                        result.IsMatch = false;
                        return result;
                    }
    
                    Debug.Assert(previousToken != null, "previousToken can not be null since i > 0 here");
    
                    result.Matches.Add(new NameValue(previousToken.Text, str.Substring(0, matchIndex)));
                    str = str.Substring(matchIndex + currentToken.Text.Length);
                }
            }
        }
    
        var lastToken = formatTokens.Last();
        if (lastToken.Type == FormatStringTokenType.DynamicValue)
        {
            result.Matches.Add(new NameValue(lastToken.Text, str));
        }
    
        return result;
    }
    
    /// <summary>
    /// Checks if given <paramref name="str"/> fits to given <paramref name="format"/>.
    /// Also gets extracted values.
    /// </summary>
    /// <param name="str">String including dynamic values</param>
    /// <param name="format">Format of the string</param>
    /// <param name="values">Array of extracted values if matched</param>
    /// <param name="ignoreCase">True, to search case-insensitive</param>
    /// <returns>True, if matched.</returns>
    public static bool IsMatch(string str, string format, out string[] values, bool ignoreCase = false)
    {
        var result = Extract(str, format, ignoreCase);
        if (!result.IsMatch)
        {
            values = new string[0];
            return false;
        }
    
        values = result.Matches.Select(m => m.Value).ToArray();
        return true;
    }
    
    /// <summary>
    /// Used as return value of <see cref="Extract"/> method.
    /// </summary>
    public class ExtractionResult
    {
        /// <summary>
        /// Is fully matched.
        /// </summary>
        public bool IsMatch { get; set; }
    
        /// <summary>
        /// List of matched dynamic values.
        /// </summary>
        public List<NameValue> Matches { get; private set; }
    
        internal ExtractionResult(bool isMatch)
        {
            IsMatch = isMatch;
            Matches = new List<NameValue>();
        }
    }
    

    }

    `

  • User Avatar
    0
    heshengli created

    Blazor Server 白屏时间成15s-10s+,但是页面还是白屏明细

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    好的, 我会测试和修复GetPermissionNameFormCacheKeyOrNull

    非常感谢

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    我没有复现问题, GetPermissionNameFormCacheKeyOrNull方法很快. 你的权限缓存key是什么?

  • User Avatar
    0
    heshengli created

    很多这样的权限 pn:R,pk:admin,n:C1:PQCManage.TestResultPqcSp.Update C1:PQCManage.TestResultPqcSp.Update 很多这种类型,C1:PQCManage.TestResultPqcSp.Update ,C2:PQCManage.TestResultPqcSp.Update,C3:PQCManage.TestResultPqcSp.Update

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    这是我的单元测试代码, 你可以试试吗?

    public class GetPermissionNameFormCacheKeyOrNull_Tests
    {
        [Fact]
        public void Test()
        {
            var key = PermissionGrantCacheItem.CalculateCacheKey("Permission1", "Role", "1");
            key.ShouldBe("pn:Role,pk:1,n:Permission1");
    
            var stopwatch = System.Diagnostics.Stopwatch.StartNew();
            for (int i = 0; i < 50000; i++)
            {
                var name = PermissionGrantCacheItem.GetPermissionNameFormCacheKeyOrNull(key);
                name.ShouldBe("Permission1");
            }
            stopwatch.Stop();
    
            var elapsed = stopwatch.Elapsed.Seconds;
            elapsed.ShouldBeLessThan(2);
    
    
            var name2 = PermissionGrantCacheItem.GetPermissionNameFormCacheKeyOrNull("invalid-key");
            name2.ShouldBeNull();
        }
    }
    
    
  • User Avatar
    0
    heshengli created

    好像不是这个函数GetPermissionNameFormCacheKeyOrNull的问题

    (await PermissionDefinitionManager.GetPermissionsAsync()).Where(x => notCacheKeys.Any(k => GetPermissionNameFormCacheKeyOrNull(k) == x.Name)).ToList()

    notCacheKeys.Any(k => GetPermissionNameFormCacheKeyOrNull(k) == x.Name),每次都要全量转字符串匹配再Any, (await PermissionDefinitionManager.GetPermissionsAsync()). Count1 notCacheKeys Count2 O(Count1*Count2)

  • User Avatar
    0
    heshengli created

    https://github.com/abpframework/abp/blob/dev/modules/permission-management/src/Volo.Abp.PermissionManagement.Domain/Volo/Abp/PermissionManagement/PermissionStore.cs#L178-L179

    var permissions = (await PermissionDefinitionManager.GetPermissionsAsync())
                    .Where(x => notCacheKeys.Any(k => GetPermissionNameFormCacheKeyOrNull(k) == x.Name)).ToList();
    

    改为

    var names = notCacheKeys.Select(k => GetPermissionNameFormCacheKeyOrNull(k)).ToArray();
    
    var permissions = (await PermissionDefinitionManager.GetPermissionsAsync())
        .Where(x => names .Any(k => k == x.Name)).ToList();
    
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    这几行虽然可以改进, 但是似乎也不会造成问题.

    PermissionDefinitionManager不会查询数据库.

    你可以分享一个项目复现问题吗?

    liming.ma@volosoft.com

    谢谢

  • User Avatar
    0
    heshengli created

    上面的问题,只解决了部分问题,我的程序是7.2.2 的微服务框架,没有升级到最新版本,但是会把一些高版本的优化,copy到本地重新发布dll替换原有的dll await Cache.SetManyAsync(cacheItems); 大量权限的时候设置缓存还是有2s,每次用户登录,获取Role ,User 权限都会有2s ,一起就是4s左右, 可能还要加OneTime方法,程序启动的时候把全部信息加载到缓存,后期用户登录才可能比较快。现在是第一次登录慢,第二次才快 系统启动热数据还是要加载到缓存

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer
    var permissions = (await PermissionDefinitionManager.GetPermissionsAsync())
                    .Where(x => notCacheKeys.Any(k => GetPermissionNameFormCacheKeyOrNull(k) == x.Name)).ToList();
    

    改为

    var names = notCacheKeys.Select(k => GetPermissionNameFormCacheKeyOrNull(k)).ToArray();
    
    var permissions = (await PermissionDefinitionManager.GetPermissionsAsync())
        .Where(x => names .Any(k => k == x.Name)).ToList();
    

    速度从6秒到几秒?

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    你可以在app模版中尝试复现问题, 然后我们可以排除出问题.

Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v10.0.0-preview. Updated on August 28, 2025, 08:29