사전 꼬리 지연
개념

꼬리 지연

gabury1고친 사람 github-actions[bot]

꼬리 지연은 요청 가운데 가장 오래 걸린 쪽이 얼마나 오래 걸렸는지를 가리킵니다. 대부분은 금방 끝나도 일부는 몇 배씩 더 걸립니다. 그 일부만 따로 떼어 봅니다. 평균 하나로 줄여 놓으면 이 오래 걸린 쪽이 보이지 않기 때문입니다.

쉽고 빠른 이해

무슨 값인가 — 요청 하나가 답을 받기까지 걸린 시간 가운데 오래 걸린 쪽을 따로 봅니다. 천 건을 처리했다면 그중 가장 오래 기다린 열 건이 얼마나 기다렸는지를 봅니다.

왜 이렇게 보나 — 평균은 대부분이 괜찮았다는 것만 알려 줍니다. 오래 기다린 사람이 있어도 그 숫자 안에 묻혀 안 보입니다.

어떻게 읽나

  1. 요청마다 걸린 시간을 하나하나 남깁니다
  2. 짧은 순으로 늘어놓고 뒤쪽 몇 건의 값을 읽습니다
  3. 그 값이 약속한 시간 안인지 견줍니다

대가 — 요청 하나하나의 시간을 남겨야 하므로 저장할 것이 늘어납니다. 서버마다 따로 잰 값을 하나로 합치기도 까다롭습니다.

상세

이 절은 「꼬리」가 무엇의 꼬리인지부터 봅니다. 그다음 그 꼬리를 숫자 하나로 집어내는 방법을 보고, 마지막으로 꼬리가 평균보다 먼저 눈에 띄어야 하는 이유를 화면 한 장의 예로 따라갑니다. 끝으로 꼬리를 만드는 원인과 줄이는 방법을 봅니다.

은행 창구에 손님 백 명이 왔다고 해 봅시다. 아흔 명은 삼 분 만에 일을 보고 나가지만 열 명은 서류가 꼬여 삼십 분을 앉아 있습니다. 창구가 내건 평균 대기 시간은 여섯 분이지만, 그날 화가 난 손님은 삼십 분을 기다린 열 명입니다.

분포의 오른쪽 끝

꼬리 지연에서 재는 시간은 응답 시간입니다. 요청을 보낸 때부터 답을 다 받은 때까지의 시간입니다. 요청 하나에 값 하나가 나오므로, 하루를 굴리면 값이 수백만 개 쌓입니다.

이 값들을 구간별로 몇 건씩인지 세어 늘어놓은 것을 분포라고 합니다. 응답 시간의 분포는 대개 한쪽으로 치우친 모양입니다. 대부분이 짧은 구간에 뭉쳐 있고, 오른쪽으로 갈수록 건수가 줄면서 길게 늘어집니다.

xychart-beta
    title "응답 시간 구간마다 몇 건인가"
    x-axis ["0.1초", "0.2초", "0.3초", "0.5초", "1초", "2초", "5초"]
    y-axis "요청 수" 0 --> 500
    bar [420, 380, 120, 40, 12, 5, 3]

오른쪽으로 길게 늘어진 이 부분이 꼬리입니다. 건수로 치면 전체의 아주 일부지만, 그 안의 요청은 왼쪽 덩어리보다 몇 배에서 몇십 배를 기다립니다. 이렇게 한쪽으로만 길게 늘어진 모양을 긴 꼬리라고 부릅니다.

백분위수와 p99

꼬리를 말로만 가리키면 견줄 수 없습니다. 「어제보다 나아졌나」를 따지려면 꼬리를 숫자 하나로 집어내야 하고, 그 도구가 백분위수입니다.

백분위수는 값들을 짧은 순으로 늘어놓고 앞에서부터 정해진 비율만큼 센 곳의 값입니다. 요청 100건을 늘어놓고 99번째 값을 읽으면 그것이 99번째 백분위수이고, 흔히 p99라고 적습니다. 100건 중 한 건만 그보다 오래 걸렸다는 뜻입니다.

어느 백분위수를 볼지는 꼬리를 얼마나 깊이 들여다볼지의 선택입니다. 아래는 같은 요청 100건을 서로 다른 값으로 읽었을 때 무엇이 보이고 무엇이 가려지는지입니다.

읽는 값 무엇을 말하나 무엇을 감추나
평균 전체 시간을 건수로 나눈 값 오래 걸린 한 건이 값을 끌어올려도 원인이 안 보인다
중앙값 딱 가운데 건이 기다린 시간 뒤쪽 절반이 어떤 모양인지 전혀 안 보여준다
p95 100건 중 뒤에서 다섯 번째 건 가장 오래 걸린 네 건이 얼마였는지는 빠진다
p99 100건 중 뒤에서 첫 번째 건 만 건에 한 번 나는 더 깊은 꼬리는 안 잡힌다
p99.9 1000건 중 뒤에서 첫 번째 건 건수가 적으면 값이 크게 흔들린다

표에서 보듯 뒤로 갈수록 더 깊은 꼬리가 보이지만, 그 값을 믿으려면 요청이 그만큼 많이 쌓여야 합니다. 요청이 100건뿐인 구간에서 p99.9를 읽으면 한 건이 그 값을 통째로 정합니다.

화면 한 장이 부르는 호출 수

꼬리가 평균보다 먼저 문제가 되는 곳은 호출이 여럿으로 갈라지는 구조입니다. 화면 한 장을 그리려고 뒤에서 여러 서비스를 부르고, 그 답이 다 와야 화면이 뜨는 구조를 말합니다.

flowchart TD
    A["화면 요청 한 건"] --> B["사용자 조회 · 0.05초"]
    A --> C["주문 목록 · 0.08초"]
    A --> D["추천 · 1.2초"]
    A --> E["배너 · 0.04초"]
    B --> F["넷이 다 와야 화면이 뜬다 · 1.2초"]
    C --> F
    D --> F
    E --> F

넷 중 셋이 빨리 왔어도 화면이 뜬 때는 가장 오래 걸린 하나가 정합니다. 화면의 응답 시간은 호출들의 평균이 아니라 최댓값입니다.

그래서 호출 수가 늘면 화면이 꼬리를 물고 올 확률도 같이 오릅니다. 호출 하나가 백 번에 한 번 오래 걸린다고 해 봅시다.

Python
느릴_확률 = 0.01               # 호출 하나 기준
1 - (1 - 0.01) ** 100         # 0.634

호출 하나로 보면 백에 하나지만, 그런 호출을 백 번 하는 화면으로 보면 셋에 둘 가까이가 오래 걸린 호출을 하나쯤 물고 옵니다. 서비스 하나하나는 약속을 지켰는데 화면은 안 지켜지는 일이 이렇게 생깁니다.

꼬리를 만드는 원인

꼬리를 만드는 원인에는 공통점이 하나 있습니다. 늘 일어나지 않고 가끔만 일어난다는 점입니다. 늘 일어나는 일은 모든 요청을 똑같이 늦추므로 꼬리가 아니라 중앙값을 올립니다.

원인 어떻게 꼬리를 만드나
가비지 컬렉션 정지 메모리를 치우는 동안 마침 들어온 요청만 멈춰 선다
줄 서기 서버가 붐빌수록 대기 시간이 가파르게 는다. 붐비는 순간에 닿은 요청만 오래 걸린다
캐시 미스 대부분은 캐시에서 끝나고, 빗나간 요청만 원본까지 다녀온다
재시도 첫 시도가 실패한 요청만 기다린 시간을 통째로 더 쓴다
락 경합 같은 자원을 노린 요청만 앞사람이 끝나기를 기다린다
서버 한 대의 이상 그 서버로 배달된 요청만 오래 걸린다

표의 둘째 줄인 줄 서기는 조금 더 풀어 둘 만합니다. 서버가 일하고 있는 시간의 비율을 사용률이라고 합니다. 서버가 한가할 때는 요청이 와도 바로 처리되지만, 사용률이 한계에 가까워지면 대기 줄이 급격히 길어집니다.

그래서 부하를 조금만 올려도 평균은 별로 안 변합니다. 그런데 꼬리는 먼저 튑니다. 꼬리가 부하의 경고등 노릇을 하는 까닭이 이것입니다.

꼬리를 줄이는 방법

첫 번째는 원인을 없애는 것입니다. 위 표의 원인마다 손댈 곳이 다르므로, 꼬리에 걸린 요청이 어느 구간에서 시간을 썼는지부터 봐야 합니다. 요청 하나가 지나간 구간을 이어 붙여 보여주는 트레이스가 그 일을 합니다.

두 번째는 여유를 두는 것입니다. 서버를 한계까지 채워 쓰면 줄 서기가 꼬리를 만들므로, 부하를 여러 대에 나누거나 대수를 늘려 사용률을 내립니다. 부하 분산과 수평 확장이 여기에 쓰입니다.

세 번째는 꼬리를 잘라 내는 것입니다. 정해 둔 시간이 지나면 기다림을 끊는 타임아웃을 걸면 아무리 오래 걸려도 그 시간 안에서 끝납니다. 대신 끊긴 요청은 실패로 남으므로 오류율이 오릅니다.

네 번째는 호출 수를 줄이는 것입니다. 화면 한 장이 부르는 호출이 적을수록 꼬리를 물고 올 확률이 낮아집니다. 여러 호출을 한 번으로 묶거나, 굳이 지금 필요 없는 호출을 나중으로 미룹니다.

다룰 때의 함정

백분위수는 더하거나 평균 낼 수 없습니다. 서버 열 대의 p99를 평균 낸 값은 전체 요청의 p99가 아닙니다. 합치려면 값 자체를 모아 다시 세거나, 그렇게 합칠 수 있게 만든 요약 구조를 써야 합니다.

재는 곳도 밝혀야 합니다. 서버 안에서 잰 값에는 서버 앞에서 줄 선 시간이 안 들어가지만, 요청을 보낸 쪽에서 잰 값에는 들어갑니다. 꼬리는 줄 선 시간이 만드는 일이 많아 두 값이 크게 벌어집니다.

마지막으로 꼬리를 늘 먼저 볼 필요는 없습니다. 사람이 앞에서 기다리지 않는 배치 처리라면 한 건의 기다림보다 전체를 언제 끝내느냐가 중요하고, 그때는 처리량을 먼저 봅니다.

관련 항목

이것과 같이 읽는 성능 지표

응답 시간 · 지연 · 처리량 · 초당 요청 수 · 오류율 · 포화 · 성능

이 값을 집어낼 때 쓰는 통계 개념

백분위수 · 분위수 · 높은 분위수 · 평균 · 중앙값 · 분포 · 히스토그램 · 긴 꼬리 · 이상치

이것을 키우는 원인

가비지 컬렉션 · 가비지 컬렉션 정지 · 캐시 미스 · 캐시 스탬피드 · 락 · 문맥 교환 · 패킷 손실 · 재시도 · 병목 · 스와핑

이 값으로 약속을 세우는 운영 규약

서비스 수준 목표 · 서비스 수준 지표 · 오류 예산 · 타임아웃

이 값을 재고 보여주는 도구

모니터링 · 메트릭 · 관측성 · 트레이스 · 대시보드 · 부하 테스트 · 가상 사용자

꼬리를 줄일 때 손대는 수단

부하 분산 · 로드 밸런서 · 수평 확장 · 커넥션 풀 · 캐싱 · 배치 처리

다른 이름: tail latency · 꼬리지연 · 테일 레이턴시