현대 IoT 텔레메트리에 대한 스트리밍 아키텍처는 Apache Kafka를 분산 이벤트 백본으로 활용하며, 초당 수백만 개의 메시지를 내구성 있게 지속하고 수평적으로 확장할 수 있습니다. Apache Flink는 스트림 처리 엔진으로 작동하며, 정교한 이벤트 시간 처리 기능을 제공하고 Kafka 트랜잭션과 조정하여 전체 파이프라인에서 정확히 한 번의 전달 의미를 보장합니다. 상태 관리는 RocksDB 내장 백엔드를 사용하여 점진적인 비동기 스냅샷을 Amazon S3로 전송하며, JVM 힙 메모리를 소모하지 않고도 테라바이트 규모의 상태 연산을 수행할 수 있도록 합니다. 즉각적인 경고를 위해 핫 집계 결과가 Redis에 지속되고, 역사적 데이터는 Apache Iceberg 테이블을 통해 S3 Glacier로 흐릅니다.
스마트 에너지 유틸리티는 200만 개의 스마트 미터를 모니터링하며, 초당 만 건의 이벤트를 생성하고, 전력망 이상을 500밀리초 내에 탐지해야 하며, 이를 통해 연쇄 실패를 방지해야 합니다. 핵심 과제는 셀룰러 네트워크 분할로 인해 최대 5분까지 지연된 이벤트를 처리하고, 미터 재시도 로직에 의해 중복된 데이터를 제거하며, 고속 텔레메트리를 느리게 변화하는 참조 데이터와 조인하는 것입니다. 엔지니어들은 이전에 순서가 어긋난 이벤트와 피크 부하 동안 데이터 손실로 인해 발생한 허위 긍정 문제로 어려움을 겪었습니다. 따라서 실시간 응답성을 희생하지 않으면서 정확성을 유지하는 견고한 아키텍처가 필요했습니다.
솔루션 1: 스파크 스트리밍 및 배치를 이용한 람다 아키텍처
초기 제안은 람다 아키텍처 패턴을 채택했습니다. Apache Spark Streaming은 근사 실시간 뷰를 위한 속도 레이어를 제공했으며, 야간 Spark SQL 배치 작업은 지난 24시간 동안 HDFS에 대해 정확한 결과를 재계산했습니다.
장점: 도구 생태계가 성숙하고, HDFS 복제를 통해 간단한 장애 내성을 제공하며, 속도 및 배치 레이어 간의 명확한 관심사 분리가 이루어집니다.
단점: 스트리밍 및 배치 논리 간 코드 중복으로 인해 상당한 유지 관리 오버헤드와 동기화 버그가 발생합니다. 매일 테라바이트를 재처리하는 데 드는 비용이 과도하며, 배치 지연으로 인해 서브 초 이상 수정 요구사항을 위반합니다.
솔루션 2: 내장 스토어를 갖춘 카프카 스트림
두 번째 디자인은 애플리케이션 포드에서 직접 실행되는 내장 RocksDB 상태 스토어를 갖춘 Kafka Streams를 고려했습니다.
장점: 별도의 처리 클러스터 없이 단순화된 운영 토폴로지, Kafka의 소비자 그룹과의 긴밀한 네이티브 통합 및 자동 파티션 할당 관리가 이루어집니다.
단점: 상태 연산의 확장은 모든 파티션의 비싼 재균형을 유발하여 상당한 지연을 초래합니다. 순서가 어긋난 이벤트 처리에는 복잡한 사용자 지정 타임스탬프 추출 로직이 필요하며, 기본 윈도잉은 처리 시간에 의존합니다. 애플리케이션 서버의 메모리 제약은 총 상태 크기를 심각하게 제한하여 큰 윈도 집계를 방지합니다.
솔루션 3: 이벤트 시간 의미론을 갖춘 아파치 플링크
선택된 아키텍처는 Kubernetes에서 Apache Flink를 배포하여 이벤트 시간 처리 의미론을 활용하고, 수중 마크와 외부에서 점진적인 체크포인트를 Amazon S3에 저장했습니다.
장점: 수조 마크와 allowedLateness 구성에 의한 네이티브 이벤트 시간 처리는 사용자 지정 로직 없이 순서가 어긋난 데이터를 처리합니다. 정확히 한 번의 의미론은 체크포인트 및 Kafka 트랜잭션 간의 2단계 커밋을 통해 달성됩니다. RocksDB의 점진적 스냅샷은 컴퓨팅과 상태를 독립적으로 확장할 수 있게 하며, 메모리 압박 없이 테라바이트 규모의 키가 포함된 윈도우를 지원합니다.
단점: 상당한 운영 복잡성은 체크포인트 조정, 수조 마크 정렬 및 역압 관리에 대한 깊은 전문 지식을 요구합니다. Flink JobManager는 Kubernetes의 고가용성 구성을 필요로 하는 잠재적인 단일 실패 지점을 나타냅니다.
선택된 솔루션 및 결과
우리는 Flink의 BoundedOutOfOrdernessWatermarks를 5분의 허용 범위를 갖도록 설정하고, RocksDB 점진적 체크포인트를 30초마다 구성하여 솔루션 3을 채택했습니다. 중복 제거는 Flink의 두 단계 커밋 프로토콜과 조정된 Kafka의 아이템포턴트 생산자 및 트랜잭션 작성을 활성화하여 이루어졌습니다. S3 Glacier로의 데이터 계층화는 과도한 저장 비용 없이 쿼리할 수 있는 역사적 데이터 세트를 유지하기 위해 Apache Iceberg 압축 전략을 사용했습니다.
이 아키텍처는 제작 시험 중 300밀리초의 p99 경고 지연과 99.99%의 처리 정확도를 달성했습니다. 이 시스템은 체크포인트 복원 후 Kafka 오프셋에서 재생함으로써 3시간의 셀룰러 네트워크 분할을 원활하게 처리했으며, 데이터 손실 제로를 기록했습니다. 저장 비용은 이전 HDFS 솔루션에 비해 60% 감소했으며, Grafana 대시보드는 Flink의 수조 마크 지연 및 체크포인트 지속 시간 메트릭에 대한 실시간 가시성을 제공했습니다.
질문: Apache Flink가 Kafka에 쓰기 시 정확히 한 번의 의미론을 어떻게 유지하고, 작업 재시작 시 중복 작성을 방지하는 방법은 무엇인가요?
Flink는 체크포인트 장벽과 Kafka 트랜잭션 간의 2단계 커밋 프로토콜을 통해 정확히 한 번을 구현합니다. 사전 커밋 단계에서 데이터는 고유한 transactional.id를 사용하여 Kafka에 플러시되지만, 체크포인트가 성공적으로 완료될 때까지 커밋되지 않습니다. 체크포인트가 실패하면 Flink는 트랜잭션을 중단하여 Kafka가 데이터를 기각하도록 합니다. 재시작 시, Flink는 마지막 성공적인 체크포인트에서 프로듀서 상태를 복원하여 불완전한 쓰기로부터 좀비 트랜잭션을 방지합니다. 지원자들은 종종 transactional.id가 체크포인트 ID를 포함해야 idempotency를 보장한다고 간과하고, Flink가 여러 테넌트 Kafka 클러스터에서 충돌을 피하기 위해 setTransactionalIdPrefix 구성이 필요하다는 점을 간과합니다.
질문: 이벤트 시간 윈도잉이 키가 포함된 연산에서 상태 폭발을 유도하며, 무한한 장치 ID 스트림을 처리할 때 이를 어떻게 완화하나요?
이벤트 시간 윈도잉은 Flink가 각 키의 모든 이벤트를 버퍼링해야 하므로 상태 폭발을 유도합니다. 이는 수조 마크가 윈도우 종료 시간과 구성된 allowedLateness 기간을 초과할 때까지 발생합니다. 고유한 장치 식별자와 같은 고차원 키의 경우, 이는 RocksDB에 수백만 개의 동시 윈도 상태를 누적시켜 결국 모든 사용 가능한 디스크 및 메모리 리소스를 소비합니다. 완화를 위해서는 오래된 윈도가 자동으로 만료되도록 상태 TTL (Time-To-Live) 구성을 구현하고, RocksDB 메모리 관리 버퍼를 구성하여 오프 힙 사용을 제한하며, 스냅샷 오버헤드를 줄이기 위해 점진적인 체크포인트를 사용하는 것이 필요합니다. 지원자들은 종종 명시적인 윈도 제거 또는 TTL 설정이 없으면 상태 백엔드가 무한정 성장하여 태스크 매니저가 메모리 부족 오류를 만날 때까지 증가할 수 있다는 점을 간과합니다. 특히 늦게 도착하는 역사적 데이터를 처리할 때 이러한 문제가 발생합니다.
질문: 단일 고장난 IoT 장치가 정상 이벤트 볼륨의 100배를 생성할 때, 특정 Flink 서브작업을 압도하는 핫 키 스큐를 어떻게 해결하나요?
핫 키 스큐는 파티션 해싱이 고볼륨 키를 단일 태스크 인스턴스에 집중시켜 파이프라인 전체의 역압 및 지연 스파이크를 유발할 때 발생합니다. 해결 방법은 키 솔팅으로, 초기 셔플 중 핫 키에 무작위 접미사(예: 0-9)를 추가하여 여러 서브작업에 걸쳐 처리할 수 있게 한 다음, 접미사를 제거하고 후속 글로벌 윈도에서 결과를 재집계합니다. 또는 Flink의 AggregateFunction을 사용하여 셔플 전 지역 키 처리 집계를 구현하여 네트워크 트래픽을 줄이거나, 특정 생산자를 저지하기 위해 Kafka의 스티키 파티셔닝을 사용할 수 있습니다. 지원자들은 종종 솔팅이 네트워크 셔플 볼륨과 상태 크기를 증가시켜야 하며, RocksDB에서 합성 키 관리의 오버헤드와 병행성 이득 간의 신중한 균형을 요구한다는 점을 간과합니다.