对大型集合

时间:2017-03-29 13:21:09

标签: performance ravendb

我正在处理审核日志,该日志会在RavenDB中保存会话。最初,用于查询审核日志的网站响应足够,但随着记录数据量的增加,搜索页面变得无法使用(在使用默认设置返回之前它会超时 - 无论使用何种查询)。现在我们在表格中有大约45mil的会话被查询但是稳定的状态预计会有大约150mil的文档。

问题在于,通过这么多实时数据,玩弄测试事物变得不切实际。我希望有人可以给我一些想法,哪些是最有效的研究领域。

索引如下所示:

public AuditSessions_WithSearchParameters()
{
    Map = sessions => from session in sessions
                      select new Result
                      {
                          ApplicationName = session.ApplicationName,
                          SessionId = session.SessionId,
                          StartedUtc = session.StartedUtc,
                          User_Cpr = session.User.Cpr,
                          User_CprPersonId = session.User.CprPersonId,
                          User_ApplicationUserId = session.User.ApplicationUserId
                      };

    Store(r => r.ApplicationName, FieldStorage.Yes);
    Store(r => r.StartedUtc, FieldStorage.Yes);
    Store(r => r.User_Cpr, FieldStorage.Yes);
    Store(r => r.User_CprPersonId, FieldStorage.Yes);
    Store(r => r.User_ApplicationUserId, FieldStorage.Yes);
}

查询的本质是这一点:

// Query input paramters
var fromDateUtc = fromDate.ToUniversalTime();
var toDateUtc = toDate.ToUniversalTime();

sessionQuery = sessionQuery
        .Where(s =>
            s.ApplicationName == applicationName &&
            s.StartedUtc >= fromDateUtc &&
            s.StartedUtc <= toDateUtc
        );

var totalItems = Count(sessionQuery);
var sessionData =
    sessionQuery
    .OrderByDescending(s => s.StartedUtc)
    .Skip((page - 1) * PageSize)
    .Take(PageSize)
    .ProjectFromIndexFieldsInto<AuditSessions_WithSearchParameters.ResultWithAuditSession>()
    .Select(s => new
    {
        s.SessionId,
        s.SessionGroupId,
        s.ApplicationName,
        s.StartedUtc,
        s.Type,
        s.ResourceUri,
        s.User,
        s.ImpersonatingUser
    })
    .ToList();

首先,要确定结果页数,我使用此方法计算查询中的结果数:

private static int Count<T>(IRavenQueryable<T> results)
{
    RavenQueryStatistics stats;
    results.Statistics(out stats).Take(0).ToArray();
    return stats.TotalResults;
}

这本身就非常昂贵,因此优化在此处和查询的其余部分都是相关的。

查询时间与任何相关方式的结果项数量无关。如果我为applicationName参数使用的值与任何结果不同,那么速度也一样慢。

改进的一个方面可能是为会话使用顺序ID。由于与这篇文章无关的原因,我发现使用基于guid的id最实用。我不确定我是否可以轻松更改现有值的ID(使用这么多数据),我宁愿不丢弃数据(但如果预期影响足够大,可能会丢弃)。据我所知,顺序id导致索引的b树表现更好,但我不知道影响有多大。

另一种方法可能是在id中包含一个时间戳,并查询具有id的文档,该字符串匹配足够的时间来过滤结果。示例ID可以是AuditSessions/2017-12-31-24-31-42/bc835d6c-2fba-4591-af92-7aab96339d84。这也要求我更新或删除所有现有数据。这当然也具有大多数顺序ID的好处。

第三种方法可能是随着时间的推移将旧数据移动到不同的集合中,这是因为您最常查看最新数据。这需要后台作业并支持跨收集时间边界查询。它还有一个问题,即如果您需要访问它,那么旧会话的集合仍然很慢。

我希望有一些比这些解决方案更简单的东西,例如以避免大量工作的方式修改查询或索引字段。

1 个答案:

答案 0 :(得分:1)

乍一看,它可能与StartedUtc上的范围查询有关。 我假设您使用的是精确数字,因此您有很多不同的值。 如果可以,您可以通过将索引更改为第二/每分钟粒度(通常是您要查询的内容)来显着降低成本,然后使用Ticks,这允许我们使用数值范围查询

                      StartedUtcTicks = new Datetime(session.StartedUtc.Year, session.StartedUtc.Month, session.StartedUtc.Day, session.StartedUtc.Hour, session.StartedUtc.Minute, session.StartedUtc.Second).Ticks,

然后按日期查询。