블록체인 검증 노드가 축출당하지 않으려면: 메모리 계층과 Kubernetes 스케줄링
블록체인 노드의 힙·DB 캐시·페이지 캐시 3중 메모리 구조와 Kubernetes QoS·토폴로지 스케줄링을 결합해 OOM과 축출을 줄이는 설계를 정리한다.
2026-08-12 · 최초 발행 2025-11-09
블록체인 노드가 Kubernetes 클러스터에서 예고 없이 재시작되는 문제는 대개 두 가지 원인 중 하나로 좁혀진다. 컨테이너 안에서 커널 OOM Killer에 죽었거나, Kubelet이 메모리 압력을 이유로 축출했거나다. 둘 다 근본 원인은 같다 — 노드 내부의 메모리 계층과 Kubernetes의 스케줄링 결정이 따로 놀고 있다는 것. 고성능 컴퓨터 구조·운영체제 관점의 메모리 관리와 클라우드 네이티브 오케스트레이션을 함께 설계해야 하는 이유다.
블록체인 노드 안에서 메모리가 쌓이는 곳
노드 프로세스는 애플리케이션 힙(Go/Rust/Java), 커널 페이지 캐시, 데이터스토어 캐시(LevelDB/RocksDB), mempool, 상태 캐시(state trie/flat storage)로 메모리를 나눠 쓴다. 이 워킹셋(working set)과 캐시 히트율이 동기화·검증 성능을 좌우하며, OOM이나 eviction이 한 번 터지면 합의 지연과 재동기화 비용이 급격히 늘어난다.
컨테이너·OS 쪽에서는 cgroup v2가 익명/파일 캐시 메모리를 제한하고 우선순위·OOM 점수를 관리한다. Kubelet은 메모리 압력 신호를 받으면 Pod를 축출(eviction)한다. 할당자(jemalloc/tcmalloc), GC(GOMEMLIMIT/GOGC), HugePages/THP, NUMA 바인딩이 여기서 지연과 처리량에 직접 영향을 준다.
Kubernetes 스케줄링은 Pod의 requests/limits가 QoS 클래스(Guaranteed/Burstable/BestEffort)를 결정하고, 스케줄러가 이를 바탕으로 노드를 고르며, Kubelet이 cgroup으로 자원을 격리하는 흐름이다. anti-affinity·topology spread로 토폴로지를 관리하고, 자원 집약 워크로드는 전용 노드 풀과 우선순위·선점으로 안정성을 높인다.
계층별로 뭘 봐야 하는가
애플리케이션 힙에서는 EVM 실행, 검증 큐, RPC 핸들러의 단편화와 GC 정지시간이 지연에 영향을 준다. Go 1.19 이상은 cgroup을 인지하는 GOMEMLIMIT을 지원한다. 데이터스토어 캐시는 LevelDB/RocksDB 블록 캐시와 OS 페이지 캐시가 이중으로 걸리는 구간이라, 캐시 크기와 compaction 메모리 상한을 따로 관리해야 한다. 프로토콜 캐시는 mempool, state trie/flat storage, bloom/method lookup 캐시로, LRU/ARC와 백프레셔로 트래픽 폭주를 막는다.
OS·컨테이너 격리 관점에서는 cgroup limit을 넘으면 커널 OOM Killer가 프로세스를 죽이고, Kubelet은 eviction 정책에 따라 BestEffort → Burstable → Guaranteed 순으로 축출한다. 목표는 워킹셋을 limit 아래로 유지하는 것인데, 파일 캐시는 압축·회수가 가능하지만 익명 메모리는 swap이 없으면 그대로 OOM 위험으로 이어진다.
Kubernetes 스케줄링·QoS 쪽에서는 requests=limits로 설정해야 Guaranteed가 부여돼 노드 메모리 압력이 커져도 축출이 최소화된다. anti-affinity와 topologySpreadConstraints로 같은 체인 노드의 장애 도메인을 분산하고, taints/tolerations로 전용 노드 풀을 구성한다.
NUMA·HugePages·할당자 최적화도 빼놓을 수 없다. NUMA를 단일 소켓에 고정하면 원격 메모리 접근이 줄고, CPU Manager(static)와 Topology Manager(single-numa-node)를 함께 쓴다. HugePages(2M/1G)는 TLB miss를 줄여주는데 THP를 비활성화하고 HugePages를 명시적으로 할당해야 한다. jemalloc/tcmalloc 선택은 단편화와 락 경합을 완화하는 데 영향을 준다.
동기화 단계별로도 메모리 패턴이 다르다. Fast/Checkpoint sync는 스냅샷 다운로드·검증 중 피크 메모리 사용량이 높고, compaction과 Trie 재구성이 동시에 일어나면 메모리가 급증한다. 정상 운영 중에는 mempool·RPC 버스트에 민감하므로 요청 큐 제한이나 rate-limit 같은 백프레셔로 스파이크를 흡수한다.
힙에서 축출까지, 하나의 그림으로
조건과 처리는 이렇게 갈린다. memory.available이 evictionThreshold 아래로 떨어지면 Kubelet이 축출하고, RSS가 limit을 넘으면 커널이 OOM으로 처리한다. 대응은 mempool 크기 제한, RPC 동시성 제한, DB compaction 스로틀링 순으로 단계를 밟고, 재시작 시에는 fast-sync 안전 모드로 진입한다.
Guaranteed QoS가 만드는 차이
| 전략 | 성능 | 확장성 | 일관성(지연 분산) | 안정성(축출/재시작) | 운영 편의 |
|---|---|---|---|---|---|
| QoS Guaranteed + 전용 노드 | 상 | 중 | 상 | 상 | 중 |
| QoS Burstable + 일반 노드 | 중 | 상 | 중 | 중 | 상 |
| QoS BestEffort(비권장) | 하 | 상 | 하 | 하 | 상 |
Guaranteed는 자원을 다소 낭비할 수 있지만 합의 지연과 슬래싱 리스크를 가장 확실하게 낮춘다. Burstable은 밀도를 높일 수 있는 대신 피크 시 축출 위험이 있고, BestEffort는 검증자·밸리데이터 워크로드에는 애초에 맞지 않는다.
체인마다 다른 튜닝 포인트
Ethereum(Geth/Erigon/Nethermind): GOMEMLIMIT을 cgroup limit의 0.7배로, GOGC는 100~200 범위에서 튜닝한다. Erigon은 flat storage 캐시와 RocksDB block cache를 분리 설정한다. DB compaction 메모리 상한을 지정(leveldb_compaction_speed 제한, RocksDB max_background_jobs/arena_block_size 조절)하고, Fast sync 같은 피크 단계에는 일시적으로 limit을 올리거나 피크 흡수용 버퍼 노드를 둔다.
Bitcoin Core: dbcache(예: 2048~8192MB)와 mempoolsize를 가변으로 설정한다. 재색인·재검증 시에는 HugePages로 TLB 미스를 줄인다.
Hyperledger Fabric: Peer/Orderer Pod를 분리하고 Raft 노드에는 anti-affinity를 건다. CouchDB 컨테이너는 따로 배치해 block cache와 CouchDB의 OS 캐시가 충돌하지 않게 한다.
멀티존 배치: topologySpreadConstraints: {topologyKey: topology.kubernetes.io/zone, maxSkew: 1}로 같은 체인의 여러 노드를 영역별로 분산해 지역 장애 내성을 확보한다.
운영 절차: 입력에서 SLO까지
먼저 TPS, 블록 간격, 상태 크기, 피크 메모리 프로파일을 수집해 요구사항을 정하고, 워킹셋(힙+DB 캐시+파일 캐시) = 평균 + 3σ에 20~30% 여유를 더해 용량을 산정한다.
Kubernetes 쪽에서는 Pod requests=limits로 Guaranteed를 설정하고 노드 풀을 taints/tolerations로 분리한다. Topology Manager를 single-numa-node, CPU Manager를 static으로 맞추고 HugePages를 할당한다. EvictionThreshold를 조정하고 시스템/쿠브 예약(Reserved) 메모리를 설정한다. 애플리케이션 쪽에서는 GOMEMLIMIT, GOGC, jemalloc/tcmalloc을 선택하고 mempool·state 캐시 상한을 정하며 백프레셔를 활성화한다. DB 캐시와 compaction 메모리는 상한을 따로 둔다.
출력 지표로는 블록 검증 p99 지연, RPC p99, 재시작율, OOM 건수, 캐시 히트율을 매 주기 점검하고 VPA 권고치를 반영한다.
메모리 압력이 감지되면 단계적으로 대응한다. RPC 동시성을 낮추고, 그래도 부족하면 mempool을 타이트닝하고, 다음은 compaction throttle, 마지막은 노드 교체 롤링이다. 재동기화가 필요하면 스냅샷 기반 fast sync로 다운타임을 줄인다.
MemoryQoS, NUMA-aware scheduling, Memory Manager/Hint 기능은 Kubernetes 버전마다 상태가 달라 클러스터 버전의 릴리스 노트를 확인하는 게 안전하다.
검증자 StatefulSet 예시
전제: K8s ≥ 1.25, Linux cgroup v2, 노드에 HugePages 2Mi 사전 할당, Go 1.19+.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: geth-validator
spec:
serviceName: "geth"
replicas: 3
selector:
matchLabels: { app: geth }
template:
metadata:
labels: { app: geth, role: validator }
spec:
priorityClassName: high-priority
nodeSelector:
nodepool: validator
tolerations:
- key: "nodepool" ; operator: "Equal" ; value: "validator" ; effect: "NoSchedule"
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector: { matchLabels: { app: geth } }
topologyKey: kubernetes.io/hostname
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector: { matchLabels: { app: geth } }
containers:
- name: geth
image: ethereum/client-go:v1.13.15
args:
- --syncmode=snap
- --cache=4096 # 내부 캐시(MB)
- --maxpeers=80
- --http --http.addr=0.0.0.0 --http.vhosts=*
env:
- name: GOMEMLIMIT
value: "12GiB" # limit의 70~80% 권장
- name: GOGC
value: "150"
resources:
requests:
cpu: "4"
memory: "16Gi"
hugepages-2Mi: "2Gi"
limits:
cpu: "4"
memory: "16Gi"
hugepages-2Mi: "2Gi"
volumeMounts:
- name: data
mountPath: /var/lib/geth
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: false
volumes:
- name: hugepage
emptyDir:
medium: HugePages
volumeClaimTemplates:
- metadata: { name: data }
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "local-ssd"
resources:
requests:
storage: 2000Gi
requests=limits로 Guaranteed를 확보하고 시스템/쿠브 예약 메모리를 설정해 노드 메모리 경합을 최소화한다. HugePages는 명시적 요청과 노드 사전 할당이 필수이며 THP는 비활성화를 권장한다. 모니터링은 container_memory_working_set_bytes, page_faults, process_resident_memory_bytes, go_gc_duration_seconds, DB block cache hit ratio를 본다.
모범사례와 맞바꿔야 하는 것들
워킹셋 기준 20~30% 헤드룸을 두고 피크·재색인 시에만 임시로 상향하는 게 모범사례이지만, 헤드룸을 과하게 잡으면 자원 활용도가 떨어진다. NUMA는 단일 도메인 고정과 2Mi HugePages를 우선하는 게 유리하지만 스케줄링 제약이 늘고 클러스터 활용도가 낮아진다. QoS·우선순위는 검증자에 Guaranteed+PriorityClass, 일반 풀노드에 Burstable을 쓰는 게 기본이지만 비용 증가와 축출 리스크 감소 사이의 트레이드오프다. 파일 캐시와 DB 캐시는 DB 캐시 상한을 명시하고 OS 페이지 캐시에 여유를 주는 게 좋지만, 중복 캐시로 메모리가 낭비될 수 있는 대신 I/O 안정성은 높아진다.
이 설계를 제대로 적용하면 OOM/eviction 건수가 50% 이상 줄고 노드 재시작율이 월간 1% 미만으로 떨어지는 사례가 있다(워크로드 의존). p99 블록 검증 지연은 1530%, RPC 타임아웃은 20% 감소하는 경향이 보고되고, Burstable 풀노드를 섞어 배치하면 클러스터 밀도가 1020% 개선되기도 한다.
결국 블록체인 노드의 메모리 관리는 CA/OS 수준의 힙·캐시·페이지 캐시 제어와 Kubernetes의 QoS·토폴로지 스케줄링을 함께 설계하는 문제다. Guaranteed QoS, NUMA/HugePages, cgroup 인지형 GC, DB 캐시 상한을 조합하면 지연과 축출 리스크를 함께 낮출 수 있고, 표준화된 운영 절차와 모니터링 체계가 그 위에서 합의 안정성과 운영 효율을 지탱한다.