我有两个表,记录不断从外部源插入到这些表中。假设这些表正在保存用户交互的统计数据。当用户单击按钮时,该单击的详细信息(用户、单击时间等)将写入其中一个表中。当用户将鼠标悬停在该按钮上时,记录及其详细信息将添加到其他表中。

如果有大量的用户不断地与系统交互,就会产生大量的数据,这些表就会巨大地增长。

当我想查看数据时,我希望以每小时或每天的分辨率查看数据。

有没有一种方法或最佳实践可以按照所需的分辨率不断增量地总结数据(在收集数据时)?

或者有更好的方法来解决此类问题吗?

附言。到目前为止,我发现像 Talend 这样的 ETL 工具可以让生活变得轻松。

更新:我目前正在使用 MySQL,但我想知道最佳实践,无论数据库、环境等如何。

有帮助吗?

解决方案

在低延迟数据仓库应用程序上执行此操作的正常方法是使用一个分区表,该表的前导分区包含可以快速更新的内容(即无需动态重新计算聚合),但尾随分区回填聚合。换句话说,前导分区可以使用与尾随分区不同的存储方案。

大多数商业和一些开源 RDBMS 平台(例如PostgreSQL)可以支持分区表,它可以以一种或另一种方式用于执行此类操作。如何从日志填充数据库留给读者作为练习。

基本上,此类系统的结构如下:

  • 您的表格分区或日期时间值是按小时,日期或任何谷物划分的分区。日志条目将附加到此表上。

  • 随着时间窗口从分区滑落,定期作业会索引或总结它并将其转换为“冷冻”状态。例如,Oracle上的作业可能会在该分区上创建位图索引,或更新实现的视图以包含该分区的摘要数据。

  • 稍后,您可以将旧数据删除,将其汇总或将分区合并在一起。

  • 随着时间的流逝,周期性的工作后背填补了前沿分区的后面。历史数据被转换为一种格式,该格式将自己适合于性能统计查询,而前边缘分区则易于快速更新。由于此分区没有太多数据,因此整个数据集的查询相对较快。

此过程的确切性质因 DBMS 平台而异。

例如,SQL Server 上的表分区并不是那么好,但这可以通过 Analysis Services(Microsoft 与 SQL Server 捆绑在一起的 OLAP 服务器)来完成。这是通过将前导分区配置为纯 ROLAP(OLAP 服务器只是对底层数据库发出查询),然后将尾随分区重建为 MOLAP(OLAP 服务器构建自己的专用数据结构,包括称为“聚合”的持久摘要)来完成的。 )。分析服务可以对用户完全透明地完成此操作。它可以在后台重建分区,而旧的 ROLAP 分区仍然对用户可见。一旦构建完成,它就会交换分区;该立方体始终可用,不会中断对用户的服务。

Oracle允许独立更新分区结构,因此可以构建索引,或者在物化视图上构建分区。通过查询重写,Oracle 中的查询优化器可以计算出从基础事实表计算出的聚合数据可以从物化视图中获得。该查询将从分区可用的物化视图和不可用的前沿分区读取聚合数据。

PostgreSQL 或许能够做类似的事情,但我从未考虑过在其上实现这种类型的系统。

如果您可以忍受定期中断,则可以通过进行汇总并设置前导数据和尾随数据的视图来显式完成类似的操作。这允许在不支持透明分区的系统上进行此类分析。但是,在重建视图时,系统会出现短暂中断,因此您实际上无法在工作时间内执行此操作 - 最常见的是在夜间。

编辑: 根据日志文件的格式或可用的日志记录选项,可以使用多种方法将数据加载到系统中。一些选项是:

  • 使用您最喜欢的编程语言编写一个脚本来读取数据,解析出相关位并将其插入数据库中。这可能会经常运行,但您必须有某种方法来跟踪您在文件中的位置。小心锁定,尤其是在 Windows 上。Unix/Linux 上的默认文件锁定语义允许您执行此操作(这就是 tail -f 有效),但 Windows 上的默认行为有所不同;两个系统都必须编写得能够很好地配合。

  • 在 unix-oid 系统上,您可以将日志写入管道,并有一个类似于上面从管道读取日志的过程。这将具有最低的延迟,但读取器中的故障可能会阻止您的应用程序。

  • 为您的应用程序编写一个日志记录接口,直接填充数据库,而不是写出日志文件。

  • 使用数据库的批量加载 API(大多数(如果不是全部)都提供此类 API)并批量加载日志数据。编写与第一个选项类似的程序,但使用批量加载 API。与逐行填充相比,这会使用更少的资源,但设置批量加载的开销更大。它适合频率较低的负载(可能每小时或每天),并且对整个系统造成的压力较小。

在大多数情况下,跟踪您去过的地方都成为一个问题。轮询文件以发现更改可能会非常昂贵,因此您可能需要设置记录器,以便它能够与您的日志阅读器很好地配合工作。

  • 一种选择是更改记录器,以便它开始每个周期(例如每隔几分钟)写入不同的文件。让日志阅读器定期启动并加载尚未处理的新文件。阅读旧文件。为此,文件的命名方案应基于时间,以便读者知道要选择哪个文件。处理应用程序仍在使用的文件更加繁琐(然后您将需要跟踪已读取的文件量),因此您只想读取最后一个周期的文件。

  • 另一种选择是移动文件然后读取它。这在行为类似于 Unix 的文件系统上效果最好,但也应该在 NTFS 上工作。您移动该文件,然后随意阅读它。但是,它要求记录器以创建/追加模式打开文件,写入文件然后关闭它 - 而不是保持它打开和锁定。这绝对是 Unix 行为 - 移动操作必须是原子的。在 Windows 上,您可能确实需要站在记录器旁边才能完成此操作。

其他提示

看一眼 RRD工具. 。这是一个循环数据库。您可以定义要捕获的指标,但也可以定义存储它的分辨率。

例如,您可以指定最后一小时,您保留每一秒的信息;过去 24 小时 - 每分钟;过去一周、每小时等

它广泛用于收集系统中的统计数据,例如 神经节仙人掌.

当涉及到数据切片和聚合(按时间或其他方式)时,星型模式(Kimball 星型)是一个相当简单但功能强大的解决方案。假设对于每次点击,我们存储时间(到第二分辨率)、用户信息、按钮 ID 和用户位置。为了轻松进行切片和切分,我将从预加载的查找表开始,查找很少更改的对象的属性 - 在 DW 世界中称为维度表。

pagevisit2_model_02

dimDate 表每天一行,其中包含描述特定日期的属性(字段)数量。该表可以提前几年预加载,如果它包含以下字段,则应每天更新一次 DaysAgo, WeeksAgo, MonthsAgo, YearsAgo;否则可能会“加载后忘记”。这 dimDate 允许轻松地按日期属性进行切片,例如

WHERE [YEAR] = 2009 AND DayOfWeek = 'Sunday'

对于 10 年的数据,该表只有约 3650 行。

dimGeography 表预加载了感兴趣的地理区域——行数取决于报告中所需的“地理分辨率”,它允许数据切片,例如

WHERE Continent = 'South America'

一旦加载,就很少改变。

对于网站的每个按钮,dimButton 表中都有一行,因此查询可能有

WHERE PageURL = 'http://…/somepage.php'

dimUser 表中每个注册用户都有一行,该行应该在用户注册后立即加载新的用户信息,或者至少在任何其他用户事务记录在事实表中之前新的用户信息应该存在于表中。

要记录按钮点击,我将添加 factClick 桌子。

pagevisit2_model_01

factClick 表中有一行记录特定用户在某一时间点的每次单击按钮。我用过 TimeStamp (第二个决议), ButtonKeyUserKey 在复合主键中,以比每秒一次的速度过滤特定用户的点击。请注意 Hour 字段,它包含的小时部分 TimeStamp, ,一个 0-23 范围内的整数,以便每小时轻松切片,例如

WHERE [HOUR] BETWEEN 7 AND 9

所以,现在我们必须考虑:

  • 如何加载表?定期(可能每小时或每隔几分钟)使用 ETL 工具或使用某种事件流流程的低延迟解决方案从博客中获取。
  • 表中的信息保留多长时间?

无论表只保留一天的信息还是几年的信息——都应该进行分区; 关注坦布里奇W 在他的回答中解释了分区,所以我在这里跳过它。

现在,几个根据不同属性(包括日期和小时)进行切片和切块的示例

为了简化查询,我将添加一个视图来展平模型:

/* To simplify queries flatten the model */ 
CREATE VIEW vClicks 
AS 
SELECT * 
FROM factClick AS f 
JOIN dimDate AS d ON d.DateKey = f.DateKey 
JOIN dimButton AS b ON b.ButtonKey = f.ButtonKey 
JOIN dimUser AS u ON u.UserKey = f.UserKey 
JOIN dimGeography AS g ON g.GeographyKey = f.GeographyKey

查询示例

/* 
Count number of times specific users clicked any button  
today between 7 and 9 AM (7:00 - 9:59)
*/ 
SELECT  [Email] 
       ,COUNT(*) AS [Counter] 
FROM    vClicks 
WHERE   [DaysAgo] = 0 
        AND [Hour] BETWEEN 7 AND 9 
        AND [Email] IN ('dude45@somemail.com', 'bob46@bobmail.com') 
GROUP BY [Email] 
ORDER BY [Email]

假设我对以下数据感兴趣 User = ALL. 。这 dimUser 是一个大表,所以我将创建一个没有它的视图,以加快查询速度。

/* 
Because dimUser can be large table it is good 
to have a view without it, to speed-up queries 
when user info is not required 
*/ 
CREATE VIEW vClicksNoUsr 
AS 
SELECT * 
FROM factClick AS f 
JOIN dimDate AS d ON d.DateKey = f.DateKey 
JOIN dimButton AS b ON b.ButtonKey = f.ButtonKey 
JOIN dimGeography AS g ON g.GeographyKey = f.GeographyKey

查询示例

/* 
Count number of times a button was clicked on a specific page 
today and yesterday, for each hour. 
*/ 
SELECT  [FullDate] 
       ,[Hour] 
       ,COUNT(*) AS [Counter] 
FROM    vClicksNoUsr 
WHERE   [DaysAgo] IN ( 0, 1 ) 
        AND PageURL = 'http://...MyPage' 
GROUP BY [FullDate], [Hour] 
ORDER BY [FullDate] DESC, [Hour] DESC



假设对于 聚合 我们不需要保留特定的用户信息,而只对日期、时间、按钮和地理位置感兴趣。中的每一行 factClickAgg 表有一个计数器,用于记录从特定地理区域单击特定按钮的每小时。

pagevisit2_model_03

factClickAgg 表可以每小时加载一次,甚至可以在每天结束时加载,具体取决于报告和分析的要求。例如,假设该表在每天结束时(午夜之后)加载,我可以使用如下内容:

/* At the end of each day (after midnight) aggregate data. */ 
INSERT  INTO factClickAgg 
        SELECT  DateKey 
               ,[Hour] 
               ,ButtonKey 
               ,GeographyKey 
               ,COUNT(*) AS [ClickCount] 
        FROM    vClicksNoUsr 
        WHERE   [DaysAgo] = 1 
        GROUP BY DateKey 
               ,[Hour] 
               ,ButtonKey 
               ,GeographyKey

为了简化查询,我将创建一个视图来展平模型:

/* To simplify queries for aggregated data */ 
CREATE VIEW vClicksAggregate 
AS 
SELECT * 
FROM factClickAgg AS f 
JOIN dimDate AS d ON d.DateKey = f.DateKey 
JOIN dimButton AS b ON b.ButtonKey = f.ButtonKey 
JOIN dimGeography AS g ON g.GeographyKey = f.GeographyKey

现在我可以查询聚合数据,例如按天:

/* 
Number of times a specific buttons was clicked 
in year 2009, by day 
*/ 
SELECT  FullDate 
       ,SUM(ClickCount) AS [Counter] 
FROM    vClicksAggregate 
WHERE   ButtonName = 'MyBtn_1' 
        AND [Year] = 2009 
GROUP BY FullDate 
ORDER BY FullDate

或者有更多选择

/* 
Number of times specific buttons were clicked 
in year 2008, on Saturdays, between 9:00 and 11:59 AM 
by users from Africa 
*/ 

SELECT  SUM(ClickCount) AS [Counter] 
FROM    vClicksAggregate 
WHERE   [Year] = 2008 
        AND [DayOfWeek] = 'Saturday' 
        AND [Hour] BETWEEN 9 AND 11 
        AND Continent = 'Africa' 
        AND ButtonName IN ( 'MyBtn_1', 'MyBtn_2', 'MyBtn_3' )

您可以使用一个历史分贝像PI或历史学家。这些可能是更多的钱比你想花这个项目,所以你可能要查找的免费替代品之一,喜欢的实时和历史记录数据库软件包

快速而肮脏的建议。

[假设您无法更改基础表,这些表已经记录了添加的时间/日期行,并且您确实有权在数据库中创建对象]。

  1. 创建一个视图(或几个视图),其上有一个逻辑字段,它通过分割表中的日期生成唯一的“槽号”。就像是:

创建视图作为选择a,b,c,substr(date_field,x,y)slot_number的选择;

上面的示例经过简化,您可能希望添加日期+时间中的更多元素。

[例如,假设日期是“2010-01-01 10:20:23,111”,您也许可以将密钥生成为“2010-01-01 10:00”:所以你的分辨率是一小时]。

  1. 可选:使用 VIEW 生成真实的表,例如:

    创建表Frozen_data作为Select *从view where slot_number ='xxx;

为什么要麻烦第 1 步呢?您实际上不必:仅使用 VIEW 可能会使事情变得更容易(从 SQL 的角度来看)。

为什么要麻烦第 2 步呢?只是(可能)减少已经繁忙的表上的负载的一种方法:如果您可以动态生成 DDL,那么您可以生成包含数据“槽”副本的单独表:然后您就可以使用它。

或者您可以设置一组表:一天中每小时一次。创建一个触发器来填充辅助表:触发器的逻辑可以隔离写入哪个表。

您每天都必须重置这些表:除非您可以在数据库的触发器中生成表。[我认为不太可能]。

(到目前为止)尚未给出的建议可能是使用 沙发数据库 或处理非结构化数据的类似数据库概念。

等待!在惊恐地跳到我身上之前,让我把话说完。

CouchDB 收集非结构化数据(JSON &c);引用网站上的技术概述,

为了解决将结构添加回非结构化和半结构化数据的问题,CouchDB集成了视图模型。视图是在数据库中汇总和报告文档的方法,并在按需构建以汇总,加入和报告数据库文档。视图是动态构建的,并且不会影响基础文档,您可以随心所欲拥有相同数据的不同视图表示形式。

视图定义严格是虚拟的,仅显示当前数据库实例中的文档,使其与它们显示的数据分开,并与复制兼容。CouchDB视图是在特殊设计文档中定义的,并且可以在常规文档(如常规文档)等数据库实例中复制,因此不仅数据在CouchDB中复制,而且整个应用程序设计也会复制。

从你的要求,我可以告诉你需要

  • 以可靠的方式收集大量数据
  • 优先考虑的是速度/可靠性,而不是在数据进入系统后立即对其进行结构化,也不是维护/检查所收集内容的结构属性(即使您错过了 1 毫秒的用户数据,也可能不是一个大问题)
  • 您需要结构化数据 出去 数据库的

就我个人而言,我会做类似的事情:

  • 缓存在客户端上收集的数据并将其批量保存到 couchdb 上
  • 根据工作负载,保持数据库集群(同样,couchdb 是为此设计的)彼此同步
  • 每个时间间隔都有一个服务器生成您需要的内容的视图(即每小时等),而其他人则继续收集数据
  • 将此类(现已结构化)视图保存到适当的数据库中,以便使用 SQL 工具或其他工具进行操作和使用

最后一点只是一个例子。我不知道你打算用它做什么。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top