HBase client API

HBase에 접근하기 위한 주요 인터페이스는 org.apache.hadoop.hbase.client의 HTable 클래스입니다. HTable 클래스를 통해서 HBase에 데이터를 저장하고 삭제하는 등 사용자 작업에 필요한 기능을 제공합니다. HBase에서 데이터를 변경하는 로우 단위의 모든 작업은 원자성(Atomic)이 보장됩니다. 원자성이 보장된다는 말은 무슨 의미일까요? 하나의 로우에 읽기나 쓰기 작업이 수행되는 동일 다른 클라이언트나 스레드에서 동일한 로우에 읽기나 쓰기를 시도해도 아무런 문제가 발생하지 않는다는 의미입니다. 읽기를 시도하는 클라이언트나 스레드는 일관성 있는 최종 변경 내용을 읽을 것이고, 쓰기의 경우는 데이터를 수정할 수 있게 될 때까지 기다려야 합니다.

주로 읽기 작업과 쓰기 작업의 충돌은 거의 무시해도 되지만, 많은 클라이언트가 하나의 로우를 동시에 업데이트하는 경우에는 문제가 됩니다. 이러한 경우는 나누어져있는 업데이트 작업을 일괄처리를 통해 가능한 한 줄여야 합니다.

HTable 인스턴스를 생성하는 일은 비용을 수반합니다. HTable을 인스턴스화할 때 메타(.META.) 테이블을 스캔하여 테이블 존재 유무를 파악하고, 활성화 여부를 체크합니다. 그리고 그 외 다른 작업을 수행하기 때문에 입니다. 따라서 HTable 인스턴스는 단 한번(스레드 당) 생성하고 클라이언트 애플리케이션의 생명 주기가 끝날때 까지 재사용하는 편이 좋습니다. HTable의 인스턴스가 여러 개 필요하면 Connection 클래스를 사용해야 합니다.

데이터베이스에서는 데이터를 저장하고 읽고 변경하고 삭제하는 것이 기본 기능입니다. 주로 이러한 기능을 CRUD라고 합니다. Create, Read, Update, Delete의 앞글자를 따서 만든 단어입니다. HBase에서 역시 이러한 기능들 제공합니다. 이제 HBase 데이터를 저장하고 읽어오는 방법에 관해서 살펴보도록 하겠습니다.

Put method

HBase에 데이터를 저장하는 메소드입니다. put 메소드의 경우 하나의 로우를 대상으로 동작하거나 여러 개의 대상으로 동작하는 2가지 유형이 있습니다. 하나씩 예제를 통해 살펴보도록 하겠습니다.

Single Put

put 메소드에 Put 객체 하나를 전달하여 데이터를 저장하는 방식입니다. 예제를 통해 살펴보겠습니다.

Configuration conf = HBaseConfiguration.create(); // 1

HTable table = new HTable(conf, "testtable"); // 2

Put put = new Put(Bytes.toBytes("row1")); // 3

put.add(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"),
  Bytes.toBytes("val1")); // 4
put.add(Bytes.toBytes("colfam1"), Bytes.toBytes("qual2"),
  Bytes.toBytes("val2")); // 5

table.put(put); // 6
  1. HBase 설정을 위한 객체를 생성합니다.
  2. 새로운 클라이언트 인스턴스를 생성합니다.
  3. 특정 로우를 갖는 Put 객체를 생성합니다.
  4. 생성한 Put 객체에 ‘colfam1:qual1’ 이름을 갖는 컬럼을 추가합니다.
  5. Put 객체에 ‘colfam1:qual2’ 이름을 갖는 컬럼을 추가합니다.
  6. HBase 테이블에 저장합니다.

Put 리스트

Put 리스트는 여러 개의 Put 인스턴스 일괄 처리하는 방식입니다.

List<Put> puts = new ArrayList<Put>(); // 1

Put put1 = new Put(Bytes.toBytes("row1"));
put1.add(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"),
    Bytes.toBytes("val1"));
puts.add(put1); // 2

Put put2 = new Put(Bytes.toBytes("row2"));
put2.add(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"),
    Bytes.toBytes("val2"));
puts.add(put2); // 3

Put put3 = new Put(Bytes.toBytes("row2"));
put3.add(Bytes.toBytes("colfam1"), Bytes.toBytes("qual2"),
    Bytes.toBytes("val3"));
puts.add(put3); // 4

table.put(puts); // 5
  1. Put 인스턴스를 저장할 리스트를 생성합니다.
  2. 리스트에 Put 하나를 추가합니다.
  3. 다른 Put을 하나 더 리스트에 추가합니다.
  4. 세 번째 Put을 추가합니다.
  5. 컬럼을 가진 로우 여러 개를 한번에 HBase에 저장합니다.

Get method

HBase의 저장한 데이터를 반환받는 클라이언트 API입니다.

Single Get

HBase에서 Get을 이용하여 데이터를 읽어들이는 예제입니다.

Configuration conf = HBaseConfiguration.create(); // 1
HTable table = new HTable(conf, "testtable"); // 2
Get get = new Get(Bytes.toBytes("row1")); // 3
get.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1")); // 4
Result result = table.get(get); // 5
byte[] val = result.getValue(Bytes.toBytes("colfam1"),
    Bytes.toBytes("qual1")); // 6
System.out.println("Value: " + Bytes.toString(val)); // 7
  1. 설정 인스턴스를 생성합니다.
  2. 새로운 테이블 참조 인스턴스를 생성합니다.
  3. 특정 로우를 지정하는 Get 인스턴스를 생성합니다.
  4. Get 인스턴스에 컬럼을 추가합니다.
  5. HBase에서 해당 로우의 선택된 컬럼을 읽어들입니다.
  6. 특정 컬럼의 값을 얻습니다.
  7. 읽은 값을 원래의 데이터 타입으로 변환하여 출력합니다.

받환 받는 결과의 개수는 기본 값인 1을 사용하고 있습니다. HBase에서는 버저닝을 지원하기 때문에 컬럼값을 여러 개 저장할 수 있습니다. setMaxVersions() 메소드를 이용하여 반환할 버전 개수를 설정하여 여러 버전 개수를 반환 받을 수 있습니다.

Get 리스트

put() 메소드와 마찬가지로 한번의 요청으로 여러 로우를 요청할 수 있습니다. 이 기능을 사용하여 여러 데이터를 좀 더 빠르게 읽어들일 수 있습니다.

byte[] cf1 = Bytes.toBytes("colfam1");
byte[] qf1 = Bytes.toBytes("qual1");
byte[] qf2 = Bytes.toBytes("qual2"); // 1
byte[] row1 = Bytes.toBytes("row1");
byte[] row2 = Bytes.toBytes("row2");

List<Get> gets = new ArrayList<Get>();  // 2

Get get1 = new Get(row1);
get1.addColumn(cf1, qf1);
gets.add(get1);

Get get2 = new Get(row2);
get2.addColumn(cf1, qf1); // 3
gets.add(get2);

Get get3 = new Get(row2);
get3.addColumn(cf1, qf2);
gets.add(get3);

Result[] results = table.get(gets); // 4

System.out.println("First iteration...");
for (Result result : results) {
    String row = Bytes.toString(result.getRow());
    System.out.print("Row: " + row + " ");
    byte[] val = null;
    if (result.containsColumn(cf1, qf1)) { // 5
    val = result.getValue(cf1, qf1);
    System.out.println("Value: " + Bytes.toString(val));
    }
    if (result.containsColumn(cf1, qf2)) {
    val = result.getValue(cf1, qf2);
    System.out.println("Value: " + Bytes.toString(val));
    }
}

System.out.println("Second iteration...");
for (Result result : results) {
    for (KeyValue kv : result.raw()) {
    System.out.println("Row: " + Bytes.toString(kv.getRow()) + // 6
        " Value: " + Bytes.toString(kv.getValue()));
    }
}
  1. 공통적으로 사용할 바이트 배열을 준비합니다.
  2. Get 리스트를 생성합니다.
  3. 리스트에 Get을 추가합니다.
  4. 컬럼이 선택된 로우를 리턴합니다.
  5. 결과를 이터레이트 하면서 어떤 값이 반환됐는지 확인합니다.
  6. 결과를 다시 한 번 이터레이트하면서 모든 값을 출력합니다.

Delete method

앞에서 HBase 테이블의 데이터를 생성(create)하고, 읽고(read), 갱신(update)하는 방법에 대해 살펴보았습니다. 이제 남은 것은 바로 삭제(delete)입니다.

Single Delete

삭제도 마찬가지로 단일 Delete가 있습니다. 바로 예제를 통해 살펴보겠습니다.

Delete delete = new Delete(Bytes.toBytes("row1")); // 1

delete.setTimestamp(1); // 2

delete.deleteColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), 1); // 3

delete.deleteColumns(Bytes.toBytes("colfam2"), Bytes.toBytes("qual1")); // 4
delete.deleteColumns(Bytes.toBytes("colfam2"), Bytes.toBytes("qual3"), 15); // 5

delete.deleteFamily(Bytes.toBytes("colfam3")); // 6
delete.deleteFamily(Bytes.toBytes("colfam3"), 3); // 7

table.delete(delete); // 8

table.close();
  1. 특정 row를 가리키는 Delete 객체를 생성합니다.
  2. 로우 삭제를 위한 타임스탬프를 지정합니다.
  3. 컬럼 하나의 특정 버전을 삭제합니다.
  4. 컬럼 하나의 모든 버전을 삭제합니다.
  5. 컬럼 하나의 특정 버전과 그 이전 버전을 모두 삭제합니다.
  6. 전체 컬럼 패밀리 및 그에 속한 모든 컬럼 및 버전을 삭제합니다.
  7. 전체 컬럼 패밀리에 속함 모든 컬럼에서 지정한 버전 및 그 이전 버전을 모두 삭제합니다.
  8. HBase 테이블에서 데이터를 삭제합니다.

위의 예제를 보면 deleteColumn 메소드와 deleteColumns 메소드 2가지를 사용하고 있습니다. deleteColumn의 경우 지정한 컬럼에서 지정한 타임스탬프와 정확히 일치하는 버전만 삭제하고 버전이 없으면 아무일도 일어나지 않습니다. deleteColumns의 경우 지정한 컬럼에서 타임스탬프가 일치하거나 그보다 오래된 버전을 삭제합니다.

Delete 리스트

Delete 리스트는 Put 리스트와 유사하게 동작합니다. Delete 리스트의 예제를 살펴보겠습니다.

List<Delete> deletes = new ArrayList<Delete>(); // 1

Delete delete1 = new Delete(Bytes.toBytes("row1"));
delete1.setTimestamp(4); // 2
deletes.add(delete1);

Delete delete2 = new Delete(Bytes.toBytes("row2"));
delete2.deleteColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1")); // 3
delete2.deleteColumns(Bytes.toBytes("colfam2"), Bytes.toBytes("qual3"), 5); // 4
deletes.add(delete2);

Delete delete3 = new Delete(Bytes.toBytes("row3"));
delete3.deleteFamily(Bytes.toBytes("colfam1")); // 5
delete3.deleteFamily(Bytes.toBytes("colfam2"), 3); // 6
deletes.add(delete3);

table.delete(deletes); // 7

table.close();
  1. 리스트를 생성합니다.
  2. 로우 삭제 타임스탬프를 설정합니다.
  3. 한 컬럼의 최신 버전만 삭제합니다.
  4. 또 다른 컬럼의 지정하 버전과 이전 버전들을 삭제합니다.
  5. 모든 컬럼 및 버전을 포함한 컬럼 패밀리 하나를 삭제합니다.
  6. 컬럼 패밀리 안의 모든 컬럼에서 지정한 버전 및 그 이전 버전들을 삭제합니다.
  7. HBase 테이블에 여러 로우를 삭제합니다.

정리

HBase에서 생성, 읽기, 갱신, 삭제하는 클라이언트 API에 관해 살펴보았습니다. 그리고 하나의 로우가 아닌 여러 로우를 리스트에 담아서 처리하는 방법에 대해서도 알아보았습니다. HBase의 기본적인 클라이언트 API를 이용해서 애플리케이션의 필요한 데이터를 저장하고 갱신하고 읽고 삭제할 수 있을 것입니다.

References



comments powered by Disqus