ServiceStack Ormlite 缓存条目在到期后不会被删除

ServiceStack Ormlite caching entries aren't deleted after expiry

我们在 OrmLite Provider (MySql) 中使用 serviceStack 缓存。我们注意到,当我们创建具有到期日期的缓存键时,键不会在到期日期到来后被删除。相反,他们在“ExpiryDate”列中得到 NULL 值。因此,当我们计算 Cache.GetTimeToLive() 时会产生奇怪的值。

这是 serviceStack 中的错误还是我们的密钥创建代码中的错误?我们正在使用 ServiceStack 版本 (4.5.4) 和 OrmLite 版本 (4.5.4)

        IAppSettings appSettings = new AppSettings();

        var userConsultsPerHourLimit = appSettings.Get<int>("throttling:consultations:requests:perHourLimit");
        var userConsultsPerDayLimit = appSettings.Get<int>("throttling:consultations:requests:perDayLimit");
        var userConsultsPerMonthLimit = appSettings.Get<int>("throttling:consultations:requests:perMonthLimit");

        var userConsultsMadePerHour = Cache.GetOrCreate<int>(UserConsultPerHourCacheKey, TimeSpan.FromHours(1), () => { return 0; });
        var userConsultsMadePerDay = Cache.GetOrCreate<int>(UserConsultPerDayCacheKey, TimeSpan.FromDays(1), () => { return 0; });
        var userConsultsMadePerMonth = Cache.GetOrCreate<int>(UserConsultPerMonthCacheKey, (new DateTime(DateTime.UtcNow.Year, DateTime.UtcNow.Month, 1).AddMonths(1).AddDays(-1) - DateTime.UtcNow), () => { return 0; });

        string retryAfter = System.Threading.Thread.CurrentThread.CurrentCulture.Name == "ar-SA" ? "يوم" : "day";

        bool shouldThrottleRequest = false;
        bool didExceedMonthlyLimit = false;

        if (userConsultsMadePerHour >= userConsultsPerHourLimit)
        {
            shouldThrottleRequest = true;
            TimeSpan? timeToLive = Cache.GetTimeToLive(UserConsultPerHourCacheKey);
            if (timeToLive.HasValue)
                retryAfter = Humanizer.TimeSpanHumanizeExtensions.Humanize(timeToLive.Value, 2, System.Threading.Thread.CurrentThread.CurrentUICulture);
        }
        else if (userConsultsMadePerDay >= userConsultsPerDayLimit)
        {
            shouldThrottleRequest = true;
            TimeSpan? timeToLive = Cache.GetTimeToLive(UserConsultPerDayCacheKey);
            if (timeToLive.HasValue)
                retryAfter = Humanizer.TimeSpanHumanizeExtensions.Humanize(timeToLive.Value, 2, System.Threading.Thread.CurrentThread.CurrentUICulture);
        }
        else if (userConsultsMadePerMonth >= userConsultsPerMonthLimit)
        {
            shouldThrottleRequest = true;
            TimeSpan? timeToLive = Cache.GetTimeToLive(UserConsultPerMonthCacheKey);
            if (timeToLive.HasValue)
                retryAfter = Humanizer.TimeSpanHumanizeExtensions.Humanize(timeToLive.Value, 3, System.Threading.Thread.CurrentThread.CurrentUICulture);
            didExceedMonthlyLimit = true;
        }

这是 working as expected 在最新版本的 ServiceStack 中,在获取过期的缓存条目后删除了该行:

var ormliteCache = Cache as OrmLiteCacheClient;
var key = "int:key";

var value = Cache.GetOrCreate(key, TimeSpan.FromMilliseconds(100), () => 1);
var ttl = Cache.GetTimeToLive(key);

using (var db = ormliteCache.DbFactory.OpenDbConnection())
{
    var row = db.SingleById<CacheEntry>(key);
    Assert.That(row, Is.Not.Null);
    Assert.That(row.ExpiryDate, Is.Not.Null);
}

Assert.That(value, Is.EqualTo(1));
Assert.That(ttl.Value.TotalMilliseconds, Is.GreaterThan(0));

Thread.Sleep(200);
value = Cache.Get<int>(key);
ttl = Cache.GetTimeToLive(key);

Assert.That(value, Is.EqualTo(0));
Assert.That(ttl, Is.Null);

using (var db = ormliteCache.DbFactory.OpenDbConnection())
{
    var row = db.SingleById<CacheEntry>(key);
    Assert.That(row, Is.Null);
}

We noticed that when we create caching keys with expiry dates, the keys don’t get deleted after the expiry date comes.

RDBMS 不会按日期自动使缓存条目过期,但在解析缓存条目时,OrmLiteCacheClient 会自动删除过期的条目(如上所示),因此它永远不会 return过期条目。

Instead, they get NULL values in the “ExpiryDate” column.

这是不可能的。 ExpiryDate 仅在创建或替换现有条目时填充,它在过期时永远不会设置为空。当条目过期时,整个条目将被删除。

我想我们已经查清楚了。这是由于我们这边滥用缓存 APIs 造成的。我们发现 API 次调用 "Increment"和 "Decrement" APIs 在不同的其他地方导致密钥(那些已经超过到期日期的)被删除(通过内部调用验证方法)然后从头开始重新创建(但没有到期日期)。 . 解决方案是在调用 Increment/Decrement 之前调用 GetOrCreate 以确保密钥确实存在,如果不存在,则使用新的到期日期值重新创建它..