您将如何根据RavenDB文档数据解决此聚合/报告方案?

时间:2013-03-18 17:10:46

标签: reporting ravendb aggregation

我们使用RavenDB(2261)作为基于队列的视频上传系统的后端,我们被要求提供有关上传系统的各种指标的“实时”SLA报告。

文档格式如下:

{
  "ClipGuid": "01234567-1234-abcd-efef-123412341234",
  "CustomerId": "ABC123",
  "Title": "Shakespeare in Love",
  "DurationInSeconds": 82,
  "StateChanges": [
    {
      "OldState": "DoesNotExist",
      "NewState": "ReceivedFromUpload",
      "ChangedAt": "2013-03-15T15:38:38.7050002Z"
    },
    {
      "OldState": "ReceivedFromUpload",
      "NewState": "Validating",
      "ChangedAt": "2013-03-15T15:38:38.8453975Z"
    },
    {
      "OldState": "Validating",
      "NewState": "AwaitingSubmission",
      "ChangedAt": "2013-03-15T15:38:39.9529762Z"
    },
    {
      "OldState": "AwaitingSubmission",
      "NewState": "Submitted",
      "ChangedAt": "2013-03-15T15:38:43.4785084Z"
    },
    {
      "OldState": "Submitted",
      "NewState": "Playable",
      "ChangedAt": "2013-03-15T15:41:39.5523223Z"
    }
  ],
}

在每个ClipInfo记录中,每次剪辑从处理链的一个部分传递到另一个部分时,都会添加一组StateChange。我们需要的是将这些StateChanges减少到两个特定的timepans - 我们需要知道一个剪辑从DoesNotExist更改为AwaitingSubmission需要多长时间,以及从DoesNotExist到Playable需要多长时间。然后,我们需要按日期/时间对这些持续时间进行分组,因此我们可以绘制一个简单的SLA报告,如下所示:

Mockup video upload SLA

必要的谓词可以表示为LINQ语句但是当我尝试在Raven查询中指定这种复杂的逻辑时,我似乎只能获得空结果(或许多DateTime.MinValue结果)

我意识到像Raven这样的文档数据库不适合报告 - 我们很乐意探索复制到SQL或其他类型的缓存机制 - 但目前我还是看不到任何提取数据的方法除了执行多个查询以检索商店的整个内容,然后在.NET中执行计算。

有什么建议吗?

谢谢,

迪伦

1 个答案:

答案 0 :(得分:0)

我做了一些你可能需要调整的假设:

  • 您严格按UTC时区操作 - 您的“日期”是午夜至午夜UTC。
  • 您的一周是周日至周六
  • 您要分组的日期是报告的第一个状态日期(标记为“DoesNotExist”的状态日期为旧状态。)

您需要为每个日期分组添加单独的地图/缩小索引 - 每日,每周,每月。

除了如何定义起始日期之外,它们几乎完全相同。如果你想获得创意,你可能想出一种方法将它们变成一般的索引定义 - 但它们总是最终成为RavenDB中的三个独立索引。

// This is the resulting class that all of these indexes will return
public class ClipStats
{
    public int CountClips { get; set; }
    public int NumPassedWithinTwentyPct { get; set; }
    public int NumPlayableWithinOneHour { get; set; }
    public DateTime Starting { get; set; }
}

public class ClipStats_ByDay : AbstractIndexCreationTask<ClipInfo, ClipStats>
{
    public ClipStats_ByDay()
    {
        Map = clips => from clip in clips
                        let state1 = clip.StateChanges.FirstOrDefault(x => x.OldState == "DoesNotExist")
                        let state2 = clip.StateChanges.FirstOrDefault(x => x.NewState == "AwaitingSubmission")
                        let state3 = clip.StateChanges.FirstOrDefault(x => x.NewState == "Playable")
                        let time1 = state2.ChangedAt - state1.ChangedAt
                        let time2 = state3.ChangedAt - state1.ChangedAt
                        select new
                        {
                            CountClips = 1,
                            NumPassedWithinTwentyPct = time1.TotalSeconds < clip.DurationInSeconds * 0.2 ? 1 : 0,
                            NumPlayableWithinOneHour = time2.TotalHours < 1 ? 1 : 0,
                            Starting = state1.ChangedAt.Date
                        };

        Reduce = results => from result in results
                            group result by result.Starting
                            into g
                            select new
                            {
                                CountClips = g.Sum(x => x.CountClips),
                                NumPassedWithinTwentyPct = g.Sum(x => x.NumPassedWithinTwentyPct),
                                NumPlayableWithinOneHour = g.Sum(x => x.NumPlayableWithinOneHour),
                                Starting = g.Key
                            };
    }
}

public class ClipStats_ByWeek : AbstractIndexCreationTask<ClipInfo, ClipStats>
{
    public ClipStats_ByWeek()
    {
        Map = clips => from clip in clips
                        let state1 = clip.StateChanges.FirstOrDefault(x => x.OldState == "DoesNotExist")
                        let state2 = clip.StateChanges.FirstOrDefault(x => x.NewState == "AwaitingSubmission")
                        let state3 = clip.StateChanges.FirstOrDefault(x => x.NewState == "Playable")
                        let time1 = state2.ChangedAt - state1.ChangedAt
                        let time2 = state3.ChangedAt - state1.ChangedAt
                        select new
                        {
                            CountClips = 1,
                            NumPassedWithinTwentyPct = time1.TotalSeconds < clip.DurationInSeconds * 0.2 ? 1 : 0,
                            NumPlayableWithinOneHour = time2.TotalHours < 1 ? 1 : 0,
                            Starting = state1.ChangedAt.Date.AddDays(0 - (int) state1.ChangedAt.Date.DayOfWeek)
                        };

        Reduce = results => from result in results
                            group result by result.Starting
                            into g
                            select new
                            {
                                CountClips = g.Sum(x => x.CountClips),
                                NumPassedWithinTwentyPct = g.Sum(x => x.NumPassedWithinTwentyPct),
                                NumPlayableWithinOneHour = g.Sum(x => x.NumPlayableWithinOneHour),
                                Starting = g.Key
                            };
    }
}

public class ClipStats_ByMonth : AbstractIndexCreationTask<ClipInfo, ClipStats>
{
    public ClipStats_ByMonth()
    {
        Map = clips => from clip in clips
                        let state1 = clip.StateChanges.FirstOrDefault(x => x.OldState == "DoesNotExist")
                        let state2 = clip.StateChanges.FirstOrDefault(x => x.NewState == "AwaitingSubmission")
                        let state3 = clip.StateChanges.FirstOrDefault(x => x.NewState == "Playable")
                        let time1 = state2.ChangedAt - state1.ChangedAt
                        let time2 = state3.ChangedAt - state1.ChangedAt
                        select new
                        {
                            CountClips = 1,
                            NumPassedWithinTwentyPct = time1.TotalSeconds < clip.DurationInSeconds * 0.2 ? 1 : 0,
                            NumPlayableWithinOneHour = time2.TotalHours < 1 ? 1 : 0,
                            Starting = state1.ChangedAt.Date.AddDays(1 - state1.ChangedAt.Date.Day)
                        };

        Reduce = results => from result in results
                            group result by result.Starting
                            into g
                            select new
                            {
                                CountClips = g.Sum(x => x.CountClips),
                                NumPassedWithinTwentyPct = g.Sum(x => x.NumPassedWithinTwentyPct),
                                NumPlayableWithinOneHour = g.Sum(x => x.NumPlayableWithinOneHour),
                                Starting = g.Key
                            };
    }
}

然后当你想查询......

var now = DateTime.UtcNow;

var today = now.Date;
var dailyStats = session.Query<ClipStats, ClipStats_ByDay>()
                        .FirstOrDefault(x => x.Starting == today);

var startOfWeek = today.AddDays(0 - (int) today.DayOfWeek);
var weeklyStats = session.Query<ClipStats, ClipStats_ByWeek>()
                         .FirstOrDefault(x => x.Starting == startOfWeek);

var startOfMonth = today.AddDays(1 - today.Day);
var monthlyStats = session.Query<ClipStats, ClipStats_ByMonth>()
                          .FirstOrDefault(x => x.Starting == startOfMonth);

在结果中,您将拥有总计。因此,如果您想要SLA的平均百分比,只需将统计数除以计数,也将返回。