Impala 를 거치지 않고 Kudu Java 클라이언트로 직접 테이블을 만들 때 부딪히는 것들이다. 의존성 · 인증 주체 · HMS 연동이라는 세 가지가 각각 다른 오류로 나타난다.
kudu-client 하나만 클래스패스에 넣으면 실행 시점에 클래스를 찾지 못한다. 최소한 다음이 함께 있어야 한다.
javac -cp kudu-client-<VERSION>.jar KuduTableCreator.java
java -cp .:kudu-client-<VERSION>.jar:async-1.4.1.jar:slf4j-api-1.7.30.jar:slf4j-simple-1.7.30.jar KuduTableCreator
async 는 Kudu 클라이언트가 쓰는 비동기 라이브러리이고, SLF4J 바인딩이 없으면 로그가 전혀 나오지 않아 원인 추적이 어려워진다. Maven 을 쓸 수 있으면 kudu-client 하나만 선언해도 전이 의존성이 따라온다. CDP 환경에서는 파셀 안의 jar 를 그대로 쓰는 편이 버전이 맞는다.
find /opt/cloudera/parcels/CDH/lib/kudu -name 'kudu-client*.jar'
Kerberos 를 쓰지 않는 클러스터에서 Kudu 는 OS 사용자 이름을 그대로 접속 주체로 쓴다. root 로 실행하면 마스터 로그에 다음처럼 남는다.
Servicing CreateTable request from {username='root'} at 192.168.x.x:35618
Kudu 의 superuser ACL 에 root 가 없으면 이 요청은 권한 오류로 끝난다. 실행 사용자를 바꾸는 것이 정석이다.
su - kudu -c 'java -cp ... KuduTableCreator'
실행 사용자를 바꾸기 어려우면 JVM 속성으로 이름만 바꿀 수 있다.
System.setProperty("user.name", "kudu");
이것은 인증이 아니라 자기 신고다. 인증이 꺼진 클러스터에서만 통하고, 그 자체가 Kudu 를 신뢰 경계 없이 열어 두고 있다는 뜻이기도 하다. 운영 클러스터라면 Kerberos 를 켜고 keytab 으로 로그인하는 쪽으로 간다.
System.setProperty("java.security.krb5.conf", "/etc/krb5.conf");
System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
KuduClient client = new KuduClient.KuduClientBuilder("mast1:7051,mast2:7051,mast3:7051")
.defaultOperationTimeoutMs(60000)
.build();
List<ColumnSchema> cols = new ArrayList<>();
cols.add(new ColumnSchema.ColumnSchemaBuilder("id", Type.INT32).key(true).build());
cols.add(new ColumnSchema.ColumnSchemaBuilder("name", Type.STRING).nullable(false).build());
Schema schema = new Schema(cols);
CreateTableOptions opts = new CreateTableOptions()
.addHashPartitions(Collections.singletonList("id"), 3)
.setNumReplicas(3);
client.createTable("default.mytable", schema, opts);
마스터 주소만 주면 된다. tablet server 주소를 코드에 넣을 필요는 없고, 클라이언트가 마스터에서 위치를 받아 각 tablet server 에 직접 붙는다. 다만 그 말은 클라이언트에서 tablet server 포트(7050)까지 닿아야 한다는 뜻이다. 마스터만 열린 방화벽에서는 생성 요청 이후 단계가 조용히 실패한다.
복제 수는 tablet server 수 이하여야 한다. 그리고 마스터는 장애 상황에서 재복제가 가능한지도 따져 경고를 남긴다.
The number of live tablet servers is not enough to re-replicate a tablet replica ...:
4 tablet servers would be needed, 3 are available.
이것은 경고이지 생성 실패가 아니다. 복제 수 3 에 tablet server 3 대면 한 대가 죽었을 때 복구할 여지가 없다는 뜻이므로, 운영에서는 복제 수보다 최소 한 대 많게 둔다.
가장 헷갈리는 증상이다. 마스터 로그에는 CreateTable 요청이 정상적으로 접수됐다고 찍히는데, 클라이언트에는 아무 테이블도 생기지 않는다.
W hms_client.cc:285] Time spent create HMS table: real 109.713s user 0.000s sys 0.000s
W client.h:208] Call to Hive Metastore failed after 1 retries:
Timed out: failed to create Hive MetaStore table: THRIFT_EAGAIN (timed out)
W catalog_manager.cc:2090] Timed out: failed to create HMS catalog entry for table [id=...]
W rpcz_store.cc:254] Call kudu.master.MasterService.CreateTable ... took 208380 ms (3.47 min).
Client timeout 29995 ms (30 s)
Kudu 마스터에 --hive_metastore_uris 가 설정돼 있으면 Kudu 는 테이블을 만들 때 자기가 직접 HMS 에 항목을 만든다. HMS 응답이 느리면 마스터 쪽 호출이 3분 넘게 붙잡히고, 그사이 클라이언트는 30초 기본 타임아웃으로 먼저 포기한다. 그래서 "마스터는 받았는데 클라이언트는 실패" 라는 모양이 된다. 같은 상황에서 Impala 로 만들어도 같은 오류가 난다.
ERROR: ImpalaRuntimeException: Error creating Kudu table 'default.mytable'
CAUSED BY: NonRecoverableException: failed to create HMS catalog entry for table ...:
failed to create Hive MetaStore table: THRIFT_EAGAIN (timed out)
즉 Java 코드나 클라이언트 설정의 문제가 아니다. 확인할 것은 HMS 쪽이다.
grep -n hive_metastore_uris /etc/kudu/conf/master.gflagfile
nc -vz <HMS_HOST> 9083
HMS 의 Thrift 스레드 고갈, 백엔드 데이터베이스 지연, 여러 HMS 인스턴스 중 하나가 죽어 있는 경우가 흔하다. HMS 로그에서 해당 시각의 처리 시간을 본다. 문제를 고치기 전에 클라이언트 타임아웃만 늘리면 오류 메시지만 바뀌고 테이블 생성은 여전히 느리다.
임시로 클라이언트 쪽을 늘려 두면 적어도 성공은 한다.
new KuduClient.KuduClientBuilder(masters)
.defaultAdminOperationTimeoutMs(300000)
.build();
defaultAdminOperationTimeoutMs 가 DDL 계열에 적용되는 값이다. 일반 읽기·쓰기에 적용되는 defaultOperationTimeoutMs 와 다르다.
생성 여부와 클러스터 건강은 CLI 로 본다.
kudu table list <MASTER>:7051
kudu cluster ksck <MASTER>:7051
ksck 는 테이블·태블릿·복제본 상태를 한 번에 훑고, 문제가 없으면 OK 로 끝난다. Cloudera Manager 의 "Wait until the Kudu cluster is healthy" 동작도 본질적으로 이 검사가 통과할 때까지 기다리는 것이다. 롤링 재시작 중 이 단계에서 오래 멈춰 있다면 태블릿이 아직 복제 중이거나 일부 tablet server 가 살아나지 못한 것이다.