Hadoop SmallFiles Issue Trouble Shooting

Alan·2023년 3월 16일
0

Standby-Namenode shutdown 이슈 발생
Standby-Namenode(mem 30G) 재시작 시, 8시간 동안 namespace image와 edit_log를 불러오다, GC를 반복히며 OOM(Out of Memory)으로 종료되는 상황

원인 분석

  1. 기존 5700개 파일/일 단위로 여러 ods에 쌓이고 있지만, 파일 사이즈가 5MB이하임
  2. HDFS MetaData의 크기는 HDFS의 파일 단위로 생성되기때문에 작은 용량의 파일이 계속 쌓이면서(10년) MetaData의 크기를 Standby-Namenode가 감당할 수 없게 된 것

해결

  • 수집 및 적재 프로세스 변경 불가능

  • ODS 파일 삭제 불가능

  • 이에 임시방편으로 Small Files를 Merge 시켜주는 (일회용) 쉘 개발 진행

    • 일자별로 hdfs getmerge를 수행(일자별 파일을 하나의 파일로 합침)

    • merge된 file 사이즈와 원본 사이즈의 차이가 1000bites 이내면, 정상 merge됐다 판단하고 hdfs 파일 삭제 후 put hdfs 진행

    • Put된 hdfs 파일 사이즈를 체크하여 동일하면 로컬의 merge된 파일을 삭제

    • 해당 수행 과정을 log 파일로 남김(tail -f로 확인하면서 수행할 수 있도록)

개발된 ShellScript

  • (디렉토리 구조 등은 보안상 xx식으로 처리함)
  1. hdfs_directory.sh
  2. hdfs_mergs.sh
  • hdfs_directory.sh

    • merge 할 디렉토리를 대상으로 목록 파일을 생성하는 쉘스크립트
#!/bin/bash

# 인자값이 1개가 아니면 Usage 출력
if [ $# != 1 ] ; then
        echo "usage : ./hdfs_directory.sh [path]"
                echo "example : ./hdfs_directory.sh /xxx/xxx/xxx/xxx/ods/xxx/xxx_ext"
fi

if [ $# == 1 ] ; then
        path=$1
        # 저장할 dir_name 가져오기 : 지정한 path의 가장 마지막 폴더 이름
        # a/b/c/d라면 d_dir_list.txt로 저장됨
        # list는 p_day를 포함한 directory만을 대상으로 나열됨
        # p_day=$$/*로 일자별 getmerge를 수행하기 위함임
        dir=`echo $path | rev | cut -d "/" -f 1 | rev`
        echo "`sudo -u hdfs hdfs dfs -ls -R ${path} | grep -E '^d' | awk '{print $8}' | grep -e 'p_day'`" > "${dir}_dir_list.txt"
fi
  • hdfs_merge.sh

    • 실제 merge 작업을 수행하는 쉘스크립트

    • getmerge의 -skip-empty-file의 경우 hadoop2.7.4 version에서는 동작 불가

#!/bin/bash
 #add_log(arg1=merge_filename, arg2 = process_name)
 #log를 hdfs_merge.log에 기록
 #merge_filename과 process_name을 표시하여, 구분하기 쉽게 처리
 #error 시, [ERROR]로 표시, 성공정보는 [INFO]로 표시 흐름을 알기 위해 시간 표기
add_log() {
        if [ $? -eq 0 ] ;then
                /bin/bash -c "echo $(date +"%Y-%m-%d_%H:%M:%S") $1 [INFO] sucess $2 >> hdfs_merge.log 2>&1"
        else
                /bin/bash -c "echo $(date +"%Y-%m-%d_%H:%M:%S") $1 [ERROR] $2 Caused by ... >> hdfs_merge.log 2>&1"
                exit 9
        fi
}
if ! [ $# == 2 ] ;
then
        # 확장자는 자동으로 찾도록 함
        echo "usage : hdfs_merge.sh [dir_list_file] [filename_prefix]"
        echo "example : ./hdfs_merge.sh xxx_ext_dir_list.txt xxx_ext"
else
        while read FILE_PATH; do
                # dir_list_file을 한 줄 씩 읽으며 getmerge 수행
                # {8자리date}_{filename_prefix}.{extension}으로 저장됨
                # extension이 없는 경우, 빈 문자열이 되어 filename만으로 merge_file_name 생성
                extension=`sudo -u hdfs hdfs dfs -ls $FILE_PATH | awk '/\./' | sed -n '1p' | cut -d '.' -f 2`
                if ! [[ -z $extension ]] ;
                then
                        extension=.$extension
                fi
                #FILENAME이 정해지지 않은 채 ERROR 발생시, FILE_PATH을 그대로 가져와서 log 기록
                add_log $FILE_PATH make_extension_variable
                
                # =(equal) 문자 이후 다음 숫자를 가져와서 date 변수 생성
                merge_file_date=`echo "$FILE_PATH" |  grep -Eo '[^0-9][=][0-9]{1,4}' | sed 's/[^0-9]//g' | tr -d '\n'`
                add_log $FILE_PATH make_merge_file_date_variable
                merge_filename="${merge_file_date}$2$extension"
                add_log $FILE_PATH make_merge_filename_variable

                # hdfs getmerge 진행
                /bin/bash -c "sudo -u hdfs hdfs dfs -getmerge $FILE_PATH/* ./$merge_filename"
                add_log $merge_filename getmerge

                hdfs_size=`sudo -u hdfs hdfs dfs -du -s $FILE_PATH | awk '{print $1}'`
                add_log $merge_filename make_hdfs_size_variable
                add_log "hdfs_size="$hdfs_size

                local_size=`ls -al $merge_filename | awk '{print $5}'`
                add_log $merge_filename make_local_size_variable
                add_log "local_size="$local_size

                diff_size=$((hdfs_size-local_size))
                add_log $merge_filename diff_size_variable
                add_log "diff_size="$diff_size

                /bin/bash -c "rm -f .${merge_filename}.crc"
                add_log $merge_filename delete_merge_file_crc

                if [[ $diff_size -lt 1000 &&  $diff_size -gt -1000 ]] ;
                then
                        # merge되지 않은 hdfs_files 삭제
                        /bin/bash -c "sudo -u hdfs hdfs dfs -rm -f -skipTrash $FILE_PATH/* >> hdfs_merge.log 2>&1"
                        add_log $merge_filename delete_hdfs_files
                        # merge된 local_file을 hdfs에 적재
                        /bin/bash -c "sudo -u hdfs hdfs dfs -put $merge_filename $FILE_PATH >> hdfs_merge.log 2>&1"
                        add_log $merge_filename diff_local_file

                        # put된 hdfs_file_size와 local_size 비교하여 1000이하면 local_merge_file 삭제
                        after_hdfs_size=`sudo -u hdfs hdfs dfs -du -s $FILE_PATH | awk '{print $1}'`
                        add_log $merge_filename make_after_hdfs_size_variable
                        add_log "local_size="$local_size
                        add_log "after_hdfs_size="$after_hdfs_size
                        after_diff_size=$((local_size-after_hdfs_size))
                        add_log "after_diff_size="$after_diff_size
                        add_log $merge_filename make_after_diff_size_variable

                        if [[ $after_diff_size -lt 1000  &&  $after_diff_size -gt -1000 ]];
                        then
                                /bin/bash -c "rm -f $merge_filename"
                                add_log $merge_filename delete_local_merge_file
                        else
                                add_log $merge_filename delete_local_merge_file
                                /bin/bash -c "echo [ERROR] $merge_filename after_diff_size exceed 1000!! >> hdfs_merge.log 2>&1"
                        fi
                else
                        add_log $merge_filename delete_local_merge_file
                        /bin/bash -c "echo [ERROR] $merge_filename diff_size exceed 1000!! >> hdfs_merge.log 2>&1"
                fi
                # 완료됐으면, 기존 dir_list_file에서 완료된 라인 삭제 처리 ==> 재처리시 편하게
                /bin/bash -c "sed -i 's|${FILE_PATH}||g' $1"
                /bin/bash -c "sed -i '/^$/d' $1"
                add_log $merge_filename dir_list_file_delete_FILE_PATH
        done < $1
fi

실제 수행 CMD

./hdfs_directory.sh /xxx/xxx/xxx/xxx/ods/xxx/xxx_ext

./hdfs_merge.sh /xxx/xxx/xxx/xxx/ods/xxx/xxx_ext_dir_list.txt xxx_ext

tail -f hdfs_merge.log

0개의 댓글