3장 클라이언트 API : 기본기능 - Put 메서드
O'REILLY HBASE 완벽가이드를 보고 따라한 내용
2018/07/17 - [2018년 상반기/DataBase] - 험난한 HBASE 설치기
로 HBASE 설치를 완료했다. 이제 책을 보고 공부할 수 있게 되었다 ^-^
들어가기 전에,,,
1) row 단위의 작업은 원자성이 보장된다. 즉, 읽을때는 항상 일관성이 보장되어 최신 데이터를 읽을 수 있고 쓰기 시도를 할때는 기다려야 한다.
-8장에서 다룰 내용이다.
2) HTable 인스턴스를 생성하는 일에는 비용이 든다. 메타 테이블을 스캔해서 HTable이 지정하는 테이블이 존재하고 활성화되어있는지 확인하고 그 외에 작업을 좀 더 수행하기때문이다. 그러니 당연히 인스턴스는 한번만 그리고 스레드당 하나씩만 만들어서 재사용을 하는게 좋다.
3) 그래도 인스턴스가 많이 필요하다면 HTablePool을 쓰면 된다.
-4장에서 다룰 내용이다.
CRUD(Create,Read,Update,Delete) 기능
1. Put 메서드 (단일로우대상/멀티로우대상)
1.단일 Put
void put(Put put) throws IOException
Put 객체는 아래와 같이 hbase의 객체로서 아래와 같은 방법들로 생성할 수 있다.
API 문서 https://hbase.apache.org/2.0/apidocs/index.html
*책에서는 RowLcok 객체를 넘겨줘서 생성할 수 있다는데,, API를 조사해보면 다음과 같이 이제 사용하지 않는다. 책의 HBASE 버전이 좀 오래 되었다.
위에를 보면 생성할 때에 byte[ ] 타입을 넘겨주어야 한다. (byte 배열) HBASE의 row는 row key로 식별되고 이들은 바이트 배열 타입이다.(HBASE에서는 대부분 byte[] 타입이라고 한다.) - key를 설계하는 방법은 9장에서 설명한다.
아래와 같이 hbase에서는 byte[ ]로 변환해주는 클래스를 제공한다.
Put 인스턴스를 생성하면 이 인스턴스에 아래의 메서드를 사용하여 데이터를 추가할 수 있다.
add(Cell cell)을 보면 KeyValue를 추가한다고 되어있다. KeyValue 클래스는 하나의 고유한 Cell을 나타낸다.
Put은 Mutation을 상속받고 있다. 그래서 Mutation에 존재하는 method를 보자면 아래와 같이 여러가지의 메서드들이 존재한다.
이중에서 소개된 것을 보자면 get Class KeyValue type 인스턴스를 얻을 수 있다.
api는 이쯤 봐두고 무작정 그냥 사용해보자. 우선 실행하기에 앞서, 메이븐을 설치해주고 다음과 같은 폴더 구성을 해준다.
pom.xml은 메이븐을 위한 디펜던시를 작성하기 위한것이고, src/main/java 아래에 코드를 작성할 java 파일을 넣어준다.
pom.xml 형태는 http://araikuma.tistory.com/448 이분의 블로그를 참조해보자. 정말 친절하시다.
그외에 필요한 repo들은 https://mvnrepository.com/ 에서 확인할 수 있다.
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.hbase/hbase-common -->
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-common</artifactId>
<version>2.1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.hbase/hbase-client -->
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>2.1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-common -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.7.6</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-client -->
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>2.7.6</version>
</dependency>
</dependencies>
그리고 추가로 아래와 같은 코드를 추가한다. 안하면 manifest가 없다는 에러를 발생하고, 또 with-dependencies를 이용해서 종속성을 포함한 jar파일이 생성된다. (처음에 이래서 compile때에는 lib를 찾다가 실행하니깐 못찾아서 한참 에러를 구경했다.....)
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>PutExample</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id> <!-- this is used for inheritance merges -->
<phase>package</phase> <!-- bind to the packaging phase -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
책에 있는 내용과 동일하게 코드를 작성해주고 예제는 깃헙에 있다.☞ https://github.com/larsgeorge/hbase-book
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.util.Bytes;
import java.io.IOException;
public class PutExample {
public static void main(String[] args) throws IOException {
Configuration conf=HBaseConfiguration.create();
HTable table = new HTable(conf,"testtable");
Put put = new Put(Bytes.toBytes("row1"));
put.add(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), Bytes.toBytes("val1"));
put.add(Bytes.toBytes("colfam1"), Bytes.toBytes("qual2"), Bytes.toBytes("val2"));
table.put(put);
}
}
이대로 하면 에러난다. 책은 0.9 버전이고 나는 2.1버전을 다운 받았기 때문ㅇ ㅔ,,, (눈물)
https://hbase.apache.org/2.0/apidocs/index.html
https://cloud.google.com/bigtable/docs/samples-java-hello
https://intellipaat.com/tutorial/hbase-tutorial/operations-using-java-api/
그래서 아래와 같이 코드를 재구성 했다. 변경된 것이 많은데 , 우선 HTable은 없어졌으니 Table을 쓰라고 한다.
.
그리고 Admin을 생성하고 table에 대한 descriptor를 생성하고 family 생성후 table을 생성해주면 된다.
//connection
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.Admin;import java.io.IOException; * 요것두 추가
//table create
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.HColumnDescriptor;
public class PutExample{
public static void main(String[] args) throws IOException {
HBaseConfiguration conf =new HBaseConfiguration(new Configuration());
Connection connection = ConnectionFactory.createConnection(conf);
Admin admin = connection.getAdmin();
HTableDescriptor tableDescriptor = new HTableDescriptor(TableName.valueOf("testtable"));
tableDescriptor.addFamily(new HColumnDescriptor("colfam1")); //적어도 하나의 family는 명시해줘야한다고 한다. 오류뜸
admin.createTable(tableDescriptor);
System.out.println("create table testTable.."+tableDescriptor.getNameAsString());
table.close();connection.close();
}
}
그리고서 shell에 접속하면 table list에 내가 생성한 테이블이 보인다.
위에 보면 Configuration이라는 객체를 맨 처음 생성하는 것을 볼 수 있다. 이는 코드상에서 클러스터의 위치를 알기 위해서 작성된 것이다.
클러스터의 위치를 알고자 할때는 hbase-site.xml에 접근하거나 코드에 명시해야하는데,이때 코드상에서 조작하려고 사용하는것이 Configuration이다.
그래서 이 configuration객체를 이용해서 hbase-site.xml 파일을 수정할 수 도 있다고 한다.
이렇게 하면, 클라이언트 측에서 외부 파일을 직접 참조해야하는 수고를 덜어줄 수 있다는 장점이 있다.
이제 Put 객체를 사용해보자. 아래쪽에 코드를 더 추가해주자. API를 보면 아래와 같이 되어있다. Connection을 이용해서 얻으세요~
//insert data
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.util.Bytes;
<.위에 코드들.....>
Table table = connection.getTable(TableName.valueOf("testtable"));
Put put = new Put(Bytes.toBytes("row1"));
put.addColumn(Bytes.toBytes("colfam1"),Bytes.toBytes("qual1"),Bytes.toBytes("val1"));
put.addColumn(Bytes.toBytes("colfam1"),Bytes.toBytes("qual2"),Bytes.toBytes("val2"));
table.put(put);
다시 hbase shell에서 scan을 해보면 내가 put으로 넣은 데이터가 잘 보인다 (*코드를 두번실행할땐 테이블이 이미존재한다는 에러가 뜨니 주의)
HBase의 특별한 기능 중 하나는 각 Cell에 여러 개의 버전을 저장할 수 있다는 것이다.
버전이라는 것은 위 그림과 같이 밀리초로 환산된 long integer 값인 timestamp를 이용하여 구현된다.
put을 이용할때 ts라는 파라미터를 명시하면 timestamp를 직접 지정할 수 있으며 그렇지 않으면 리전 서버에서 자동으로 설정해준다. 유닉스 시간이 default
.
사진 출처 : https://ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%89%EC%8A%A4_%EC%8B%9C%EA%B0%84
아래 예시를 보면 VERSIONS => 3을 한경우에 3개까지 버전이 나왔다 (크거나 작다가 아니라 명시를 뜻하는 것)
책에 있는데로 그냥 create 'test'하면 versions를 3으로 해도 1개만 나오고 안된다.
기본적으로 versions가 1로 생성되어서 그러는 것 같다. 그래서 versions를 3으로 명시하여 테이블을 생성하니 아래와 같이 잘 나왔다.
그냥 scan을 하면 가장 최신 것을 보여준다.
참고 : https://stackoverflow.com/questions/26546743/hbase-table-is-not-showing-any-versioning
hbase(main):052:0> create 'test', {NAME => 'cf1', VERSIONS => 3}
Created table test
Took 1.2488 seconds
=> Hbase::Table - test
hbase(main):053:0> put 'test', 'row1','cf1','val1'
Took 0.2747 seconds
hbase(main):054:0> put 'test', 'row1','cf1','val2'
Took 0.0184 seconds
hbase(main):055:0> put 'test', 'row1','cf1','val3'
Took 0.0101 seconds
hbase(main):056:0> scan 'test'
ROW COLUMN+CELL
row1 column=cf1:, timestamp=1533870527379, value=val3
1 row(s)
Took 0.1733 seconds
hbase(main):057:0> scan 'test',{VERSIONS =>3}
ROW COLUMN+CELL
row1 column=cf1:, timestamp=1533870527379, value=val3
row1 column=cf1:, timestamp=1533870525282, value=val2
row1 column=cf1:, timestamp=1533870521845, value=val1
1 row(s)
Took 0.0355 seconds
KEY VALUE 클래스
keyvalue는 특성 cell 하나의 데이터 및 데이터 베이스 내에서의 좌표 정보(로우 키, 컬럼패밀리 이름, 컬럼 퀄리파이어,타임스탬프)를 담고 있다.
생성하는 방법은 너무~ 많다. https://hbase.apache.org/2.0/apidocs/index.html
이중에서 책에서 소개한 생성자는 아래와 같은 모양이다.
데이터 및 좌표 정보는 모두 자바의 byte[] 타입으로 저장된다.
위에 파라미터를 보면 offset과 length가 있다. 이는 필요한 추가 저장공간을 최소화하여서 꼭~ 필요한 공간만 사용하기 위함이다. 이렇게 공간 효율적이기때문에 연산을 빠르게 수행 할 수 있다.
get메서드를 이용하면 각각의 바이트 배열 및 offset과 length를 얻을 수 있다. 이 수준까지 이용할 상황은 거의 없다고한다.^-^;; 그러니 이런게 있다고만 알아두라고 한다.
다음과 같은 메서드도 있다. (8장 아키텍처에서 더 자세하게 설명될 것이다.)
여기서 간단히만 살펴보자면 Key와 Row의 차이는 Row는 말그대로 Row key를 말하는 것이고 Cell의 Byte타입으로 저장된 좌표정보라고 설명했던 개념이 Key다. (음,,,책에서는 딱 이렇게 작성되어있는데, keyvalue생성자의 첫번째 파라미터를 말하는 것인가,,?)
비교 연산자를 제공해주고 있다. 문서에도 나와있지만 일부 클래스들은 이제 사라진다...~* 바이바이
아래와 같은 filed를 이용하여 위 class에 접근할 수 있다. 그래서 새로 생성안하고 고냥 접근이 가능하다.
keyvale 생성할때 Type이라는게 있었다. Type에 대해 알아보자.
이 필드에 사용 가능한 값은 다음과 같다. 책에서는 Put과 Delete<..>만 소개하고 있다.
Put : 해당 keyvale 인스턴스가 일반적인 Put 연산임을 의미함
Delete : 해당 keyvale 인스턴스가 Delete 연산임을 의미함 일명 툼스톤 마커(tombstone marker)라고도 한다.
DeleteColumn : Delete보다 더 광범위하게 전체 칼럼 삭제
DeleteFamily : Delete 보다 더 광범위하게 전체 칼럼 삭제 및 칼럼 패밀리까지 모두 삭제
toString()이라는 메서드를 사용하면 Keyvalue의 위의 어떤 유형인지를 <row-key>/<family>:<qualifier>/<version><type>/<value-length> 로 출력해준다.
클라이언트 측 쓰기 버퍼
HBASE API는 클라이언트 측 쓰기 버퍼를 내장하여 제공한다.
이 쓰기 버퍼는 쓰기 연산을 수집하여 한 번의 PRC로 서버에 전달한다. 이는 아래 그림과 같이 client와 server간에 요청을 주고받을때 요청을 주는시간과 그에 대한 응답을 하는데에 걸리는 시간인 round-trip time을 줄이기 위해 한번에 buffer에 연산을 담았다가 한번에 보내서 처리하는것을 이야기한다.
RPC는 https://ko.wikipedia.org/wiki/%EC%9B%90%EA%B2%A9_%ED%94%84%EB%A1%9C%EC%8B%9C%EC%A0%80_%ED%98%B8%EC%B6%9C 에 나와있다.
작년에 xml-rpc도 공부한 적 있다. http://weejw.tistory.com/81?category=776732
버퍼를 활성화 시키려면 메서드를 사용하면 된다(어디 클래스에 있는 메서드인지도 안알려준다 ㅡㅡ 하..)
void setAutoFlush(boolean autoFlush)
boolean isAutoFlush()
이 두가지 메서드는 HTable class에 있었다. 지금은 BufferedMutator 를 사용하여 버퍼를 설정해줄 수 있다.
(참고 https://stackoverflow.com/questions/31356639/how-to-set-autoflush-false-in-hbase-table)
setAuto,isAuto는 아예 쓰지 않으며 아래와 같이 해당 테이블의 BufferedMutator 객체를 만들어서 사용해야한다.
Table t = connection.getTable(TableName.valueOf("foo"));
BufferedMutator t = connection.getBufferedMutator(TableName.valueOf("foo"));
t.mutate(p);
책에 있는 예제를 살펴보자. 아니다. 어차피 안될테니 쳐다도 안보겠다. 순서만 따라서 API보고 코드를 또 수정해보자.
아래 disableWriteBufferPeriodicFlush를 이용하여 버퍼를 사용할지 말지 할 수 있다. 파라미터는 없다. 그냥 호출만으로 disable시킬 수 있는 것 같다.
148 default void disableWriteBufferPeriodicFlush() { 149 setWriteBufferPeriodicFlush(0, MIN_WRITE_BUFFER_PERIODIC_FLUSH_TIMERTICK_MS); 150 }
그에 반해 다음과 같이 주기를 설정할 수 있는 메서드도 있다.
그렇게 문서들을 참고해서 코드를 수정했다. 수정된건 사실 크게 없다. 앞에서 언급했던것들이 바뀐 것 뿐. flush 메서드가 HTable의 메서드였기때문에 이를 BufferedMutator Class의 메서드로 변경하였다.
BufferedMutator mutator = connection.getBufferedMutator(TableName.valueOf("testtable"));
Put put = new Put(Bytes.toBytes("row1"));
put.addColumn(Bytes.toBytes("colfam1"),Bytes.toBytes("qual1"),Bytes.toBytes("val1"));
mutator.mutate(put);
Put put2 = new Put(Bytes.toBytes("row2"));
put2.addColumn(Bytes.toBytes("colfam1"),Bytes.toBytes("qual1"),Bytes.toBytes("val2"));
mutator.mutate(put2);
Put put3 = new Put(Bytes.toBytes("row3"));
put3.addColumn(Bytes.toBytes("colfam1"),Bytes.toBytes("qual1"),Bytes.toBytes("val3"));
mutator.mutate(put3);
System.out.println("flush time : " + mutator.getWriteBufferPeriodicFlushTimerTickMs());
Get get = new Get(Bytes.toBytes("row1"));
Result res1 = table.get(get);
System.out.println("Result1 : "+res1);
mutator.flush();
Result res2 = table.get(get);
System.out.println("Result2 : "+res2);
mutator.close();
table.close();
connection.close();
create table testTable..testtable
flush time : 1000
Result1 : keyvalues=NONE
Result2 : keyvalues={row1/colfam1:qual1/1533889064018/Put/vlen=4/seqid=0}
import java.util.ArrayList;
import java.util.List;
< 중간 내용.. >
List<Mutation> mutations = new ArrayList<Mutation>();
Put put = new Put(Bytes.toBytes("row1"));
put.addColumn(Bytes.toBytes("colfam1"),Bytes.toBytes("qual1"),Bytes.toBytes("val1"));
mutations.add(put);
Put put2 = new Put(Bytes.toBytes("row2"));
put2.addColumn(Bytes.toBytes("colfam1"),Bytes.toBytes("qual1"),Bytes.toBytes("val2"));
mutations.add(put2);
Put put3 = new Put(Bytes.toBytes("row3"));
put3.addColumn(Bytes.toBytes("colfam1"),Bytes.toBytes("qual1"),Bytes.toBytes("val3"));
mutations.add(put3);
mutator.mutate(mutations);
Result2 : keyvalues={row1/colfam1:qual1/1533889915315/Put/vlen=4/seqid=0}
위에 코드에서 있지도 않음 컬럼 페밀리에다가 레코드를 삽입하려고 해보자. 당연히 없는 컬럼이라는 에러가 발생할 것이다.
Put put2 = new Put(Bytes.toBytes("row2"));
put2.addColumn(Bytes.toBytes("BOGUS"),Bytes.toBytes("qual1"),Bytes.toBytes("val2"));
mutations.add(put2);
List<Put> puts = new ArrayList<Put>();
Put put4 = new Put(Bytes.toBytes("row2"));
puts.add(put4);
try{
table.put(puts);
}catch (Exception e){
System.out.println("Error:"+e);
}
Put put1 = new Put(Bytes.toBytes("row1"));
put1.addColumn(Bytes.toBytes("colfam1"),Bytes.toBytes("qual1"),Bytes.toBytes("val1"));
boolean res1 = table.checkAndPut(Bytes.toBytes("row1"),
Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), null, put1);
System.out.println("Put 1a applied: " + res1);
boolean res2 = table.checkAndPut(Bytes.toBytes("row1"),
Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"), null, put1);
System.out.println("Put 1b applied: " + res2);
Put put2 = new Put(Bytes.toBytes("row1"));
put2.addColumn(Bytes.toBytes("colfam1"),Bytes.toBytes("qual2"),Bytes.toBytes("val2"));
boolean res3 = table.checkAndPut(Bytes.toBytes("row1"),
Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"),Bytes.toBytes("val1"), put2);
System.out.println("Put 2 applied: " + res3);
Put put3 = new Put(Bytes.toBytes("row2"));
put3.addColumn(Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"),Bytes.toBytes("val3"));
boolean res4 = table.checkAndPut(Bytes.toBytes("row1"),
Bytes.toBytes("colfam1"), Bytes.toBytes("qual1"),Bytes.toBytes("val1"), put3);
System.out.println("Put 3 applied: " + res4);
Put 1b applied: false
Put 2 applied: true
Exception in thread "main" org.apache.hadoop.hbase.DoNotRetryIOException: org.apache.hadoop.hbase.DoNotRetryIOException: Action's getRow must match