Software Engineering Note

shell script 부분 병렬화 사례 본문

일하며 개발하며

shell script 부분 병렬화 사례

devmoons 2020. 3. 1. 18:41

파일 다운로드 > 압축 해제 > hdfs 업로드 > hdfs to storage 업로드

 

이런 플로우로 데이터를 처리할 일이 있었다. (n = 0 ... ?)

 

처음 구현한 플로우

shell script로 구현을 하고 돌려보는데 속도가 너무 느렸다.

어디가 병목일까? 보니 압축 해제하는 부분이 특히 느렸다.

그래서 그 부분부터 병렬화 하기로 했다. 

병렬화는 script 파일을 나누고 백그라운드(&) 로 돌리면 된다.

 

ex) hdfs_uploader.sh ... &

 

병렬화를 이용한 첫 번째 개선

여기서 다시 아래와 같은 문제가 발생했다.

1) unzip 하는 작업이 많아지면 cpu를 너무 많이 차지한다.

2) storage upload 작업이 너무 빈번해지면 문제가 된다.

 

이제 다시 한 번 정리를 해보자.

1) 파일 다운로드는 빠르다. 문제없는 부분

2) unzip & hdfs upload 부분은 느리다. 병렬화가 필요하다. 하지만 너무 많은 병렬화는 cpu 를 무리하게 사용한다.

- 병렬화는 그대로 간다. 여전히 백그라운드로 실행 시키면 된다.

- 단, 일정 갯수만 실행되도록 컨트롤한다. pgrep -f /path/to/hdfs_uploader.sh | wc -l  와 같은 커맨드로 실행되는 수를 확인할 수 있다.

3) storage upload 부분은 병렬화 시키기 부담스럽다. 몇 개씩 묶어서 한 번에 처리하면 좋겠다.

- storage uploader 는 하나만 띄운다. 스크립트 시작 부분에 storage uplader 를 실행시키고 PID를 받아놓는다. (pid=$($!)

   나중에 kill 해야 하므로 필요하다.

- 그룹 단위로 처리하기 위해 hdfs upload 시 그룹 아이디를 부여한다. 이제 "그룹" 이라는 개념이 들어가야 한다.

   각 그룹에 속한 task 는 동일한 hdfs path 에 upload 한다.

- storage uploader 는 하나의 그룹에 속한 task 들이 모두 끝나길 기다렸다가 upload 를 수행한다. 

  - 하나의 그룹이 끝났다는 것을 알기 위해서 hdfs uploader 는 파일을 남기도록 만든다.

  - 각각의 hdfs uploader task 는 running_${groupId}_${taskId} 파일을 만들어 실행중임을 알리고, 작업이 완료되면

    done_${groupId}_${taskId} 파일을 만든다.

  - 그룹의 마지막 녀석은 작업이 끝나면 success_${groupId} 파일을 만든다.

  - storage uploader 는 running 파일이 없고, success 파일이 존재하면 하나의 그룹이 작업을 완료했다고 판단하고 

    storage upload 를 실행한다. 그리고 다음 그룹을 기다린다.

 

정리하면 이렇게 된다.

병렬화를 이용한 두 번째 개선

문제를 하나씩 해결하다보니 고려해야 할 것들이 많아졌다.

특히 두 번째 개선은 많이 복잡해졌다. 제한 사항이 많으면 시스템이 복잡해지기 마련이다.

 

이렇게 구현을 하고 문제점을 찾고 다시 개선을 하는 것은 개발자들이 항상 겪는 일이다.

난관에 부딪칠 때마다 머리가 아프지만 그 속에 또 즐거움이 있다.

특히 번뜩이는 아이디어가 떠오를 때는 궁극의 즐거움을 맛볼 수 있다.

 

이 사례에서는 병렬화 후 특정 그룹으로 묶어서 처리해야하는 부분이 난관이었고,

진행 상태를 파일로 남기는 방법이 해결책이 되었다. 초기 구현에 비해 코드는 꽤 복잡해졌다.

 

문제)

다운로드 받는 파일의 수가 각 그룹에 할당되는 task 개수에 딱 맞지 않기 때문에 이런 경우도 별도로 처리해줘야 한다.

- ex) 각 그룹이 처리하는 파일의 개수: 10, 실제 업로드해야 하는 파일의 수: 93

너무 복잡해져서 여기에는 적지 않았는데 어떻게 하면 될지 각자 생각해보자 : )