2

我很好奇是否有任何最佳实践来索引按月/天聚合的集合的指标。

文档示例:

{
  track: {
    2012: { # year
      1: { # month
        page_views: ...,
        clicks: ...,
        visits: ...
      },
      5: {
        page_views: ...,
        clicks: ...,
        visits: ...
      },
      ...
  }
}

编辑:

由于正在进行关于如何改进文档的讨论以及将其拆分的一些建议(我已经考虑过)。我会更新为什么要求是这样的。该文档用于跟踪用户。随着时间的推移跟踪他们的浏览量、访问量等。用户在文档上有其他数据。例如,有一个 registeration_date。目标是能够说出类似“显示在 X 日期注册并且在 A 和 B 跟踪日期之间的页面浏览量超过 Z 的用户”之类的内容。我无法想出一个没有嵌入的模式来促进这一点。

更新的文档示例:

{
  registration_date: ...,
  email: ...,
  track: {
    2012: { # year
      1: { # month
        page_views: ...,
        clicks: ...,
        visits: ...
      },
      5: {
        page_views: ...,
        clicks: ...,
        visits: ...
      },
      ...
  }
}
4

3 回答 3

3

不幸的是,您的数据库模式对索引非常不友好。当您像这样嵌套对象时,您唯一的选择是为每个可能的年/月组合创建一个索引。也很难查询。例如,当您想按降序获得最好的三个月时,您将很难在数据库上执行此操作。

更好的选择是将年份和月份都放入对象中,将对象放入数组中(因为索引可用于数组查找),并在年份、月份和周围文档的唯一字段上创建唯一的复合索引.

{
    name: "Some Unique Name",
    tracking:    [
        {year: 2011, month: 11, page_views: 235, clicks: 132, visits: 87 },
        {year: 2011, month: 12, page_views: 176, clicks: 122, visits: 67 },
        {year: 2012, month: 1, page_views: 53, clicks: 32, visits: 17 },
        {year: 2012, month: 2, page_views: 89, clicks: 72, visits: 67 },
        {year: 2012, month: 3, page_views: 99, clicks: 82, visits: 72 }
    ]
}

ensureIndex({name:1, tracking.year:1, tracking.month:1});

当您需要频繁访问单个天、月或年的累积统计数据时,您可以将这些指标存储在单个子文档中:

    tracking_daily: [
        ...
        {year: 2012, month: 3, day: 1, ...  }, 
        {year: 2012, month: 3, day: 2, ...  }, 
        {year: 2012, month: 3, day: 3, ...  }, 
        {year: 2012, month: 3, day: 4, ...  }, 
        {year: 2012, month: 3, day: 5, ...  }, 
        {year: 2012, month: 3, day: 6, ...  }, 
        {year: 2012, month: 3, day: 7, ...  }, 
        {year: 2012, month: 3, day: 8, ...  }, 
        ...
    ],
    tracking_monthly: [
        ...
        {year: 2011, month: 11, ... },
        {year: 2011, month: 12, ... },
        {year: 2012, month: 1, ...  },
        {year: 2012, month: 2, ...  },
        {year: 2012, month: 3, ...  } 
        ...
    ],
    tracking_yearly:    [
        ...
        {year: 2011, ...  },
        {year: 2012, ...  }
    ]
于 2012-12-04T21:28:15.080 回答
1

再考虑一下,我可能会建议一个模式。

我个人根本不会将子文档用于度量,因为我可以想象在度量时间跨度上会有日期查询。

您还必须考虑从子文档中提取指标,特别是在数年内很容易导致客户端大量处理的子文档,至少需要聚合框架;即使这样,我也不确定它是否可以在足够快的时间内完成真正的分析查询,让您满意。

省略子文档的另一个原因是将来与根文档的大小兼容。我在上一段中谈到了这一点,指出随着时间的推移子文档可能会变得相当大。

所以一般来说,为了将来的兼容性和查询速度,我不会广泛使用子文档。

根据我自己的个人经验和对此类模式的许多讨论发现,通常一个好方法是将您的跟踪分布实际拆分为时间桶集合,这样您就可以收集每天、每月和每年的统计数据;共创建 3 个集合。

在这种情况下,我个人也会提供一个相对扁平的文档,以确保跨优化索引的线性范围查询,但是嵌套并不总是一个坏主意。让我举一个可用于日常统计的文档示例:

{
    hours: [
        {views: 2, unique: 1} // This is actually index 0 which denotes hour 0 of the day
    ],
    pageviews: 1000,
    unique_visitors: 4,
    visitors: 67,
    clicks: 5
}

您将看到,为了便于查询,我如何将一天中的时间放入子文档中。这意味着要查询那几天的统计信息,我只需要进行一次往返,但是我不会失去真正的分析能力,因为我不太可能希望在复杂查询中使用两天的小时子文档。

所以是的,我个人会注意我的评论,并尝试对您的数据进行一些非规范化。您认为 MongoDB atm 过于规范化。

于 2012-12-04T21:53:30.143 回答
0

您确定在用户级别汇总跟踪数据真的值得吗?像这样处理时间戳怎么样:

{
 userId: 1234,
 registered: ISODate(""),
 visits: [ 
   ISODate(""), 
   ISODate(""),
   ISODate("")
 ],
 clicks: [
   ISODate(""),
   ISODate("")
 ]
}

然后只是聚合框架以匹配注册日期,例如计算访问次数。

如果您有能力对 users 集合进行额外的查找,那么基于对象存储跟踪数据会更好:

visits_collection
{
  {userId: 1234, time: ISODate(""), registration: ISODate("")},
  {userId: 1234, time: ISODate(""), registration: ISODate("")},
  {userId: 1234, time: ISODate(""), registration: ISODate("")},
}

对于再次查询,请使用聚合框架。如果您愿意,这也可以是一个有上限的集合,并在注册字段上有一个索引。它也更灵活,因为您可以稍后添加更多字段,例如访问持续时间。

于 2012-12-05T09:48:03.527 回答