Workspace IndexAlgorithms › Day 44

Tail Latency — Hedged Requests and Load Shedding TODO

Algorithms · Day 44 / 100 · C. Concurrency & Performance Engineering (Day 36-51)

Concept

Tail latency refers to response times in the tail of the distribution — p99, p999 — not the average. When a single request fans out to multiple backends, the overall response is bound by the slowest one, so a rare delay on one individual server gets amplified into a common delay at the aggregate level. A hedged request is a technique where, if the first request hasn't gotten a response by, say, its p95 latency, a second identical request goes out to another replica and whichever response comes back first is used — extra load rises only a few percent, but the tail shrinks substantially. Load shedding runs the opposite direction: instead of queueing requests beyond capacity, it rejects them quickly at the door, in order to protect the latency target for requests already accepted. Because wait time worsens exponentially as the queue grows, this is usually paired with mechanisms like discarding requests past their deadline or adaptive concurrency limits.

A dashboard that only tracks average response time misses user-facing failures entirely, and a service that queues without bound under overload collapses further once retries pile on top. Managing the tail and shedding load are basic building blocks of availability design.

Code & Formula

# 테일 레이턴시 — hedged request(느리면 복제본에 한 번 더 요청)로 p99 꼬리를 줄이는 걸 시뮬레이션한다.
# 백엔드 3대 중 하나가 가끔 크게 느려질 때, 단일 요청 대비 hedge 를 걸면 총 요청은 조금 늘지만 꼬리는 크게 줄어든다.

import random

random.seed(11)

def backend_latency_ms(server_id):
    # 대부분 10ms 내외, 가끔(5%) 200ms 근처로 튀는 "롱테일" 서버를 흉내낸다.
    if random.random() < 0.05:
        return random.uniform(150, 250)
    return random.uniform(5, 15)

def single_request(num_backends=3):
    server = random.randrange(num_backends)
    return backend_latency_ms(server), 1  # (지연, 사용한 요청 수)

def hedged_request(hedge_delay_ms=20, num_backends=3):
    """첫 요청이 hedge_delay_ms 안에 안 끝나면 다른 서버에 한 번 더 보내고, 먼저 끝난 쪽을 쓴다."""
    server1 = random.randrange(num_backends)
    latency1 = backend_latency_ms(server1)
    if latency1 <= hedge_delay_ms:
        return latency1, 1                       # 첫 응답이 충분히 빨랐다 -> hedge 발동 안 함
    server2 = (server1 + 1) % num_backends
    latency2 = backend_latency_ms(server2)
    finish = min(latency1, hedge_delay_ms + latency2)   # 두 번째 요청은 hedge_delay 이후에 출발
    return finish, 2

def percentile(values, p):
    s = sorted(values)
    idx = int(len(s) * p) - 1
    return s[max(0, idx)]

N = 5000
single_latencies, single_calls = [], 0
hedged_latencies, hedged_calls = [], 0

for _ in range(N):
    lat, calls = single_request()
    single_latencies.append(lat)
    single_calls += calls
for _ in range(N):
    lat, calls = hedged_request()
    hedged_latencies.append(lat)
    hedged_calls += calls

print(f"{'metric':>14} {'single':>10} {'hedged':>10}")
print(f"{'p50 (ms)':>14} {percentile(single_latencies, 0.50):>10.1f} {percentile(hedged_latencies, 0.50):>10.1f}")
print(f"{'p99 (ms)':>14} {percentile(single_latencies, 0.99):>10.1f} {percentile(hedged_latencies, 0.99):>10.1f}")
print(f"{'total calls':>14} {single_calls:>10} {hedged_calls:>10}")
print(f"\nextra request overhead: {(hedged_calls / single_calls - 1) * 100:.1f}%  (요청 수는 조금만 늘었다)")

Exercise

Set up three identical backends, artificially inject delay into one, then measure and tabulate p50, p99, and total request count for a single request versus a hedged request issued after p95 latency.

Practical Connection

A service like Verex that talks to multiple RPC providers can hedge eth_call and receipt lookups to keep one node's momentary delay from turning into delayed order fills or settlement transactions.

If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/dev-100-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.


한국어

테일 레이턴시 TODO

Algorithms · Day 44 / 100 · C. 동시성·성능 엔지니어링 (Day 36–51)

hedged request·load shedding

개념

테일 레이턴시는 평균이 아니라 p99·p999처럼 분포 꼬리에 있는 응답 시간을 말한다. 한 요청이 여러 백엔드로 fan-out되면 전체 응답은 가장 느린 하나에 묶이므로, 개별 서버의 드문 지연이 상위 레벨에서는 흔한 지연으로 증폭된다. hedged request는 첫 요청이 예컨대 p95를 넘겨도 응답이 없을 때 다른 복제본에 같은 요청을 하나 더 보내고 먼저 오는 응답을 쓰는 기법으로, 추가 부하는 몇 퍼센트인데 꼬리는 크게 줄어든다. load shedding은 반대 방향으로, 용량을 넘는 요청을 큐에 쌓지 않고 입구에서 빨리 거절해 이미 받아들인 요청의 지연 목표를 지키는 전략이다. 큐가 길어질수록 대기 시간이 지수적으로 나빠지므로, 데드라인이 지난 요청 폐기나 적응형 동시성 제한 같은 장치가 함께 쓰인다.

평균 응답 시간만 보는 대시보드는 사용자 체감 실패를 통째로 놓치고, 과부하 때 무한정 큐잉하는 서비스는 재시도까지 겹쳐 붕괴한다. 꼬리 관리와 부하 차단은 가용성 설계의 기본기다.

코드 · 수식

# 테일 레이턴시 — hedged request(느리면 복제본에 한 번 더 요청)로 p99 꼬리를 줄이는 걸 시뮬레이션한다.
# 백엔드 3대 중 하나가 가끔 크게 느려질 때, 단일 요청 대비 hedge 를 걸면 총 요청은 조금 늘지만 꼬리는 크게 줄어든다.

import random

random.seed(11)

def backend_latency_ms(server_id):
    # 대부분 10ms 내외, 가끔(5%) 200ms 근처로 튀는 "롱테일" 서버를 흉내낸다.
    if random.random() < 0.05:
        return random.uniform(150, 250)
    return random.uniform(5, 15)

def single_request(num_backends=3):
    server = random.randrange(num_backends)
    return backend_latency_ms(server), 1  # (지연, 사용한 요청 수)

def hedged_request(hedge_delay_ms=20, num_backends=3):
    """첫 요청이 hedge_delay_ms 안에 안 끝나면 다른 서버에 한 번 더 보내고, 먼저 끝난 쪽을 쓴다."""
    server1 = random.randrange(num_backends)
    latency1 = backend_latency_ms(server1)
    if latency1 <= hedge_delay_ms:
        return latency1, 1                       # 첫 응답이 충분히 빨랐다 -> hedge 발동 안 함
    server2 = (server1 + 1) % num_backends
    latency2 = backend_latency_ms(server2)
    finish = min(latency1, hedge_delay_ms + latency2)   # 두 번째 요청은 hedge_delay 이후에 출발
    return finish, 2

def percentile(values, p):
    s = sorted(values)
    idx = int(len(s) * p) - 1
    return s[max(0, idx)]

N = 5000
single_latencies, single_calls = [], 0
hedged_latencies, hedged_calls = [], 0

for _ in range(N):
    lat, calls = single_request()
    single_latencies.append(lat)
    single_calls += calls
for _ in range(N):
    lat, calls = hedged_request()
    hedged_latencies.append(lat)
    hedged_calls += calls

print(f"{'metric':>14} {'single':>10} {'hedged':>10}")
print(f"{'p50 (ms)':>14} {percentile(single_latencies, 0.50):>10.1f} {percentile(hedged_latencies, 0.50):>10.1f}")
print(f"{'p99 (ms)':>14} {percentile(single_latencies, 0.99):>10.1f} {percentile(hedged_latencies, 0.99):>10.1f}")
print(f"{'total calls':>14} {single_calls:>10} {hedged_calls:>10}")
print(f"\nextra request overhead: {(hedged_calls / single_calls - 1) * 100:.1f}%  (요청 수는 조금만 늘었다)")

연습

동일 백엔드 3대를 두고 인위적으로 한 대에 지연을 주입한 뒤, 단일 요청 vs p95 지연 후 hedged request의 p50·p99·총 요청 수를 측정해 표로 정리하라.

실무 · Verex 연결

Verex처럼 여러 RPC 제공자에 붙는 서비스는 eth_call·영수증 조회를 hedged request로 이중화하면 특정 노드의 순간 지연이 주문 체결이나 정산 트랜잭션 지연으로 번지는 것을 막을 수 있다.

공부한 날 원본 커리큘럼(docs/knowledge/dev-100-curriculum.md)의 이 줄에 노트 링크와 ✅ 를 붙이면, 이 자리는 노트 본문으로 바로 이어집니다. 노트 없이 이 페이지에 바로 적어도 됩니다 — 다만 다시 생성하면 덮어쓰이므로, 남길 글은 docs/algorithms/ 의 마크다운으로 쓰는 편이 안전합니다.

← 43. 백프레셔와 큐 이론45. 프로파일링 심화 →