step6_excel_builder.py에서 사용하는 openpyxl 라이브러리는 엑셀 파일(.xlsx)의 내부 XML 구조를 순차적으로 직렬화하고 압축해야 하는 특성과 파이썬의 GIL(Global Interpreter Lock) 제약 때문에 코드 내부적으로 스레드를 나눠 병렬화하는 것이 불가능합니다. 즉, 아무리 코어를 많이 할당해도 팟 내의 프로세스 1개는 무조건 코어 1개만 사용(100% 병목)하게 됩니다.
이를 해결하고 대용량 COMPUTE와 STORAGE 분석을 동시에 수행하여 가동 시간을 절반 이하로 단축하는 두 가지 병렬화 아키텍처 패턴(쿠버네티스 레벨 / 파이썬 런타임 레벨)을 알려드립니다. 환경에 맞는 방식을 선택하여 적용하시면 됩니다.
기존에 배포하신 단일 CronJob 또는 수동 Job 매니페스트의 args 영역만 수정하여, 리눅스 백그라운드 연산자(&)와 프로세스 대기 명령(wait)을 조합하는 방식입니다. 쿠버네티스 오브젝트를 여러 개 만들 필요가 없어 관리 공수가 가장 적습니다.
args 수정 명세이 방식을 사용할 때는 두 프로세스가 각각 코어 1개씩(총 2개 이상) 온전히 선점할 수 있도록 resources.limits.cpu를 최소 4 이상으로 넉넉하게 확장해 주어야 병렬 가속 효과가 나타납니다.
# ─── 🏃 [수정 전 기본 구조 (순차 실행 - 병목 발생)] ───
# command: ["/bin/sh", "-c"]
# args:
# - |
# python3 step6_excel_builder.py --cluster COMPUTE && \
# python3 step6_excel_builder.py --cluster STORAGE
# ─── 🚀 [수정 후 고도화 구조 (백그라운드 병렬 풀가동)] ───
command: ["/bin/sh", "-c"]
args:
- |
echo "⏳ [병렬 기동] COMPUTE 및 STORAGE 마스터 엑셀 빌더 동시 슈팅..."
# 1. COMPUTE 백그라운드 기동 (&) 및 프로세스 ID(PID) 저장
python3 step6_excel_builder.py --cluster COMPUTE &
pid_compute=$!
# 2. STORAGE 백그라운드 기동 (&) 및 프로세스 ID(PID) 저장
python3 step6_excel_builder.py --cluster STORAGE &
pid_storage=$!
echo "📡 두 클러스터 배치가 병렬 백그라운드 프로세스(PID: $pid_compute, $pid_storage)로 쪼개졌습니다."
print("커널 로그를 모니터링하며 두 작업이 모두 마감될 때까지 대기합니다...")
# 3. 🛡️ 두 프로세스가 모두 끝날 때까지 팟이 종료되지 않도록 대기 가드레일 주입
wait $pid_compute $pid_storage
echo "🏁 === [병렬 컴파일 완료] 두 클러스터의 리포트가 오염 없이 AIStor로 동시 배포되었습니다. ==="
# 🚨 중요: 병렬 연산 버스트를 위한 CPU 가드레일 상향 조정
resources:
requests:
cpu: "4" # 병렬 기동 시 코어 경합 예방
memory: "16Gi"
limits:
cpu: "6" # openpyxl 압축 피크 버스트 대응 공간
memory: "32Gi"
장애 복구(Ad-hoc)를 하거나 두 클러스터의 물리적 리소스 점유(CPU/메모리)를 완벽하게 격리하여 서로 간섭받지 않게 하고 싶을 때 사용하는 전통적인 분할 정복 방식입니다. yaml 파일을 분리하여 동시에 클러스터에 던집니다.
finops-step6-parallel.yamlapiVersion: batch/v1
kind: Job
metadata:
name: finops-step6-compute
namespace: devops-finops
spec:
backoffLimit: 0
template:
spec:
restartPolicy: Never
containers:
- name: builder
image: harbor.internal.zone/devops/finops-pipeline:v2
command: ["python3", "step6_excel_builder.py", "--cluster", "COMPUTE"]
resources:
requests: { cpu: "2", memory: "8Gi" }
limits: { cpu: "4", memory: "16Gi" }
envFrom:
- configMapRef: { name: finops-minio-config }
---
apiVersion: batch/v1
kind: Job
metadata:
name: finops-step6-storage
namespace: devops-finops
spec:
backoffLimit: 0
template:
spec:
restartPolicy: Never
containers:
- name: builder
image: harbor.internal.zone/devops/finops-pipeline:v2
command: ["python3", "step6_excel_builder.py", "--cluster", "STORAGE"]
resources:
requests: { cpu: "2", memory: "8Gi" }
limits: { cpu: "4", memory: "16Gi" }
envFrom:
- configMapRef: { name: finops-minio-config }
kubectl apply -f finops-step6-parallel.yamlCOMPUTE 파드와 STORAGE 파드를 각각 꽂아버리기 때문에, 단일 노드 내부의 CPU 경합 레이텐시마저 원천 차단됩니다.만약 명령어를 무조건 단일 파이썬 명령어(python3 run_all.py) 형태로 유지해야 하는 제약이 있다면, concurrent.futures 패키지를 사용해 2개의 서브 프로세스를 강제로 포크(Fork)시키는 파이썬 오케스트레이터를 작성하여 파이프라인 전면부에 배치하면 됩니다.
step6_orchestrator.py (일종의 실행기 래퍼)import subprocess
import os
from concurrent.futures import ProcessPoolExecutor
def run_script(cluster_name):
print(f"🚀 [Subprocess Start] {cluster_name} Excel Compilation initiated...")
# 메인 바이너리를 서브프로세스로 분리 기동 (GIL 우회 및 별도 코어 선점)
cmd = ["python3", "step6_excel_builder.py", "--cluster", cluster_name]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
return f"❌ {cluster_name} Failed:\n{result.stderr}"
return f"✅ {cluster_name} Completed Successfully."
def main():
clusters = ["COMPUTE", "STORAGE"]
print(f"⚡ 파이썬 멀티프로세싱 오케스트레이터 기동 -> 대상: {clusters}")
# 프로세스 풀을 열어 독립된 코어에 프로세스 배정
with ProcessPoolExecutor(max_workers=2) as executor:
results = executor.map(run_script, clusters)
for res in results:
print(res)
if __name__ == "__main__":
main()
CronJob 매니페스트에 이식하는 것이 가장 우아합니다.