分区范围投影

Vertica 支持指定分区键范围的投影。默认情况下,投影存储分区表数据的所有行。随着时间的推移,此要求可能会产生越来越多的开销:

  • 随着数据的累积,就算有大量数据不经常查询,也需要越来越多的存储量。
  • 大型投影可能会阻止优化,例如更好的编码,或者更改投影排序顺序或分段。对投影的 DDL 进行此类更改需要刷新整个投影。根据投影大小,此刷新操作可能会持续数小时甚至数天。

您可以通过为分区表创建投影,指定相对较窄的分区键范围,最大程度地减少这些问题。例如,表 store_ordersorder_date 进行分区,如下所示:

=> CREATE TABLE public.store_orders(order_no int, order_date timestamp NOT NULL, shipper varchar(20), ship_date date);
CREATE TABLE
=> ALTER TABLE store_orders PARTITION BY order_date::DATE GROUP BY date_trunc('month', (order_date)::DATE);
ALTER TABLE

如果需要,可以创建 store_orders 的投影,指定表的连续分区键范围。在以下示例中,投影 ytd_orders 指定仅包括从一年的第一天开始下单的订单:

=> CREATE PROJECTION ytd_orders AS SELECT * FROM store_orders ORDER BY order_date
    ON PARTITION RANGE BETWEEN date_trunc('year',now())::date AND NULL;
WARNING 4468:  Projection <public.ytd_orders_b0> is not available for query processing. Execute the select start_refresh() function to copy data into this projection.
          The projection must have a sufficient number of buddy projections and all nodes must be up before starting a refresh
WARNING 4468:  Projection <public.ytd_orders_b1> is not available for query processing. Execute the select start_refresh() function to copy data into this projection.
          The projection must have a sufficient number of buddy projections and all nodes must be up before starting a refresh
CREATE PROJECTION
=> SELECT refresh();
                                        refresh
---------------------------------------------------------------------------------------
 Refresh completed with the following outcomes:
Projection Name: [Anchor Table] [Status] [Refresh Method] [Error Count] [Duration (sec)]
----------------------------------------------------------------------------------------
"public"."ytd_orders_b1": [store_orders] [refreshed] [scratch] [0] [0]
"public"."ytd_orders_b0": [store_orders] [refreshed] [scratch] [0] [0]

(1 row)

每个 ytd_orders 伙伴实例投影仅需要每节点 7 个 ROS 容器,而锚表的超投影需要 77 个容器:

=> SELECT COUNT (DISTINCT ros_id) NumROS, projection_name, node_name FROM PARTITIONS WHERE projection_name ilike 'store_orders_b%' GROUP BY node_name, projection_name ORDER BY node_name;
 NumROS | projection_name |    node_name
--------+-----------------+------------------
     77 | store_orders_b0 | v_vmart_node0001
     77 | store_orders_b1 | v_vmart_node0001
     77 | store_orders_b0 | v_vmart_node0002
     77 | store_orders_b1 | v_vmart_node0002
     77 | store_orders_b0 | v_vmart_node0003
     77 | store_orders_b1 | v_vmart_node0003
(6 rows)

=> SELECT COUNT (DISTINCT ros_id) NumROS, projection_name, node_name FROM PARTITIONS WHERE projection_name ilike 'ytd_orders%' GROUP BY node_name, projection_name ORDER BY node_name;
 NumROS | projection_name |    node_name
--------+-----------------+------------------
      7 | ytd_orders_b0   | v_vmart_node0001
      7 | ytd_orders_b1   | v_vmart_node0001
      7 | ytd_orders_b0   | v_vmart_node0002
      7 | ytd_orders_b1   | v_vmart_node0002
      7 | ytd_orders_b0   | v_vmart_node0003
      7 | ytd_orders_b1   | v_vmart_node0003
(6 rows)

分区范围要求

分区范围表达式必须符合适用于表级分区的要求,例如,分区键格式和数据类型验证。

以下要求和约束特别适用于分区范围投影:

  • 锚表必须已经分区。

  • 分区范围表达式必须与表的分区表达式兼容。

  • 第一个范围表达式必须解析为小于或等于第二个表达式的分区键。

  • 如果投影未分段,则锚表的至少一个超投影也必须未分段。如果不是,Vertica 会将投影添加到数据库编录,但会发出警告,指出此投影无法用于处理查询,直到您创建未分段的超投影。

  • 分区范围表达式不支持子查询。

锚表依赖

如前所述,分区范围投影取决于按同一表达式分区的锚表。如果从投影的锚表中移除表分区,Vertica 会删除从属投影。同样,如果修改锚表的 partition 子句,Vertica 也会删除投影。

以下例外情况适用:如果锚表的新 partition 子句保持分区表达式不变,则从属投影不会被删除,并且仍可用于查询。例如,表 store_orders 及其投影 ytd_orders 最初按如下方式分区:

=> ALTER TABLE store_orders PARTITION BY order_date::DATE GROUP BY DATE_TRUNC('month', (order_date)::DATE);
 ...
=> CREATE PROJECTION ytd_orders AS SELECT * FROM store_orders ORDER BY order_date
    ON PARTITION RANGE BETWEEN date_trunc('year',now())::date AND NULL;

如果现在修改 store_orders 以使用分层分区,Vertica 会对表数据及其分区范围投影进行重新分区:


=> ALTER TABLE store_orders PARTITION BY order_date::DATE GROUP BY CALENDAR_HIERARCHY_DAY(order_date::DATE, 2, 2) REORGANIZE;
NOTICE 4785:  Started background repartition table task
ALTER TABLE

由于 store_ordersytd_orders 投影继续按照 order_date 列进行分区,因此 ytd_orders 投影仍然有效。此外,投影数据的范围保持不变,因此投影不需要刷新。但是在后台,Tuple Mover 会根据其锚表的新分层分区以静默方式重新组织投影 ROS 容器:


=> SELECT COUNT (DISTINCT ros_id) NumROS, projection_name, node_name FROM PARTITIONS WHERE projection_name ilike 'ytd_orders%' GROUP BY node_name, projection_name ORDER BY node_name;
 NumROS | projection_name |    node_name
--------+-----------------+------------------
     38 | ytd_orders_b0   | v_vmart_node0001
     38 | ytd_orders_b1   | v_vmart_node0001
     38 | ytd_orders_b0   | v_vmart_node0002
     38 | ytd_orders_b1   | v_vmart_node0002
     38 | ytd_orders_b0   | v_vmart_node0003
     38 | ytd_orders_b1   | v_vmart_node0003
(6 rows)

修改现有投影

可以使用 ALTER PROJECTION 修改投影的分区范围。如果新范围位于前一个范围内,则不需要刷新。否则,需要先刷新,然后才能使用投影处理查询。

例如,投影 ytd_orders 之前指定了一个从当年第一天开始的分区范围。以下 ALTER PROJECTION 语句将范围更改为从去年 10 月 1 日开始。新范围先于前一个范围,因此 Vertica 会发出刷新指定的投影 ytd_orders_b0 及其伙伴实例投影 ytd_orders_b1 的警告:

=> ALTER PROJECTION ytd_orders_b0 ON PARTITION RANGE BETWEEN
     add_months(date_trunc('year',now())::date, -3) AND NULL;
WARNING 10001:  Projection "public.ytd_orders_b0" changed to out-of-date state as new partition range is not covered by existing partition range
HINT:  Call refresh() or start_refresh() to refresh the projections
WARNING 10001:  Projection "public.ytd_orders_b1" changed to out-of-date state as new partition range is not covered by existing partition range
HINT:  Call refresh() or start_refresh() to refresh the projections
ALTER PROJECTION

动态分区范围

投影的分区范围可以是静态范围,由始终解析为相同值的表达式设置。例如,以下投影指定一个介于 21/06/01 和 21/06/30 之间的静态范围:

=> CREATE PROJECTION last_month_orders AS SELECT * FROM store_orders ORDER BY order_date ON PARTITION RANGE BETWEEN
     '2021-06-01' AND '2021-06-30';
 ...
CREATE PROJECTION

更典型的是,分区范围表达式使用稳定的日期函数(例如 ADD_MONTHSDATE_TRUNCNOW)来指定动态范围。在以下示例中,分区范围设置为从上个月的第一天开始。随着日历日期推进到下个月,分区范围也随之向前推进:

=> ALTER PROJECTION last_month_orders_b0 ON PARTITION RANGE BETWEEN
     add_months(date_trunc('month', now())::date, -1) AND NULL;
ALTER PROJECTION

最佳实践是,始终通过将最大范围设置为 NULL 来将其保留为无限期,并依靠查询来确定要提取的最大数据量。例如,提取上个月下单的所有物料订单的查询可能如下所示:

=> SELECT * from store_orders WHERE order_date BETWEEN
     add_months(date_trunc('month', now())::date, -1) AND
     add_months(date_trunc('month', now())::date + dayofmonth(now()), -1);

为执行此查询而生成的查询计划表明它使用的是分区范围投影 last_month_orders

=> EXPLAIN SELECT * from store_orders WHERE order_date BETWEEN
     add_months(date_trunc('month', now())::date, -1) AND
     add_months(date_trunc('month', now())::date + dayofmonth(now()), -1);

 Access Path:
 +-STORAGE ACCESS for store_orders [Cost: 34, Rows: 763 (NO STATISTICS)] (PATH ID: 1)
 |  Projection: public.last_month_orders_b0
 |  Materialize: store_orders.order_date, store_orders.order_no, store_orders.shipper, store_orders.ship_date
 |  Filter: ((store_orders.order_date >= '2021-06-01 00:00:00'::timestamp(0)) AND (store_orders.order_date <= '2021-06-3
0 00:00:00'::timestamp(0)))
 |  Execute on: All Nodes

动态分区范围维护

Projection Maintainer 是一项后台服务,它每小时使用投影范围表达式检查一次投影。如果投影中任一表达式的值发生变化,Projection Maintainer 将比较 PARTITION_RANGE_MIN PARTITION_RANGE_MAX 中的新旧值,以确定分区范围是缩小还是扩大了:

  • 如果分区范围在任一方向缩小(即 PARTITION_RANGE_MIN 大于其先前值或 PARTITION_RANGE_MAX 小于其先前值),则 Projection Maintainer 按照以下方式操作:

    • 使用列 PARTITION_RANGE_MIN PARTITION_RANGE_MAX 中的新值更新系统表 PROJECTIONS

    • MERGEOUT 请求进行排队,以清除此范围内未使用的数据。投影仍然可用于在更新的范围内执行查询。

  • 如果分区范围在任一方向扩大(即 PARTITION_RANGE_MIN 小于其先前值,或 PARTITION_RANGE_MAX 大于其先前值),则 Projection Maintainer 保持投影和 PROJECTIONS 表不变。由于分区范围保持不变,Vertica 将现有投影数据视为最新数据,因此也永远不会刷新。

例如,以下投影会创建一个分区范围,其中包括当前月的所有订单:

=> CREATE PROJECTION mtd_orders AS SELECT * FROM store_orders ON PARTITION RANGE BETWEEN
     date_trunc('month', now())::date AND NULL;

如果您在 2021 年 7 月创建此分区,则最小分区范围表达式 date_trunc('month', now())::date 最初解析为该月的第一天:2021-07-01。在下个月开始时,在 2021‑08‑01 00:002021‑08‑01 01:00 之间的某个时间,Projection Maintainer 会将最小范围表达式与系统时间进行比较。然后,它按如下所示进行操作:

  1. 更新 PROJECTIONS 表,并将投影 mtd_ordersPARTITION_RANGE_MIN 设置为 2021-08-01

  2. 对 MERGEOUT 请求进行排队,以从此投影的分区范围中清除键早于 2021-08-01 的所有行。