对大集合的缓慢查询
Slow query over large collection
我正在处理在 RavenDB 中保存会话的审计日志。最初,用于查询审计日志的网站响应足够快,但随着记录数据量的增加,搜索页面变得不可用(它在使用默认设置返回之前超时 - 无论使用的查询如何)。现在我们在 table 中有大约 4500 万个会话被查询,但稳定状态预计约为 1.5 亿个文档。
问题是有了这么多的实时数据,四处测试已经变得不切实际了。我希望有人能给我一些想法,哪些是最有成效的调查领域。
索引如下所示:
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。由于与此 post 无关的原因,我发现使用基于 guid 的 id 最实用。我不确定我是否可以轻松更改现有值的 ID(使用这么多数据)并且我宁愿不删除数据(但如果预期影响足够大则可能)。我知道顺序 ID 会导致索引的 B 树表现更好,但我不知道影响有多大。
另一种方法可能是在 id 中包含时间戳,并查询 id 以字符串匹配的时间足以过滤结果的文档。示例 ID 可以是 AuditSessions/2017-12-31-24-31-42/bc835d6c-2fba-4591-af92-7aab96339d84
。这也需要我更新或删除所有现有数据。这当然也有大多数顺序 ID 的好处。
第三种方法可能是随着时间的推移将旧数据移动到不同的集合中,以认识到您最常查看最新数据这一事实。这需要后台作业并支持跨收集时间边界查询。它还存在一个问题,如果您需要访问它,旧会话的集合仍然很慢。
我希望有比这些解决方案更简单的方法,例如以避免大量工作的方式修改查询或索引字段。
一看,大概和StartedUtc
上的范围查询有关。
我假设您使用的是精确数字,因此那里有很多不同的值。
如果可以,你可以通过将索引更改为以秒/分钟粒度(通常是你正在查询的内容)上的索引来显着降低成本,然后使用 Ticks
,这允许我们使用数字范围查询.
StartedUtcTicks = new Datetime(session.StartedUtc.Year, session.StartedUtc.Month, session.StartedUtc.Day, session.StartedUtc.Hour, session.StartedUtc.Minute, session.StartedUtc.Second).Ticks,
然后按日期刻度查询。
我正在处理在 RavenDB 中保存会话的审计日志。最初,用于查询审计日志的网站响应足够快,但随着记录数据量的增加,搜索页面变得不可用(它在使用默认设置返回之前超时 - 无论使用的查询如何)。现在我们在 table 中有大约 4500 万个会话被查询,但稳定状态预计约为 1.5 亿个文档。
问题是有了这么多的实时数据,四处测试已经变得不切实际了。我希望有人能给我一些想法,哪些是最有成效的调查领域。
索引如下所示:
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。由于与此 post 无关的原因,我发现使用基于 guid 的 id 最实用。我不确定我是否可以轻松更改现有值的 ID(使用这么多数据)并且我宁愿不删除数据(但如果预期影响足够大则可能)。我知道顺序 ID 会导致索引的 B 树表现更好,但我不知道影响有多大。
另一种方法可能是在 id 中包含时间戳,并查询 id 以字符串匹配的时间足以过滤结果的文档。示例 ID 可以是 AuditSessions/2017-12-31-24-31-42/bc835d6c-2fba-4591-af92-7aab96339d84
。这也需要我更新或删除所有现有数据。这当然也有大多数顺序 ID 的好处。
第三种方法可能是随着时间的推移将旧数据移动到不同的集合中,以认识到您最常查看最新数据这一事实。这需要后台作业并支持跨收集时间边界查询。它还存在一个问题,如果您需要访问它,旧会话的集合仍然很慢。
我希望有比这些解决方案更简单的方法,例如以避免大量工作的方式修改查询或索引字段。
一看,大概和StartedUtc
上的范围查询有关。
我假设您使用的是精确数字,因此那里有很多不同的值。
如果可以,你可以通过将索引更改为以秒/分钟粒度(通常是你正在查询的内容)上的索引来显着降低成本,然后使用 Ticks
,这允许我们使用数字范围查询.
StartedUtcTicks = new Datetime(session.StartedUtc.Year, session.StartedUtc.Month, session.StartedUtc.Day, session.StartedUtc.Hour, session.StartedUtc.Minute, session.StartedUtc.Second).Ticks,
然后按日期刻度查询。