分区范围投影
Vertica 支持指定分区键范围的投影。默认情况下,投影存储分区表数据的所有行。随着时间的推移,此要求可能会产生越来越多的开销:
- 随着数据的累积,就算有大量数据不经常查询,也需要越来越多的存储量。
- 大型投影可能会阻止优化,例如更好的编码,或者更改投影排序顺序或分段。对投影的 DDL 进行此类更改需要刷新整个投影。根据投影大小,此刷新操作可能会持续数小时甚至数天。
您可以通过为分区表创建投影,指定相对较窄的分区键范围,最大程度地减少这些问题。例如,表 store_orders
按 order_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_orders
和 ytd_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_MONTHS、DATE_TRUNC 和 NOW)来指定动态范围。在以下示例中,分区范围设置为从上个月的第一天开始。随着日历日期推进到下个月,分区范围也随之向前推进:
=> 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:00
和 2021‑08‑01 01:00
之间的某个时间,Projection Maintainer 会将最小范围表达式与系统时间进行比较。然后,它按如下所示进行操作:
-
更新 PROJECTIONS 表,并将投影
mtd_orders
的PARTITION_RANGE_MIN
设置为2021-08-01
。 -
对 MERGEOUT 请求进行排队,以从此投影的分区范围中清除键早于
2021-08-01
的所有行。
重要
鉴于上面显示的示例,您可以考虑按如下方式设置投影的最大分区范围表达式:
add_months(date_trunc('month', now()), 1) - 1
此表达式将始终解析为当前月的最后一天。对于每个新的月份,最大分区范围将比之前的值大一个月。如前所述,Projection Maintainer 会忽略分区范围的任何扩大,因此它将保持 mtd_orders
的最小和最大分区范围值不变。为避免此类问题,请始终将最大分区表达式设置为 NULL。