分层分区
元函数 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] ] )
重要
在 partition 子句中使用 CALENDAR_HIERARCHY_DAY 有两个要求:
-
partition-expression 必须是 DATE。
-
PARTITION BY
子句和 CALENDAR_HIERARCHY_DAY 指定的分区表达式必须相同。
例如,给定上一个表,您可以按如下方式对其进行重新分区:
=> 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
与当前日期进行比较,如下所示:
-
确定
order_date
是否处于非活动年份。如果
order_date
处于非活动年份,则该行的分区组键解析为该年份。该行将被合并到该年份的 ROS 容器中。 -
如果
order_date
处于活动年份,则 CALENDAR_HIERARCHY_DAY 会计算order_date
以确定它是否处于非活动月份。如果
order_date
处于非活动月份,则该行的分区组键解析为该月份。该行将被合并到该月份的 ROS 容器中。 -
如果
order_date
处于活动月份,则该行的分区组键解析为order_date
日期。该行将被合并到该日期的 ROS 容器中。order_date
是未来日期的任何行都将以相同的方式处理。重要
CALENDAR_HIERARCHY_DAY 算法假定大多数表活动都集中在最近的日期。将 active‑years 和 active‑months 设置为 ≥ 2 的较小数字,有助于将大多数合并活动隔离到特定于日期的容器,并产生最小的开销。Vertica 建议您对 active‑years 和 active‑months 使用默认设置 2。对于大多数用户,这些设置会在 ROS 存储和性能之间实现最佳平衡。
最佳实践是,永远不要将 active-years 和 active-months 设置为 0。
例如,如果当前日期是 2017-09-26,则 CALENDAR_HIERARCHY_DAY 会将 active-years 和 active-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)
注意
无论 Tuple Mover 如何对分区进行分组和合并,它始终将一个或多个分区或分区组标识为活动。有关详细信息,请参阅活动和非活动分区。动态重新分组
如前所述,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 容器中:
当心
在将较旧的分区按月份和年份分组之后,对较旧分区组的子集执行的任何分区操作都可能会将 ROS 容器拆分为每个分区的较小 ROS 容器,例如 MOVE_PARTITIONS_TO_TABLE,其中 force-split 设置为 true。这些操作可能会导致 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);