2025.06.24 본_캠프 88일차

민동·2025년 6월 24일
1

본캠프

목록 보기
68/74

Pandas/Requests 코드 성능 저하 원인 분석 및 최적화

1. 문제점

pandasrequests를 사용한 데이터 수집 및 처리 스크립트에서 18분 이상 실행이 멈추는 등 심각한 성능 저하 현상이 발생했다. 분석 결과, 원인은 ①네트워크 요청의 timeout 부재, ②반복문 내에서의 비효율적인 DataFrame 직접 수정으로 파악되었다.

2. 기존 코드와 문제점 분석

2.1. 원본 코드 (Before)

# 원본 문제 코드
# ... (설정 부분 생략) ...

for idx, row in result_df.iterrows():
    urls = row["사고 관련 url모음"]
    field_values = { "수위": [], "수온": [], "EC": [] }

    for url in urls:
        success = False
        for attempt in range(3):
            try:
                # 문제 1: timeout이 없음
                res = requests.get(url) 
                # ... (데이터 처리) ...
                success = True
                break
            except Exception as e:
                # ... (에러 처리) ...
    # ... (실패 및 길이 보정 처리) ...

    # 문제 2: 루프 내에서 DataFrame을 직접, 반복적으로 수정
    for label in field_values:
        for i, month in enumerate(month_names):
            result_df.at[idx, f"{month} {label}"] = field_values[label][i]
        # ... (통계 계산 및 저장) ...

# ... (결과 저장 및 출력) ...

2.2. 문제점 상세 분석

  • ① 네트워크 요청의 timeout 부재

    • 현상: requests.get() 함수는 기본적으로 서버의 응답을 무기한 대기한다. 요청 URL 중 하나라도 응답 불가능한 상태일 경우, 해당 요청에서 전체 프로세스가 멈추는 '행(Hang)' 현상이 발생한다.
    • 결과: 스크립트의 안정성을 심각하게 저해하며, 18분간의 프로세스 멈춤을 유발한 직접적인 원인이다.
  • ② 반복문 내 DataFrame 직접 수정의 비효율성

    • 현상: .iterrows() 루프 내에서 .at[] 접근자를 사용하여 DataFrame의 셀 값을 하나씩 수정하고 있다.
    • 결과: Pandas에서 행 단위의 반복적인 쓰기 작업은 내부적으로 많은 비용(인덱싱, 데이터 타입 체크, 복사 등)을 발생시킨다. 데이터의 행 수가 증가할수록 전체 실행 시간이 기하급수적으로 늘어나는 주요 성능 병목 구간이다.

3. 최적화된 코드와 개선 사항

3.1. 수정 코드 (After)

# 최적화된 코드
# ... (설정 부분 생략) ...

all_rows_data = [] # 임시 데이터 저장용 리스트

total_rows = len(result_df)
for idx, row in result_df.iterrows():
    print(f"--- 처리 중: {idx+1} / {total_rows} 번째 행 ---") # 개선점 3
    # ... (데이터 처리) ...
    for url in urls:
        try:
            # 개선점 1: timeout=10 설정
            res = requests.get(url, timeout=10)
            # ... (데이터 처리) ...
        # ... (예외 처리) ...
    
    # 개선점 2: 처리 결과를 딕셔너리로 취합
    new_data_row = {}
    for label in field_values:
        # ... (길이 보정 및 월별/통계 데이터 new_data_row에 저장) ...
    all_rows_data.append(new_data_row)

# 루프 종료 후, 리스트를 DataFrame으로 일괄 변환
new_cols_df = pd.DataFrame(all_rows_data, index=result_df.index)
# 기존 DataFrame과 일괄 병합
result_df = pd.concat([result_df, new_cols_df], axis=1)

# ... (결과 저장 및 출력) ...

3.2. 주요 개선 사항

  • timeout 파라미터 추가

    • 적용: requests.get(url, timeout=10)
    • 효과: 각 URL 요청에 최대 10초의 대기 시간을 설정하여, 특정 요청에서 프로세스가 멈추는 현상을 원천 차단하고 안정성을 확보했다.
  • ② "선수집, 후처리" 패턴 적용

    • 적용: 루프 내에서는 모든 계산 결과를 파이썬 리스트(all_rows_data)에 딕셔너리 형태로 수집(Collect)만 한다. 루프가 모두 종료된 후, 이 리스트를 pd.DataFrame()으로 한 번에 변환하고 pd.concat으로 기존 DataFrame에 병합(Process)한다.
    • 효과: DataFrame에 대한 직접적인 쓰기 작업을 루프 밖에서 단 한 번만 수행하도록 변경하여, 반복적인 쓰기 작업으로 인한 성능 저하를 해결하고 실행 속도를 크게 단축시켰다.
  • ③ 진행 상황 로깅 추가

    • 적용: print()문을 통해 전체 진행률을 표시했다.
    • 효과: 긴 시간이 소요되는 작업의 현재 상태를 파악하기 용이하게 만들어 디버깅 편의성을 높였다.

4. 결론

  • 외부 네트워크와 통신하는 I/O 작업에는 반드시 timeout을 명시하여 예기치 않은 대기 상태를 방지해야 한다.

  • Pandas DataFrame의 반복적인 수정 작업은 성능에 큰 부담을 주므로, 처리할 데이터를 메모리(리스트 등)에 모두 준비한 뒤 일괄적으로 생성 및 결합하는 방식이 훨씬 효율적이다.

profile
아자아자

0개의 댓글