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模版中尝试复现问题, 然后我们可以排除出问题.

Learn More, Pay Less
33% OFF
All Trainings!
Get Your Deal
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 September 12, 2025, 10:20