초기 개발 과정 ②
이번 4편에서는 앞선 편들에서 다루지 않았던 GDB의 또 다양한 여러 특성들과 함께, GDB 도입이 가져온 장·단점들, 그리고 문제 해결을 위해 노력했던 부분을 위주로 설명드리고자 합니다. 저희와 비슷한 상황에 놓인 현업 팀이 있다면, 이번 글의 내용이 여러분들의 당면한 문제를 해결해 나가는 데 도움이 되길 바랍니다 :D
먼저 말씀드리고 싶은 부분은 GDB의 데이터 관리 형태입니다. 저는 평소 RDB는 노트, GDB는 A4에 비유하곤 하는데요. 그 이유는 다음과 같습니다.
RDB는 기록해야 하는 내용의 특성에 맞춰 테이블의 스키마를 만들고, 스키마에 맞춰 데이터를 입력합니다. 이는 용도별 노트(예를 들어 영어는 4선 노트, 악보는 5선 노트, 일기는 일기장 노트)를 두고 해당 노트의 형태에 맞추어 기록하는 것과 같습니다. 따라서 어떤 노트인지만 알게 된다면, 내부에 포함되는 내용 또한 어떠한 형태로 들어있는지를 알 수 있게 됩니다.
GDB는 스키마로부터 자유로운 형태를 갖고 있습니다. 이것은 빈 A4용지에 그때그때 더 좋은 형태나 구조를 선택하여 기록해두는 것과 같죠. 그런데 만약 이러한 A4용지들을 별다른 처리 없이 그냥 그대로 모아만 둔 채, 이후에 특정 내용을 다시 찾으려 한다면 어떻게 될까요? 당연하게도 내가 원하는 내용을 빠르게 찾기 어려워지겠죠. 그렇기 때문에 각각의 A4 페이지에 해당 페이지를 설명하는 인덱스 스티커를 붙여 두고 인덱스를 통해 어떤 데이터인지 확인하는 방법을 취하게 됩니다. (너무 쉽죠?!)
그리고 GDB에는 이러한 특성으로부터 생겨나는 다양한 장·단점들이 있습니다. 이어지는 내용에서 저희 팀이 GDB의 특성을 어떻게 활용해 어떤 기능들을 구현했는지 설명드리겠습니다.
장점 : Multi-Label 활용, Relationship의 Property 활용
Multi-Label 활용
GDB의 가장 강력한 기능 중 하나가 바로 이 Multi-Label 기능입니다. 투덥에서는 꾸준한 더빙을 바탕으로 다른 유저들과 좋은 인터랙션을 가져가고 있는 더비(= 투덥 유저를 가리키는 애칭)들을 ‘추천 더비’로 지정해 더 많은 더비들이 그들과 함께 투덥을 사용할 수 있도록 하고 있는데요. 이 부분을 구현함에 있어 만약 RDB를 사용했다면 기존 User 정보를 가진 테이블에 별도의 컬럼을 추가하거나 별도의 테이블을 구성해 표현해야만 했을 것입니다.
반면, GDB를 통해서는 해당 User Node에 추가적인 Label을 붙여주는 것만으로 이를 간단히 구현할 수 있었습니다. 즉, A4 페이지에 기존에 적힌 내용과 인덱스 스티커가 그대로 붙어있는 상태에서 추가로 인덱스 스티커를 더 붙여줌으로써 훨씬 다양하게 사용할 수 있게 된 것이라 볼 수 있죠. RDB였다면 하나의 데이터를 각기 다른 테이블에 저장할 수 없고 여러 개의 데이터를 만들어 연결하는 형태를 취해야만 했던 것을, GDB를 통해 Label만 추가하면 되었기에 매우 간단하면서도 강력한 기능이었습니다.
Relationship의 Property 활용
투덥의 초기 소셜 시스템을 구성하고 사용성 테스트를 진행하던 과정에서 발생했던 이슈 중 하나가 ‘다른 유저를 팔로우 했을 때, 팔로우 하기 전 그 유저의 활동으로 인해 발생한 Message가 내 메시지 함에서 보여요.’라는 것이었습니다. 이는 기존에 설명드렸던 In/Out 메시지 구조에서 Out 메시지의 경우, 발생시킨 유저를 중심으로 만들다 보니 실제 받지 않은 메시지가 내 메시지 함에 나타나는 것이었는데요. 이 부분에 있어 저희는 User 간 FOLLOW 관계 속성에 관계 생성 시간을 기록하여 메시지 구성 시 해당 시간을 기준 값으로 필터링 함으로써 해결할 수 있었습니다.
단점 : 여러 개의 데이터에 대한 일괄 작업, Random Sampling
여러 개의 데이터에 대한 일괄 작업
GDB가 하나의 Node를 중심으로 작업할 때는 매우 빠른 퍼포먼스를 보여주지만, 반대로 여러 개의 Node에 대한 일괄 작업에 있어서만큼은 효율적이지 못할 때가 많습니다. 이 때문에 많은 수의 Node에 대한 일괄 변경 처리라든지 특정 조건을 만족하는 Node 목록 생성 등을 작업함에 있어 상당한 시간을 요구하거나 Timeout을 발생시키기도 하죠. 물론, GDB의 특성을 감안해 이러한 작업들이 GDB에서 수행되는 것을 가능한 피해야겠지만, 데이터 입력 과정에서는 어쩔 수 없이 필요한 순간들이 발생하기도 합니다.
위에서 언급한 ‘추천 더비’가 대표적 예시입니다. 추천 더비 구현에 있어 GDB는 Multi-Label를 비롯해 Read 처리 과정에서 매우 좋은 성능을 보여주었지만 초기 입력 과정에서만큼은 수시로 발생하는 Timeout 때문에 진행에 어려움이 있었습니다.
저희는 문제 해결을 위해 apoc 라이브러리를 활용하기로 했습니다. Neo4j에서 제공하는 apoc 라이브러리는 CQL이 가진 부족함을 충분히 보완해 주고 있었기 때문입니다. 그중에서도 일괄 작업을 지원하는 iterate function을 가장 적극적으로 사용했습니다.
이 함수는 총 3개의 인자를 받는데, 첫 번째는 일괄 작업의 대상 Data에 대한 쿼리, 두 번째는 대상 Data 들에 대한 처리 쿼리, 세 번째는 일괄 작업의 옵션 값입니다. Neo4j가 구동 중인 시스템 스펙에 따라 batchSize만 잘 조절해 주기만 한다면 대부분의 일괄처리는 Timeout을 발생시키는 일 없이 잘 처리되었습니다(저희는 대부분 500 정도로 사용했습니다).
간혹 동시성의 문제가 생겨 에러가 발생하기도 했는데, 이때는 parallel 옵션을 false로 넣어주게 되면 처리 시간은 조금 더 걸리지만 문제없이 잘 동작하는 것을 확인할 수 있었습니다.
Random Sampling
CQL의 경우, 아무 정렬을 지정하지 않고 기본 쿼리를 작성하면 내부적으로 생성한 ID에 따라 정렬된 결과를 받게 되며 Property에 따른 정렬까지는 간단히 지정할 수 있는데요. 투덥 피드에서는 일정 기간 내의 메시지들을 Random하게 보여주는 것이 요구되었기 때문에 Random 정렬이 필요한 상황이었고, 이 부분의 구현이 다소 까다롭게 느껴졌습니다.
처음에는 GDB 쿼리에서 Random 조건을 빼고 전체 목록(혹은 필요한 수보다 많은 목록)을 가져와서 코드 상에서 Random 샘플링을 하는 형태였습니다. 그런데 이는 Return되는 Node의 숫자가 커질수록 Response가 증가하는 구조였기 때문에 기본적으로 쿼리 성능을 크게 떨어뜨렸고, 전송되는 데이터의 양도 커질 수밖에 없어 좋은 방법은 아니라고 판단했습니다. 이에 GDB에서 처리하는 아래의 형태를 생각하게 되었습니다.
CQL 내에서 따로 Random 정렬을 지원하지 않았기 때문에 우선 필요한 목록을 만들어낸 뒤, 해당 목록을 UNWIND 절을 통해 각 요소로 나누었습니다. 이후 WITH 절에서 Random 변수를 생성하여 각각의 Feed 데이터와 Random 변수를 연결했고, Random 변수를 통한 정렬을 통해 랜덤 추출을 구현했습니다.
다른 쿼리들에 비해 성능이 만족스러운 편은 아니었지만 그래도 처음 시도했던 방법에 비해서는 성능 및 데이터 측면에서 모두 더 낫다고 판단되어 최종적으로 이 형태로 GDB 내 Random 샘플링을 구현했습니다.
GDB 기반의 소셜 시스템을 만들며 기존의 RDB 기반 알림 시스템에서의 성능 저하 문제와 비효율적 데이터 발생 문제를 해결했고, 이전보다 훨씬 더 강화된 소셜 기능 업데이트에 성공할 수 있었습니다. 또한 이번 작업을 통해 명확한 문제 설정과 함께 이를 해결할 수 있는 적절한 기술 스택의 선택, 그리고 해당 기술의 특징과 장단점을 효과적으로 살린 동작 설계가 중요하다는 것도 다시 한번 느낄 수 있었습니다.
물론, 새로운 기술을 사용해 기능을 구축하는 과정은 간단하지 않았고, 업데이트한 뒤에도 예상치 못한 이슈들로 인해 또 한 번 고생하기도 했습니다. 하지만 이러한 시행착오를 계기로 서비스 내 다른 부분의 문제들을 GDB를 통해 해결하기도 했기에, 이번 경험은 저희 팀 전체에 매우 값진 시간이었다고 생각합니다.
1~4편을 통해 보여드린 저희 팀의 노력이 현재 GDB에 관심이 있거나 도입을 생각하고 계신 분들 또는 저희와 같이 알림이나 소셜 기능 구현에 있어 고민하시는 분들께 조금이나마 도움이 되셨길 바랍니다.
(이어지는 편에서는 업데이트 이후에 발생한 이슈들에 대해 다룰 예정입니다.)