ETL用于处理历史记录

时间:2012-05-18 10:07:06

标签: performance oracle etl

我是一个DWH项目(不完全,但仍然)。我们经常遇到这个问题,我想知道是否会有更好的解决方案。如下

我们会收到一些大型文件,其中的记录包含用户所处的所有状态,例如:

UID | State    | Date
1   | Active   | 20120518
2   | Inactive | 20120517
1   | Inactive | 20120517
...

我们通常对每个用户的最新状态感兴趣。到目前为止一切都很好,只需要一点点排序,我们就可以得到我们想要的方式。唯一的问题是,这些文件通常很大..比如20-60gb,排序这些人有时是一种痛苦,因为排序的逻辑通常不那么直接。

我们通常做的是将所有内容加载到Oracle中,并使用中间表和物化视图来完成它。不过,有时表现会让我们感到害怕。

20-60gb可能很大,但 大。我的意思是,应该是一种更专业的方式来处理这些记录,不应该吗?

我想到了解决这个问题的两种基本方法:

1)在DBMS,脚本和编译的东西之外进行编程。但也许这不是很灵活,除非投入更多的时间来开发某些东西。此外,我可能不得不忙着自己管理盒子资源,而我不想担心。

2)将所有内容加载到DBMS(在我的情况下为Oracle)中,并使用它提供的任何工具对数据进行排序和剪辑。但这是我的情况,我不确定我们是使用所有工具还是只是以适合Oracle 10g的方式进行操作。

问题是:

你有一个包含数百万条历史记录的60gb文件,如上图所示,你的用户想要一个DB中的表,每个用户都有最后一个状态。

你会怎么做?

谢谢!

4 个答案:

答案 0 :(得分:2)

您可以采取两项措施来加快这一进程。

首先要为它提供计算能力。如果您拥有Enterprise Edition和大量内核,则可以通过并行查询显着减少加载时间。

另一件事是避免加载你不想要的记录。这就是你提到预处理文件的原因。我不确定你能做多少事情,除非你有权访问Hadoop集群在你的文件上运行一些map-reduce作业(好吧,主要是减少你发布的结构就像映射一样)已经可以了。)

但还有另一种选择:外部表格。外部表是在OS文件中而不是表空间中包含数据的表。并且它们可以并行启用(提供您的文件符合某些标准)。 Find out more

所以,你可能有一个像这样的外部表

CREATE TABLE user_status_external (
   uid     NUMBER(6),
   status      VARCHAR2(10),
   sdate        DATE
ORGANIZATION EXTERNAL
(TYPE oracle_loader
 DEFAULT DIRECTORY data_dir
 ACCESS PARAMETERS
 (
  RECORDS DELIMITED BY newline
  BADFILE 'usrsts.bad'
  DISCARDFILE 'usrsts.dis'
  LOGFILE 'usrsts.log'
  FIELDS TERMINATED BY ","  OPTIONALLY ENCLOSED BY '"'
  (
   uid     INTEGER EXTERNAL(6),
   status     CHAR(10),
   sdate       date 'yyyymmdd' )
 )
 LOCATION ('usrsts.dmp')
)
PARALLEL
REJECT LIMIT UNLIMITED;

请注意,您需要对DATA_DIR目录对象具有读写权限。

创建外部表后,您可以使用insert语句将唯一需要的数据加载到目标表中:

insert into user_status (uid, status, last_status_date)
    select  sq.uid
            ,  sq.status
            ,  sq.sdate
    from (
        select /*+ parallel (et,4) */ 
               et.uid
               , et.status
               , et.sdate
               , row_number() over (partition by et.uid order by et.sdate desc) rn  
        from user_status_external et
        ) sq
    where sq.rn = 1

请注意,与所有绩效建议一样,没有任何保证。您需要对环境中的事物进行基准测试。

另一件事是使用INSERT:我假设这些都是新的USERID,因为这是你的帖子建议的场景。如果你有一个更复杂的场景,那么你可能想要看看MERGE或完全不同的方法。


最后一件事:您似乎认为这是一种常见情况,它有一些标准方法。但是大多数数据仓库都会加载他们获得的所有数据。然后,他们可以过滤它以用于各种不同的用途,数据集市等。但是他们几乎总是在所有不同记录的实际仓库中维护历史。这就是为什么您可能无法获得行业标准解决方案的原因。

答案 1 :(得分:1)

我会选择APC首先说的话。但是,如果数据在多个文件中,我认为并行表只能并行加载数据,因此您可能需要将文件切割成多个。文件是如何生成的?一个20-60GB的文件真的很难处理 - 你能不能改变文件的生成,以便获得X 2GB文件?

将所有记录存入数据库后,您可能会遇到尝试对60GB数据进行排序的问题 - 值得查看用于提取最新状态的查询的排序阶段。在过去,我通过对要排序的字段之一上的数据进行散列分区来帮助大量排序,在本例中为user_id。然后Oracle只需做X个较小的排序,每个排序都可以并行进行。

所以,我的想法是:

  1. 尝试生成许多较小的文件而不是1个大文件
  2. 使用外部表,查看是否可以直接从外部表中提取所需数据
  3. 如果没有,请将文件的全部内容加载到散列分区表中 - 在此阶段确保插入/ * +追加nologging * /以避免撤消生成和重做生成。如果您的数据库将force_logging设置为true,则nologging提示将不起作用。
  4. 对分阶段数据运行选择以仅提取您关注的行,然后删除暂存的数据。
  5. nologging选项对于获得良好性能可能至关重要 - 要加载60GB的数据,您将生成至少60GB的重做日志,因此如果可以避免,那就更好了。您可能需要与您的DBA聊聊!

    假设您有大量可用的CPU,在将数据批量加载到临时表时压缩数据也是有意义的。如果压缩有重复的字段,压缩可能是磁盘上数据大小的一半 - 写入时保存的磁盘IO通常比加载它时消耗的额外CPU要多。

答案 2 :(得分:0)

我可能会过度简化问题,但为什么不能这样:

create materialized view my_view
tablespace my_tablespace
nologging
build immediate
refresh complete on demand
with primary key
as
select uid,state,date from
(
  select /*+ parallel (t,4) */ uid, state, date, row_number() over (partition by uid order by date desc) rnum
  from my_table t;
)
where rnum = 1;

然后在需要时完全刷新。

编辑:任何不要忘记重建统计数据,并可能在uid上抛出唯一索引。

答案 3 :(得分:-1)

我会写一个程序迭代每条记录,只保留那些比之前看到的记录更新的记录。最后,将数据插入数据库。

实际情况取决于我们谈论的用户数量 - 您最终可能需要仔细考虑您的中间存储。

一般来说,这会变成(伪代码):

foreach row in file
    if savedrow is null
        save row
    else
        if row is more desirable than savedrow
             save row
        end
    end
end

send saved rows to database

重点是,您需要定义一行被认为比另一行更合适。在简单的情况下,对于给定用户,当前行的日期晚于我们保存的最后一行。最后,您将拥有一个行列表,每个用户一个,每个行都有您最近看到的日期。

您可以对脚本或程序进行通用,以便框架与理解每个数据文件的代码分开。

它还需要一段时间,请注意: - )