HBaseクライアントを作る際のページング処理について

HBaseにデータをいっぱい突っ込んでHBaseクライアント作ってさーがりがり検索するぞーっていう場合に注意する点があります。

row keyで検索するとして単純に考えるとGetオブジェクを作って以下のように検索するでしょう。

		Get get = new Get(rowkey);
		get.addFamily(columnFamily);
		Result result = table.get(get);
		List<KeyValue> list = result.list();
		for (KeyValue keyValue : list) {
			System.out.println(Bytes.toString(keyValue.getValue()));
		}

こうすると1行に大量の列がある場合にResult#listでメモリ不足になる危険性があります。

じゃあ、どうするか?まあページング処理が必要ですよね。指定した列数だけ取得するためにはScan#setBatchを使います。

具体例をあげます。まずは以下のスクリプトを用意してHBaseにテスト用のテーブルを作成します。

create 'testtable', 'cf'

10.times { |i|
  put 'testtable', 'r1', "cf:q#{i}", "value#{i}"
}

10.times { |i|
  put 'testtable', 'r2', "cf:q#{i}", "value#{i}"
}

scan 'testtable' すると

 r1                                                                   column=cf:q0, timestamp=1357640105830, value=value0                                                                                                                                                       
 r1                                                                   column=cf:q1, timestamp=1357640105848, value=value1                                                                                                                                                       
 r1                                                                   column=cf:q2, timestamp=1357640105863, value=value2                                                                                                                                                       
 r1                                                                   column=cf:q3, timestamp=1357640105866, value=value3                                                                                                                                                       
 r1                                                                   column=cf:q4, timestamp=1357640105869, value=value4                                                                                                                                                       
 r1                                                                   column=cf:q5, timestamp=1357640105871, value=value5                                                                                                                                                       
 r1                                                                   column=cf:q6, timestamp=1357640105873, value=value6                                                                                                                                                       
 r1                                                                   column=cf:q7, timestamp=1357640105876, value=value7                                                                                                                                                       
 r1                                                                   column=cf:q8, timestamp=1357640105878, value=value8                                                                                                                                                       
 r1                                                                   column=cf:q9, timestamp=1357640105884, value=value9                                                                                                                                                       
 r2                                                                   column=cf:q0, timestamp=1357640105929, value=value0                                                                                                                                                       
 r2                                                                   column=cf:q1, timestamp=1357640105931, value=value1                                                                                                                                                       
 r2                                                                   column=cf:q2, timestamp=1357640105934, value=value2                                                                                                                                                       
 r2                                                                   column=cf:q3, timestamp=1357640105937, value=value3                                                                                                                                                       
 r2                                                                   column=cf:q4, timestamp=1357640105941, value=value4                                                                                                                                                       
 r2                                                                   column=cf:q5, timestamp=1357640105945, value=value5                                                                                                                                                       
 r2                                                                   column=cf:q6, timestamp=1357640105949, value=value6                                                                                                                                                       
 r2                                                                   column=cf:q7, timestamp=1357640105952, value=value7                                                                                                                                                       
 r2                                                                   column=cf:q8, timestamp=1357640105954, value=value8                                                                                                                                                       
 r2                                                                   column=cf:q9, timestamp=1357640105957, value=value9                                                                                                                                                       
2 row(s) in 0.4520 seconds

となります。

20行あるように見えますが、見方としては2行(r1とr2)あって各行が10列(cf:q0〜cf:q9)あります。

Scan#setBatchはこんな感じで使います。

		Scan scan = new Scan(Bytes.toBytes("r1"));
		scan.addFamily(Bytes.toBytes("cf"));
		scan.setBatch(5);
		ResultScanner scanner = table.getScanner(scan);
		for (Result result : scanner) {
			List<KeyValue> list = result.list();
			for (KeyValue keyValue : list) {
				System.out.println(Bytes.toString(keyValue.getRow()) + " " + Bytes.toString(keyValue.getValue()));
			}
		}
		scanner.close();

実行結果

r1 value0
r1 value1
r1 value2
r1 value3
r1 value4
r1 value5
r1 value6
r1 value7
r1 value8
r1 value9
r2 value0
r2 value1
r2 value2
r2 value3
r2 value4
r2 value5
r2 value6
r2 value7
r2 value8
r2 value9

5つづつ進むので一番外側のループは20/5=4回ループします。
Scanのコンストラクタには開始行しか渡していないのでr2まで含んだ結果がかえってきます。

Scan scan = new Scan(Bytes.toBytes("r1"), Bytes.toBytes("r1"));

として終了行も指定すればr1の結果のみ取得できます。
Scan(Get get)を使っても同様の結果になります。

GetとScanを使った場合のサンプルの完全版はこちら

import java.util.List;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.util.Bytes;


public class GetScanSample {
	
	public static void main(String[] args) throws Throwable {
		Configuration conf = HBaseConfiguration.create();
		String tableName = "testtable";
		HTable table = new HTable(conf, tableName);
		
		System.out.println("Get start");
		Get get = new Get(Bytes.toBytes("r1"));
		get.addFamily(Bytes.toBytes("cf"));
		Result result1 = table.get(get);
		List<KeyValue> list1 = result1.list();
		for (KeyValue keyValue : list1) {
			System.out.println(Bytes.toString(keyValue.getRow()) + " " + Bytes.toString(keyValue.getValue()));
		}
		System.out.println("Get end");
		
		System.out.println("Scan start");
		Scan scan = new Scan(get);
		int limit = 5;
		int offset = 0;
		scan.setBatch(limit);
		ResultScanner scanner = table.getScanner(scan);
		int count = 0;
		for (Result result : scanner) {
			if(count == offset) {
				List<KeyValue> list = result.list();
				for (KeyValue keyValue : list) {
					System.out.println(Bytes.toString(keyValue.getRow()) + " " + Bytes.toString(keyValue.getValue()));
				}
				break;
			}
			count++;
		}
		scanner.close();
		System.out.println("Scan end");
	}

}

実行結果

Get start
r1 value0
r1 value1
r1 value2
r1 value3
r1 value4
r1 value5
r1 value6
r1 value7
r1 value8
r1 value9
Get end
Scan start
r1 value0
r1 value1
r1 value2
r1 value3
r1 value4
Scan end

offsetを1にすれば後半のvalue5〜value9を取得できますのでページング処理はこんな感じでできます。

この辺は馬本の3.5 スキャンに書かれているので読むと良いでしょう。

列に対してのフェッチ数はsetBatchで指定し行に対してのフェッチ数はsetCachingで指定します。

いじょ