10

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

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

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

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

或者有没有更好的方法来解决这类问题?

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

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

4

6 回答 6

8

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

大多数商业和一些开源 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 上,您可能真的必须站在记录器旁边才能完成这项工作。

于 2010-01-14T21:36:02.493 回答
2

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

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

它被广泛用于在GangliaCacti等系统中收集统计数据。

于 2010-01-07T17:02:34.643 回答
2

在对数据进行切片和聚合(按时间或其他方式)时,星型模式(Kimball 星型)是一个相当简单但功能强大的解决方案。假设对于每次点击,我们都会存储时间(秒分辨率)、用户信息、按钮 ID 和用户位置。为了实现简单的切片和切块,我将从预先加载的查找表开始,用于查找很少更改的对象的属性——在 DW 世界中称为维度表。 该表每天都有一行,其中包含描述特定日期的属性(字段)的数量。该表可以提前数年预加载,如果包含以下字段,则应每天更新一次;否则它可能是“加载并忘记”。允许按日期属性轻松切片,例如

pagevisit2_model_02

dimDateDaysAgo, WeeksAgo, MonthsAgo, YearsAgodimDate

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

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

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

WHERE Continent = 'South America'

一旦加载,它很少改变。

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

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

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

要记录按钮点击,我将添加factClick表格。 在某个时间点,特定用户每点击一次按钮,该表就有一行。我已经使用(第二个分辨率),并在复合主键中以比特定用户每秒一次的速度更快地过滤掉点击。请注意该字段,它包含 的小时部分,一个 0-23 范围内的整数,以允许每小时轻松切片,例如

pagevisit2_model_01

factClickTimeStampButtonKeyUserKeyHourTimeStamp

WHERE [HOUR] BETWEEN 7 AND 9

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

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

不管表是只保存一天的信息还是保存几年的信息——它都应该被分区;ConcernedOfTunbridgeW在他的回答中解释了分区,所以我会在这里跳过。

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

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

/* 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' )
于 2010-01-16T18:26:39.547 回答
0

您可以使用历史数据库,例如 PI 或 Historian。这些钱可能比您想为这个项目花费的钱多,因此您可能想查找免费软件替代品之一,例如Realtime and History Database Package

于 2010-01-14T18:58:07.320 回答
0

快速'n肮脏的建议。

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

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

CREATE VIEW 视图 AS SELECT a,b,c, SUBSTR(date_field,x,y) slot_number FROM TABLE;

上面的例子被简化了,你可能想从日期+时间添加更多元素。

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

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

    CREATE TABLE freeze_data AS SELECT * FROM VIEW WHERE slot_number='xxx;

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

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

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

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

于 2010-01-14T21:04:39.793 回答
0

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

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

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

为了解决向非结构化和半结构化数据添加结构的问题,CouchDB 集成了一个视图模型。视图是对数据库中的文档进行聚合和报告的方法,并且是按需构建的,用于对数据库文档进行聚合、连接和报告。视图是动态构建的,不会影响底层文档,您可以根据需要拥有相同数据的多个不同视图表示。

视图定义是严格虚拟的,只显示来自当前数据库实例的文档,使它们与它们显示的数据分开并与复制兼容。CouchDB 视图是在特殊设计文档中定义的,并且可以像常规文档一样跨数据库实例进行复制,因此不仅数据在 CouchDB 中复制,整个应用程序设计也可以复制。

根据您的要求,我可以告诉您需要

  • 以可靠的方式收集大量数据
  • 优先级是速度/可靠性,而不是在数据进入系统后立即构建数据,也不是维护/检查您收集的数据的结构属性(即使您错过了 1ms 的用户数据,也可能不是什么大问题)
  • 当数据来自数据库,您需要结构化数据

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

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

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

于 2010-01-15T11:29:30.100 回答