작성일:

리눅스 환경에서 대량의 데이터나 파일을 처리하다 보면, 단일 코어만 사용하는 쉘 스크립트의 성능 한계에 부딪히게 됩니다. xargs를 통해 어느 정도 병렬 처리가 가능하지만, 더 복잡하고 강력한 기능을 제공하는 도구가 바로 GNU Parallel입니다.

🚀 parallel 개요

GNU Parallel은 로컬 또는 원격 컴퓨터에서 작업을 병렬로 실행하기 위한 쉘 도구입니다. 덴마크의 Ole Tange가 Perl로 개발했으며, 표준 입력(stdin)이나 파일로부터 인자를 받아 명령어를 병렬로 실행합니다.

가장 큰 특징은 xargsfind -exec와 같은 기존 도구들의 사용성을 유지하면서도, 출력 제어, 작업 슬롯 관리, 원격 실행 등 고급 기능을 제공한다는 점입니다.

⚙️ 주요 기능 및 내부 구현

1. 출력 그룹화 (Output Grouping)

병렬 처리 시 가장 큰 골칫거리 중 하나는 여러 프로세스의 출력이 뒤섞이는 것입니다.

  • xargs -P를 사용하면 여러 줄의 출력이 섞여서 알아보기 힘든 경우가 많습니다.
  • Parallel은 각 작업의 출력을 임시 파일이나 메모리에 버퍼링했다가, 작업이 완료되는 순간 한꺼번에 출력합니다. 따라서 출력이 섞이지 않고 깔끔하게 보장됩니다.

2. 지능적인 작업 슬롯 관리

  • parallel은 기본적으로 시스템의 CPU 코어 수를 감지하여 최적의 병렬 작업 수(Job slot)를 결정합니다.
  • --load 옵션을 통해 시스템 부하(Load Average)에 따라 새로운 작업을 시작할지 대기할지 동적으로 결정할 수도 있습니다.

3. 강력한 인자 치환 (Replacement Strings)

xargs보다 훨씬 유연한 인자 치환 기능을 제공합니다.

  • {}: 전체 인자 (기본값)
  • {.}: 확장자를 제외한 파일명 (file.txt -> file)
  • {/}: 경로를 제외한 파일명 (Basename, dir/file.txt -> file.txt)
  • {//}: 파일명을 제외한 경로 (Dirname, dir/file.txt -> dir)
  • {/.}: 경로와 확장자를 모두 제외한 순수 파일명 (dir/file.txt -> file)
  • {#}: 작업의 시퀀스 번호 (1부터 시작)
  • {%}: 작업 슬롯 번호 (1 ~ 병렬 작업 수)
  • {n}: n번째 입력 소스의 인자 (예: {1}, {2} - ::: 또는 :::: 사용 시)

4. Perl 기반 구현

GNU Parallel은 단일 Perl 스크립트로 구현되어 있습니다. 이는 별도의 컴파일 과정 없이 Perl이 설치된 대부분의 유닉스/리눅스 시스템에서 즉시 사용할 수 있음을 의미합니다.

🎛️ 주요 옵션 (Major Options)

parallel은 매우 방대한 옵션을 제공하지만, 자주 사용되는 핵심 옵션들은 다음과 같습니다.

  • --jobs N / -j N: 동시에 실행할 작업(Job)의 개수를 지정합니다. (기본값: CPU 코어 수)
    • +N: 코어 수 + N개
    • -N: 코어 수 - N개
    • 50%: 코어 수의 50%
  • --load N / -l N: 시스템 부하(Load Average)가 N 이상이면 새 작업을 시작하지 않고 대기합니다.
  • --delay N: 각 작업 시작 사이에 N초의 지연 시간을 둡니다. (API 호출 제한 등에 유용)
  • --timeout N: 작업이 N초 이상 걸리면 강제로 종료합니다.
  • --joblog FILE: 작업 실행 기록(Exit code, 실행 시간 등)을 파일에 저장합니다.
  • --resume: --joblog를 사용하여 중단된 작업 지점부터 다시 시작합니다.
  • --bar: 진행 상황을 프로그레스 바 형태로 보여줍니다.
  • --dry-run: 명령어를 실제로 실행하지 않고, 실행될 명령어 구문을 출력합니다.
  • --keep-order / -k: 작업 완료 순서와 상관없이, 입력 순서대로 출력을 정렬합니다.
  • --pipe: 표준 입력을 덩어리(Block)로 나누어 여러 작업으로 분산 처리합니다. (대용량 파일 처리에 필수)
  • --block N: --pipe 사용 시 나눌 블록의 크기를 지정합니다. (예: 10M, 1G)
  • --tag: 각 출력 라인의 앞에 인자를 태그로 붙여서 어떤 작업의 결과인지 식별하게 해줍니다.
  • --colsep regexp: 입력 라인을 정규표현식 구분자로 나누어 {1}, {2}, … 로 사용할 수 있게 합니다. (예: --colsep ',' - CSV 처리)

🆚 xargs와의 비교

특징 GNU Parallel xargs (-P 옵션)
출력 순서 작업 단위로 그룹화되어 섞이지 않음 라인 단위로 섞일 수 있음 (Interleaved)
사용 편의성 직관적이고 다양한 치환 문자 제공 ({.}, {/} 등) 비교적 제한적 (-I {} 등)
원격 실행 SSH를 통한 손쉬운 분산 처리 지원 (-S) 기본 지원 없음 (별도 구현 필요)
입력 처리 빈 칸, 따옴표 등 특수 문자 처리가 강력함 특수 문자 처리가 까다로울 수 있음 (-0 필요)
성능 Perl 오버헤드가 약간 있음 (작업당 수 ms) C로 구현되어 매우 가벼움

Note: 수백만 개의 아주 짧은 작업(ms 단위)을 실행할 때는 xargs가 더 빠를 수 있지만, 대부분의 실무 작업(초 단위 이상)에서는 parallel의 오버헤드는 무시할 만하며 편의성이 압도적입니다.

💻 샘플 명령어 10선

1. 기본 사용법: 파일 압축하기 (Basic)

현재 디렉토리의 모든 *.log 파일을 gzip으로 병렬 압축합니다.

ls *.log | parallel gzip
  • 기본 동작: 파이프(|)로 전달된 표준 입력의 각 라인을 인자로 받아 gzip 명령어를 병렬 실행합니다.
  • 별도 옵션이 없으면 CPU 코어 수만큼의 작업이 동시에 실행됩니다.

2. 인자 치환 활용: 이미지 변환 (Image Processing)

images 폴더의 모든 .jpg 파일을 .png로 변환하여 converted 폴더에 저장합니다.

ls images/*.jpg | parallel convert {} converted/{/.}.png
  • {}: 입력 파일 전체 경로 (images/photo.jpg)
  • {/.}: 경로와 확장자를 제외한 파일명 (photo)

3. 여러 인자 조합하기 (Cartesian Product)

여러 리스트의 모든 조합을 실행할 때 매우 유용합니다. :::, :::: 구분자를 사용합니다.

parallel echo "Color: {1}, Size: {2}" ::: Red Blue Green ::: S M L
  • ::: : 커맨드 라인 인자로 리스트를 전달할 때 사용합니다.
  • {1}, {2}: 첫 번째, 두 번째 리스트의 인자를 각각 참조합니다.

파일로부터 인자를 읽어올 때는 ::::를 사용합니다.

# userlist.txt와 hostlist.txt의 모든 조합 실행
parallel echo "User: {1}, Host: {2}" :::: userlist.txt :::: hostlist.txt
  • :::: : 파일의 내용을 인자 리스트로 사용할 때 사용합니다. 각 파일의 라인들이 조합됩니다.

4. 원격 서버 분산 처리 (Remote Execution)

로컬의 파일을 여러 서버로 전송하지 않고, 명령어만 분산 실행합니다. (SSH 설정 필요)

# server1, server2에서 hostname 실행
parallel --nonall -S server1,server2 hostname
  • --nonall: 인자를 전달하지 않고, 모든 서버에서 명령어를 한 번씩만 실행합니다.
  • -S server1,server2: 작업을 실행할 원격 서버 목록을 지정합니다. (콤마로 구분)

5. 대량 파일 다운로드 (Multiple URL Download)

urls.txt에 있는 수많은 URL을 4개의 작업으로 병렬 다운로드합니다.

cat urls.txt | parallel -j 4 wget {}
  • -j 4: 동시에 실행할 작업 수를 4개로 고정합니다. (서버 부하 조절 시 유용)

6. 대규모 문자열 검색 (Large Scale Grep)

수만 개의 파일에서 특정 문자열을 검색할 때 grep 하나로는 느릴 수 있습니다.

find . -name "*.txt" | parallel grep "pattern" {}
  • find와 조합하여 재귀적으로 파일을 찾고, grep을 병렬로 수행하여 검색 속도를 높입니다.

7. 동영상 변환 (Video Transcoding)

ffmpeg를 사용하여 여러 동영상 파일을 동시에 변환합니다. CPU를 많이 쓰는 작업에 효과적입니다.

ls *.mp4 | parallel ffmpeg -i {} -c:v libx264 output/{.}.mkv
  • {.}: 입력 파일명에서 확장자를 제거합니다. (video.mp4 -> video)
  • CPU 집약적인 작업에서 parallel의 진가가 발휘됩니다.

8. 데이터베이스 쿼리 병렬 실행 (Database Processing)

여러 테이블이나 데이터베이스에 대해 SQL 쿼리를 병렬로 수행합니다.

cat tables.txt | parallel "mysql -e 'CHECK TABLE {};'"
  • 명령어 전체를 따옴표(")로 감싸서 복잡한 구문을 하나의 명령어로 전달합니다.
  • {}가 SQL 쿼리 내부의 테이블명으로 치환됩니다.

9. 진행 상황 및 로그 기록 (Progress Bar & Job Log)

오래 걸리는 작업의 진행 상황을 확인하고, 결과를 로그로 남깁니다.

seq 100 | parallel --bar --joblog my_jobs.log sleep {}
  • --bar: 터미널 하단에 진행률(Progress Bar)을 표시합니다.
  • --joblog my_jobs.log: 각 작업의 실행 결과(성공/실패, 소요 시간 등)를 로그 파일에 기록합니다.

10. 실패 시 재시도 (Fail/Retry Handling)

네트워크 불안정 등으로 작업이 실패할 경우 자동으로 재시도하게 할 수 있습니다.

# 실패 시 3번까지 재시도
cat urls.txt | parallel --retries 3 wget {}
  • --retries 3: 작업이 실패(Exit code != 0)할 경우, 최대 3번까지 재시도합니다.

11. CSV/TSV 데이터 처리 (Column Separation)

CSV나 TSV 같은 구분자가 있는 데이터를 처리할 때 --colsep을 사용하면 각 컬럼을 개별 인자로 사용할 수 있습니다.

# data.csv: name,age,email
# Alice,30,alice@example.com
cat data.csv | parallel --colsep ',' echo "Name: {1}, Age: {2}, Email: {3}"
  • --colsep ',': 쉼표(,)를 구분자로 사용하여 입력 라인을 분리합니다.
  • 분리된 각 컬럼은 {1}, {2}, {3}… 순서대로 접근할 수 있습니다. 탭 구분자 파일(TSV)의 경우 --colsep '\t'를 사용합니다.

📝 결론

GNU Parallel은 단순한 병렬 실행 도구를 넘어, 복잡한 배치 작업을 효율적으로 처리할 수 있게 해주는 “Swiss Army Knife”와 같습니다. 특히 데이터 처리 파이프라인이나 시스템 관리 스크립트에서 xargs의 한계를 느꼈다면, 꼭 도입해 보시기를 추천합니다.

댓글남기기