分层分区

元函数 CALENDAR_HIERARCHY_DAY 利用分区分组。您可以将此函数指定为分区 GROUP BY 表达式。CALENDAR_HIERARCHY_DAY 将表的日期分区组织成组的层次结构:最早的日期分区按年分组,较新的分区按月分组,最近的日期分区保持未分组状态。分组动态进行:随着最近的数据老化,Tuple Mover 将它们的分区合并到月份组中,并最终合并到年份组中。

管理带时间戳的数据

分区合并策略对于带时间戳的数据管理尤其重要,因为分区数量可能会迅速升级并面临 ROS 推回风险。例如,以下语句创建 store_orders 表并将数据加载到其中。CREATE TABLE 语句包含一个简单的 partition 子句,它指定按日期对数据进行分区:

=> DROP TABLE IF EXISTS public.store_orders CASCADE;
=> CREATE TABLE public.store_orders
(
    order_no int,
    order_date timestamp NOT NULL,
    shipper varchar(20),
    ship_date date
)
UNSEGMENTED ALL NODES PARTITION BY order_date::DATE;
CREATE TABLE
=> COPY store_orders FROM '/home/dbadmin/export_store_orders_data.txt';
41834
(1 row)

当 COPY 将新表数据加载到 ROS 存储中时,它通过将每日订单划分为不同的分区(在本例中为 809 个分区)来执行该表的 partition 子句,其中每个分区都需要有自己的 ROS 容器:

=> SELECT COUNT (DISTINCT ros_id) NumROS, node_name FROM PARTITIONS
    WHERE projection_name ilike '%store_orders_super%' GROUP BY node_name ORDER BY node_name;
 NumROS |    node_name
--------+------------------
    809 | v_vmart_node0001
    809 | v_vmart_node0002
    809 | v_vmart_node0003
(3 rows)

这远远高于每个投影最多 50 个分区的建议值。这一数字也接近每个投影 1024 个 ROS 容器的默认系统限制,从而面临不久的将来发生 ROS 推回的风险。

您可以通过多种方式解决此问题:

  • 考虑将表数据合并到更大的分区中,例如,按月而不是按天进行分区。但是,在此级别对数据进行分区可能会限制分区管理函数的有效使用。

  • 定期存档旧分区,从而最大限度地减少累积分区的数量。但是,这需要多一层数据管理,并且还要禁止访问历史数据。

或者,可以使用 CALENDAR_HIERARCHY_DAY 将分区自动合并到基于日期的分区组层次结构中。每个分区组都存储在其自己的一组 ROS 容器中,与其他组区别开来。您可以在表 partition 子句中指定此函数,如下所示:

PARTITION BY partition‑expression
  GROUP BY CALENDAR_HIERARCHY_DAY( partition‑expression [, active‑months[, active‑years] ] )

例如,给定上一个表,您可以按如下方式对其进行重新分区:


=> ALTER TABLE public.store_orders
      PARTITION BY order_date::DATE
      GROUP BY CALENDAR_HIERARCHY_DAY(order_date::DATE, 2, 2) REORGANIZE;

按层次结构对 DATE 数据进行分组

CALENDAR_HIERARCHY_DAY 会创建分区组的层次结构,并将分区合并到相应的组中。为此,它使用以下算法计算每个表行的分区表达式,来确定其分区组键:

GROUP BY (
CASE WHEN DATEDIFF('YEAR', partition-expression, NOW()::TIMESTAMPTZ(6)) >= active-years
       THEN DATE_TRUNC('YEAR', partition-expression::DATE)
     WHEN DATEDIFF('MONTH', partition-expression, NOW()::TIMESTAMPTZ(6)) >= active-months
       THEN DATE_TRUNC('MONTH', partition-expression::DATE)
     ELSE DATE_TRUNC('DAY', partition-expression::DATE) END);

在此示例中,算法将每个 store_orders 行中的 order_date 与当前日期进行比较,如下所示:

  1. 确定 order_date 是否处于非活动年份。

    如果 order_date 处于非活动年份,则该行的分区组键解析为该年份。该行将被合并到该年份的 ROS 容器中。

  2. 如果 order_date 处于活动年份,则 CALENDAR_HIERARCHY_DAY 会计算 order_date 以确定它是否处于非活动月份。

    如果 order_date 处于非活动月份,则该行的分区组键解析为该月份。该行将被合并到该月份的 ROS 容器中。

  3. 如果 order_date 处于活动月份,则该行的分区组键解析为 order_date 日期。该行将被合并到该日期的 ROS 容器中。order_date 是未来日期的任何行都将以相同的方式处理。

例如,如果当前日期是 2017-09-26,则 CALENDAR_HIERARCHY_DAY 会将 active-yearsactive-months 解析为以下日期范围:

  • active‑years:2016-01-01 到 2017-12-31。处于活动年份的分区将被分组到每月的 ROS 容器中,或合并到每日的 ROS 容器中。早期年份的分区被认为是非活动年份,合并到年度 ROS 容器中。

  • active‑months:2017/8/1 到 2017/9/30。处于活动月份的分区将被合并到每日的 ROS 容器中。

现在,每个投影的 ROS 容器总数减少到 40 个:

=> SELECT COUNT (DISTINCT ros_id) NumROS, node_name FROM PARTITIONS
    WHERE projection_name ilike '%store_orders_super%' GROUP BY node_name ORDER BY node_name;
 NumROS |    node_name
--------+------------------
     40 | v_vmart_node0001
     40 | v_vmart_node0002
     40 | v_vmart_node0003
(3 rows)

动态重新分组

如前所述,CALENDAR_HIERARCHY_DAY 在创建分区组键和合并分区时引用当前日期。随着日历向前推进,Tuple Mover 会重新计算使用此函数分区的表的分区组键,并根据需要将分区移动到不同的 ROS 容器中。

因此,给定前面的示例,在 2017-10-01,Tuple Mover 会为 8 月分区创建一个月度 ROS 容器。2017-08-01 和 2017-08-31 之间的所有分区键都将合并到新的 ROS 容器 2017-08 中:

同样,在 2018-01-01,Tuple Mover 会为 2016 年分区创建一个 ROS 容器。之前按月份分组的 2016-01-01 和 2016-12-31 之间的所有分区键都将合并到新的年度 ROS 容器中:

自定义分区组层次结构

Vertica 提供了一个函数 CALENDAR_HIERARCHY_DAY,以方便分层分区。Vertica 将 GROUP BY 子句存储为 CASE 语句,您可以对其进行编辑以满足自己的要求。

例如,Vertica 按如下所示存储 store_orders partition 子句:

=> ALTER TABLE public.store_orders
      PARTITION BY order_date::DATE
      GROUP BY CALENDAR_HIERARCHY_DAY(order_date::DATE, 2, 2);
=> select export_tables('','store_orders');
...
CREATE TABLE public.store_orders ( ... )

PARTITION BY ((store_orders.order_date)::date)
GROUP BY (
CASE WHEN ("datediff"('year', (store_orders.order_date)::date, ((now())::timestamptz(6))::date) >= 2)
       THEN (date_trunc('year', (store_orders.order_date)::date))::date
     WHEN ("datediff"('month', (store_orders.order_date)::date, ((now())::timestamptz(6))::date) >= 2)
       THEN (date_trunc('month', (store_orders.order_date)::date))::date
     ELSE (store_orders.order_date)::date END);

您可以修改 CASE 语句,以自定义分区组的层次结构。例如,以下 CASE 语句创建月、日和小时的层次结构:

=> ALTER TABLE store_orders
PARTITION BY (store_orders.order_date)
GROUP BY (
CASE WHEN DATEDIFF('MONTH', store_orders.order_date, NOW()::TIMESTAMPTZ(6)) >= 2
       THEN DATE_TRUNC('MONTH', store_orders.order_date::DATE)
     WHEN DATEDIFF('DAY', store_orders.order_date, NOW()::TIMESTAMPTZ(6)) >= 2
       THEN DATE_TRUNC('DAY', store_orders.order_date::DATE)
     ELSE DATE_TRUNC('hour', store_orders.order_date::DATE) END);