보안 인사이트 확장: 글로벌 스캔 처리 역량을 10배 향상한 방법 (새 탭에서 열림)
Security Insights는 기존 주 1~2회에 그치던 보안 검사를 모든 계정에 더 자주 적용하기 위해 처리량을 초당 10건에서 100건으로 약 10배 높여야 했다. Cloudflare는 Kafka 소비 병렬화, 느린 작업 분리, Postgres 대량 삽입 최적화, 지역 간 네트워크 지연 분석을 통해 스캔 처리량을 10배 이상 향상시켰다. 그 결과 수백만 고객에게 보안 인사이트를 제공하고 전체 고객의 검사 주기를 두 배로 늘릴 수 있었다. ## 기존 보안 검사 구조와 확장 과제 - 스케줄러가 검사 시점이 된 계정과 Zone을 감지한다. - 검사 작업을 Apache Kafka 메시지로 발행하고, 여러 Go 기반 checker 마이크로서비스가 메시지를 나누어 처리한다. - 각 checker는 특정 자산이나 설정을 검사한 뒤 내부 API로 결과를 전송한다. - API는 결과를 Postgres 데이터베이스에 저장한다. - 기존 시스템은 다음 문제를 겪고 있었다. - 검사가 주 1~2회만 수행되어 위험이 최대 2주간 탐지되지 않을 수 있었다. - 많은 무료 요금제 계정에서 자동 검사가 선택 사항이라 검사 자체가 이뤄지지 않았다. - Kafka backlog 증가, API 타임아웃, 프로세스 충돌이 발생했다. - 모든 계정에 자동 검사를 적용하고 검사 빈도를 높이려면 평균 처리량을 초당 10건에서 100건으로 늘려야 했다. ## Kafka 파티션의 병목 - Kafka는 일반적인 큐와 달리 파티션 내 메시지를 순서대로 소비하고 처리한다. - 하나의 consumer group에서는 파티션당 활성 consumer를 하나만 둘 수 있다. - 따라서: - 처리 시간이 긴 메시지 하나가 뒤따르는 메시지의 처리를 막을 수 있다. - checker별 병렬 소비자 수는 Kafka 파티션 수에 제한된다. - 파티션을 추가하면 확장할 수 있지만, 여러 서비스가 공유하는 Kafka 브로커의 자원 사용량이 증가하므로 최후의 수단으로 남겨두었다. ## 배치 기반 병렬 처리 - 메시지를 하나씩 처리하던 checker를 배치 단위로 소비하도록 변경했다. - 배치 안의 각 메시지는 별도의 Go goroutine에서 동시에 처리했다. - 이 방식의 trade-off는 다음과 같다. - 배치 처리 중 프로세스가 중단되면 이미 처리한 작업을 다시 수행해야 할 수 있다. - 동시에 처리하는 작업이 늘어 메모리 사용량이 증가한다. - 검사 시스템에서는 재처리 비용과 메모리 증가가 감당 가능한 수준이었기 때문에 병렬 처리를 선택했다. ## 느린 작업으로 인한 Head-of-Line Blocking 제거 - 일부 계정이나 Zone은 자산 수가 많아 검사에 수초가 아니라 수분 또는 수시간이 걸릴 수 있었다. - 이런 느린 메시지가 일반 메시지 앞에 있으면 Kafka 소비가 멈춰 빠른 작업까지 지연됐다. - 해결책으로 checker와 consumer group을 두 개의 처리 경로로 분리했다. - **Fast lane**: 빠르게 처리할 수 있는 일반 메시지 담당 - **Slow lane**: 처리 시간이 긴 메시지 전담 - 메시지의 예상 처리 시간을 빠르게 판단하고, fast lane이 느린 메시지를 만나면 건너뛰도록 했다. - 그 결과 느린 작업은 전용 자원을 사용하고, 빠른 작업은 지연 없이 계속 처리할 수 있었다. ## Postgres 대량 저장 최적화 - 기존 API는 인사이트 하나마다 별도의 `INSERT ... ON CONFLICT DO UPDATE` 쿼리를 실행했다. - 한 요청에 최대 50만 개의 인사이트가 포함될 수 있어, 최악의 경우 50만 번의 데이터베이스 왕복과 쿼리 실행이 발생했다. - 처음에는 임시 테이블에 `COPY`하는 Postgres 표준 대량 삽입 방식을 시도했지만, Postgres 시스템 테이블의 bloat가 증가하는 문제가 나타났다. - 최종적으로 입력 규모에 따른 하이브리드 방식을 채택했다. - 작은 데이터셋: `UNNEST`를 사용해 빠르게 삽입 - 큰 데이터셋: `COPY`를 사용해 대량 삽입 - 이 방식은 대규모 데이터에는 수초 수준의 처리 시간을, 소규모 데이터에는 밀리초 수준의 빠른 처리를 제공했다. ## 지역 간 지연으로 발생한 API 타임아웃 - 확장 과정에서 다음 현상이 관찰됐다. - 클라이언트 타임아웃 증가 - checker 처리 시간의 20~90%가 단일 API 호출에 소요 - 대량 검사 시 초기 처리량은 높지만 시간이 지나며 감소 - 원인은 API와 데이터베이스 간 네트워크 지연이었다. - 주 데이터베이스는 미국 오리건주 포틀랜드에 위치했다. - API는 포틀랜드와 네덜란드 암스테르담에서 active-active로 운영됐다. - 포틀랜드 API 호출은 평균 10ms였지만, 암스테르담 인스턴스에서는 거의 3초가 걸렸다. - 암스테르담 API가 데이터베이스 연결을 오래 점유하면서 checker의 클라이언트 연결 풀이 고갈됐다. - 연결을 기다리는 요청이 타임아웃되고, 암스테르담 API에 연결된 Kafka 파티션만 지속적으로 지연됐다. - 결국 API의 단순한 지역 분산이 전체 Kafka 소비 처리량의 불균형과 lag를 유발할 수 있음을 확인했다. ## 실용적인 결론 대규모 이벤트 처리 시스템에서는 소비자 수를 늘리는 것만으로 충분하지 않다. Kafka의 파티션 제약을 고려한 병렬 처리, 느린 작업의 별도 격리, 대량 데이터베이스 작업의 배치화, 데이터베이스와 API 간 지역 지연 관리까지 함께 최적화해야 안정적인 처리량 확장이 가능하다. 특히 분산 배포 환경에서는 평균 latency뿐 아니라 연결 풀 점유 시간과 파티션별 처리 편차까지 함께 측정해야 한다.