PaaS/MQ

Kafka 심화) 데이터 파이프라인 구축하기

armyost 2026. 2. 2. 00:31
728x90

데이터 파이프라인 구축 시 고려사항

적시성

하루에 한 번 대량의 데이터를 받는 시스템이 있는 반면 데이턱 생성된 뒤 몇 밀리초 안에 받아야하는 시스템도 있다. 대부분의 데이터 파이프라인은 이 두가지 형태의 중간쯤 어딘가에 위치한다. 이러한 맥락에서 카프카를 이해하는 좋은 방법은 쓰는 쪽과 읽는 쪽 사이의 시간적 민감도에 대한 요구조건을 분리시키는 거대한 버퍼로 생각하는 것이다. 쓰는 쪽에서는 실시간으로 쓸 수 있지만, 읽는 쪽에서는 배치 단위로 읽을 수 있으며, 그 반대로 가능하다. 이것은 백프레셔(Producer가 cONSUMING 가능 이상의 데이터를 발생시킬때) 적용 역시 단순하게 해준다. 

 

신뢰성

데이터 파이프라인은 많은 경우 중요한 비즈니스 시스템에 데이터가 전달되는 통로이기도 하기 때문에, 몇 초간의 장애가 발생하는 것만으로도 전체 시스템에 큰 지장을 줄 수 있다. 데이터 유실을 허용하는 시스템도 있지만, 대부분의 경우 최소 한 번 보장을 요구하는게 보통이기 때문에 원본 시스템에서 발생한 이벤트가 모두 목적지에 도착해야 한다. '정확히 한번' 전달 보장을 요구하는 경우도 자주 볼 수 있다. 

 

높으면서도 조정 가능한 처리율

카프카가 쓰는 쪽과 읽는 쪽 사이에서 버퍼 역할을 하기 때문에 더 이상 프로듀서의 처리율과 컨슈머의 처리율을 묶어서 생각하지 않아도 된다. 카프카는 높은 처리율을 받아낼 수 있는 분산 시스템이다. 카프카 커넥트 API는 작업을 병렬화 하는데 초점을 맞추기 때문에, 시스템 요구 조건에 따라 하나의 노드에서든 수평 확장된 여러개의 노드에서든 아무 상관없이 실행될 수 있다. 

 

데이터 형식

데이터 파이프라인에서 가장 중요하게 고려해야 할 것 중 하나는 서로 다른 데이터 형식과 자료형을(type)을 적절히 사용하는 것이다. 예를 들어서 에이브로 타입을 사용해서 XML이나 관계형 데이터를 카프카에 적재한 뒤 엘라스틱서치에 쓸때는 JSON 형식으로 HDFS에 쓸때는 파케이 Parguet 형식으로 S3에 쓸때는 csv로 변환해야 할 수 있다. 

이 레코드를 어떠한 형식으로든 저장할 수 있도록 장착 가능한 pluggable할 컨버터 역시 지원한다. 카프카의 데이터를 외부 시스템에 쓸 경우, 싱크 커넥터가 외부 시스템에 쓰여지는 데이터의 형식을 책임진다.

 

카프카 커넥트는 원본 시스템의 데이터를 카프카로 옮길 때 혹은 카프카의 데이터를 대상 시스템으로 옮길 때 단위 레코드를 변환할 수 있게 해주는 단일 메시지 변환 기능을 탑재하고 있다. 조인이나 집적과 같이 더 복잡한 변환 작업은 카프카 스트림을 사용해서 처리할 수 있는데, 이것은 별도의 장에서 자세히 알아볼 것이다. 

 

보안 

데이터 파이프라인의 관점에서 주로 고려해야할 점은 다음과 같다. 

- 누가 카프카로 수집되는 데이터에 접근할 수 있는가?

- 파이프라인을 통과하는 데이터가 암호화 되었다고 확신할 수 있는가? 이것은 여러 데이터 세ㅔ터에 걸쳐 구축된 데이터 파이프라인의 경우 특히 중요하다. 

- 누가 파이프라인을 변경할 수 있는가?

- 만약 파이프라인이 접근이 제한된 곳의 데이터를 읽거나 써야 할 경우 문제없이 인증을 통과할 수 있는가?

- 개인 식별 정보를 저장하고, 접근하고, 사용할 때 법과 규제를 준수하는가?

데이터 전송과정에서 SASL을 사용한 인증과 인가 역시 지워한다. 따라서 토픽이 민감한 정보를 담고 있을 경우, 여기 저장되어 있는 정보가 권한이 없는 누군가에 의해 덜 안전한 시스템으로 전달될 걱정은 할 필요가 없는 것이다. 카프카는 허가 받거나 허가 받지 않은 접근 내역을 추적할 수 있는 감사 로그 역시 지원한다. 

 

카프카 커넥트

개별 메시지 변환

Single Message Transformation (SMT) 는 카프카 커넥트가 메시지를 복사하는 도중에 데이터 변호나 작업의 일부로써, 보통 코드를 작성할 필요없이 수행된다. 조인이나 집적을 포함하곤 하는 더 복잡한 변환의 경우 상태가 있는 카프카 스트림즈 프레임워크를 사용해야 할 것이다. 카프카 스트림즈에 대해서는 나중에 설명한다. 

- Cast : 필드의 데이터 타입을 바꾼다. 

- MaskField : 특정 필드의 내용물을 null로 채운다. 민감한 정보나 개인 식별 정보를 제거할 때 유용하다.

- Filter : 특정한 조건에 부합하는 모든 메시지를 제외하거나 포함한다. 기본으로 제공되는 조건으로는 토픽 이름 패턴, 특정 헤더, 툼스톤 메시지 여부를 판별할 수 있다. 

- Flatten : 중첩된 자료 구조를 편다. 각 밸류값의 경로 안에 있는 모든 필드의 이름을 이어붙인 것이 새 키값이 된다. 

- HeaderFrom : 메시지에 포함되어 있는 필드를 헤더로 이동시키거나 복사한다. 

- InsertHeader : 각 메시지의 헤더에 정적인 문자열을 추가한다.

- InsertField : 메시지에 새로운 필드를 추가해 넣는다. 오프셋과 같은 메타데이터에서 가져온 값일 수 도 있고, 정적인 값일 수 도 있다. 

- RegexRouter : 정규식과 교체할 문자열을 사용해서 목적지 토픽의 이름을 바꾼다. 

- ReplaceField : 메시지에 포함된 필드를 삭제하거나 이름을 변경한다.

- TimerstampConverter : 필드의 시간 형식을 바꾼다. 예를 들어서 유닉스 시간값을 문자열로 바꾼다.

- TimestampRouter : 메시지에 포함된 타임스템프 값을 기준으로 토픽을 변경한다. 이것은 상크 커넥터에서 특히나 유용한데, 타임스캠프 기준으로 저장된 특정 테이블의 파티션에 메시지를 복사해야 할 경우, 토픽 이름만으로 목적지 시스템의 데이터세트를 찾아야 하기 때문이다. 

 

컨버터

 

카프카 커넥트는 자료 객체를 카프카에 쓸때 사용되는 형식으로 바꿀 수 있도록 컨버터를 지원한다. 

커넥트가 어떻게 작동하는지를 이해하려면 3개의 기본적인 개념과 함께 이들이 어떻게 상호작용하지는 지를 알아야 한다.

 

1) 커넥터와 테스크

커넥터는 다음의 세가지 작업을 수행한다. 

- 커넥터에서 몇개의 태스크가 실행되어야 하는지 결정한다. 

- 데이터 복사 작업을 각 테스트에 어떻게 분할해 줄지 결정한다. 

- 워커로부터 테스크 설정을 얻어와서 테스크에 전달해준다. 

 

테스크는 데이터를 실제로 카프카에 넣거나 거져오는 작업을 담당한다. 모든 태스크는 워커로부터 컨텍스트를 받아서 초기화된다. 소스 컨텍스트는 소스테스크가 소스 레코드의 오프셋을 저장할 수 있게 해주는 객체를 포함한다. 

 

2) 워커 

커넥터와 태스크를 실행시키는 역할을 맡는 '컨테이너' 프로세스라고 할 수 있다. 워커 프로세스는 커넥터와 그 설정을 같이하는 HTTP 요청을 처리할 뿐만 아니라, 커넥터 설정을 내부 카프카에 토픽에 저장하고, 커넥터와 태스크를 실행시키고, 여기에 적절한 설정값을 전달해주는 역할을 한다. 만약 워커 프로세스가 정지하거나 크래시 날 경우, 커넥트 클러스터 안의 다른 워커들이 이것을 감지해서 해당 워커에서 실행중이던 커넥터와 태스크를 다른 워커로 재할당한다. 

 

3) 컨버터 및 커넥트 데이터 모델

원본 시스템의 이벤트를 읽어와서 Schema, Value 순서쌍을 생성하는 것이다. 싱크 커넥터는 정확히 반대 작업을 수행한다. Schema, Value 순서쌍을 받아온뒤 Schema를 사용해서 해당 값을 파싱하고, 대상 시스템에 쓰는 것이다. 

 

워커는 설정된 컨버터를 사용해서 이 레코드를 Avro객체나 JSON 객체, 혹은 문자열로 변환한뒤 카프카에 쓴다. 싱크 커넥터에서는 정확히 반대 반향의 처리과정을 거친다. 커넥트 워커는 카프카로부터 레코드를 읽어온뒤, 설정된 컨버터를 사용해서 읽어온 레코드를 카프카에 저장된 형식 커넥트 데이터 API레코드로 변환한다. 이러헥 변환된 데이터는 다시 싱크 커넥터에 전달되어 최종적으로 대상 시스템에 쓰여진다. 

 

4) 오프셋 관리

소스 커넥터의 경우, 커넥터가 커넥트 워커에 리턴하는 레코드에는 논리적인 파티션과 오프셋이 포함된다. 이것은 카프카의 파티션과 오프셋이 아니라 원본 시스템에서 필요로하는 파티션과 오프셋이다. 예를 들어 파일 소스의 경우, 파일이 파티션 역할을, 파일안의 줄 혹은 문자 위치가 오프셋 역할을 할 수 있다. 워커는 이 레코드를 카프카 브로커로 보낸다. 만약 보로커가 해당 레코드를 성공적으로 쓴 뒤 해당 요청에 대한 응답을 보내면, 그제서야 워커는 방금전 카프카로 보낸 레코드에 대한 오프셋을 저장한다. 이러헥 함으로써 커넥터는 재시작 혹은 크래시 발생 후에도 마지막으로 저장되었던 오프셋에서부터 이벤트 처리를 시작할 수 있다. 싱크 커넥터는 비슷한 과정을 정반대 순서로 실행한다. 토픽, 파티션, 오프셋 식별자가 이미 포함되어 있는 카프카 레코드를 읽은뒤 커넥터의 put() 메서드를 호출해서 이 레코드를 대상 시스템에 저장한다. 작업이 성공하면 싱크 커넥터는 커넥터에 주어졌던 오프셋을 카프카에 커밋한다. 평범한 컨슈머 에플리키이션이 하는 것과 완전히 동일한 방법으로 말이다.