本文为您介绍表设计的最佳实践方式,为实际开发提供依据和指导。

产生大量小文件的操作

MaxCompute表的小文件会影响存储和计算性能。在进行表设计时,应考虑避开产生大量小文件的操作。

  • 使用MaxCompute Tunnel SDK上传数据的过程中,每commit一次就会产生一个文件。这时每个文件过小(例如几KB),并且频繁上传(例如每5秒上传一次),则一小时就会产生720个小文件,一天就会产生17280个小文件。
  • 使用MaxCompute Tunnel SDK上传数据,如果创建了session却没有上传数据,而是直接commit,则会产生大量空目录(服务侧等同于小文件)。
  • 使用MaxCompute Console命令行工具Tunnel命令上传时,将本地大文件切分过小会导致上传后产生的文件数过多,文件过小。
  • 通过DataHub做数据归档,DataHub的每个shard写入MaxCompute时存在条件限制,即数据总量每到64MB,commit一次,或每隔5分钟commit一次,形成一个文件。当开启的shard数过多(例如20个shard)时,每个shard数据在5分钟内都远达不到64M(例如几百KB),就会产生大量小文件。相应地,一天会产生24*12*20=5760个小文件。
  • 通过Dataworks等数据开发工具进行数据增量插入(insert into)到MaxCompute表(或表分区)时,每次进行数据增量插入都会产生一个文件。若每次插入10条,则每天累计插入10000条记录,即会产生1000个小文件。
  • 使用阿里云DTS将数据从RDS等数据库同步到MaxCompute时,会创建全量表和增量表。在增量表进行数据插入的过程中,会因为每次数据插入条数较少而造成增量表中的小文件问题。例如每隔5分钟执行一次同步,每次同步的数据量为10条,一天内的增量为10000条,则会产生1000个小文件。此种场景下,需要在数据同步完成后进行全量极限表和增量数据表的merge。
  • 源数据采集客户端太多时,如果源数据通过Tunnel直接进入到一个分区,则每个源数据采集客户端提交一次数据,都会在同一分区下产生一个独立的文件,从而导致大量小文件的出现。
  • 当SLS触发FunctionCompute持续高频地往MaxCompute中心传入文件时,小文件流式数据会进入MaxCompute。

根据数据划分项目空间

项目空间(Project)是MaxCompute最高层的对象。按项目空间进行资源的分配、隔离和管理,实现了多租户的管理能力。如果多个应用需要共享“数据”,则推荐使用同一个项目空间;反之,如果多个应用所需“数据”是无关的,则推荐使用不同的项目空间。项目空间的表和分区可以通过Package授权的方式进行交换。

维度表设计的最佳实践

描述属性的表通常被设计为维度表。维度表可与任意表组中的任意表进行关联,且创建时无需配置分区信息,但是对单表数据量大小有所限制。

说明
  • 通常要求维度表的单表量不超过1000万个。
  • 维度表的数据不应被大量更新。
  • 可以使用mapjoin语句进行维度表和其它表的join操作。

拉链表设计

在数据仓库的数据模型设计过程中,经常会遇到如下需求:
  • 数据量较大。
  • 表中的部分字段被更新,例如用户的地址、产品的描述信息、订单的状态、手机号码等。
  • 需要查看某一个时间点或时间段的历史快照信息。例如,查看某一个订单在某一个历史时间点的状态,或查看某一个用户在过去某段时间内更新过几次等。
  • 变化的比例不大、频率不高。假设总共有1000万个会员,且每天新增和发生变化的会员只有10万左右,如果每天都在表中保留一份全量,那么每次全量中会保存很多不变的信息,极大地浪费了存储资源。
MaxCompute提供了将不同表转化为极限存储表的方法。极限存储操作示例如下:
  1. 创建源表。
    create table src_tbl (key0 STRING, key1 STRING, col0 STRING, col1 STRING, col2 STRING) PARTITION (datestamp_x STRING, pt0 STRING);
  2. 导入数据。
  3. 将src_tbl转变为极限存储的表。
    set odps.exstore.primarykey=key0,key1;
    [set odps.exstore.ignorekey=col0;]
    EXSTORE exstore_tbl PARTITION (datestamp_x='20140801');
    EXSTORE exstore_tbl PARTITION (datestamp_x='20140802');
说明 极限存储功能待发布,在此主要介绍其设计思想。

采集源表的设计

数据采集方式包括流式数据写入、 批量数据写入、周期调度条式数据插入。数据量较大时,需确保同一个业务单元的数据使用分区和表进行分;数据量较小时,需优化采集频率。

  • 流式数据写入
    • 对于流式写入的数据,采集的通道通常较多,相关采集通道应做有效区分。在单个数据通道写入量较大的情况下,应该按照时间进行分区设计。
    • 在采集通道数据量较小的情况下,适合采取非分区表设计,将终端类型和采集时间设计成标准列字段。
    • 采用DataHub进行数据写入时,应该合理规划shard数量,避免出现由于shard过多而导致的采集通道流量较小而通道较多的问题。
  • 批量数据写入

    使用批量数据写入方式时,应重点关注写入周期。

  • 周期调度条式数据插入

    应避免使用周期调度条式数据插入的方法。若无法避免使用此方法,则需建立分区表,在新分区进行插入操作,减小对于原来分区的影响。

日志表的设计

日志的本质是个流水表,不涉及记录的更新。日志表设计需注意以下几点:
  • 考虑是否需要对日志进行去重处理。
  • 考虑是否需要扩展维度属性。
    • 您需要考虑业务使用的频次以及关联是否会造成产出的延迟,来确定是否需要关联维度表、扩展维度属性字段。
    • 需要谨慎选择是否对维度表进行扩展。
  • 考虑区分终端类型。
    • 日志表中的数据量很庞大,在业务分析使用时,通常会按PC端、APP端来统计分析。由于PC端、APP端采用不同的体系采集数据,所以通常的做法是按终端设计多个明细DWD表。
    • 如果终端较多但数据量不大,例如一个终端的数据量小于1TB但采集次数较多,则可以不对终端进行分区,设置终端信息为普通列。
说明
  • 对日志表进行分区设计时,可以按照日志采集的时间进行分区。在写入数据前进行数据的采集和整合,整合好后,一次性提交数据(通常是每64M提交一次)。
  • 日志数据很少有对原来分区的更新操作,可以用insert进行少量数据的插入,但通常需要限制插入次数。
  • 如果有大量的更新操作,则需采用insert overwrite操作避免小文件问题。
  • 为日志表设置合理的分区,并对长久不被访问的冷热数据配置归档操作。

互动明细表的设计

周期快照表中存放的是每天收藏的所有记录的快照。
  • 面临的问题:历史累计的记录有很多,每天需要将当天增量表与前一天的全量表合并才能生成快照,非常耗资源。统计最近1天的新增收藏数,需要扫描全量表。如何才能降低资源?
  • 建议:建立一个事务性事实表,在建立一个存放当前有效收藏的周期快照表,以满足各种不同业务的统计分析需要。
说明
  • 设计互动明细表时,区分存量数据和增量数据之间的关系非常重要。
  • 新增数据应作为增量数据写入新分区。
  • 应尽量减少对已有分区中的数据进行修改和插入新数据。
  • 在数据插入和全表覆盖写种选择时应尽量选用insert overwrite而并选择insert into。

MaxCompute表数据更新与删除操作

MaxCompute实现关系型数据库所支持的delete/update/merge SQL的示例如下:
  • 表准备
    -- 上日全量表
    table1(key1 string,key2 string,col1 string,col2 string);
    -- 今日增量表
    table2(key1 string,key2 string,col1 string,col2 string);
    -- 今日增量表(删除)
    table3(key1 string,key2 string,col1 string,col2 string);
  • update(将table2表中的记录的值更新到table1表中)
    insert overwrite table table1
    select t1.key1
          ,t1.key2
          ,case when t2.key1 is not null then t2.col1 else t1.col1 end as col1
          ,case when t2.key1 is not null then t2.col2 else t1.col2 end as col2
      from table1 t1
      left outer join table2 t2 on t1.key1 = t2.key1 and t1.key2 = t2.key2
    ;
  • delete(从table1表中删除table2表中的记录)
    insert overwrite table table1
    select t1.key1
          ,t1.key2
          ,t1.col1
          ,t1.col2
      from table1 t1
      left outer join table2 t2 on t1.key1 = t2.key1 and t1.key2 = t2.key2
      where t2.key1 is null
    ;                        
  • merge(当日发生过删除操作)
    insert overwrite table table1
    select 
      from(
    -- 先把上日和今日都存在的记录从上日表中排除,再把今日删除的记录排除。剩下的就是今日没有更新的记录。
    select t1.key1
          ,t1.key2
          ,t1.col1
          ,t1.col2
      from table1 t1
      left outer join table2 t2 on t1.key1 = t2.key1 and t1.key2 = t2.key2
      left outer join table3 t3 on t1.key1 = t3.key1 and t1.key2 = t3.key2
      where t2.key1 is null or t2.key1 is null
      union all
    -- 再合并上今日增量,就是今日的全量。
    select t2.key1
          ,t2.key2
          ,t2.col1
          ,t2.col2
      from table2 t2)tt
    ;
  • merge(当日没有发生过删除操作)
    insert overwrite table table1
    select 
      from(
    -- 先把上日存在,今日也存在的记录从上日表中排除。剩下的就是今日没有更新的记录。
    select t1.key1
          ,t1.key2
          ,t1.col1
          ,t1.col2
      from table1 t1
      left outer join table2 t2 on t1.key1 = t2.key1 and t1.key2 = t2.key2
      where t2.key1 is null
      union all
    -- 再合并上今日增量,就是今天的全量。
    select t2.key1
          ,t2.key2
          ,t2.col1
          ,t2.col2
      from table2 t2)tt
    ;

表创建设计示例

  • 场景

    天气情况信息采集。

  • 基本信息
    • 数据信息包括地名、关于此地的属性信息(例如面积、基本人口数量等)、天气信息。
    • 属性的数据变化较小,但天气信息数采用多个终端采集,且数据量较大。
    • 天气信息变化较大,但在终端数量稳定的情况下流量基本稳定。
  • 表设计指南
    • 建议将数据信息划分为基本属性表和天气日志表,分别用于存储变化小和变化大的数据。
    • 因为天气信息的数据量巨大,在对天气日志表按照地域进行分区后,可以按照时间(例如天)进行二级分区。此种分区方式可避免发生因某一个地点或某一个时间的天气变化而造成其他无关数据变化。
    • 建议采集终端上使用DataHub进行数据汇聚,然后依据稳定的流量值选择合适的shard通道数量,以批量数据传输的方式写入到天气日志表中,而非insert into。