LabHub
배우기 러닝패스 코스

Linux Fundamentals

Pipes Join Small Programs Into a Sentence

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

한 줄 요약

grep 은 고르고, sed 는 고치고, awk 는 센다. 이 세 도구를 파이프로 이으면 로그 수십만 줄에서 답을 뽑는 데 다섯 줄이면 충분하다.

Concept map: 질문을 명령으로 옮기는 능력 · 인접한 · 도구 선택 기준 · 파이프의 정체

왜 이게 필요했나

장애 보고를 받았다. "오후 3시부터 응답이 느립니다." 로그는 40만 줄이고, 편집기로 열면 그 자체가 몇 초 걸린다. 여기서 필요한 것은 로그 뷰어가 아니라 질문을 명령으로 옮기는 능력이다.

세 질문이 전부 같은 모양이다. 그래서 관용구를 하나만 외우면 된다.

... | sort | uniq -c | sort -rn | head

uniq -c인접한 중복만 세기 때문에 앞에 sort가 반드시 필요하고, 뒤의 sort -rn은 개수 기준 내림차순이다. 이 네 토막이 "무엇이 가장 많은가"라는 질문의 표준 답이다.

어떻게 동작하나

도구 선택 기준은 단순하다. 줄을 고르기만 하면 grep, 줄을 고치면 sed, 필드를 다루거나 계산하면 awk 다. 필요 이상으로 강한 도구를 쓰면 느려지고 읽기 어려워진다.

파이프의 정체도 알아 둘 것. 파이프로 이어진 각 명령은 별도의 프로세스에서 동시에 실행되며, 앞의 stdout 이 뒤의 stdin 으로 연결된다. 여기서 두 가지 성질이 따라 나온다.

  1. 큰 파일도 전부 메모리에 올리지 않고 흘려보내며 처리한다. 그래서 40만 줄도 빠르다.
  2. 파이프 안에서 바꾼 변수는 바깥에 남지 않는다. 서브셸이기 때문이다.

그리고 파이프라인의 종료 코드는 기본적으로 맨 마지막 명령의 것이다. curl ... | jq ... | wc -l 에서 curl 이 실패해도 wc 가 0을 내놓으면 전체는 성공으로 보인다. 스크립트에서 이 함정을 막는 장치가 set -o pipefail 이고, 다음 코스에서 자세히 다룬다.

성능 안티패턴 몇 가지는 지금 고쳐 두는 게 좋다.

이렇게 쓰지 말고 이렇게
`cat f grep p`
`sort uniq`
`cat f wc -l`
`echo "$s" cut -d. -f1`

현장에서 만나는 모습

첫째, 상태코드 집계. awk '$9 ~ /^5[0-9][0-9]$/ {print $7}' access.log | sort | uniq -c | sort -rn | head -10 한 줄이면 5xx 를 가장 많이 낸 경로 열 개가 나온다. 대시보드를 열기 전에 이걸 먼저 친다.

둘째, 비율 계산은 awk 로. 개수만으로는 판단이 안 될 때가 많다. awk '{t++; if ($5 != 200) e++} END {printf "%.1f\n", e*100/t}' 처럼 END 블록에서 한 번에 계산하면 파일을 두 번 읽지 않아도 된다.

셋째, 로그를 처음부터 읽지 말 것. 장애 시각 앞뒤 5분만 잘라 내는 것이 첫 동작이다. 40만 줄 전체를 훑는 습관은 시간을 잡아먹을 뿐 아니라, 이미 I/O 가 포화된 디스크에서는 진단 자체가 장애를 키운다.

큰 파일 앞에서의 습관

로그가 수십 기가바이트가 되면 도구 선택보다 읽는 양을 줄이는 것이 훨씬 크게 작용한다. 몇 가지 습관만 들여도 몇 분짜리 명령이 몇 초로 바뀐다.

먼저 잘라 낸다. 시각으로 범위를 좁히는 것이 첫 동작이다. 로그가 시각순으로 정렬돼 있다면 sed -n '/03:00/,/03:10/p' 처럼 구간만 뽑거나, 애초에 그 시간대 파일만 고른다. 그리고 찾는 것이 확실하면 grep -m 1 로 첫 일치에서 멈춘다. 40만 줄을 끝까지 읽을 이유가 없다.

필요한 열만 꺼낸다. awk '{print $7}' 처럼 필드를 먼저 좁히면 뒤의 sort 가 다루는 데이터가 훨씬 작아진다. sort 는 파이프라인에서 가장 비싼 자리이므로, 그 앞에서 줄이는 것이 가장 효과가 크다.

정렬이 정말 필요한지 본다. 개수를 세기만 할 거라면 awk 의 연관 배열로 한 번에 셀 수 있다. awk '{c[$7]++} END {for (k in c) print c[k], k}' | sort -rn | head 는 전체 정렬을 하지 않고 마지막에 결과만 정렬하므로, 줄 수가 많고 종류가 적을 때 훨씬 빠르다.

로케일을 끈다. LC_ALL=C 를 앞에 붙이면 sortgrep 이 문자 비교 규칙을 단순한 바이트 비교로 바꿔서 눈에 띄게 빨라진다. 다만 정렬 순서가 달라지므로 사람에게 보여 줄 결과에는 주의한다.

그리고 압축된 로그는 풀지 말고 그대로 읽는다. zgrep, zcat 을 쓰면 디스크에 임시 파일을 만들지 않아도 되고, 이미 꽉 찬 디스크에서 진단하다 남은 공간을 마저 채우는 사고를 피할 수 있다. 진단이 장애를 키우지 않게 하는 것이 큰 시스템에서 일할 때의 기본 예의다.

다음 실습에서 할 것

/opt/lab/data/app.log 를 재료로 오류 건수, 최다 요청 IP, 상태코드별 집계, 상위 경로, 오류율을 차례로 뽑고, 마지막에는 그 결과를 세 줄짜리 요약 리포트로 묶는다.