跳转至

Vertica Epoch 机制详解

原文:Understanding Vertica Epochs

Epoch 概述

Epoch 是一个 64 位数字,代表 Vertica 中数据的逻辑时间戳。每一行都有一个隐式存储的列,记录该行被提交时的 epoch。

=> CREATE TABLE testdata (a INT, b INT);
=> INSERT INTO testdata VALUES (1,2);
=> INSERT INTO testdata VALUES (3,4);
=> COMMIT;
=> INSERT INTO testdata VALUES (5,6);
COMMIT;

=> SELECT a,b,epoch FROM testdata;
 a | b | epoch
---+---+-------
 1 | 2 |  1898
 3 | 4 |  1898
 5 | 6 |  1899
(3 rows)

当系统的逻辑状态发生变化,或者数据通过 DML 操作(INSERT、UPDATE、MERGE、COPY、DELETE)提交时,epoch 会推进。

EPOCHS 系统表包含每个已关闭 epoch 的日期时间和对应的 epoch 编号,可用于确定哪些时间段对应哪些 epoch:

=> SELECT * FROM epochs;
      epoch_close_time         | epoch_number
-------------------------------+--------------
 2015-11-09 17:47:21.013641+00 |         1350
 2015-11-09 18:03:30.454707+00 |         1351
 2015-11-09 18:04:48.329494+00 |         1371
 2015-11-09 18:18:42.796702+00 |         1372
 2015-11-09 18:19:52.738018+00 |         1392
 2015-11-09 18:26:43.655577+00 |         1393
 2015-11-11 15:21:47.918074+00 |         1394
 2015-11-11 15:23:16.80952+00  |         1897
 2015-11-20 18:13:20.324436+00 |         1898
 2015-11-20 18:13:20.372756+00 |         1899
(10 rows)

Epoch 类型

Epoch Map 是 AHM 到 LE 之间的 epoch 列表,存储在数据库 catalog 中。

Epoch Mechanism Diagram

Epoch Map:  [AHM] ... [LGE] ... [LE] [CE(open)]
              ↑        ↑        ↑      ↑
           最旧可查  全节点一致  最新   当前写入中

Current Epoch (CE)

当前 epoch 是一个开放的 epoch,在 COMMIT 操作后成为 Latest Epoch (LE)。COMMIT 时的 current_epoch 即为该 DML 的 epoch。

=> SELECT CURRENT_EPOCH FROM SYSTEM;
 CURRENT_EPOCH
---------------
        629415
(1 row)

=> INSERT INTO test VALUES (10,20); COMMIT;

=> SELECT current_epoch FROM SYSTEM;
 current_epoch
---------------
        629416
(1 row)

Latest Epoch (LE)

最新关闭的 epoch。COMMIT 操作后的 current epoch 即成为 latest epoch。

Checkpoint Epoch (CPE)

每个 projection 的 checkpoint epoch 是 WOS 中无数据的最新 epoch,即 projection 可以恢复到的时间点。Tuple Mover 的 moveout 操作将数据从 WOS 移到 ROS 时推进 CPE。

=> SELECT node_name, projection_schema schema, projection_name p_name,
          is_up_to_date UTD, checkpoint_epoch CPE,
          would_recover WR, is_behind_ahm BAHM
   FROM projection_checkpoint_epochs;

       node_name        |  schema | p_name                   |   UTD |    CPE    | WR    | BAHM
------------------------+---------+--------------------------+-------+-----------+-------+-----
 v_test_israel_node0001 | public  |   product_dimension_b1   |   t   |    1347   |   f   |   f
 v_test_israel_node0003 | public  |   product_dimension_b1   |   t   |    1347   |   f   |   f
 v_test_israel_node0002 | public  |   product_dimension_b1   |   t   |    1347   |   f   |   f
 v_test_israel_node0002 | store   |   store_dimension_b1     |   t   |    1347   |   f   |   f
 v_test_israel_node0003 | store   |   store_dimension_b1     |   t   |    1347   |   f   |   f
 v_test_israel_node0001 | store   |   store_dimension_b1     |   t   |    1347   |   f   |   f
 v_test_israel_node0002 | public  |   promotion_dimension_b1 |   t   |    1347   |   f   |   f
 v_test_israel_node0001 | public  |   promotion_dimension_b1 |   t   |    1347   |   f   |   f
 v_test_israel_node0003 | public  |   promotion_dimension_b1 |   t   |    1347   |   f   |   f
 v_test_israel_node0001 | public  |   warehouse_dimension_b1 |   t   |    1347   |   f   |   f
 v_test_israel_node0002 | public  |   warehouse_dimension_b1 |   t   |    1347   |   f   |   f
 v_test_israel_node0003 | public  |   warehouse_dimension_b1 |   t   |    1347   |   f   |   f

Last Good Epoch (LGE)

所有节点中最小的 checkpoint epoch。LGE 是手动恢复可以恢复到的最新 epoch。LGE 由磁盘上所有数据的快照组成。集群异常关闭时,LGE 之后的数据会丢失

  • Tuple Mover 推进 CPE 并设置新的 LGE
  • 如果 Tuple Mover 失败,数据不会从 WOS 移到 ROS,CPE 和 LGE 都不会推进
  • 每个节点有一个 LGE(该节点上 projection 的最小 CPE)
  • Vertica 从各节点 LGE 计算集群 LGE,利用 K-safety 提供尽可能高的 LGE
-- 查看集群 LGE
=> SELECT GET_LAST_GOOD_EPOCH();
 GET_LAST_GOOD_EPOCH
---------------------
                1347
(1 row)

-- 查看各节点 LGE(含恢复计算过程)
=> SELECT GET_EXPECTED_RECOVERY_EPOCH();
INFO 4544:  Recovery Epoch Computation:
Node Dependencies:
011 - cnt: 253
101 - cnt: 253
110 - cnt: 253
111 - cnt: 239
Nodes certainly in the cluster:
        Node 0(v_test_israel_node0001), epoch 1347
        Node 1(v_test_israel_node0002), epoch 1347
Filling more nodes to satisfy node dependencies:
Data dependencies fulfilled, remaining nodes LGEs don't matter:
        Node 2(v_test_israel_node0003), epoch 1347
--
 GET_EXPECTED_RECOVERY_EPOCH
-----------------------------
                        1347
(1 row)

Ancient History Mark (AHM)

AHM 之前的 epoch 的历史数据可以从物理存储中 purge。任何早于 AHM 的历史查询都无法执行。

  • 默认情况下,Vertica 每 3 分钟将 AHM 推进到与 LGE 相等
  • 当集群中有节点 DOWN 或存在未刷新的 projection 时,AHM 不会推进
  • AHM 永远不会超过 LGE
=> SELECT GET_AHM_EPOCH();
 GET_AHM_EPOCH
---------------
          1347
(1 row)

Epoch 推进过程(完整演示)

DML 事务(INSERT、UPDATE、MERGE、COPY、DELETE)的 COMMIT 同时推进 CE 和 LE。CE 移动 1,LE 也移动 1,current epoch 变为 latest epoch。

=> CREATE TABLE test_epochs (c1 int);
CREATE TABLE

=> SELECT CURRENT_EPOCH, AHM_EPOCH, LAST_GOOD_EPOCH FROM SYSTEM;
 CURRENT_EPOCH | AHM_EPOCH | LAST_GOOD_EPOCH
---------------+-----------+-----------------
          1348 |      1347 |            1347
(1 row)

-- 插入新数据
=> INSERT INTO test_epochs VALUES (1);
 OUTPUT
--------
      1
(1 row)

-- INSERT 未提交,epoch 列为空
=> SELECT epoch, * FROM test_epochs;
 epoch | c1
-------+----
       |  1
(1 row)

=> COMMIT;
COMMIT

-- COMMIT 后,该行记录当前 epoch 值
=> SELECT epoch, * FROM test_epochs;
 epoch | c1
-------+----
  1348 |  1
(1 row)

-- CE 因 COMMIT 而推进
=> SELECT CURRENT_EPOCH, AHM_EPOCH, LAST_GOOD_EPOCH FROM SYSTEM;
 CURRENT_EPOCH | AHM_EPOCH | LAST_GOOD_EPOCH
---------------+-----------+-----------------
          1349 |      1347 |            1347
(1 row)

-- Vertica 中 UPDATE = DELETE + INSERT
=> UPDATE test_epochs SET c1 = 2 WHERE c1 = 1;
 OUTPUT
--------
      1
(1 row)

-- 未提交,epoch 列为空
=> SELECT epoch, * FROM test_epochs;
 epoch | c1
-------+----
       |  2
(1 row)

=> COMMIT;
COMMIT

-- COMMIT 后 epoch 变为当前值
=> SELECT epoch, * FROM test_epochs;
 epoch | c1
-------+----
  1349 |  2
(1 row)

=> SELECT CURRENT_EPOCH, AHM_EPOCH, LAST_GOOD_EPOCH FROM SYSTEM;
 CURRENT_EPOCH | AHM_EPOCH | LAST_GOOD_EPOCH
---------------+-----------+-----------------
          1350 |      1348 |            1348
(1 row)

要点:

  • 执行 SELECT 时,Vertica 在 latest epoch 获取数据,同时也会检索同一事务中未提交的数据
  • INSERT/COPY 加载数据时,每行都有一个表示提交时间的 epoch 值
  • DELETE 时,Vertica 创建存储 epoch 的 delete vector,记录 ROS 容器中标记为删除的位置
  • 早于 AHM 的被删除数据可以被物理清除

故障排查

LGE 不推进

LGE 推进的正常状态:Tuple Mover 将数据从 WOS 移到 ROS 时,LGE 推进。

=> SELECT CURRENT_EPOCH, LAST_GOOD_EPOCH FROM SYSTEM;
 CURRENT_EPOCH | LAST_GOOD_EPOCH
---------------+-----------------
        630384 |          620380

如果 LGE 不推进,按以下步骤排查:

1. 检查 WOS 中是否有数据:

=> SELECT sum(wos_used_bytes) FROM projection_storage;
  sum
-------
 49152

2. 如果有 WOS 数据,强制 moveout:

=> SELECT do_tm_task('moveout');

3. Tuple Mover moveout 失败的常见原因:

原因 说明
无法获取 T-lock Tuple Mover 无法获取表的 T 锁
ROS pushback ROS 容器数达到最大值,moveout 无法创建新容器
WOS 分区数过多 WOS 中有超过 1024 个分区

查看 vertica.log 排查 Tuple Mover 失败原因:

$ FIND <catalog_path> -name vertica.log | xargs grep 'TM Moveout.* <ERROR>'

4. Replay delete 会拖慢 Tuple Mover:

=> SELECT * FROM tuple_mover_operations WHERE is_executing;

AHM 不推进

场景 1:存在未刷新的 Projection

找到未刷新的 projection:

=> SELECT * FROM projections WHERE is_up_to_date = 'f';

此时尝试手动推进 AHM 会报错:

=> SELECT MAKE_AHM_NOW();
ERROR 2154:  AHM must lag behind the create epoch of unrefreshed
projection public.test_2_b0 (Create epoch: 1372)

解决方案:

  • 常规做法:导出 DDL → 刷新 projection 或 REFRESH
  • 如果是大表 projection 刷新太慢(会使 AHM 长期被卡住),可采用变通方案:先 drop projection,推进 AHM,再重建
-- 导出 DDL 以便后续重建
=> SELECT export_objects('','test_epochs');
...

-- 删除 projection
=> DROP PROJECTION test_2;
DROP PROJECTION

-- 推进 AHM
=> SELECT MAKE_AHM_NOW();
         MAKE_AHM_NOW
-------------------------------
 AHM set (New AHM Epoch: 1393)
(1 row)

场景 2:集群中有节点不在 UP 状态

=> SELECT * FROM nodes WHERE node_state != 'UP';

⚠️ 警告: 在有 DOWN 节点的情况下,手动执行 MAKE_AHM_NOW(true)SET_AHM_EPOCHSET_AHM_TIME 将 AHM 强行推进超过该节点的 LGE,将导致该节点重启后必须从头恢复

Epoch 管理函数

函数 说明
GET_AHM_EPOCH() 返回 AHM 所在 epoch 编号
GET_AHM_TIME() 返回 AHM 对应的时间戳
GET_CURRENT_EPOCH() 返回当前 epoch 编号(COPY/INSERT/UPDATE/DELETE 写入数据的 epoch)
GET_LAST_GOOD_EPOCH() 返回 LGE 编号(手动恢复可恢复到的最新 epoch)
GET_EXPECTED_RECOVERY_EPOCH() 返回各节点的恢复 epoch 及集群 LGE 计算过程
SET_AHM_EPOCH(epoch) 将 AHM 设为指定 epoch(通常仅用于测试)
SET_AHM_TIME(time) 将 AHM 设为指定时间点
MAKE_AHM_NOW([force]) 将 AHM 设为最大允许值(force=true 忽略 DOWN 节点警告)
ADVANCE_EPOCH(n) 手动将 epoch 推进 n 个(向后兼容用)

Epoch 配置参数

参数 默认值 说明
AdvanceAHMInterval 180 AHM 推进检查间隔
EpochMapInterval 180 epoch→时间映射精度,影响历史查询和 LGE 报告。必须 ≥ AdvanceAHMInterval
HistoryRetentionTime 0 已删除数据保留时长(秒)。推荐用于控制 purge 策略
HistoryRetentionEpochs -1 保留的历史 epoch 数量;-1 = 禁用
AHMBackupManagement 0 1 时 AHM 不超过最近一次全量备份的 epoch

扩展阅读