풀 피드의 실제 비용
추정하는 대신 운영 스키마에 풀 테이블을 넣고 측정했다. 그 측정이 내 기본값 하나를 죽였다.
Michel Wijnberg
“풀 테이블을 감당하나요?”는 모든 BGP 제품이 받는 질문이고, 숫자로 답하는 제품은 거의 없습니다. 정직한 이유는 그 답을 만들어 내기가 민망하기 때문입니다. 진짜 테이블을 진짜 스키마에 올려놓고 들여다봐야 하니까요.
그래서 이 글이 그것입니다. 풀 IPv4 테이블, 오리진 AS 78,538개에서 온 베스트 경로 1,071,538개를 랩과 같은 PostgreSQL 메이저 버전 위의 운영 스키마에 적재했습니다. 아래의 모든 수치는 그것에 대한 측정이지 그것에 대한 모델이 아닙니다.
또한 측정이 해 주는 가장 유용한 일, 즉 당신을 반박하는 일도 들어 있습니다. 아래 숫자 중 하나는 이미 Osprey의 기본값으로 출하되어 있었는데, 측정 결과 그것이 평범한 트래픽 아래에 놓여 있었습니다. 그 뒤로 네 배로 올렸습니다.
단위는 세션이지 라우터가 아니다
가장 흔한 사이징 실수이며, 먼저 치워 두는 편이 낫습니다.
풀 테이블 업스트림 넷을 가진 라우터는 BMP 연결 하나로 피어 네 개를 보고합니다. 업스트림 하나짜리 라우터의 네 배가 듭니다. 장비 수로 사이징하면 정확히 당신의 멀티호밍 배수만큼 과소 계산하게 되는데, 그건 애초에 그 망을 그렇게 지은 이유 바로 그것입니다.
풀 테이블 하나가 단위당 드는 비용
크기는 PostgreSQL이 스스로 보고하는 릴레이션 크기이며, MB와 GB라는 표기에도 불구하고 1024 기반입니다. 행당 수치는 거기서 유도했습니다. 그러니 412 “MB”는 412 MiB입니다. 1,071,538행에 403바이트를 곱하면 411.8 MiB이지, 10진으로 읽었을 때의 432 MB가 아닙니다.
| 단위 | 비용 | 범위 |
|---|---|---|
bgp_rib_entry (인덱스 9개 포함) | 412 MB (403 B/행) | 모니터링 피어 세션당 |
bgp_best_path (인덱스 4개 포함) | 327 MB (319 B/행) | AS 및 라우팅 도메인당 |
bgp_rib_entry_history (인덱스 3개 포함) | 구간당 376 B | 기록된 전이당 |
| BMP 피어별 프리픽스 집합, Go 힙 | 53 MiB (52 B/프리픽스) | 모니터링 피어 세션당 |
| 엔진 RIB 디바운스 버퍼(기본 상한) | 267 MiB (560 B/항목) | 엔진당, 피드 수와 무관 |
저 표에서 둘은 따로 꺼낼 만합니다.
인덱스가 발자국의 절반을 조금 넘습니다. bgp_rib_entry의 세션당 내역: 기본 키 82 MB,
peer_prefix 42 MB, prefix_gist 32 MB, prefix 23 MB, origin_as 8.9 MB,
staleness 7.5 MB, next_hop 7.3 MB. 이름을 댈 만한 것은 이 일곱이고, 남은 둘은 더 작아
논점을 바꾸지 않습니다. 이 일곱만으로 총 412 MB 중 203 MB이며, 기본 키 하나가 전체의 5분의
1입니다. 이 크기의 테이블에서 인덱스 하나 더 얹는 게 공짜인지 궁금했던 적이 있다면, 여기서
가장 싼 것도 세션당 7.3 MB입니다.
그 측정 과정에서 아무도 눈치채지 못했던 결함도 발견했습니다. idx_bgp_rib_peer_lastseen이
staleness 인덱스와 바이트 단위로 동일했던 것입니다. 7.5 MB와 모든 RIB 행마다의 추가
btree 쓰기를 지불하면서 아무것도 사지 않고 있었습니다. 제거했습니다. 이런 것은 크기를 나란히
놓아 보기 전까지는 보이지 않습니다.
행당 403바이트는 커뮤니티를 포함한 값입니다. 현실적인 커뮤니티 여섯 개짜리 문자열이 들어 있습니다. 커뮤니티가 없는 경로는 약 55바이트 적게 듭니다. 어느 쪽인지 밝히지 않고 싼 쪽을 인용하면, 아무도 무엇도 배포하기 전에 이미 당신의 사이징 가이드는 14% 낙관적입니다.
계산해 본 배치들
| 배치 | 라이브 DB | BMP 힙 |
|---|---|---|
| 보더 라우터 1대, 업스트림 2 | 1.15 GB | 107 MiB |
| 보더 라우터 2대, 각각 업스트림 2 | 1.98 GB | 213 MiB |
| 라우터 4대 × 업스트림 2 | 3.6 GB | 426 MiB |
| 라우트 리플렉터 피드, 풀 테이블 세션 20개 | 8.6 GB | 1.04 GiB |
이건 라이브 상태 수치입니다. 히스토리는 별개이고, 재미있어지는 쪽은 히스토리입니다.
처른은 스냅숏에서 읽어 낼 수 없다
테이블은 그것을 붙들고 있는 비용을 알려 줍니다. 기록하는 비용에 대해서는 아무것도 알려 주지 않습니다. 그건 테이블이 얼마나 자주 바뀌느냐에 달렸고, 스냅숏에는 시간축이 없기 때문입니다.
그래서 처른은 공개 MRT 아카이브에서 측정했습니다. RIPE RIS 컬렉터 rrc00, 2026-08-10, 하루에 흩어 놓은 한 시간짜리 창 네 개, 서로 다른 피어 세션 98개에 걸친 UPDATE 메시지 680만 건. 표본에는 Osprey의 30초 디바운스를 적용했습니다. 속성 해시 억제는 의도적으로 모델링하지 않았으므로, 이 값들은 실제로 기록될 양의 상한입니다.
여기서 구간이란 디바운스 이후 영속화된 상태 전이 하나입니다. 한 번에 여러 프리픽스를 실을 수 있는 원시 BGP UPDATE가 아니고, 디바운스가 기존 구간으로 접어 넣을 수 있는 프리픽스 이벤트도 아닙니다.
| 세션당 | 하루 기록 구간 수 | 히스토리 스토리지 |
|---|---|---|
| 중앙값 | 413,000 | 155 MB/일 → 90일에 14 GB |
| p90 | 840,000 | 316 MB/일 → 90일에 28 GB |
| 관측된 최대 | 8,300,000 | 3.1 GB/일 → 90일에 281 GB |
디바운스는 실제로 일을 합니다. 30초 창마다 (세션, 프리픽스)당 레코드 하나로 접어서 **원시 프리픽스 이벤트의 45.6%**를 없앱니다. 풀 테이블 대상에서 이것을 끄면 히스토리 청구서가 약 1.8배가 되고, 그 대가로 얻는 것은 창 안에서 일어난 전이에 대한 더 촘촘한 가시성입니다. DFZ 피드에서 그것은 거의 전부, 한 행씩 저장하지 않기를 바랐던 바로 그 종류의 세부입니다.
제가 독자라면 원했을 단서 둘. rrc00은 RIPE의 멀티홉 컬렉터이고 그 피어들은 전형적인 IXP 로컬 트랜싯 세션보다 시끄러우므로, 이 값들은 비관적인 쪽으로 보십시오. 그리고 중앙값보다 산포가 훨씬 중요합니다. 가장 무거운 세션이 중앙값의 20배입니다. p90으로 사이징하고, 꼬리에서 경보를 울리고, 누구도 중앙값을 용량 계획이라며 팔지 못하게 하십시오.
틀렸던 숫자
Osprey에는 히스토리 기록에 차단기가 있습니다. 어떤 대상이 storm_ceiling_per_hour를 넘는
구간을 만들어 내면 그 시간의 나머지 동안 기록이 억제되어, 플랩하는 세션이 아무도 안 보는
사이 디스크를 채우지 못하게 합니다.
출하된 기본값은 시간당 250,000이었습니다.
이제 그것을 측정 옆에 놓아 보십시오. rrc00 표본에서 가장 무거운 단일 실제 세션은 시간당 약 346,000 구간으로 돕니다. 위 표의 하루 830만이 그것입니다. 폭주가 아닙니다. 플랩 루프도 아닙니다. 실제 컬렉터 위의 실제 피어 하나가, 평범한 월요일에 낸 값입니다.
기본값은 정상 운영에 의해, 저절로 걸렸습니다. 게다가 억제 차단기의 실패 양상은 조용한 쪽입니다. 아무것도 죽지 않고, 크게 경보가 울리지도 않으며, 그저 히스토리가 완전하기를 멈추고 그 빈틈은 찾아 나서야만 보입니다.
기본값은 이제 시간당 1,000,000이며, 직관이 아니라 같은 측정에서 유도했습니다. p90 처른은 세션당 시간당 약 35,000 구간이므로, 위의 가장 큰 프로필인 풀 테이블 세션 20개짜리 라우트 리플렉터 피드는 p90에서 시간당 70만 언저리입니다. 새 상한은 그 위에 여유를 두고 앉아 있고 가장 무거운 단일 실제 세션보다도 위이면서, 진짜 폭주가 만들어 내는 양보다는 여전히 한참 아래입니다. 테이블을 플랩 루프로 재생하는 세션은 이것의 몇 배를 만들어 내므로 차단기는 여전히 차단합니다.
같은 측정이 분명히 해 주는 것 하나를, 함의로 남기지 않고 말해 두겠습니다. 시끄러운 세션을 많이 집계하는 대상은 정당하게 이 상한을 넘길 수 있습니다. rrc00의 98개 세션을 합치면 하루 약 5천만 구간, 시간당 200만 정도로 새 상한을 여유 있게 넘깁니다. Osprey를 그런 것에 겨누면 차단기는 작동할 것이고, 그게 옳습니다.
두 숫자가 서로 어긋나 보이니 대놓고 말해 둘 만합니다. 이 상한은 폭주 방지 장치이지 용량 계획이 아닙니다. 대상별로 설정되며, 우리가 사이징하는 가장 큰 프로필의 정상 운영이 내는 양보다 위, 자기 테이블을 루프로 재생하는 세션이 내는 양보다 아래에 있습니다. 정당하게 이를 넘기는 집계 대상은 대상 하나를 넘어 자란 것이지, 제품을 넘어선 것이 아닙니다.
그래서 이 작동은 단순히 로그에 남는 게 아니라 기록됩니다. 상한이 걸리면 그 시간의 나머지 동안 히스토리는 불완전해지고, 그 빈틈은 조회 가능한 사각지대 원장에 기록됩니다. 조용히 모니터링을 멈추는 모니터링 시스템은 시끄럽게 멈추는 것보다 나쁘며, 둘을 가르는 유일한 것은 그 빈틈이 일급 레코드인지 여부입니다.
이것이 말해 주지 않는 것
위의 모든 것은 바이트입니다. 풀 테이블이 PostgreSQL에서 차지하는 크기, BMP 서버가 Go 힙에 쥐고 있는 양, 히스토리가 쌓이는 속도. 그것은 한 축이고, 사이징 질문에는 여러 축이 있습니다.
여기서 측정하지 않았고 따라서 주장하지도 않는 것:
- CPU. 수집 시점이든 질의 시점이든.
- 세션이 올라온 뒤 첫 유용한 답까지의 시간. 운영자가 실제로 체감하는 숫자입니다.
- 메시지 경로. BMP 서버와 데이터베이스 사이에서, 테이블이 수렴하는 동안 버스와 라이브 웹소켓 상태가 치르는 비용.
- 질의 지연. 발자국과는 별개로, 이 크기의 테이블에 대한 지연.
이들은 별개의 측정이고 저는 하지 않았습니다. 자신 있는 저장 용량 숫자를 내주고 나머지도 같은 방식으로 확장되리라 가정하게 두어 슬그머니 반대를 시사하는 사이징 가이드는, 이 글이 반대하려고 존재하는 바로 그 일을 하고 있는 것입니다.
왜 틀렸던 기본값을 공개하는가
대안이 특정한 방식으로 더 나쁘기 때문입니다.
추정으로 지은 사이징 가이드는 반증 불가능합니다. 아무도 확인할 수 없으니 아무도 오류를 찾지 못하고, 그래서 오류가 출하됩니다. 이번 것이 발견된 이유는 숫자들이 진짜 테이블을 적재하고 진짜 업데이트를 재생해서 나왔기 때문이고, 진짜 업데이트 스트림은 설정 파일이 무엇을 합리적이라 여기든 개의치 않기 때문입니다.
측정에는 하루쯤 들었습니다. 중복 인덱스 하나, 네 배 어긋난 기본값 하나를 찾아냈고, “풀 테이블을 감당하나요”에 대해 이 제품이 갖게 된 첫 정직한 답을 내놓았습니다. 그 답은: 그렇습니다. 라이브 상태로 세션당 412 MB와 힙 53 MiB, 히스토리는 90일에 14 GB에서 281 GB 사이 어딘가이며, 그것은 전적으로 당신의 피어가 얼마나 시끄러운지에 달려 있습니다. 마지막의 그 범위가 진짜 답이지, 그 중간값이 아닙니다.
요점
이 글의 모든 숫자는 출하된 스키마에 대한 측정이고, 그중 하나는 출하된 코드를 반박했습니다.
그것이 자기 제품에 대해 추론하는 대신 측정해야 하는 이유입니다. 추론이 부주의해서가 아니라, 추론으로 고른 기본값에는 그것이 틀렸다고 당신에게 말해 줄 방법이 없기 때문입니다. 진짜 테이블은 그 일을 하루 만에 합니다.
이것이 이 시리즈의 마지막 글입니다. 다음 글은 이 모든 것의 아래에서 시작합니다. 레코더가 남의 프로토콜에, 그 일부가 되지 않으면서 합류하는 방법을, 정문 하나씩.