Phoenix創(chuàng)建二級索引

為什么需要Secondary Index

對于HBase而言,如果想精確地定位到某行記錄根穷,唯一的辦法是通過rowkey來查詢仅醇。如果不通過rowkey來查找數(shù)據(jù),就必須逐行地比較每一列的值兽赁,即全表掃瞄。對于較大的表冷守,全表掃瞄的代價(jià)是不可接受的刀崖。

但是,很多情況下拍摇,需要從多個(gè)角度查詢數(shù)據(jù)亮钦。例如,在定位某個(gè)人的時(shí)候充活,可以通過姓名蜂莉、身份證號孙咪、學(xué)籍號等不同的角度來查詢,要想把這么多角度的數(shù)據(jù)都放到rowkey中幾乎不可能(業(yè)務(wù)的靈活性不允許巡语,對rowkey長度的要求也不允許)。

所以淮菠,需要secondary index來完成這件事男公。secondary index的原理很簡單,但是如果自己維護(hù)的話則會(huì)麻煩一些『狭辏現(xiàn)在枢赔,Phoenix已經(jīng)提供了對HBase secondary index的支持,下面將說明這樣用Phoenix來在HBase中創(chuàng)建二級索引拥知。

配置HBase以支持Secondary Index

在每一個(gè)RegionServer的hbase-site.xml中加入如下的屬性:

<property>
  <name>hbase.regionserver.wal.codec</name>
  <value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>
</property>
<property>
  <name>hbase.region.server.rpc.scheduler.factory.class</name>
  <value>org.apache.hadoop.hbase.ipc.PhoenixRpcSchedulerFactory</value>
  <description>Factory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updates</description>
</property>
<property>
  <name>hbase.rpc.controllerfactory.class</name>
  <value>org.apache.hadoop.hbase.ipc.controller.ServerRpcControllerFactory</value>
  <description>Factory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updates</description>
</property>
<property>
  <name>hbase.coprocessor.regionserver.classes</name>
  <value>org.apache.hadoop.hbase.regionserver.LocalIndexMerger</value>
</property>

在每一個(gè)Master的hbase-site.xml中加入如下的屬性:

<property>
  <name>hbase.master.loadbalancer.class</name>
  <value>org.apache.phoenix.hbase.index.balancer.IndexLoadBalancer</value>
</property>
<property>
  <name>hbase.coprocessor.master.classes</name>
  <value>org.apache.phoenix.hbase.index.master.IndexMasterObserver</value>
</property>

Global Indexing v.s. Local Indexing

Global Indexing

Global indexing targets read heavy, low write uses cases. With global indexes, all the performance penalties for indexes occur at write time. We intercept the data table updates on write (DELETE, UPSERT VALUES and UPSERT SELECT), build the index update and then sent any necessary updates to all interested index tables. At read time, Phoenix will select the index table to use that will produce the fastest query time and directly scan it just like any other HBase table. By default, unless hinted, an index will not be used for a query that references a column that isn’t part of the index.

Local Indexing

Local indexing targets write heavy, space constrained use cases. Just like with global indexes, Phoenix will automatically select whether or not to use a local index at query-time. With local indexes, index data and table data co-reside on same server preventing any network overhead during writes. Local indexes can be used even when the query isn’t fully covered (i.e. Phoenix automatically retrieve the columns not in the index through point gets against the data table). Unlike global indexes, all local indexes of a table are stored in a single, separate shared table. At read time when the local index is used, every region must be examined for the data as the exact region location of index data cannot be predetermined. Thus some overhead occurs at read-time.

翻譯

Global Indexing

Global indexing踏拜,全局索引,適用于讀多寫少的業(yè)務(wù)場景低剔。使用Global indexing在寫數(shù)據(jù)的時(shí)候開銷很大速梗,因?yàn)樗袑?shù)據(jù)表的更新操作(DELETE, UPSERT VALUES and UPSERT SELECT),都會(huì)引起索引表的更新襟齿,而索引表是分布在不同的數(shù)據(jù)節(jié)點(diǎn)上的姻锁,跨節(jié)點(diǎn)的數(shù)據(jù)傳輸帶來了較大的性能消耗。在讀數(shù)據(jù)的時(shí)候Phoenix會(huì)選擇索引表來降低查詢消耗的時(shí)間猜欺。在默認(rèn)情況下如果想查詢的字段不是索引字段的話索引表不會(huì)被使用位隶,也就是說不會(huì)帶來查詢速度的提升。

Local Indexing

Local indexing开皿,本地索引涧黄,適用于寫操作頻繁以及空間受限制的場景。與Global indexing一樣赋荆,Phoenix會(huì)自動(dòng)判定在進(jìn)行查詢的時(shí)候是否使用索引笋妥。使用Local indexing時(shí),索引數(shù)據(jù)和數(shù)據(jù)表的數(shù)據(jù)存放在相同的服務(wù)器中糠睡,這樣避免了在寫操作的時(shí)候往不同服務(wù)器的索引表中寫索引帶來的額外開銷挽鞠。使用Local indexing的時(shí)候即使查詢的字段不是索引字段索引表也會(huì)被使用,這會(huì)帶來查詢速度的提升狈孔,這點(diǎn)跟Global indexing不同信认。對于Local Indexing,一個(gè)數(shù)據(jù)表的所有索引數(shù)據(jù)都存儲在一個(gè)單一的獨(dú)立的可共享的表中均抽。

Immutable index And Mutable index

immutable index

immutable index嫁赏,不可變索引,適用于數(shù)據(jù)只增加不更新并且按照時(shí)間先后順序存儲(time-series data)的場景油挥,如保存日志數(shù)據(jù)或者事件數(shù)據(jù)等潦蝇。不可變索引的存儲方式是write one款熬,append only。當(dāng)在Phoenix使用create table語句時(shí)指定IMMUTABLE_ROWS = true表示該表上創(chuàng)建的索引將被設(shè)置為不可變索引攘乒。Phoenix默認(rèn)情況下如果在create table時(shí)不指定IMMUTABLE_ROW = true時(shí)者冤,表示該表為mutable俄周。不可變索引分為Global immutable index和Local immutable index兩種。

mutable index

mutable index,可變索引摇予,適用于數(shù)據(jù)有增刪改的場景勃教。Phoenix默認(rèn)情況創(chuàng)建的索引都是可變索引宾符,除非在create table的時(shí)候顯式地指定IMMUTABLE_ROWS = true藻烤。可變索引同樣分為Global immutable index和Local immutable index兩種爽雄。
特別注意】
如果使用的是CDH部署的HBase蝠检,需要在Cloudera Manager管理頁面里面的HBase“配置”頁面里的hbase-site.xml項(xiàng)增加上述配置,并在管理頁面里面重啟HBase才能使得配置生效挚瘟。

image

master

regenServer

5. 測試案例

5.1 測試案例1

HBase 1000w數(shù)據(jù)
users_test表

users_test表

【Global Indexing】

在沒有創(chuàng)建二級索引之前查詢特定USER_NAME的用戶信息耗時(shí)大約16s左右叹谁。

image

在USER_NAME列上面創(chuàng)建二級索引:

create index USERS_TEST_IDX0 on "users_test ("info".USER_NAME)

創(chuàng)建二級索引后查詢特定USER_NAME的用戶名稱耗時(shí)為ms級別

image

【說明】 可以通過explain命令來查看查詢是否用到二級索引

image

【注意】 如果在select條件里面選擇了其他的列,如USER_NO刽沾,因?yàn)樵摿袥]有存在于索引表本慕,因此查詢不會(huì)走索引表。

image

如果想在select USER_NAME,USER_NO查詢?nèi)匀蛔咚饕嗬欤仨殑?chuàng)建如下索引:

  • 方式一锅尘,采取INCLUDE(index cover,即索引覆蓋)的方式:
create index USERS_TEST_IDX1 on "users_test"("info".USER_NAME) INCLUDE("info".USER_NO)

索引覆蓋其實(shí)就是將INCLUDE里面包含的列都存儲到索引表里面布蔗,當(dāng)檢索的時(shí)候就可以從索引表里直接帶回這些列值藤违。要特別注意索引列和索引覆蓋列的區(qū)別,索引列在索引表里面是以rowkey的形式存在纵揍,多個(gè)索引列以某個(gè)約定的字節(jié)分割然后一起存儲在rowkey里面顿乒,也就是說當(dāng)索引列有很多個(gè)的時(shí)候,rowkey的長度也相應(yīng)會(huì)變長泽谨,大小取決于索引列值的大小璧榄。而索引覆蓋列,是存儲在索引表的列族中吧雹。

  • 方式二骨杂,采取多列索引:
create index USERS_TEST_IDX2 on "users_test"("info".USER_NAME, "info".USER_NO) 

【說明】

多列索引在滿足前綴式的情況才會(huì)用到,如創(chuàng)建了A,B,C順序的多列索引雄卷,當(dāng)在where條件指定A條件搓蚪、A B條件或者A B C條件均會(huì)走索引,但是 B C條件則無法走索引丁鹉。

【Local Indexing】

在users_test表創(chuàng)建local index類型的二級索引:

create local index USERS_TEST_LOCAL_IDX ON "users_test"("info".USER_NAME)

與Global Indexing不同的是妒潭,如果select子句里面帶有除了索引列(USER_NAME)以外的列悴能,仍然可以走索引表。

image
image

【說明】

創(chuàng)建Local Indexing時(shí)候指定的索引名稱會(huì)與實(shí)際創(chuàng)建在Hbase里面的表名稱不一致雳灾,這應(yīng)該是Phoenix做了映射的關(guān)系漠酿,而且對于同一個(gè)Hbase里面的table創(chuàng)建多個(gè)Local Indexing,索引表在Hbse list命令查詢的時(shí)候也只有一個(gè)谎亩。

5.2 測試案例2

HBase 1e數(shù)據(jù)
ammeter_test表

image
image

【Global Indexing】

create index AMMETER_TEST_IDX
 on AMMETER_TEST ("info"."ammeter_no1", "info"."ammeter_no2") include("info"."ammeter_price");

(1) 條件查詢包含rowkey

> explain select * from AMMETER_TEST where "info"."ammeter_no1" = '11000000005281' AND "ammeter_no2" = '11000000001004' and ROW = '11000002310462'
> select * from AMMETER_TEST where "info"."ammeter_no1" = '11000000005281' AND "ammeter_no2" = '11000000001004' and ROW = '11000002310462'
image
image

(2) 條件查詢不包含rowkey但滿足二級索引查找條件

> explain select ROW,"ammeter_price" from AMMETER_TEST where "info"."ammeter_no1" = '11000000005281' and "ammeter_no2" = '11000000001004'
> select ROW,"ammeter_price" from AMMETER_TEST where "info"."ammeter_no1" = '11000000005281' and "ammeter_no2" = '11000000001004' LIMIT 5
image
image

【分析】

  • 對于包含rowkey的條件查詢记靡,Phoenix會(huì)啟用服務(wù)器端過濾器快速篩選匹配的行并返回,億級數(shù)據(jù)也能達(dá)到毫秒級別響應(yīng)团驱。
  • 對于沒有包含rowkey的條件查詢,如果條件滿足Phoenix二級索引查找空凸,Phoenix會(huì)查二級索引表并快速返回記錄嚎花。

6. 同步創(chuàng)建索引與異步創(chuàng)建索引

前面所講的創(chuàng)建索引為同步創(chuàng)建索引,當(dāng)執(zhí)行create index的時(shí)候呀洲,索引表會(huì)直接與源數(shù)據(jù)表進(jìn)行同步紊选。但是,有時(shí)候我們的源表數(shù)據(jù)量很大道逗,同步創(chuàng)建索引會(huì)拋出異常兵罢。異常信息大致如下所示:

15/12/11 14:20:08 WARN client.ScannerCallable: Ignore, probably already closed
org.apache.hadoop.hbase.UnknownScannerException: org.apache.hadoop.hbase.UnknownScannerException: Name: 37, already closed?
at org.apache.hadoop.hbase.regionserver.RSRpcServices.scan(RSRpcServices.java:2092)
at org.apache.hadoop.hbase.protobuf.generated.ClientProtos$ClientService$2.callBlockingMethod(ClientProtos.java:31443)
at org.apache.hadoop.hbase.ipc.RpcServer.call(RpcServer.java:2035)
at org.apache.hadoop.hbase.ipc.CallRunner.run(CallRunner.java:107)
at org.apache.hadoop.hbase.ipc.RpcExecutor.consumerLoop(RpcExecutor.java:130)
at org.apache.hadoop.hbase.ipc.RpcExecutor$1.run(RpcExecutor.java:107)
at java.lang.Thread.run(Thread.java:745)
at sun.reflect.GeneratedConstructorAccessor13.newInstance(Unknown Source)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:526)
at org.apache.hadoop.ipc.RemoteException.instantiateException(RemoteException.java:106)
at org.apache.hadoop.ipc.RemoteException.unwrapRemoteException(RemoteException.java:95)
at org.apache.hadoop.hbase.protobuf.ProtobufUtil.getRemoteException(ProtobufUtil.java:313)
at org.apache.hadoop.hbase.client.ScannerCallable.close(ScannerCallable.java:329)
at org.apache.hadoop.hbase.client.ScannerCallable.call(ScannerCallable.java:184)
at org.apache.hadoop.hbase.client.ScannerCallableWithReplicas.call(ScannerCallableWithReplicas.java:136)
at org.apache.hadoop.hbase.client.ScannerCallableWithReplicas.call(ScannerCallableWithReplicas.java:56)
at org.apache.hadoop.hbase.client.RpcRetryingCaller.callWithoutRetries(RpcRetryingCaller.java:200)
at org.apache.hadoop.hbase.client.ClientScanner.call(ClientScanner.java:288)
at org.apache.hadoop.hbase.client.ClientScanner.close(ClientScanner.java:507)
at org.apache.phoenix.iterate.ScanningResultIterator.close(ScanningResultIterator.java:49)
at org.apache.phoenix.iterate.TableResultIterator.close(TableResultIterator.java:95)
at org.apache.phoenix.jdbc.PhoenixResultSet.close(PhoenixResultSet.java:162)
at org.apache.phoenix.compile.UpsertCompiler.upsertSelect(UpsertCompiler.java:199)
at org.apache.phoenix.compile.UpsertCompiler.access$000(UpsertCompiler.java:114)
at org.apache.phoenix.compile.UpsertCompiler$UpsertingParallelIteratorFactory.mutate(UpsertCompiler.java:229)
at org.apache.phoenix.compile.MutatingParallelIteratorFactory.newIterator(MutatingParallelIteratorFactory.java:62)
at org.apache.phoenix.iterate.ParallelIterators$1.call(ParallelIterators.java:109)
at org.apache.phoenix.iterate.ParallelIterators$1.call(ParallelIterators.java:100)
at java.util.concurrent.FutureTask.run(FutureTask.java:262)
at org.apache.phoenix.job.JobManager$InstrumentedJobFutureTask.run(JobManager.java:183)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.apache.hadoop.hbase.ipc.RemoteWithExtrasException(org.apache.hadoop.hbase.UnknownScannerException): org.apache.hadoop.hbase.UnknownScannerException: Name: 37, already closed?
at org.apache.hadoop.hbase.regionserver.RSRpcServices.scan(RSRpcServices.java:2092)
at org.apache.hadoop.hbase.protobuf.generated.ClientProtos$ClientService$2.callBlockingMethod(ClientProtos.java:31443)
at org.apache.hadoop.hbase.ipc.RpcServer.call(RpcServer.java:2035)
at org.apache.hadoop.hbase.ipc.CallRunner.run(CallRunner.java:107)
at org.apache.hadoop.hbase.ipc.RpcExecutor.consumerLoop(RpcExecutor.java:130)
at org.apache.hadoop.hbase.ipc.RpcExecutor$1.run(RpcExecutor.java:107)
at java.lang.Thread.run(Thread.java:745)
at org.apache.hadoop.hbase.ipc.RpcClientImpl.call(RpcClientImpl.java:1199)
at org.apache.hadoop.hbase.ipc.AbstractRpcClient.callBlockingMethod(AbstractRpcClient.java:216)
at org.apache.hadoop.hbase.ipc.AbstractRpcClient$BlockingRpcChannelImplementation.callBlockingMethod(AbstractRpcClient.java:300)
at org.apache.hadoop.hbase.protobuf.generated.ClientProtos$ClientService$BlockingStub.scan(ClientProtos.java:31889)
at org.apache.hadoop.hbase.client.ScannerCallable.close(ScannerCallable.java:327)
... 20 more

這個(gè)時(shí)候,我們可以采用異步創(chuàng)建索引滓窍,方式如下:

CREATE INDEX async_index ON my_schema.my_table (v) ASYNC

通過create index的時(shí)候指定 ASYNC 關(guān)鍵字來指定異步創(chuàng)建索引卖词。執(zhí)行這個(gè)命令之后并不會(huì)引起索引表與源表的直接同步。這個(gè)時(shí)候查詢并不會(huì)使用這個(gè)索引表吏夯。那么索引數(shù)據(jù)的導(dǎo)入還需要采用phoenix提供的索引同步工具類 IndexTool 此蜈, 這是一個(gè)mapreduce工具類,使用方式如下:

${HBASE_HOME}/bin/hbase org.apache.phoenix.mapreduce.index.IndexTool
  --schema MY_SCHEMA --data-table MY_TABLE --index-table ASYNC_IDX
  --output-path ASYNC_IDX_HFILES

當(dāng)mapreduce任務(wù)執(zhí)行結(jié)束噪生,這個(gè)索引表才會(huì)變成可用裆赵。

7. 參考

(1) 可變索引與不可變索引

(2) Globaling Indexing

(3) Local Indexing

(4) 配置二級索引及測試

(5) Phoenix官網(wǎng)文檔

Examples

為已有的Phoenix表創(chuàng)建secondary index

首先,在Phoenix中創(chuàng)建一個(gè)salted table跺嗽。

create table EXAMPLE (my_pk varchar(50) not null, M.C0 varchar(50), M.C1 varchar(50), M.C2 varchar(50), M.C3 varchar(50), M.C4 varchar(50), M.C5 varchar(50), M.C6 varchar(50), M.C7 varchar(50),M.C8 varchar(50), M.C9 varchar(50)  constraint pk primary key (my_pk)) salt_buckets=10;

這里創(chuàng)建的表名為EXAMPLE战授,有10個(gè)列(列名為{M.C0, M.C1, ···, M.C9})。

然后桨嫁,再將一個(gè)CSV文件中的數(shù)據(jù)通過Bulkload的方式導(dǎo)入到該表中植兰。
該CSV文件中有1000萬條記錄,在HDFS中的路徑為/user/tao/xt-data/data-10m.csv瞧甩,則導(dǎo)入的命令為

HADOOP_CLASSPATH=$(hbase classpath) hadoop jar phoenix-4.3.1-client.jar org.apache.phoenix.mapreduce.CsvBulkLoadTool -libjars 3rdlibs/commons-csv-1.0.jar,3rdlibs/joda-time-2.7.jar --table EXAMPLE --input /user/tao/xt-data/data-10m.csv

由于導(dǎo)入的過程用到了一些其他的類钉跷,所以需要通過-libjars 3rdlibs/commons-csv-1.0.jar,3rdlibs/joda-time-2.7.jar來將相關(guān)的Jar包傳給這個(gè)MapReduce任務(wù)。另外肚逸,Bulk CSV Data Loading中提到的方法爷辙,要求對于Phoenix 4.0+的版本彬坏,指定 HADOOP_CLASSPATH=$(hbase mapredcp),但是通過實(shí)踐膝晾,發(fā)現(xiàn)這樣不行栓始,應(yīng)該用HADOOP_CLASSPATH=$(hbase classpath)

在為表EXAMPLE創(chuàng)建secondary index之前血当,先看看查詢一條數(shù)據(jù)所需的時(shí)間:
這里寫圖片描述

可以看到幻赚,對名為M.C0的列進(jìn)行按值查詢需要7秒多。

現(xiàn)在臊旭,為表EXAMPLE的列M.C0創(chuàng)建Index落恼,如下:

create index my_index on example (m.c0);

此時(shí),查看HBase离熏,會(huì)發(fā)現(xiàn)其中多了一個(gè)名為MY_INDEX的表佳谦。而從Phoenix中則看不到該表。

在為EXAMPLE創(chuàng)建了index之后滋戳,我們再來進(jìn)行查詢钻蔑。


這里寫圖片描述

這次,查詢時(shí)間從7秒多降到了0.097秒奸鸯。

確保Query使用Index

By default, a global index will not be used unless all of the columns referenced in the query are contained in the index.

在上例中咪笑,由于我們只對M.C0創(chuàng)建了索引,所以如果查詢項(xiàng)中包含其他列的話(主鍵MY_PK除外)娄涩,是不會(huì)使用index的窗怒。此外,如果查詢項(xiàng)不包含其他列蓄拣,但是條件查詢語句中包含了其他列(主鍵MY_PK除外)兜粘,也會(huì)引發(fā)全表掃瞄。如下:

這里寫圖片描述

要讓一個(gè)查詢使用index弯蚜,有三種方式:

1. 創(chuàng)建 convered index孔轴;

2. 在查詢中提示其使用index;

3. 創(chuàng)建 local index

#方式1 - Covered Index

What is a Covered Index?
Using Covering Index to Improve Query Performance

如果在某次查詢中碎捺,查詢項(xiàng)或者查詢條件中包含除被索引列之外的列(主鍵MY_PK除外)路鹰。默認(rèn)情況下,該查詢會(huì)觸發(fā)full table scan收厨,但是使用covered index則可以避免全表掃描晋柱。
例如,我們按照EXAMPLE的方式創(chuàng)建了另一張表EXAMPLE_2诵叁,表中的數(shù)據(jù)和表的schema與EXAMPLE都是相同的雁竞,不同之處在于EXAMPLE_2對其M.C1這一列創(chuàng)建了covered index。

create index my_index_2 on example_2 (m.c0) include (m.c1);

現(xiàn)在,如果查詢項(xiàng)中不包含除M.C0M.C1之外的列碑诉,而且查詢條件不包含除M.C0之外的列彪腔,則可以確保該查詢使用Index,如下:

這里寫圖片描述

#方式2 - Hint

selectcolumn_name之間加上/*+ Index(<表名> <index名>)*/进栽,如下:

這里寫圖片描述

This will cause each data row to be retrieved when the index is traversed to find the missing M.C1 column value. This hint should only be used if you know that the index has good selective (i.e. a small number of table rows have a value of ‘c0_00000000’ in this example), as otherwise you’ll get better performance by the default behavior of doing a full table scan

#方式3 - Local Index

Unlike global indexes, local indexes will use an index even when all columns referenced in the query are not contained in the index. This is done by default for local indexes because we know that the table and index data coreside on the same region server thus ensuring the lookup is local.

對于HBase 0.98.6的版本德挣,似乎不支持創(chuàng)建local index,如下:


這里寫圖片描述

其他方面

Functional Index

Another useful feature that was introduced in the 4.3 release is functional indexes. Functional indexes allow you to create an index not just on columns, but on an arbitrary expressions. Then when a query uses that expression, the index may be used to retrieve the results instead of the data table. For example, you could create an index on UPPER(FIRST_NAME||' '||LAST_NAME) to allow you to do case insensitive searches on the combined first name and last name of a person.

Phoenix supports two types of indexing techniques: global and local indexing. Each are useful in different scenarios and have their own failure profiles and performance characteristics.

下面為表EXAMPLE創(chuàng)建一個(gè)Functional Index快毛,如下:

create index index_upper_c2 on example (upper(m.c2)) include m.c2

這里格嗅,我們實(shí)際上為表EXAMPLE又創(chuàng)建了一個(gè)名為INDEX_UPPER_C2的Index。也就是說唠帝,可以為同一張表創(chuàng)建多個(gè)Index屯掖。

這里寫圖片描述

Index 排序

create index my_index on example (M.C1 desc, M.C0) include (M.C2);

刪除Index

drop index `index-name` on `table-name`

索引表屬性

create tablecreate index都可以將一些屬性傳遞給對應(yīng)的HBase表,例如:

CREATE INDEX my_index ON my_table (v2 DESC, v1) INCLUDE (v3)
    SALT_BUCKETS=10, DATA_BLOCK_ENCODING='NONE';

對于global indexes襟衰,如果primary table是salted table懂扼,則index會(huì)自動(dòng)地成為salted index。對于local indexes右蒲,則不允許指定salt_buckets

Immutable Indexing

For a table in which the data is only written once and never updated in-place, certain optimizations may be made to reduce the write-time overhead for incremental maintenance. This is common with time-series data such as log or event data, where once a row is written, it will never be updated. To take advantage of these optimizations, declare your table as immutable by adding the IMMUTABLE_ROWS=true property to your DDL statement:

CREATE TABLE my_table (k VARCHAR PRIMARY KEY, v VARCHAR) IMMUTABLE_ROWS=true;

All indexes on a table declared with IMMUTABLE_ROWS=true are considered immutable (note that by default, tables are considered mutable). For global immutable indexes, the index is maintained entirely on the client-side with the index table being generated as change to the data table occur. Local immutable indexes, on the other hand, are maintained on the server-side. Note that no safeguards are in-place to enforce that a table declared as immutable doesn’t actually mutate data (as that would negate the performance gain achieved). If that was to occur, the index would no longer be in sync with the table.

如果創(chuàng)建的表是immutable table赶熟,如下:

create table my_tablek(VARCHAR PRIMARY KEY, v VARCHAR) IMMUTABLE_ROWS=true

那么瑰妄,為該表創(chuàng)建的所有index都是immutable indexes。

可以將一個(gè)已有的immutable table轉(zhuǎn)變?yōu)閙utable table映砖,可以通過如下命令:

alter table my_table set IMMUTABLE_ROWS=false

問題

#1 能不能為多個(gè)column建立index(在同一個(gè)index table中)间坐?

例如,如下命令似乎是可以對兩個(gè)column創(chuàng)建index:

create index my_idx on example(m.c0, m.c1)

但是邑退,似乎只有列m.c0的真正具有索引竹宋,列m.c1似乎沒有索引:

這里寫圖片描述

答案是:可以為多個(gè)column創(chuàng)建索引,但是在查詢時(shí)要按照索引列的順序來查詢地技。例如蜈七,為M.C0M.C1M.C2建立索引:

create index idx on example (m.c0, m.c1, m.c2)

在查詢時(shí)莫矗,可以同時(shí)根據(jù)將這3列作為條件飒硅,且順序不限。但是作谚,第一列必須是M.C0三娩。

這里寫圖片描述

這是因?yàn)椋?strong>當(dāng)為多列建立索引時(shí),rowkey實(shí)際上是這些column的組合妹懒,并且是按照它們的先后順序的組合雀监。

這里寫圖片描述

如果查詢時(shí)第一列不是M.C0,那么就要進(jìn)行full scan眨唬,速度會(huì)很慢会前。而如果查詢的第一列是M.C0好乐,就可以直接將滿足關(guān)于M.C0的數(shù)據(jù)記錄找出來。即使后面還有沒有被索引的列回官,也可以很快得到結(jié)果曹宴,因?yàn)闈M足關(guān)于M.C0的結(jié)果集已經(jīng)不大了(如果是這種情況的話),對其再進(jìn)行一次查詢不會(huì)是full scan歉提。

這里寫圖片描述

#2 Bulkload的數(shù)據(jù)的index能不能自動(dòng)同步笛坦?

維護(hù)Index的原理:當(dāng)對data table執(zhí)行寫入操作時(shí),delete苔巨、upsert valuesupsert select會(huì)被捕獲版扩,并據(jù)此來更新data table所對應(yīng)的index。

We intercept the data table updates on write (DELETE, UPSERT VALUES and UPSERT SELECT), build the index update and then sent any necessary updates to all interested index tables

當(dāng)以bulkload的方式來將數(shù)據(jù)導(dǎo)入到data table時(shí)侄泽,會(huì)繞開HBase的常規(guī)寫入路徑(client –> buffer –> memstore –> HLog –> StoreFile –> HFile)礁芦,直接生成最終的HFiles。對于bulkload悼尾,對data table的更新能不能被捕獲柿扣,進(jìn)而自動(dòng)維護(hù)相應(yīng)index呢?我們來驗(yàn)證闺魏。

首先建立一個(gè)空的data table:

create table EXAMPLE (PK varchar primary key,  M.C0 varchar, M.C1 varchar, M.C2 varchar, M.C3 varchar, M.C4 varchar, M.C5 varchar, M.C6 varchar, M.C7 varchar, M.C8 varchar, M.C9 varchar)  salt_buckets = 20

再為其創(chuàng)建2個(gè)index:

create index IDX_C0 on EXAMPLE(M.C0);
create index IDX_C1 on EXAMPLE(M.C1);

現(xiàn)在用MapReduce將一個(gè)包含了1億行記錄的CSV文件bulkload到數(shù)據(jù)表EXAMPLE中去:

sudo -u hbase HADOOP_CLASSPATH=$(hbase classpath) hadoop jar phoenix-4.3.1-client.jar org.apache.phoenix.mapreduce.CsvBulkLoadTool -libjars 3rdlibs/commons-csv-1.0.jar,3rdlibs/joda-time-2.7.jar --table EXAMPLE --input /user/tao/data/data-100m.csv

從輸出來看未状,一共啟動(dòng)了3個(gè)MR任務(wù),分別針對數(shù)據(jù)表EXAMPLE析桥、索引表IDX_C0和索引表IDX_C1司草,如下:

這里寫圖片描述

現(xiàn)在,再看看查詢時(shí)間:


這里寫圖片描述

所以泡仗,一旦index創(chuàng)建之后埋虹,不論是否使用bulkload來更新data table,都會(huì)保證index的自動(dòng)更新娩怎。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末搔课,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子截亦,更是在濱河造成了極大的恐慌辣辫,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件魁巩,死亡現(xiàn)場離奇詭異急灭,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)谷遂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進(jìn)店門葬馋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事畴嘶〉坝猓” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵窗悯,是天一觀的道長区匣。 經(jīng)常有香客問我,道長蒋院,這世上最難降的妖魔是什么亏钩? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮欺旧,結(jié)果婚禮上姑丑,老公的妹妹穿的比我還像新娘。我一直安慰自己辞友,他們只是感情好栅哀,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著称龙,像睡著了一般留拾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鲫尊,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天痴柔,我揣著相機(jī)與錄音,去河邊找鬼马昨。 笑死,一個(gè)胖子當(dāng)著我的面吹牛扛施,可吹牛的內(nèi)容都是我干的鸿捧。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼疙渣,長吁一口氣:“原來是場噩夢啊……” “哼匙奴!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起妄荔,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤泼菌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后啦租,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哗伯,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年篷角,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了焊刹。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖虐块,靈堂內(nèi)的尸體忽然破棺而出俩滥,到底是詐尸還是另有隱情,我是刑警寧澤贺奠,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布霜旧,位于F島的核電站,受9級特大地震影響儡率,放射性物質(zhì)發(fā)生泄漏挂据。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一喉悴、第九天 我趴在偏房一處隱蔽的房頂上張望棱貌。 院中可真熱鬧,春花似錦箕肃、人聲如沸婚脱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽障贸。三九已至,卻和暖如春吟宦,著一層夾襖步出監(jiān)牢的瞬間篮洁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工殃姓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留袁波,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓蜗侈,卻偏偏與公主長得像篷牌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子踏幻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容