> 原文出處:http://www.infoq.com/cn/articles/database-timestamp-01
> 作者: 陶文
什么是時間序列數據?最簡單的定義就是數據格式里包含timestamp字段的數據。比如股票市場的價格,環境中的溫度,主機的CPU使用率等。但是又有什么數據是不包含timestamp的呢?幾乎所有的數據都可以打上一個timestamp字段。時間序列數據更重要的一個屬性是如何去查詢它。在查詢的時候,對于時間序列我們總是會帶上一個時間范圍去過濾數據。同時查詢的結果里也總是會包含timestamp字段。
## 選擇什么樣的時間序列數據庫
時間序列數據無處不在。而幾乎任意數據庫都可以存時間序列數據。但是不同的數據能支持的查詢類型并不相同。按照能支持的查詢類型,我們可以把時間序列數據庫分為兩類,第一類的數據庫按照關系型數據庫的說法,其表結構是這樣的:
~~~
[metric_name] [timestamp] [value]
~~~
其優化的查詢方式是:
~~~
SELECT value FROM metric WHERE metric_name=”A” AND timestamp >= B AND timestamp < C
~~~
也就說這類數據庫是什么樣子的數據存進去,就什么樣子取出來。

在這種模式下,首先要知道你需要的圖表是什么樣子的。然后按照這個圖表的數據,去把數據入庫。查詢的字段,就是數據庫存儲的字段。然后再按照數據庫存儲的字段,去從原始數據里采集上報。存儲什么字段,就上報什么字段。這種模式很容易優化,可以做到非常快。但是這種模式有兩個弊端。
* 無法快速響應變化:如果需要的圖表有變更,需要從上報的源頭重新來一遍。而且要等新數據過來之后,才能查看這些新數據。
* 存儲膨脹:總有一些數據是需要從不同維度查詢的要求。比如廣告點擊流數據,需要按省份聚合,按運營商聚合,按點擊人的喜好聚合等。這些維度的交叉組合會產生非常巨大的組合數量,要預先把所有的維度組合都變成數據庫里的表存儲起來會很浪費空間。
這類時間序列數據庫最多,使用也最廣泛。一般人們談論時間序列數據庫的時候指代的就是這一類存儲。按照底層技術不同可以劃分為三類。
* 直接基于文件的簡單存儲:[RRD Tool](http://oss.oetiker.ch/rrdtool/),[Graphite Whisper](http://graphite.wikidot.com/whisper)。這類工具附屬于監控告警工具,底層沒有一個正規的數據庫引擎。只是簡單的有一個二進制的文件結構。
* 基于K/V數據庫構建:[opentsdb](http://opentsdb.net/)(基于[hbase](http://hbase.apache.org/)),[blueflood](http://blueflood.io/),[kairosDB](https://kairosdb.github.io/)(基于[cassandra](http://cassandra.apache.org/)),[influxdb](https://influxdb.com/),[prometheus](http://prometheus.io/)(基于[leveldb](http://leveldb.org/))
* 基于關系型數據庫構建:[mysql](https://www.mysql.com/),[postgresql?](http://www.postgresql.org/)都可以用來保存時間序列數據
另外一類數據庫其表結構是:
~~~
[timestamp] [d1] [d2] .. [dn] [v1] [v2] .. [vn]
~~~
其優化的查詢方式不限于查詢原始數據,而是可以組合查詢條件并且做聚合計算,比如:
~~~
SELECT d2, sum(v1) / sum(v2) FROM metric WHERE d1 =
“A” AND timestamp >= B AND timestamp < C GROUP BY d2
~~~

我們希望時間序列數據庫不僅僅可以提供原始數據的查詢,而且要支持對原始數據的聚合能力。這種聚合可以是在入庫階段完成的,所謂物化視圖。也可以是在查詢階段完成,所謂實時聚合。根據實際情況,可以在這兩種方式中進行取舍。
想要在在查詢階段做數據的聚合和轉換,需要能夠支持以下三點。
* 用索引檢索出行號:能夠從上億條數據中快速過濾出幾百萬的數據。
* 從主存儲按行號加載:能夠快速加載這過濾出的幾百萬條數據到內存里。
* 分布式計算:能夠把這些數據按照GROUP BY 和 SELECT 的要求計算出最終的結果集。

要想盡可能快的完成整個查詢過程,需要在三個環節上都有絕招。傳統上說,這三個步驟是三個不同的技術領域。
* 檢索:這是搜索引擎最擅長的領域。代表產品是[Lucene](https://lucene.apache.org/core/)。其核心技術是基于高效率數據結構和算法的倒排索引。
* 加載:這是分析型數據庫最擅長的領域。代表產品是[C-store](http://db.csail.mit.edu/projects/cstore/)和[Monetdb](https://www.monetdb.org/Home)。其核心技術是按列組織的磁盤存儲結構。
* 分布式計算:這是大數據計算引擎最擅長的領域。代表產品是[Hadoop](https://hadoop.apache.org/)和[spark](http://spark.apache.org/)。其核心技術是sharding 和 map/reduce等等。
前面提到的時間序列庫(比如[opentsdb](http://opentsdb.net/))有不少從功能上來說是沒有問題。它們都支持過濾,也支持過濾之后的聚合計算。在數據量小的時候勉強是可用的。但是如果要實時從十億條里取百萬記錄出來,再做聚合運算,對于這樣的數據量可能就勉為其難了。滿足海量數據實時聚合要求的數據庫不多,比較常見的有這么幾種:
* 基于[Lucene](https://lucene.apache.org/core/)構建的“搜索引擎”:[Elasticsearch](https://www.elastic.co/products/elasticsearch),?[Crate.io](https://crate.io/)(雖然是基于[Elasticsearch](https://www.elastic.co/products/elasticsearch),但是聚合邏輯是自己實現的),[Solr](http://lucene.apache.org/solr/);
* 列式存儲數據庫:[Vertica](http://www.vertica.com/)([C-store](http://db.csail.mit.edu/projects/cstore/)的后裔)[Actian](http://www.actian.com/products/analytics-platform/)([Monetdb](https://www.monetdb.org/Home)的后裔)等;
* Druid.io。
其中Elasticsearch是目前市場上比較很少有的,能夠在檢索加載和分布式計算三個方面都做得一流的數據庫。而且是開源并且免費的。它使用了很多技術來達到飛一般的速度。這些主要的優化措施可以列舉如下。
* Lucene的inverted index可以比mysql的b-tree檢索更快。
* 在 Mysql中給兩個字段獨立建立的索引無法聯合起來使用,必須對聯合查詢的場景建立復合索引。而lucene可以任何AND或者OR組合使用索引進行檢索。
* Elasticsearch支持nested document,可以把一批數據點嵌套存儲為一個document block,減少需要索引的文檔數。
* Opentsdb不支持二級索引,只有一個基于hbase rowkey的主索引,可以按行的排序順序scan。這使得Opentsdb的tag實現從檢索效率上來說很慢。
* Mysql 如果經過索引過濾之后仍然要加載很多行的話,出于效率考慮query planner經常會選擇進行全表掃描。所以Mysql的存儲時間序列的最佳實踐是不使用二級索引,只使用clustered index掃描主表。類似于Opentsdb。
* Lucene 從 4.0 開始支持 DocValues,極大降低了內存的占用,減少了磁盤上的尺寸并且提高了加載數據到內存計算的吞吐能力。
* Lucene支持分segment,Elasticsearch支持分index。Elasticsearch可以把分開的數據當成一張表來查詢和聚合。相比之下Mysql如果自己做分庫分表的時候,聯合查詢不方便。
* Elasticsearch 從1.0開始支持aggregation,基本上有了普通SQL的聚合能力。從 2.0 開始支持 pipeline aggregation,可以支持類似SQL sub query的嵌套聚合的能力。這種聚合能力相比Crate.io,Solr等同門師兄弟要強大得多。
后面我們分為兩篇文章用科普的方式,具體來看看Elasticsearch是基于什么原理如何做到比mysql和opentsdb更快地查詢和聚合時間序列數據的。
## 作者簡介
**陶文**,曾就職于騰訊IEG的藍鯨產品中心,負責過告警平臺的架構設計與實現。2006年從ThoughtWorks開始職業生涯,在大型遺留系統的重構,持續交付能力建設,高可用分布式系統構建方面積累了豐富的經驗。