【问题标题】:Scalable way to keep track of user activity跟踪用户活动的可扩展方式
【发布时间】:2012-12-30 03:39:39
【问题描述】:

我正在开发一个 HR 系统,我需要跟踪用户个人资料上的所有视图,因为每个招聘人员对候选人个人资料的视图都是有限的。我主要关心的是我的方法的可扩展性,如下所示: 我目前创建了一个有 2 列的表,即被查看的候选人的 id 和查看候选人的招聘人员的 id,每个视图只计算一次,因此如果您再次看到相同的候选人,则不会插入任何记录。

根据数据库中招聘人员和候选人的数量,我可以肯定地说我的表格会增长得非常快,更糟糕的是,我必须在每次请求时查询我的表格,因为我必须在 UI 中显示数字招聘人员查看过的候选人。考虑到可扩展性,哪种方法最好?


我会稍微解释一下这个案例: 我们有公司,每家公司都有很多招聘人员。

ViewsAssigner_Identifier 表

  • ID:int PK
  • Company_Id:int FK 非集群
  • Views_Assigned:int 非集群
  • 日期:非聚集日期

CandidateViewCounts 表

  • ID:int PK
  • Recruiter_id: int FK NON-CLUSTERED ?
  • Candidate_id: int FK NON-CLUSTERED ?
  • ViewsAssigner_Identifier_Id: int FK NON-CLUSTERED ?
  • 查看日期:日期非聚集

我将通过 [ViewsAssigner_Identifier_id] 查询所有 [Candidate_id] 的 Select

我们希望按公司而不是招聘人员进行搜索,因为同一公司中的所有招聘人员都对公司使用了相同的 [Views_Assigned]。换句话说,第一个查看候选人的 Recuiter 将被存储在“CandidateViewCounts”表中,随后查看相同候选人的 Recruiter 将不会被存储。

结果: 我需要通过 [ViewsAssigner_Identifier_id] 检索所有 [Candidate_Id] 的列表,然后我可以对所有这些候选人 ID 求和。

查询示例:

从 [dbo].[CandidateViewCounts] 中选择 [Candidate_Id],其中 [ViewsAssigner_Identifier_id] = 1

有什么建议吗?

【问题讨论】:

  • 当您说可扩展性时,我们指的是多少?因为如果你有 100 名招聘人员查看 10000 人,那真的不会那么大。
  • 有 60,000 名招聘人员和 2,000,000 名应聘者
  • 如果您只跟踪每个招聘人员的一次视图 -> 候选人组合,您将如何“限制候选人资料的视图”?似乎他们可以查看候选人 1000 次,而您只知道它发生了一次(因为您丢弃了所有后续视图)。
  • 标识符_Id?这不是多余的吗?
  • 我正在考虑为 [ViewsAssigner_Identifier] 中的每一行设置一个 Id PK,但如果它是多余的,我可以删除它,有什么建议吗?

标签: sql-server asp.net-mvc scalability


【解决方案1】:

如果您认为每个招聘人员可能会查看每个候选人一次,那么您所讨论的最大行数为 60,000 * 2,000,000 行。这是一个很大的数字,但它们不是很宽的行;正如 ErikE 解释的那样,您将能够在每个页面上获得许多行,因此即使是表扫描,总 I/O 也不会像听起来那么糟糕。

也就是说,出于维护原因,只要您不按 CandidateID 进行搜索,您可能希望在 RecruiterID 上对该表进行分区。例如,您的分区方案可能有一个用于 1 到 2000 之间的 RecruiterID 的分区,一个用于 2001 -> 4000 的分区,等等。这样您就可以最大限度地增加每个分区的行数,并可以相应地规划文件空间(您可以将每个分区在自己的文件组上,分隔 I/O)。

另一点是:如果您希望运行诸如“对此候选人有多少意见(我们不关心哪些招聘人员)之类的查询?”或者“这个招聘人员查看了多少候选人(我们不在乎哪些候选人)?”那么你可以考虑索引视图。例如

CREATE VIEW dbo.RecruiterViewCounts
WITH SCHEMABINDING
AS
  SELECT RecruiterID, COUNT_BIG(*)
    FROM dbo.tablename;
GO
CREATE UNIQUE CLUSTERED INDEX pk_rvc ON dbo.RecruiterViewCounts(RecruiterID);
GO

CREATE VIEW dbo.CandidateViewCounts
WITH SCHEMABINDING
AS
  SELECT CandidateID, COUNT_BIG(*)
    FROM dbo.tablename;
GO
CREATE UNIQUE CLUSTERED INDEX pk_cvc ON dbo.CandidateViewCounts(CandidateID);
GO

现在,这些聚集索引的维护成本很高,因此您需要针对它们测试写入工作负载。但是他们应该非常、非常快速地进行这两个查询,而不必寻找您的大表并可能为非常忙碌的招聘人员或非常受欢迎的候选人读取多个页面。

【讨论】:

  • 非常感谢,我在问题中添加了更多细节,还有其他建议吗?
  • 我需要两者,在一个场景中我需要一个列表,另一个我需要一个计数
  • @user1981827 我的意思是你问了一个问题,人们努力回答了那个问题,现在你已经把它变成了两个问题。我知道从您的角度来看,您正在尝试解决一个大问题,但是事后像那样分裂您的问题并不公平。它不仅降低了我们已经完成的工作的价值,还让我们想知道在我们解决了您问题的第二部分之后您会要求什么......
  • @AaronBertrand 是的,抱歉,我是使用 stackoverflow 的新手,感谢您告诉我。我们可以说我真正需要的是列表。
【解决方案2】:

如果您的表聚集在 RecruiterID 上,您的搜索速度会非常快,而且在我看来完全没有性能问题。

在您描述的如此狭窄的表格中,找出任何一位招聘人员查看的个人资料应该需要 99% 以上的时间进行一次阅读。 (假设填充因子 = 80,页面拆分最少;行宽假设两个 int 列 = 16 字节 + 开销,称之为 20 字节;每页 8040 字节左右;假设每个招聘人员平均获得 4 次查看 2.5 行 = 球场 128每个数据页的招聘人员)。表中的总行数无关紧要,因为它可以查找聚集索引。是的,它必须遍历树,但它仍然会非常快。只要每个候选人必须计算一次意见,就没有更好的方法了。如果只是总观看次数,则可以改为计数。

我认为您不必担心太多。如果您担心系统可能会增长到每秒数万个请求,并且您将获得某种限制活动热点,只要在任何一个时间点访问的招聘人员不会巧合地分配有顺序 ID 到他们,你会没事的。

这里的主要原则是您要避免任何必须从上到下扫描表格的事情。只要您始终按RecruiterIDRecruiterID, CandidateID 搜索,就可以避免这种情况。当您想单独通过CandidateID 搜索时,如果没有额外的索引,您将遇到麻烦。在CandidateID 上添加非聚集索引将使您的表占用的空间增加一倍(一半用于聚集,一半用于非聚集),但这没什么大不了的。然后通过CandidateID 搜索将同样快,因为非聚集索引将正确覆盖查询并且不需要书签查找。

更新

这是对您在问题更新中提供的大量新信息的回应。

首先,您的CandidateViewCounts 表命名不正确。它更像CandidateFirstViewedByRecruiterAtCompany。它只能间接回答您的问题,即关于公司而不是招聘人员的问题,所以在我看来,您所描述的场景确实需要CompanyCandidateViewed 表:

CompanyID int FK
CandidateID int FK
PRIMARY KEY CLUSTERED (CompanyID, CandidateID)

存储查看候选人的招聘人员的 CompanyID 和 CandidateID。简单的!现在我原来的答案仍然适用于你,只需将RecruiterIDCompanyID 交换即可。

如果您确实想跟踪哪些招聘人员查看了哪些候选人,请在RecruiterCandidateViewed 表中执行此操作(并存储所有招聘人员->候选人视图)。这可以稍后或在数据仓库中查询。但您的实时 OLTP 需求将通过上述表格得到满足。

另外,我想提一下,您可能会将标识列放在不需要它们的表中。您应该避免使用标识列,除非该列将用作另一个表中的 FK(即使那样也不总是,因为有时在正确的数据建模中为了防止可能的非规范化,您必须在 FK 中使用复合键)。例如,您的ViewsAssigner_Identifier 表在我看来需要一些帮助(当然我没有这里的所有信息并且可能不在基地)。如果 CompanyDate 是该表最重要的部分,请将它们组合成集群 PK 并尽可能去掉标识列。

【讨论】:

  • 非常感谢,我在问题中添加了更多细节,还有其他建议吗?
  • 根据您的回答,我只有一个问题,我可以将 [Date] 列添加为 NON-CLUSTERED 吗?
  • 列不能集群或非集群。只有索引可以。一张表只能有一个聚集索引。它可以由许多列组成——所有其余的列都将与它一起保存,但不被视为键的一部分。您可以在一个表上拥有许多非聚集索引,每个索引的列数与您的喜好一样少或多。所以...我不明白你的问题。任何数据类型,包括Date,只要是NOT NULL,并且索引中的所有列加起来不超过900字节,都可以成为索引的一部分。
  • 根据在索引中有Date 的问题是因为我想知道如果我将Date 包含在非聚集索引中,性能会如何表现
  • 我没有足够的信息来帮助你。与其在这里提出更多问题,不如发布一个新问题,其中包含您要完成的工作的完整详细信息。包括您的表格的详细布局——更好的是,转到sqlfiddle.com 并创建表格和查询,向我们展示您已经拥有的数据是如何工作的。在此处提供给我的链接,我会看看我能做什么。同时,如果我或 Aaron 的问题对您有所帮助,请使用我们答案左上角的投票/答案选择选项。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多