S-JIS[2010-08-01/2010-08-15] 変更履歴

Filter

HBaseJava APIFilter系クラスは、ScanGetにおける条件を指定するクラス。
Filter自身はインターフェースで、色々な具象クラスが存在する。


Filterの使用方法の基本形

Filterのインスタンスを作り、条件を指定する。
そしてScanにセットしてHTable#getScanner()でデータ取得を実行する。
後はScanの使用方法と同様。

import org.apache.hadoop.hbase.KeyValue;
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.filter.Filter;
	Filter filter = new Filterの具象クラス(〜);

	Scan scan = new Scan();
	scan.setFilter(filter);

//	byte[] startRow = HConstants.EMPTY_START_ROW; //なるべく検索範囲に近い値を指定する方が良い
//	Scan scan = new Scan(startRow, filter);

	ResultScanner rs = table.getScanner(scan);
	try {
		〜
	} finally {
		rs.close();
	}

Getに対してFilterをセットすることも出来る。が、Get自体をサーバー側ではScanに置き換えて使っているっぽい。

	Get get = new Get(row);
	get.setFilter(filter);
	Result r = table.get(get);

↓HRegionクラス内

	private List<KeyValue> get(final Get get) throws IOException {
		Scan scan = new Scan(get);
〜

Filterの具象クラス


を条件とするフィルター。

フィルター条件 コーディング例 備考
値指定
BinaryComparator comparator =
	new BinaryComparator(Bytes.toBytes("value1"));
Filter filter = new ValueFilter(CompareOp.EQUAL, comparator);
値が条件に合致するカラムを返す。
カラムの値を指定
byte[] f = Bytes.toBytes("ColumnFamilyA");
byte[] q = Bytes.toBytes("Column1");
byte[] v = Bytes.toBytes("value1");
SingleColumnValueFilter filter = new SingleColumnValueFilter(
  f, q, CompareOp.EQUAL, v
);
filter.setFilterIfMissing(true);
byte[] f = Bytes.toBytes("ColumnFamilyA");
byte[] q = Bytes.toBytes("Column1");
BinaryComparator comparator =
	new BinaryComparator(Bytes.toBytes("value1"));
SingleColumnValueFilter filter = new SingleColumnValueFilter(
  f, q, CompareOp.EQUAL, comparator
);
filter.setFilterIfMissing(true);
指定したカラムの値が指定した条件に合致している場合に、その行(ROW)の全カラムを返す。
filterIfMissingがfalse(デフォルト)だと、指定カラムが存在しない行(ROW)も返る。

SQLの「select * from テーブル where "ColumnFamilyA:Column1" = 'value1'」に相当。
カラムの値を指定
(指定したカラムは除外)
byte[] f = Bytes.toBytes("ColumnFamilyA");
byte[] q = Bytes.toBytes("Column1");
byte[] v = Bytes.toBytes("value1");
SingleColumnValueExcludeFilter filter =
	new SingleColumnValueExcludeFilter(
	  f, q, CompareOp.EQUAL, v
	);
filter.setFilterIfMissing(true);
条件の判定方法はSingleColumnValueFilterと全く同じだが、
判定に使ったカラム(コンストラクターで指定したカラム)は返されない点だけが異なる。
HBase0.89以降

行キーや行数を条件とするフィルター。

フィルター条件 コーディング例 備考
行キー指定
BinaryComparator comparator =
	new BinaryComparator(Bytes.toBytes("KEY00002"));
Filter filter = new RowFilter(CompareOp.EQUAL, comparator);
行キー(ROW)が条件に合致するカラムを返す。
(キーの範囲指定であれば、Scanで指定する方がいいのではないかと思う)

SQLの「select * from テーブル where ROW='KEY00002'」に相当。
行キー接頭辞指定
byte[] prefix = Bytes.toBytes("KEY");
Filter filter = new PrefixFilter(prefix);
行キーの先頭部分が一致する行(ROW)の全カラムを返す。

SQLの「select * from テーブル where ROW like'KEY%'」に相当。
最終行指定
byte[] stop = Bytes.toBytes("KEY00003");
Filter filter = new InclusiveStopFilter(stop);
指定された行キー以前の行(ROW)の全カラムを返す。
Scan#setStopRow()との違いは、
setStopRow()は指定キーは含まないのに対し、
InclusiveStopFilterは指定キーも含むこと。

SQLの「select * from テーブル where ROW<='KEY00003'」に相当。
取得行数指定
long pageSize = 10;	//N
Filter filter = new PageFilter(pageSize);
最大N行分の全カラムを返す。
ColumnPaginationFilterは行毎のカラム数を指定するものであり、
当フィルターは行数を指定するところが異なる)

SQLの「select * from テーブル limit 10」に相当。

カラム名やカラム数を条件とするフィルター。

フィルター条件 コーディング例 備考
qualifier指定
BinaryComparator comparator =
	new BinaryComparator(Bytes.toBytes("Column2"));
Filter filter = new QualifierFilter(CompareOp.EQUAL, comparator);
qualifier名(カラム名からファミリー名を除いた部分)が条件に合致するカラムを返す。
qualiferが無いカラムも含まれる。
(カラム名そのものを指定したい場合はScan#addColumn()を使う。
ファミリー名を指定したい場合はScan#addFamily()を使う)
行毎の先頭カラム取得
Filter filter = new FirstKeyOnlyFilter();
各行(ROW)において、先頭のカラムを返す。
(1行につき1個)
行毎のスキップ・取得カラム数指定
int limit  = 2;	//N
int offset = 1;	//M
Filter filter = new ColumnPaginationFilter(limit, offset);
各行(ROW)において、先頭M個を除いた最大N個のカラムを返す。
HBase0.89以降
行内の取得カラム数指定
int limit = 3;	//N
Filter filter = new ColumnCountGetFilter(limit);
一行(ROW)だけを指定する前提で、その行の先頭N個のカラムを返す。
つまりGet#setFilter()で使用する想定であり、Scan#setFilter()で使うのは不適切。
(Scanの場合はColumnPaginationFilterを使う)
タイムスタンプ依存
byte[] f = Bytes.toBytes("ColumnFamilyA");
byte[] q = Bytes.toBytes("Column1");
Filter filter = new DependentColumnFilter(f, q);
各行(ROW)において、指定したカラムとタイムスタンプが一致するカラムを返す。
HBase0.89以降
左記の例の場合、同じ行(ROW)内で「ColumnFamilyA:Column1」とタイムスタンプが一致するカラムだけを返す。
byte[] f = Bytes.toBytes("ColumnFamilyA");
byte[] q = Bytes.toBytes("Column1");
Filter filter = new DependentColumnFilter(f, q, true);
dropDependentColumnにtrueを指定すると、
指定したカラム自身は除外される(返されない)。
デフォルトはfalse。
byte[] f = Bytes.toBytes("ColumnFamilyA");
byte[] q = Bytes.toBytes("Column1");
BinaryComparator comparator =
	new BinaryComparator(Bytes.toBytes("value1"));
Filter filter = new DependentColumnFilter(
  f, q, false, CompareOp.EQUAL, comparator
);
指定したカラム指定した値の条件に合致する場合だけ判定される。
左記の例の場合、「ColumnFamilyA:Column1」の値が「value1」の行(ROW)において、そのタイムスタンプと一致するカラムだけを返す。
タイムスタンプ指定
List<Long> list = new ArrayList<Long>();
list.add(1280582757890L);
list.add(1280586240375L);
list.add(1280586245515L);
Filter filter = new TimestampsFilter(list);
指定されたタイムスタンプと一致するデータを返す。
ひとつのカラムで複数の履歴が一致する場合、その中の最新データのみを返すようだ。
HBase0.89.20100726以降

他のフィルターをラップして条件を変更するフィルター。

フィルター条件 コーディング例 備考
FilterList   複数のフィルター条件を組み合わせたひとつのフィルターを形成する。
FilterList filter = new FilterList(FilterList.Operator.MUST_PASS_ALL);

filter.addFilter(
  new ValueFilter(
    CompareOp.GREATER_OR_EQUAL, new BinaryComparator(Bytes.toBytes("a"))
  )
);
filter.addFilter(
  new ValueFilter(
    CompareOp.LESS_OR_EQUAL,    new BinaryComparator(Bytes.toBytes("z"))
  )
);
全てのフィルターを満たす場合にカラムを返す。
いわばAND条件。

SQLの「select * from テーブル where 'a'<=値 AND 値<='z'」に相当。
FilterList filter = new FilterList(FilterList.Operator.MUST_PASS_ONE);

filter.addFilter(
  new ValueFilter(
    CompareOp.LESS,    new BinaryComparator(Bytes.toBytes("a"))
  )
);
filter.addFilter(
  new ValueFilter(
    CompareOp.GREATER, new BinaryComparator(Bytes.toBytes("zzz"))
  )
);
ひとつでも条件を満たす場合にカラムを返す。
いわばOR条件。

SQLの「select * from テーブル where 値<'a' OR 値>'zzz'」に相当。
SkipFilter
Filter f = new ValueFilter(
  CompareOp.EQUAL, new BinaryComparator(Bytes.toBytes("zzz"))
);

Filter filter = new SkipFilter(f);

Scan scan = new Scan();
scan.setFilter(filter);
scan.addColumn(Bytes.toBytes("ColumnFamilyA"), Bytes.toBytes("Column1"));
scan.addColumn(Bytes.toBytes("ColumnFamilyA"), Bytes.toBytes("Column2"));
scan.addColumn(Bytes.toBytes("ColumnFamilyA"), Bytes.toBytes("Column3"));
行(ROW)内の(指定された)カラムが別フィルター条件を満たす場合にそれらのカラムを返す。
ひとつでも条件を満たさないカラムがあったら、その行をスキップする。
たぶん、Scan#addFamily()addColumn()と組み合わせて、
それらのカラム全てが初期値であるようなレコードを探す為に使うのだろう。
左記の例の場合、指定された全てのカラムの値が「zzz」の場合にそれらのカラムを返す。
SQLの「select * from テーブル where "ColumnFamilyA:Column1"='zzz' AND "ColumnFamilyA:Column2"='zzz' AND "ColumnFamilyA:Column3"='zzz'」に相当。
ただし、カラムそのものが存在しない場合は、そのカラムの値は'zzz'に等しいものとして扱われる。
WhileMatchFilter
Filter f = new ValueFilter(
  CompareOp.NOT_EQUAL, new BinaryComparator(Bytes.toBytes("zzz"))
);

Filter filter = new WhileMatchFilter(f);
別フィルターの条件を満たす間、それらのカラムを返す。
一度でも条件外になったら、それ以降の全カラムをスキップする。
左記の例の場合、'zzz'以外の値である間は出力される。
逆に言うと、'zzz'が来るまで出力される。
「'zzz'と等しい」というのが終了条件と言える。
 

CompareOpとWritableByteArrayComparable

CompareFilter系クラス(ValueFilterRowFilter等)やSingleColumnValueFilter等では、値の比較条件を指定することが出来る。

それらのフィルターでは、
comparator(WritableByteArrayComparableの具象クラス)で大小関係の算出方法比較する固定値を指定し、
CompareFilter$CompareOp(比較演算子・コンペアオペレーター)で比較方法を指定する。

comparatorの種類によっては、指定できないCompareOpもある。
例えばSubstringComparatorは条件に合致したかしないかの二種類しか返さない(compareTo()が0・1しか返さず、-1を返す事が無い)ので、EQUAL・NOT_EQUAL以外を指定した場合の動作は不定。

CompareOp 備考
LESS 値<指定条件
LESS_OR_EQUAL 値≦指定条件
EQUAL 値=指定条件
NOT_EQUAL 値≠指定条件
GREATER_OR_EQUAL 値≧指定条件
GREATER 値>指定条件
NO_OP HBase0.89以降。何らかの演算をするわけではなく、
「何もしない」という値を(デフォルト値として)指定する為に使用する。
comparator 備考
バイナリー比較 バイナリー(バイト列)で値全体を比較する。
new ValueFilter(
  CompareOp.LESS,
  new BinaryComparator(Bytes.toBytes("a"))
);
「a」より小さい値
のカラムを返す。
接頭辞比較 値の接頭辞(先頭)部分をバイナリー(バイト列)で比較する。
new QualifierFilter(
  CompareOp.NOT_EQUAL,
  new BinaryPrefixComparator(Bytes.toBytes("Column"))
);
qualifierの先頭が「Column」
でないカラムを返す。
文字列包含 値をString(英小文字)に変換して、文字列が含まれるかどうかを判定する。
大小比較不可。
new RowFilter(
  CompareOp.EQUAL,
  new SubstringComparator("KEY1")
);
行キーに「KEY1」が含まれている
カラムを返す。
正規表現 値をStringに変換して、パターン(正規表現)が含まれるかどうかを判定する。
大小比較不可。
Stringに変換する際のエンコードは別途指定可能。デフォルトではUTF8。
new RowFilter(
  CompareOp.EQUAL,
  new RegexStringComparator("^KEY[12]")
);
行キーが「KEY1」または「KEY2」で始まっている
カラムを返す。

Filterメソッド(検索の処理手順)

スキャンによる検索は、サーバー側のRegionScanner#nextInternal()でデータを取得し、Filterの各メソッドを呼び出してフィルタリングしている。
Fitler#filterXXX()メソッドは、trueを返すとフィルターする。つまり該当データを除去する。

resultsはRegionScanner内のList<KeyValue>型の変数であり、フィルターされずに残ったデータを保持する。

RegionScannerの処理内容 Filterのメソッド Filterメソッドの返り値 Filterメソッドの目的
行初期処理 1 (次レコードの)行キーを取得する。      
2 Scanのストップキー条件の判定を行う。
ストップする場合、行終了処理を行う。
     
3 行キーを条件としてフィルターするかどうか判定する。 filterRowKey(行キー) trueを返すと、次の行へスキップする。 そのキー(ROW)を処理するかどうかを判定する。
4 (カラムのデータ(KeyValue)を取得する前に)
スキャンを終了するかどうか判定する。
filterAllRemaining() trueを返すと、スキャンを終了する。 条件に応じてスキャン自体を終了させる。
KeyValue処理 5 カラムのデータ(KeyValue)を取得し、
KeyValueを条件としてフィルターするかどうか判定する。
有効なKeyValueはresultsに追加される。
filterKeyValue(kv) INCLUDEを返すとresultsにKeyValueが追加される。
SKIPを返すとそのデータはスキップされる。
NEXT_ROWを返すと、次の行へスキップする。
NEXT_COLを返すと次のカラムにスキップする。[2010-08-15]
(HBase0.89.20100726以降)
データ(KeyValue)をフィルターするかどうか判定する。
6 resultsの個数がlimit(Scan#setBatch()でセットした値)に達するか
その行のデータがなくなるまで繰り返す
     
行終了処理 7 (一行分のデータを取得した後の処理) hasFilterRow()
HBase0.89以降
trueを返すと、filterRow(results)が呼ばれる。 filterRow(results)を呼び出すかどうかを指定する。
実行時の状態に関わらず、“当クラスはfilterRow(results)を使うかどうか”でtrue/falseを返す必要がある。
(このメソッドがtrueを返す場合、Scan#setBatch()との併用は不可となる)
filterRow(results)
HBase0.89以降
  条件・状態に応じて、不要なデータ(KeyValue)をresultsから削除する。
8 resultsにデータが入っている場合に、それらのデータをフィルターするかどうか判定する。 filterRow() trueを返すとresultsをクリアーする。 行全体(resultsに入っているデータ)が有効かどうかを最終的に判定する。
9 (resultsが空でない場合、)クライアントへresultsを返す。      
行スキップ処理 a (次の行へスキップする際の処理)
resultsをクリアーする。
     
b フィルターのリセットを行う。 reset()   行(ROW)毎に、フィルター内の状態の初期化を行う。
c 行初期処理へ戻る。      

検索を終了する(スキップする)判定は随所にある(スキャン自体を終了させる方法もある)が、それまでの間は、データを先頭から全て調査していくことになる。
したがって、検索条件に合致するデータがなるべく近くなるように、検索範囲を絞っておく(検索開始行(startRow)をScanで指定しておく)のが良さそう。
ScanのFilterを指定するコンストラクターでstartRowを一緒に指定するようになっている(Filterのみを指定するコンストラクターが無い)のは、そういう意図がこめられているのではないかと思う。


自作フィルターの作り方

Filterインターフェースを実装(あるいはFilterBase(HBase0.89以降)を継承/CompareFilterを拡張)して、自作フィルターを作れる。
が、フィルター処理はHBaseのサーバー側で行われる点に留意する必要がある。
つまり、自作したクラス(の入ったjarファイル)を、HBaseがインストールされている全ノードにも配置しなければならない。

HBaseで提供されているフィルター等のクラスについてはHbaseObjectWritableクラスの中で型をキャッシュしていたりするのだが、
Scanクラスのシリアライゼーション(write()・readFields())でフィルターのクラス名も受け渡しているので、
自作クラスではHbaseObjectWritableを気にする必要は無い。


Java APIへ戻る / HBaseへ戻る / HBase変更点へ / Java目次へ行く / 技術メモへ戻る
メールの送信先:ひしだま