如何在ClickHouse中快速实现AB表切换

2021-05-27 14:46:20 浏览数 (1)

AB 表切换的使用场景应该说还是很广泛的,比如历史表归档、批量抽数的时候都可以采用 AB 表切换的思路来实现。

比如有这样一个场景,test_a 是面向终端查询的数据表,数据每天定点全量更新。为了减少数据更新对查询的影响,这里准备用 AB 表切换的思路进行。

在数据更新时,首先写入一张按日期编号的 B 表,例如 test_a_2021_04_29。当 B 表数据写完以后,将 AB 两张表切换。

那么在 ClickHouse 中怎样实现 AB 两张表的快速切换呢? 这里介绍两种主要的方法。

  • 第一种是利用 RENAME 语法

首先新建 AB 两张表

代码语言:javascript复制
 CREATE TABLE test_a_2021_04_29(
   id UInt32,
   name String,
   value UInt32
 )ENGINE = MergeTree()
 ORDER BY id
 
 CREATE TABLE test_a(
   id UInt32,
   name String,
   value UInt32
 )ENGINE = MergeTree()
 ORDER BY id

首先向 test_a_2021_04_29 写入一千万测试数据:

代码语言:javascript复制
INSERT INTO TABLE test_a_2021_04_29(id,name,value)
WITH(
  SELECT ['A','B','C','D','E','F','G']
)AS dict
SELECT number AS id, dict[number%7 1],id FROM numbers(10000000)

写完之后进行 AB 切换:

代码语言:javascript复制
RENAME TABLE test_a_2021_04_29 TO tmp, test_a TO test_a_2021_04_29, tmp TO test_a

Query id: d23eda37-b494-48ea-8a6f-64f319b172f6

Ok.

0 rows in set. Elapsed: 0.013 sec.

可以发现,这里利用了一张临时表 tmp,实现了 AB 表名的切换,是不是很方便呢?

现在验证一下效果,首先查询 test_a,可以看到一千万数据已经在这张表了:

代码语言:javascript复制
SELECT COUNT()
FROM test_a

Query id: 51335286-55f6-4e79-8207-d39afffbd18c

┌──count()─┐
│ 10000000 │
└──────────┘

而 test_a_2021_04_29 已经没有数据了:

代码语言:javascript复制
SELECT COUNT()
FROM test_a_2021_04_29

Query id: 7ad19597-0ad8-4544-878b-701a8fa6c0f6

┌─count()─┐
│       0 │
└─────────┘

1 rows in set. Elapsed: 0.003 sec.

RENAME TABLE A TO C, B TO A, C TO B 虽然很方便,但也存在一定的风险,因为利用到了中间临时表,所以整个过程不是原子的,如果出现意外可能会导致不一致的情况发生,于是 ClickHouse 提供了第二种方式。

  • 第二种是利用 EXCHANGE TABLES 语法

在新版本中,ClickHouse 提供了一种新的 Atomic 数据库引擎,在这个引擎下创建的数据表,能够支持无锁的 CREATE/DROP/RENAME 操作,并且支持

EXCHANGE TABLES A and B 直接交换两张表。

继续用例,首先新建一个 Atomic 数据库:

代码语言:javascript复制
CREATE DATABASE test_atom ENGINE = Atomic

查看它的元数据,就会发现它和 Ordinary 数据库的不同:

代码语言:javascript复制
% cat ./data/metadata/test_atom.sql 
ATTACH DATABASE _ UUID 'fa22ace8-05a9-4cba-9366-97e625fad12f'
ENGINE = Atomic

元数据中,Atomic 数据库没有名字,取而代之的是一个 UUID。

进一步查看数据库文件,也能发现不同。Ordinary 数据库是一个目录,而 Ordinary 数据库则是一个文件链接,指向了 store 目录下,以 UUID 为名称的目录。

代码语言:javascript复制
% ls -l
total 24
drwxr-xr-x  27 nauu  staff  864  4 29 21:17 default
-rw-r-----   1 nauu  staff   42  8 10  2020 default.sql
drwxr-xr-x   6 nauu  staff  192  1 28 21:52 system
-rw-r-----   1 nauu  staff   41  1 28 21:51 system.sql
lrwxr-xr-x   1 nauu  staff   71  4 29 21:41 test_atom -> /media/psf/ch9-data/data/store/fa2/fa22ace8-05a9-4cba-9366-97e625fad12f
-rw-r-----@  1 nauu  staff   78  4 29 21:41 test_atom.sql

在 Atomic 数据库下新建 AB 表:

代码语言:javascript复制
 CREATE TABLE test_atom.test_a_2021_04_29(
   id UInt32,
   name String,
   value UInt32
 )ENGINE = MergeTree()
 ORDER BY id
 
 CREATE TABLE test_atom.test_a(
   id UInt32,
   name String,
   value UInt32
 )ENGINE = MergeTree()
 ORDER BY id

在以 test_atom 数据库 UUID 命名的目录下,发现了两张表的元数据:

代码语言:javascript复制
% pwd
/data/store/fa2/fa22ace8-05a9-4cba-9366-97e625fad12f

% ls -l
total 16
-rw-r-----  1 nauu  staff  183  4 29 21:52 test_a.sql
-rw-r-----  1 nauu  staff  183  4 29 21:52 test_a_2021_04_29.sql

同样的,这些表的元数据也没有表名,用唯一的 UUID 取而代之:

代码语言:javascript复制
% cat ./test_a_2021_04_29.sql 
ATTACH TABLE _ UUID 'ae429533-a558-4e59-8232-950f5dc970be'
(
    `id` UInt32,
    `name` String,
    `value` UInt32
)
ENGINE = MergeTree
ORDER BY id
SETTINGS index_granularity = 8192

 % cat ./test_a.sql 
ATTACH TABLE _ UUID '2839166f-4c7c-4d57-ad21-8615a79d71f6'
(
    `id` UInt32,
    `name` String,
    `value` UInt32
)
ENGINE = MergeTree
ORDER BY id
SETTINGS index_granularity = 8192

从系统表也能看出两种数据库的表差异,非 Atomic 是没有 UUID 的:

代码语言:javascript复制
SELECT database ,name,uuid ,data_paths ,metadata_path FROM  `system`.tables t WHERE name = 'test_a'

现在可以直接用 EXCHANGE TABLES 交换两张表,这个操作是原子的。

代码语言:javascript复制
EXCHANGE TABLES test_atom.test_a AND test_atom.test_a_2021_04_29;

不过这里有一点值得注意,EXCHANGE TABLES 的原子性是利用了底层系统的 renameat2 命令。它只有至少在 linux kernel 3.15 的操作系统上才能支持。

所以如果你的 linux kernel 版本不够,是不能使用 EXCHANGE TABLES 的,会得到 RENAME EXCHANGE is not supported 的错误。

0 人点赞