Clickhouse是什么?
Clickhouse是一个用于联机分析处理(OLAP)的列式数据库管理系统(columnar DBMS)。
在通常的按行存储的数据库中,数据是按照如下顺序存储的:
5123456789123456789 1 Eurobasket - Greece - Bosnia and Herzegovina - example.com 1 2011-09-01 01:03:02 6274717 1294101174 11409 612345678912345678 0 33 6 http://www.example.com/basketball/team/123/match/456789.html http://www.example.com/basketball/team/123/match/987654.html 0 1366 768 32 10 3183 0 0 13 0\0 1 1 0 0 2011142 -1 0 0 01321 613 660 2011-09-01 08:01:17 0 0 0 0 utf-8 1466 0 0 0 5678901234567890123 277789954 0 0 0 0 0
5234985259563631958 0 Consulting, Tax assessment, Accounting, Law 1 2011-09-01 01:03:02 6320881 2111222333 213 6458937489576391093 0 3 2 http://www.example.ru/ 0 800 600 16 10 2 153.1 0 0 10 63 1 1 0 0 2111678 000 0 588 368 240 2011-09-01 01:03:17 4 0 60310 0 windows-1251 1466 0 000 778899001 0 0 0 0 0
...
换句话说,一行内的所有数据都彼此依次存储。像这样的行式数据库包括MySQL、Postgres、MS SQL-Server等。
在面向列的数据库管理系统中,数据是这样存储的:
WatchID: 5385521489354350662 5385521490329509958 5385521489953706054 5385521490476781638 5385521490583269446 5385521490218868806 5385521491437850694 5385521491090174022 5385521490792669254 5385521490420695110 5385521491532181574 5385521491559694406 5385521491459625030 5385521492275175494 5385521492781318214 5385521492710027334 5385521492955615302 5385521493708759110 5385521494506434630 5385521493104611398
JavaEnable: 1 0 1 0 0 0 1 0 1 1 1 1 1 1 0 1 0 0 1 1
Title: Yandex Announcements - Investor Relations - Yandex Yandex — Contact us — Moscow Yandex — Mission Ru Yandex — History — History of Yandex Yandex Financial Releases - Investor Relations - Yandex Yandex — Locations Yandex Board of Directors - Corporate Governance - Yandex Yandex — Technologies
GoodEvent: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
EventTime: 2016-05-18 05:19:20 2016-05-18 08:10:20 2016-05-18 07:38:00 2016-05-18 01:13:08 2016-05-18 00:04:06 2016-05-18 04:21:30 2016-05-18 00:34:16 2016-05-18 07:35:49 2016-05-18 11:41:59 2016-05-18 01:13:32
这些例子只显示了数据排列的顺序。来自不同列的值分别存储,而来自同一列的数据存储在一起。列式数据库例如有:Vertica, Paraccel (Actian Matrix) (Amazon Redshift), Sybase IQ, Exasol, Infobright, InfiniDB, MonetDB (VectorWise) (Actian Vector), LucidDB, SAP HANA, Google Dremel, Google PowerDrill, Druid, kdb+等。
不同的数据存储顺序适合不同的应用场景。对于数据访问场景而言,通常关注的是:多久、以多少比例进行怎样的查询;对不同类型(行、列、字节)的查询,需要读取多少数据量;读取与更新数据之间的关系;数据的工作规模量和如何在本地使用数据;是否使用事务和事务的隔离问题;数据复制和逻辑完整性的要求;对各种查询类型的延迟和吞吐量的要求。
系统负载越高,为场景进行系统定制化就越重要,定制化就越具体。没有一个系统能同样适用于极其不同的场景。在高负载下,一个能适应众多场景的系统,要么在各个场景下表现得都很差,要么仅仅只能较好地处理某一场景的问题。
我们认为,以下几条针对的是联机分析处理(OLAP)应用场景:
绝大多数请求都是用于读访问的。
数据需要以大批次(大于1000行)进行更新,而不是单行更新;或者根本没有更新操作。
数据只是添加到数据库,没有必要修改。
读取数据时,会从数据库中提取出大量的行,但只用到一小部分列。
表很“宽”,即表中包含大量的列
查询频率相对较低(通常每台服务器每秒查询数百次或更少)。
对于简单查询,允许大约50毫秒的延迟。
列的值是比较小的数值和短字符串(例如,每个URL只有60个字节)。
在处理单个查询时需要高吞吐量(每台服务器每秒高达数十亿行)。
不需要事务。
数据一致性要求较低。
每次查询中只会查询一个大表。除了一个大表,其余都是小表。
查询结果显著小于数据源。即数据有过滤或聚合。返回结果不超过单个服务器内存大小。
显然,OLAP场景与其他常用的应用场景非常不同,如OLTP或key-Value获取的场景。所以,如果你在处理分析型查询中想要获得高性能,没有任何理由去使用OLTP或键值数据库。打个比方,如果你想用MongoDB或者Elliptics做数据分析,你会“爽”到极点,谁用谁知道。
列式数据库更适合OLAP场景(大多数查询的处理速度至少提高100倍),原因如下:
- I/O的原因:
a. 对于分析型查询,只需要读取少量的列。在列式数据库中,你能只读取你需要的。例如,如果你需要100个列中的5个,你可以预期I/O减少20倍。
b. 由于数据是打包读取的,所以更容易压缩。列式数据更容易压缩,也进一步降低了I/O量。
c. 由于减少了I/O,更多数据可以进入系统缓存。例如,查询“计算每个广告平台的记录数”,需要读取一个“广告平台ID”的列,该列未压缩时占用1字节空间。如果大多数流量不是来自广告平台,你可以期望把此列至少压缩10倍。采用快速压缩算法,数据解压缩的速度可以达到每秒解出几个GB的数据。换句话说,这个查询可以以每秒大约数十亿行的速度在单台服务器上处理。这个速度在实践中是被检验过的。
举个栗子:
milovidov@hostname:~$ clickhouse-client
ClickHouse client version 0.0.52053.
Connecting to localhost:9000.
Connected to ClickHouse server version 0.0.52053.
:) SELECT CounterID, count() FROM hits GROUP BY CounterID ORDER BY count() DESC LIMIT 20
SELECT
CounterID,
count()
FROM hits
GROUP BY CounterID
ORDER BY count() DESC
LIMIT 20
┌─CounterID─┬──count()─┐
│ 114208 │ 56057344 │
│ 115080 │ 51619590 │
│ 3228 │ 44658301 │
│ 38230 │ 42045932 │
│ 145263 │ 42042158 │
│ 91244 │ 38297270 │
│ 154139 │ 26647572 │
│ 150748 │ 24112755 │
│ 242232 │ 21302571 │
│ 338158 │ 13507087 │
│ 62180 │ 12229491 │
│ 82264 │ 12187441 │
│ 232261 │ 12148031 │
│ 146272 │ 11438516 │
│ 168777 │ 11403636 │
│ 4120072 │ 11227824 │
│ 10938808 │ 10519739 │
│ 74088 │ 9047015 │
│ 115079 │ 8837972 │
│ 337234 │ 8205961 │
└───────────┴──────────┘
20 rows in set. Elapsed: 0.153 sec. Processed 1.00 billion rows, 4.00 GB (6.53 billion rows/s., 26.10 GB/s.)
:)
- CPU的原因。
由于执行查询需要处理大量的行,所以它有助于以整个向量方式分发所有运算,而不是按单独的行。它也有助于实现查询引擎,因此几乎没有分发成本。如果不这样做,对于任何像样的磁盘子系统,查询解释器不可避免地会阻塞CPU。因此,如果可以的话,将数据按列存储和处理,是明智之举。
有两种方法可以实现这一点:
一个向量引擎。所有操作都是为向量,而不是为单独的值编写的。这意味着你不需要经常调用运算,而且分发成本可以忽略不计运算代码包含一个优化的内部循环。
代码生成。为查询生成的代码包含了所有的间接调用。
这不是在“普通”的数据库中完成的,因为执行简单查询是没有意义的。然而,也有例外,例如MemSQL使用代码生成来减少处理SQL查询时的延迟。(对比而言,分析型数据库系统需要优化吞吐量,而不是优化延迟)。
注意,为了CPU效率,查询语言必须是声明式的(SQL或MDX),或者至少是一个向量(J.K)。考虑到优化,查询应该只包含隐式循环。