• 프로젝트를 마치며 #1. 데이터 검증

    2022. 12. 26. 16:57

    by. 위지원

    벌써 2022년도의 마지막 주이고 나는 이번주를 끝으로 프로젝트에서 철수한다.

    서론


    이번에 데이터 이행 프로젝트를 진행하게 되었다.
    프로젝트를 진행하면서 꽤나 많은 기술을 접할 수 있었는데, 프로젝트가 종료되어가는 이 시점에서 각 기술들을 정리하면서 머릿속에 남겨보고자 한다. 아, 당연히 기술을 플젝에서 어떻게 썼는지에 대한 자세한 내용은 기술하지 않을 것이다.

    나는 이번 프로젝트에서 초반에는 데이터 이행을 도와주다가 후반 부에는 검증을 도맡아 했다. 검증 해야하는 테이블의 양이 약 4000개로 매우 많았고, 테이블의 크기가 커 어려움이 많았다.

    무엇을 배웠는가?


    우선 프로젝트에서 접한 것들을 나열하자면
    1. Linux Bash shell
    2. Sqoop
    3. Impala
    4. Kudu
    5. Hive
    6. Hadoop yarn
    7. Nifi
    8. Oracle
    9. Kafka
    10. Log stash
    11. Ranger

    이렇게 나열하고나니 정말 많은 것을 할 수 있었던 프로젝트같다. 직접적으로 다뤄 본 것도 있고 그렇지 않은 것도 존재하지만 대부분은 직접 다뤄본 것들이다. 그렇지 않은 것들도 어깨너머로 보았다..ㅎㅎ..

    아무튼 오늘은 툴이나 언어 등 기술적인 것을 작성하기에 앞서 데이터 검증에 대해서 이야기하고자 한다.


    데이터 검증에 대하여


    나는 이번에 데이터 검증 파트를 맡아 진행했다. 나름 데이터 관련 프로젝트를 여러 번 진행했다고 생각했는데 데이터 검증은 처음이었다. 그리고 얼마나 신경쓸게 많은 작업인지도 알게 되었다. 물론 내가 처음이니까 이렇게 생각하는 것 일 수도 있다.. 사실 엄첨 쉽고 간단한 문제일지도 모른다. 적어도 나는 이랬다는 것이다.

    검증의 목표는 데이터 이행이 무사히 완료되었는지를 체크하는 것 이었다. 데이터 이행은 A->B로 이루어졌는데, A와 B는 다른 DB라서 쿼리 문법도 달랐다. 그래서 이에서 오는 혼란도 꽤 컸다. Impala와 oracle쿼리를 번갈아가면서 사용해야 했다.

    필요한 것은 다음과 같았다.
    1. 컬럼별로 값의 특정 통곗값(sum, max, min등)을 계산하는 검증 쿼리
    2. 각 테이블마다 자동으로 1번의 쿼리가 실행 될 수 있도록 하는 Bash Shell Script 1번 같은 경우는 상사 분들께서 큰 틀을 짜주셨기때문에 크게 손을 대지는 않았고, 필요에 맞게 쿼리를 조금씩만 수정한게 다였다. 2번이 내가 주로 맡은 역할이었다. 검증이 필요한 테이블의 개수가 너무 많았기 때문에 일일히 수동으로 돌릴 수가 없었다. 그러므로 자동화와 한 번에 여러개를 해결할 수 있도록 멀티 프로세싱이 필요했다.

    쉘을 한번도 짜본 적이 없어서 처음엔 난해했지만, 팀원이 주신 샘플과 그리고 갓글링의 도움으로 무사히 완성은 할 수 있었다.

    리눅스 검증 쉘 작성


    쉘의 실행 순서는 다음과 같았다.

    1. 검증쿼리를 만들어내는 generation 쿼리와 검증 대상을 찾아내는 쿼리를 텍스트 파일로 저장한다.
    2. 검증 대상이 될 테이블을 리스트업하는 쿼리를 실행한다.
    3. 2번의 결과로 나온 검증 대상 테이블 목록을 loop문을 이용하여 테이블별로 생성된 검증 쿼리를 실행하여 검증을 진행한다.
    4. 각 데이터 베이스마다 접근할 수 있는 툴이 달랐다. 소스테이블은 sqoop을 이용하였고, 타겟 테이블은 Impala를 이용하여 쿼리를 날렸다.
    5. 검증 결과를 한 데이터 베이스(테이블은 달랐다)에 저장한 뒤 join을통해 두 결과가 같은지 비교한다. Sqoop, impala shell 사용법은 샘플도 있었고, 인터넷에도 많기 때문에 코드를 짜는 것은 굉장히 간단했다. 그러나 역시 예상치 못한 문제들이 터졌었다.

    가장 골치를 앓게했던 문제는 바로 컬럼의 개수였다. 컬럼별로 통곗값을 내다보니 컬럼 갯수가 많은 테이블들은 검증 결과 컬럼이 많게는 천 단위로 생성되었다. 그렇다보니 generation 된 쿼리의 길이가 무척이나 길었다. 리눅스에서 돌리다보니 parameter too long과 같은 에러가 발생했다. Impala shell같은 경우는 쿼리 파일로 전달하면 되었지만 sqoop은 불가능했다. Sqoop같은 경우는 파일을 전달하면 파일을 읽어서 내용을 파라미터로 전달하는 식이었기 때문이다.

    그래서 두 번의 과정을 거쳤는데
    첫번째로, 선임분의 도움으로 컬럼을 50개 단위로 잘라서 진행했다. 이렇게 하니 too long의 문제를 해결할 수 있었다.
    그러나 또 다른 문제를 마주했다. 메모리 문제였다. 컬럼마다 통곗값을 내려니, row 수가 너무 많은 경우에 쿼리가 결국 터져버렸다.
    그래서 두번쨰로, 윈도우 함수를 decode를 이용해 튜닝하였다. 이는 팀장님의 도움이 있었는데 윈도우 함수는 메모리를 엄첨 잡아먹기 떄문에 되도록 사용하면 안된다고 하셨다.

    이렇게 보니 도움을 참 많이 받을 수 있는 귀한 플젝이었다는게 세삼 느껴진다 ㅋㅋㅋ..


    멀티프로세싱으로 짜기


    나는 멀티 프로세스로 짜기 위해서 프로세스의 개수를 파라미터로 받았다. 리눅스 쉘은 스레드의 개념은 없고 프로세스의 개념만 있었다.

    처음에는 프로세스 개수크기를 가진 배열을 만들어서 배열에 테이블을 담고, 해당 배열에 있는 테이블들을 검증하여 모두 비우고 다시 담는 방식으로 진행했다.

    그러나, 이는 만약에 한 테이블이 용량이 매우 커서 오래 걸릴 경우 배열이 비워지는 데에 오래걸리기 때문에 전체 프로세스에 영향을 주었다. 그래서, 팀장님의 조언으로 현재 실행중인 프로세스의 개수를 체크하여 진행하는 방식으로 바꾸었다.

    아래와 같은 명령어를 이용하여 현재 실행 중인 job의 개수를 체크하고 job개수가 지정한 프로세스 수와 같아질 때 까지 프로세스를 늘리는 방식을 택했다.

    코드는 아래 블로그를 참고했다.

    bash 에서의 멀티 쓰레드 구현 - 내일은 치킨집 CEO

    bash 는 단순히 순차 실행이 되는 구조이기 때문에 일반적으로 싱글 쓰레드로 볼 수 있다 ‘ㅅ’a 하지만 실행속도 향상을 위해 다중실행하는것이 유리 하다면 그렇게 만들면 되겠다 ‘ㅅ’a 리

    www.enteroa.com

    J_count=Jobs -l | wc | awk ‘{print $1}’

    If [[ $j_count -ge $num_process ]] ; then
    Until [[ $j_count -lt $num_process
    Do
    J_count=`jobs -l | wc | awk ‘{print $1}’`
    Sleep 0.1
    Done
    Fi


    검증을 진행하면서 느낀 것들


    검증하는 방식은 단순했다. 숫자형일 경우 최댓값, 최솟값, 문자형인 경우에는 문자의 길이 등과 같이 직관적인 값을 검증값으로 측정했다. 팀장님 말씀으로는 모든 컬럼 값을 concat해서길이를 재는 경우도 있다고 한다. 검증하는 방식은 사이트마다 다를 것 같다.

    검증에서 가장 중요하면서도 기본인 것은 row count였다. 데이터가 이행이 잘 되었는지 안되었는지를 직관적으로 볼 수 있는 기본 지표였다.

    이행이 잘 안 된 경우에는 여러가지 이유가 있었다.
    1. 소스와 타겟 테이블간의 파티션 또는 컬럼 등 스키마의 불일치
    2. Sqoop,impala,nifi 자체의 오류(nifi같은경우는 버그도 많았다. 갑자기 flowfile이 생기거나 없어지는 등..)
    3. 그 외..?

    그외에는 초반에 검증을 처음하다보니 실수가 많았다. 실수를 통해 배운 것은 다음과 같다.

    1. 데이터값에 어떠한 변화도 주지말 것(round 와같은 함수를 이용), 설령 양쪽 다 동일하게 준다고 해도 그러면 안된다.
    2. Sqoop freeform query의 $conditions위치는 꼭 끝이 아니어도 된다.
    3. 윈도우 함수는 실행 시간 및 메모리 사용률이 굉장히 크므로 되도록 사용을 지양해야 한다.
    4. 각 DB별 함수의 정확한 계산법이나 결과를 반드시 숙지하고 사용해야 한다.

    1번은 내가 한 실수인데 oracle과 impala의 쿼리 결과(sum과 같은 함수)가 소숫점 표현이 살짝 달랐다. 나는 이를 casting, round등으로 맞추려했었다. 그래선 안됐다. 항상 자연 그대로의 상태(?)를 보존해야 한다.


    2번 같은 경우는 sqoop에서 freeform query를 사용할 때 쿼리문 끝에 $CONDITIONS를 추가하라는 룰이 있다.

    Significance of $conditions in Sqoop

    What is the significance of $conditions clause in sqoop import command? select col1, col2 from test_table where \$CONDITIONS

    stackoverflow.com

    “끝에”라는 말 때문에 무조건 쿼리 가장 끝에 붙였었다.근데 굳이 안그래도 되었다. 그냥 Where 절에만 위치하면 되었다. 이것도 팀장님이 발견하셨다..하하..

    내가 날린 freeform query는 서브쿼리를 가지고 있었고, 서브쿼리에서 출력된 결과를 토대로 결과를 한 번 더 생성해내는 형태였다.
    $conditions는 후에 보면 (1=0) (0=0) 으로 변환되어 쿼리가 날아간다. 그렇다면 … 서브쿼리 결과에 따라 (1=0)이 여러번 실행된다. 때문에 쿼리 속도가 매우 느렸던 것이다. 그래서 $conditions를 서브쿼리의 where 절로 옮겼고 결과는 초초초 단축된 쿼리 실행속도였다.

    5번은 문자열 길이에서 겪은 일이었다. Oracle에서의 문자열 길이는 말그대로 ‘길이’ 였고 impala에서의 문자열 길이는 byte의 크기였다. 그래서 oracle에서lengthb함수를 사용하여야 했다.ㅎㅎ 이처럼 해당 함수의 정확한 인풋, 처리내용, 아웃풋을 어렴풋이 알고 진행하면 큰 시간낭비를 불러일으킬 수 있다(내가 그랬다..)

    검증 결과는 어떻게 해석할까?


    그렇다면 검증 결과가 서로 다른 경우엔 추가로 어떤 일을 해야할까? 이게 제일 어려웠다. 진짜 어려웠다. 처음에 팀장님과 책임님이 길잡이를 해주셨지만 이해하지 못하고 계속 갈피를 잡지 못 했다. 그러다가 이해를 하게되었는데 쉽게 말하면 분할 정복 방식으로 풀이하는 것이었다.


    데이터는 보통 날짜와 함께 저장되었는데, 이를 이용할 수 있었다. 날짜를 큰 범주에서 작은 범주로(년 월 일 시간) 점점 좁혀가면서 차이가 나는 영역을 찾으면 됐다.

    만일 차이가 난다면 해당 영역의 데이터를 임시 테이블로 재적재 한 뒤 이전에 이행한 데이터와 키 값을 기준으로 outer join을 한다. 이후 join되지 않은(일치하지 않는다는 뜻이니까) 데이터들이 어떤 데이터인지 확인 해 볼 수 있다.

    결론


    결론적으로 보면 굉장히 간단한 과정이었던 것 같다. 내가 모자라서인지.. 이를 터득하기까지 참 오래걸렸던 것 같다. 그래도 이 경험 덕분에 다음에 데이터 검증을 할 떄는 훨씬 더 빠르게 접근할 수 있을 것 같다.

    대화의 장 💬