26M29c

Young-Kyoo Kim·2026년 5월 29일

네, 96개 core가 모두 NIC IRQ/NET_RX 처리에 실제로 참여하고 있다면, bias 판단의 기준 분모를 96으로 잡아도 됩니다. 다만 /proc/interrupts는 “CPU별/디바이스별 interrupt 누적 횟수”라서, 모든 core에 값이 있다는 것트래픽이 균등하게 분산된다는 것은 다릅니다. 반드시 10초 delta로 봐야 합니다. /proc/interrupts는 CPU별 I/O interrupt 횟수를 보여주는 파일이고, RSS/RPS는 수신 처리 부하를 여러 CPU로 분산하기 위한 구조입니다. (Red Hat Documentation)

1. 96 core가 모두 active일 때 기준

96개 core가 모두 active라고 보면 이상적인 균등 분산은:

1 core당 ideal share = 100 / 96 = 약 1.04%

입니다.

10초 평균 기준으로는 아래처럼 보시면 됩니다.

지표정상/허용약한 편중의심강한 편중
top 1 core share1~2%2~3%3~5%5% 이상
top 4 core share4~8%8~12%12~20%20% 이상
top 8 core share8~16%16~25%25~35%35% 이상
max / median1~2배2~3배3~5배5배 이상
p95 / p501~2배2~3배3배 이상5배 이상

즉, 96 core 전체가 active라면 저는 이렇게 봅니다.

top1 share >= 3%    → 편중 의심
top1 share >= 5%    → 강한 편중

top4 share >= 12%   → 편중 의심
top4 share >= 20%   → 강한 편중

max/median >= 3     → 편중 의심
max/median >= 5     → 강한 편중

다만 이것만으로 장애라고 단정하면 안 됩니다. 편중 + time_squeeze 증가 + TCP retransmission/NIC RX drop 증가가 같이 있어야 application timeout과 연결 가능성이 커집니다.


2. 예시로 보면

정상에 가까운 경우

top1 share = 1.8%
top4 share = 6.5%
top8 share = 12.0%
max/median = 1.7
softnet dropped/s = 0
time_squeeze/s = 0

이 정도면 96 core 전체 기준으로 괜찮습니다.

편중 의심

top1 share = 3.8%
top4 share = 15.0%
top8 share = 27.0%
max/median = 3.5
softnet dropped/s = 0
time_squeeze/s = 0~소량

이 경우는 RSS/RPS/IRQ 분산을 확인할 만한 상태입니다. 하지만 dropped=0, time_squeeze 증가가 거의 없으면 바로 timeout 원인이라고 보긴 어렵습니다.

강한 편중

top1 share = 7.0%
top4 share = 28.0%
top8 share = 45.0%
max/median = 6.5
time_squeeze/s > 0

이 정도면 RX 처리 편중이 tail latency에 영향을 줄 수 있는 상태로 봅니다.

장애와 연결 가능성이 높은 경우

top1 share = 6% 이상
top4 share = 20% 이상
max/median = 5 이상
time_squeeze/s 증가
TCP retransmission 증가
ethtool RX no_buffer / missed / queue drop 증가
MinIO 5xx 또는 quorum warning 시각과 일치

이 조합이면 application timeout과 관련성이 꽤 높습니다.


3. /proc/interrupts 기준으로도 delta를 봐야 함

/proc/interrupts도 누적값입니다. 10초 동안의 증가량을 봐야 합니다. Red Hat 문서도 /proc/interrupts가 CPU core별 interrupt 처리 횟수를 보여준다고 설명합니다. (Red Hat Documentation)

아래 스크립트는 bond1 slave NIC의 IRQ line만 잡아서, 10초 동안 CPU별 interrupt 증가율과 편중도를 계산합니다.

ice, i40e, ixgbe, 실제 NIC 이름 중 환경에 맞는 패턴을 넣으시면 됩니다.

cat > /tmp/irq-bias.py <<'PY'
#!/usr/bin/env python3
import time
import re
import statistics
import sys

interval = 10

# 환경에 맞게 수정:
# 예: "ice", "i40e", "ixgbe", "ens", "eno", "enp", 또는 bond1 slave NIC 이름
pattern = sys.argv[1] if len(sys.argv) > 1 else r"(ice|i40e|ixgbe|ens|eno|enp)"

def read_interrupts():
    rows = []
    with open("/proc/interrupts") as f:
        header = f.readline().split()
        cpus = header

        for line in f:
            if not re.search(pattern, line):
                continue

            parts = line.split()
            irq = parts[0].rstrip(":")
            counts = []

            for p in parts[1:1+len(cpus)]:
                try:
                    counts.append(int(p))
                except ValueError:
                    counts.append(0)

            name = " ".join(parts[1+len(cpus):])
            rows.append((irq, counts, name))

    return cpus, rows

cpus, a = read_interrupts()
time.sleep(interval)
_, b = read_interrupts()

cpu_delta = [0] * len(cpus)
irq_rows = []

for (irq_a, counts_a, name_a), (irq_b, counts_b, name_b) in zip(a, b):
    if irq_a != irq_b:
        continue

    deltas = [counts_b[i] - counts_a[i] for i in range(len(cpus))]
    total = sum(deltas)
    if total > 0:
        irq_rows.append((irq_a, total / interval, name_a))
    for i, d in enumerate(deltas):
        cpu_delta[i] += d

rates = [(cpus[i], cpu_delta[i] / interval) for i in range(len(cpus))]
rates.sort(key=lambda x: x[1], reverse=True)

total_rate = sum(r for _, r in rates)
active = [(cpu, r) for cpu, r in rates if r > 0]
values = [r for _, r in active]

if values:
    median = statistics.median(values)
    p95 = sorted(values)[int(len(values) * 0.95) - 1]
    max_rate = max(values)
    max_median = max_rate / median if median else 0
    p95_p50 = p95 / median if median else 0
    top1_share = rates[0][1] / total_rate * 100 if total_rate else 0
    top4_share = sum(r for _, r in rates[:4]) / total_rate * 100 if total_rate else 0
    top8_share = sum(r for _, r in rates[:8]) / total_rate * 100 if total_rate else 0
else:
    median = p95 = max_rate = max_median = p95_p50 = top1_share = top4_share = top8_share = 0

if max_median >= 5 or top1_share >= 5 or top4_share >= 20:
    verdict = "STRONG_BIAS"
elif max_median >= 3 or top1_share >= 3 or top4_share >= 12:
    verdict = "SUSPECT_BIAS"
elif max_median >= 2 or top1_share >= 2 or top4_share >= 8:
    verdict = "MILD_BIAS"
else:
    verdict = "OK_OR_ACCEPTABLE"

print(f"pattern={pattern}")
print(f"interval_sec={interval}")
print(f"matched_irq_lines={len(a)}")
print(f"active_cpu_count={len(active)}")
print(f"total_irq_per_sec={total_rate:,.0f}")
print(f"ideal_share_if_96_active={100/96:.2f}%")
print(f"top1_share={top1_share:.2f}%")
print(f"top4_share={top4_share:.2f}%")
print(f"top8_share={top8_share:.2f}%")
print(f"max_to_median={max_median:.2f}")
print(f"p95_to_p50={p95_p50:.2f}")
print(f"verdict={verdict}")
print()

print(f"{'CPU':<8} {'IRQ/s':>15} {'share%':>10}")
print("-" * 36)
for cpu, rate in rates[:30]:
    share = rate / total_rate * 100 if total_rate else 0
    print(f"{cpu:<8} {rate:15,.0f} {share:9.2f}%")

print()
print("Top IRQ vectors:")
print(f"{'IRQ':<8} {'IRQ/s':>15}  name")
print("-" * 70)
for irq, rate, name in sorted(irq_rows, key=lambda x: x[1], reverse=True)[:20]:
    print(f"{irq:<8} {rate:15,.0f}  {name}")
PY

chmod +x /tmp/irq-bias.py

실행 예:

/tmp/irq-bias.py 'ice'

또는 NIC 이름이 ens5f0, ens5f1이면:

/tmp/irq-bias.py 'ens5f0|ens5f1'

결과가 예를 들어:

active_cpu_count=96
top1_share=1.9%
top4_share=7.2%
top8_share=13.8%
max_to_median=1.8
verdict=OK_OR_ACCEPTABLE

이면 IRQ 분산은 괜찮은 편입니다.

반대로:

active_cpu_count=96
top1_share=6.4%
top4_share=24.0%
top8_share=39.5%
max_to_median=6.1
verdict=STRONG_BIAS

이면 96 core가 모두 active여도 실제 interrupt 부하는 일부 core에 강하게 몰리는 상태입니다.


4. /proc/softirqs의 NET_RX와 같이 봐야 함

IRQ가 균등해도 NET_RX softirq가 특정 core에 몰릴 수 있습니다. RSS는 NIC hardware queue/interrupt 분산이고, RPS는 software level에서 수신 packet 처리를 다른 CPU로 보낼 수 있는 구조입니다. (커널 도메인)

그래서 아래 두 결과를 같이 보세요.

/tmp/irq-bias.py 'ice'
/tmp/netrx-softnet-bias.py

판단은 이렇게 합니다.

IRQ 분산NET_RX 분산해석
IRQ 균등NET_RX 균등정상에 가까움
IRQ 편중NET_RX 편중RSS/IRQ affinity/queue 분산 문제 가능성
IRQ 균등NET_RX 편중RPS/Cilium/flow hash/softirq 처리 위치 확인 필요
IRQ 편중NET_RX 균등RPS가 보정 중일 수 있음. CPU 비용은 볼 필요 있음

5. 96개 모두 active인데 bias가 생기는 이유

96개 core 모두 active여도 다음 때문에 편중이 생길 수 있습니다.

원인설명
elephant flow큰 TCP flow 몇 개가 특정 RSS queue에 고정됨
LACP hash 편중bond는 flow 단위 hash라 단일 connection을 여러 NIC로 쪼개지 않음
RSS indirection table 불균형특정 queue/core에 hash bucket이 더 많이 매핑
IRQ affinity 불균형IRQ vector가 특정 NUMA node나 core subset에 치우침
Cilium/eBPF processing 위치packet은 들어왔지만 이후 processing이 특정 CPU에 몰림
NUMA localityNIC와 가까운 NUMA node로 interrupt가 몰림
interrupt coalescinginterrupt 수와 실제 packet 수가 완전히 비례하지 않음

따라서 모든 core active = 문제 없음은 아닙니다. 10초 delta 기준으로 share와 ratio를 봐야 합니다.


6. 현재 환경에서의 최종 판단 기준

96 core 모두 active라는 전제에서는 제가 이렇게 판단하겠습니다.

정상:
  top1 <= 2%
  top4 <= 8~10%
  max/median <= 2
  time_squeeze/s = 0
  dropped/s = 0

의심:
  top1 >= 3%
  또는 top4 >= 12%
  또는 max/median >= 3

강한 의심:
  top1 >= 5%
  또는 top4 >= 20%
  또는 max/median >= 5

장애 관련성 높음:
  위 강한 의심 조건
  + time_squeeze/s 증가
  + TCP retransmission 증가
  + ethtool RX no_buffer/missed/drop 증가
  + MinIO timeout/5xx/quorum warning 시각과 일치

특히 /proc/net/softnet_stat에서 dropped=0이라고 하셨으니, 지금은 packet loss 직접 원인보다는 RX 처리 지연/tail latency 가능성을 보는 단계입니다. time_squeeze가 장애 시각에 증가하고, 그 core가 NET_RX 상위 core와 겹치면 네트워크 처리 편중이 application timeout에 간접적으로 영향을 줬을 가능성이 커집니다. Red Hat 문서도 netdev_budget/netdev_budget_usecs가 한 번의 softirq polling cycle에서 처리할 packet 수와 시간을 제어한다고 설명합니다. (Red Hat Documentation)

0개의 댓글