DataHub Opensource에 Protobuf Multi-level nested message support 기능 PR 작성 후기

써주
네이버 플레이스 개발 블로그
11 min readFeb 10, 2023

--

오픈소스에 기여한 계기

안녕하세요. G플레이스데이터개발 팀에서 근무하고 있는 서주현입니다. 저희 팀에서는 한국 및 일본의 Place data를 Hadoop eco-system에서 활용 할 수 있도록 Data Lakehouse를 구축하는 업무를 담당하고 있습니다. 또 누구라도 Data Lakehouse에 적재된 데이터를 손쉽게 탐색 할 수 있도록 Data discovery 솔루션인 DataHub를 도입하였습니다.

DataHub 내에서는 여러 데이터 활용 할 수 있지만, Glace CIC에서는 Protobuf를 데이터명세를 정의한 IDL(Interface Definition Language)로 활용하고 있습니다. 그리고, 해당 UserMsg message 의 id, name field의 주석정보를 DataHub UI 상의 description 으로 보여지도록 PoC(Proof of Concept) 를 진행하고 있었습니다.

다음과 같이 하나의 Protobuf 파일 안에 Message가 1뎁스 기준으로 작성이 되어있다면 DataHub 에서 문제없이 description을 볼 수가 있습니다.

message UserMsg {
string id = 1; // user id
string name = 2; // user name
}

하지만, 아래의 코드처럼 UserMsg message 내의 또 UserInfo message 가 있는 경우 (nested 타입 message) 해당 UserInfo message field인 nickname과 protfile_url의 주석정보를 가져오지 못해 DataHub UI 상의 description에 표기가 되지 않는 문제가 발생했습니다.

message UserMsg {
message UserInfo {
string nickname = 1; // user info nickname
string profile_url = 2; // user info profile url
}

string id = 1; // user id
string name = 2; // user name
UserInfo user_info = 2; // user info
}

저희 팀에서는 Protobuf 의 작성 시, nested 타입 message를 허용하고 있기 때문에 nested 타입 message의 주석정보를 처리하는 기능은 꼭 필요한 기능이었습니다.

미약하지만 DataHub 오픈소스 프로젝트에 참여해 볼 수 있는 기회다 싶어 해당 이슈를 진행하기로 했습니다.

DataHub 팀과의 커뮤니케이션

Apache 오픈소스 및 다른 오픈소스 커뮤니케이션과는 다르게 DataHub 팀의 커뮤니케이션은 주로 Slack을 활용합니다. Slack을 이용할 때 가이드라인이 있습니다. 그 가이드라인만 잘 준수하면 무리 없이 DataHub팀과 소통을 할 수 있습니다.

먼저, Protobuf의 정보를 DataHub의 Metadata로 만드는 ingestion 과정이 문제였기 때문에 Slack의 ingestion 채널에 문의했습니다.

Slack의 메시지에서 처럼 nested 타입 message가 DataHub UI 상의 description 이 표기가 되지 않는다고 이슈를 던졌고, DataHub 팀에서는 저희가 파악 한 대로 Protobuf의 nested 타입 message를 사용할 경우 DataHub 에서 description 이 정상적으로 표시가 되지 않을 수 있어 message를 nested로 사용하지 않는 게 좋겠다고 가이드를 줬습니다.

하지만, 저희 팀이 Data owner 들과 함께 관리하는 수많은 데이터 명세서에는 nested 타입 message를 이미 사용하고 있던 상황이었고 이를 모두 수정 하기가 어렵다고 판단하였습니다. DatHub팀에게 해당 이슈에 대해 수정할 계획이 있는지 먼저 물어본 뒤, 수정 계획이 없으면 우리 팀이 수정해서 PR을 올려도 괜찮은지 문의했습니다.

다행히 DataHub 팀에서는 원하면 해당 이슈의 PR를 올려도 괜찮다고 수락을 해서 바로 코드 수정에 들어갔습니다.

DataHub 소스코드 파악 및 수정

우선 해당 프로젝트를 디버깅하기 위해 Repository를 Clone 한 다음 DataHub 문서에 나와있는 Datahub Developer’s Guide를 참고하며 진행했습니다.

먼저, Protobuf의 파일이 어떤 함수에서 가공이 되는지 파악하기 위해 datahub-protobufdatahub-protobuf-example 의 test case를 참고했습니다.

그리고 main 함수를 시작으로 하나씩 디버깅을 진행하며 ProtobufField.java 파일을 위주로 코드 분석을 했습니다.

Protobuf message field의 description을 처리하는 로직은 크게 두 가지의 정보를 조합해서 생성됩니다.

  1. Protobuf 원본 파일의 정보를 가지고 있는 DescriptorProto 클래스
  2. DescriptorProto 에 정의된 부분을 식별할 수 있는 Location 클래스의 Path

간단한 예제로 설명하겠습니다.

다음은 User.proto 정의된 부분과 User message의 id field의 name을 가져오는 path입니다.

message UserMsg {
string id = 1;
}

[4, 0, 2, 0, 1] // name path

file.message_type(0) // 4, 0
.field(0) // 2, 0
.name() // 1

위와 같이 path 정보로 해당 User.proto 파일의 정보를 가져올 수 있습니다. 여기서 눈여겨봐야 할 것은 path의 2번 요소의 값이 2라는 점입니다. 2번 요소는 일반 field 정보(DescriptorProto.FIELD_FIELD_NUMBER (value=2))도 올 수 있지만, nested 타입 message의 field 정보(DescriptorProto.NESTED_TYPE_FIELD_NUMBER (value=3))도 올 수 있습니다.

그럼, DataHub 에서 기존 사용하던 코드를 살펴보겠습니다.

...
.filter(loc -> loc.getPathCount() > 3
&& loc.getPath(2) == DescriptorProto.FIELD_FIELD_NUMBER // 2
...

기존 코드에서는 locaion의 path 2번 요소가 DescriptorProto.FIELD_FIELD_NUMBER(value=2)인 경우에만 동작하게 되어 있었습니다.

이렇게 될 경우 path의 2번 요소에 DescriptorProto.NESTED_TYPE_FIELD_NUMBER(value=3) 이 올 경우 nested 타입 message의 field 들은 무시하게 되므로 컨트롤할 수 없게 됩니다.

[4, 0, 3, 0, 2, 0] // nested path

...
&& location.getPath(2) == DescriptorProto.NESTED_TYPE_FIELD_NUMBER // 3
...

다음은 수정된 코드의 일부만 가져왔습니다. 여기서 nested 정보를 가진 path가 올 경우 다음과 같은 코드를 추가하므로 인해 nested 타입 mseeage의 field를 처리할 수 있게 됩니다.

수정된 코드로 실행할 경우 nested 타입 message의 field 들의 주석정보를 이용해 DataHub UI 상의 description 이 잘 표기되는 걸 확인할 수 있었습니다.

PR 에서 피드백

코드 수정 후 DataHub Repository에 PR을 올리고 담당자에게 확인을 부탁한다고 Slack에 메시지를 보냈습니다. 너무 급하게 PR을 올리는 나머지 test case를 깜빡했었습니다.

DataHub 팀에서는 검증할 수 있는 test case도 같이 추가해달라고 요청하여 test 할 수 있는 Protobuf 파일을 생성 후 nested 타입 message의 field를 검증할 수 있는 test case를 만들어 추가했습니다.

message UserMsg {
message UserInfo {
string nickname = 1; // nickname info
string profile_url = 2; // profile url info
}

string id = 1; // user id
string name = 2; // user name
UserInfo user_info = 3; // user info
}

...

SchemaField nicknameField = testMetadata.getFields()
.stream()
.filter(f -> f.getFieldPath()
.equals("[version=2.0].[type=extended_protobuf_UserMsg].[type=extended_protobuf_UserMsg_UserInfo].user_info.[type=string].nickname"))
.findFirst()
.orElseThrow();

assertEquals("nickname info", nicknameField.getDescription());

...

해당 test case는 DataHub의 Metadata로 생성된 결과물을 기반으로 UserInfo message의 nickname field의 주석정보가 정상적으로 생성되었는지 체크하는 test case입니다.

그리고 DataHub 팀은 Checkstyle을 사용하고 있기 때문에 만약 DataHub에 기여하는 걸 생각하시는 분이 계신다면 intelliJ에 Check style을 셋업 해서 사용하는 걸 추천합니다. intelliJ에서 Check style을 검증하지 않고 PR을 올리게 되면 Github PR Hook에서 계속 실패가 발생하는 걸 경험하실 수 있습니다.

마치며

이번 오픈소스에 기여하면서 총 두 번의 PR을 진행했었습니다. 첫 번째 PR은 앞서 설명해 드린 nested 타입 message의 주석정보를 수정한 건이었고, 두 번째 PR은 첫 번째 PR의 오류를 수정한 부분이었습니다. 저희 팀에서는 google wrappers 타입을 사용하고 있는데요. 해당 wrappers 타입을 추가해서 테스트를 진행했을 때 에러가 발생하는 현상이였습니다. 이번을 계기로 테스트를 진행할 때 실제 데이터와의 차이도 고려해서 코드를 수정해야겠다고 다짐했습니다.

정말 간단한 코드지만 이 코드를 수정하기 위해 이 프로젝트에서 정해 놓은 규칙을 파악하고 따르는 게 처음 기여를 하면서 어려운 점이었지만, 다른 나라의 개발자들과 커뮤니케이션을 통해 DataHub 프로젝트의 기여자(contributor) 목록에 이름을 올릴 수 있게 되어 즐거운 경험이었습니다. (DataHub Release)

마지막으로 DataHub UI 에서 3중첩 message의 경우 제대로 보이지 않는 이슈가 있습니다. 다음에 시간이 된다면 나머지 이슈도 도전해볼까 합니다. 그리고, DataHub와 관련해서 개선할 과제로 Hive table도 사용하는 사용자들도 있기 때문에 Protobuf 명세와 Hive table 명세를 연결할 수 있는 Linege 기능 적용을 검토해서 Hive table명을 기반으로 탐색해도 Protobuf Message의 풍부한 정보를 함께 살필 수 있도록 고도화 해 나갈 예정입니다.

감사합니다.

--

--