TT Lab
시작하기
배우기 러닝패스 코스

리눅스 기초

파이프는 작은 프로그램을 문장으로 잇는다

TT Lab 에서 이어서 보기

한 줄 요약

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

왜 이게 필요했나

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

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

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

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

무엇이 가장 많은가에 답하는 표준 관용구

access.log 에서 경로 필드만 뽑아 흘려보낸다고 하자. 각 토막은 별도 프로세스로 돌며 앞의 출력이 뒤의 입력이 된다.

  1. sort 로 같은 줄을 붙인다같은 경로가 로그 곳곳에 흩어져 있다. 정렬하면 같은 줄끼리 이웃한다.
  2. uniq -c 로 센다이웃한 같은 줄을 하나로 줄이고 앞에 개수를 붙인다. 이웃하지 않은 중복은 보지 못한다.
  3. sort -rn 으로 순위를 매긴다개수를 숫자로 보고 큰 것부터 늘어놓는다. head 로 위만 자르면 답이다.

여기서 구분할 것 첫 sort 를 빼도 오류는 나지 않는다. 숫자는 그럴듯하게 나오지만 같은 경로가 여러 줄에 나뉘어 개수가 쪼개진다.

잠깐, 예측해 보세요 sort 없이 uniq -c 만 썼더니 /api/orders 가 세 줄에 나뉘어 나왔다. 왜인가?

설명 확인 · 채점 없는 자가 점검

그 경로가 로그에서 세 덩어리로 떨어져 있었기 때문이다. uniq 는 바로 앞 줄과만 견주므로 먼저 정렬해 같은 줄을 붙여야 한 번에 센다.

근거 문서

어떻게 동작하나

도구 선택 기준은 단순하다. 줄을 고르기만 하면 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 를 앞에 붙이면 sort 와 grep 이 문자 비교 규칙을 단순한 바이트 비교로 바꿔서 눈에 띄게 빨라진다. 다만 정렬 순서가 달라지므로 사람에게 보여 줄 결과에는 주의한다.

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

센 다음에 정렬하면 정렬할 것이 줄어든다

40만 줄에 경로 종류가 스무 개뿐인 로그를 생각하자. 같은 답을 내는 두 파이프라인이 정렬하는 양은 크게 다르다.

  • sort, uniq -c, sort -rn40만 줄 전체를 먼저 정렬한다. 파이프라인에서 가장 비싼 자리가 가장 큰 입력을 받는다.
  • awk 연관 배열, sort -rnawk 가 한 번 훑으며 경로별 개수를 센다. 마지막 정렬은 스무 줄만 다룬다.

여기서 구분할 것 줄 수가 많고 종류가 적을수록 차이가 커진다. 종류가 줄 수만큼 많다면 연관 배열도 그만큼 메모리를 쓰므로 이득이 줄어든다.

잠깐, 예측해 보세요 수십 기가바이트 로그에서 오후 3시 전후의 상위 경로를 찾아야 한다. 무엇부터 하겠는가?

설명 확인 · 채점 없는 자가 점검

읽는 양부터 줄인다. 그 시각 구간만 잘라 내고 필요한 열만 꺼낸 뒤에 센다. 정렬 앞에서 줄이는 것이 가장 효과가 크고, 압축된 로그는 풀지 말고 zgrep 이나 zcat 으로 읽는다.

근거 문서

다음 실습에서 할 것

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