Software Development/Data Engineering

[Flink] 플링크 공부

루ㅌ 2025. 1. 27. 22:20

 

https://m.yes24.com/Goods/Detail/89927694

 

아파치 플링크로 하는 스트림 데이터 처리 - 예스24

아파치 플링크를 사용하면 초저지연으로 스트림 데이터를 처리하고 실시간으로 스트림 데이터를 분석할 수 있다. 강력한 아파치 플링크의 스트림 처리 기능을 바탕으로 매일 수천억 건 이상의

m.yes24.com

https://github.com/streaming-with-flink
위의 책을 보고 공부하기 위해 간단하게 내용 정리를 진행하였습니다. 자세한 내용은 책을 통해서 확인하실 수 있습니다.

 

Stream Processing with Apache Flink

Stream Processing with Apache Flink has 3 repositories available. Follow their code on GitHub.

github.com

예제 코드는 위의 링크에 있습니다.

 

1. 상태가 있는 스트림 처리 소개

플링크는 원격에 신뢰성있는 저장소에 애플리케이션 상태의 체크포인트를 주기적으로 기록한다.

상태가 있는 스트리밍 + 이벤트 로그의 결합의 특징

장애가 발생한다? 직전 체크포인트에서 애플리케이션 상태를 복구한다.

상태 + 이벤트 로그의 특징을 조합하면 버그 수정, 이전 결과 수정, Migration 등에도 적용할 수 있다.

이벤트 주도 애플리케이션

이벤트 스트림을 입력으로 받아 특정 비즈니스 로직을 처리하는 '상태가 있는 스트리밍 애플리케이션' -> 알람, 이메일, 다른 이벤트 트리거, 실시간 추천, 패턴 감지 및 복합 이벤트 처리, FDS, ...

데이터 파이프라인

DB 복제를 저지연으로 지원하기 위해서 이벤트 로그를 사용해 데이터 변경 내용을 처리한다.

스트리밍 분석

이벤트 스트림을 가져와 짧은 지연 시간으로 최신 이벤트를 처리한 후 결과를 갱신한다.

오프소스 스트리밍 처리의 진화

1세대(2011년)

- 밀리초 단위의 지연 시간으로 이벤트 처리, 장애 시 유실 허용.

- 짧은 지연 시간과 정확한 결과의 트레이드 오프

- 람다 아키텍처 -> 배치 처리(정확한 결과), 스트림 처리(빠르게)


2세대(2013년)

 - Exactly Once 보장. 그러나 처리 결과는 시간과 이벤트 도착 순서에 따라 달라짐(일관성 X)

 

3세대(2015년)

 - 2세대의 문제를 해결하기 위해 등장. 일관성과 정확한 결과를 만들어 냄(아파치 플링크가 3세대임)

플링크 빠르게 살펴보기

높은 처리율, 짧은 지연, 정확한 스트림 처리 결과를 제공

- 이벤트 시간과 처리 시간 시멘틱: 이벤트 시간 시멘틱은 순서가 바뀐 이벤트가 들어오더라도 일관성 있고 정확한 결과를 제공

- 상태 일관성 보장: 밀리초 단위의 지연 시간 보장

- 여러 종류의 커텍터 제공

- 리소스 매니저와의 호환성: 빠른 장애 복구, 동적 할당

플링크 웹 UI

2. 스트리밍 처리 기초

데이터플로우 프로그래밍 소개

데이터플로우 그래프

 

데이터플로우 프로그램은 데이터 처리 연산 사이로 데이터가 어떻게 흐르는지 기술한다.

Direct Graph는 데이터 플로우 프로그램을 표현하는 일반적인 방법이다.

 

데이터 병렬화와 태스크 병렬화

데이터 병렬화: 동일 연산을 수행하는 태스크를 각 입력 파티션에 할당해 병렬로 실행. 데이터 병렬화는 대용량 데이터를 여러 장비로 분산하여 처리.

태스크 병렬화: 같거나 다른 데이터에 여러 연산을 수행하는 태스크를 할당. 태스크 병렬화를 이용하면 클러스터의 계산 자원을 좀 더 효울적으로 사용.

 

데이터 교환 전략

물리적 데이터플로우 그래프에서 어떤 태스크로 레코드를 할당할지 정의

- 전진(Forward): 한 태스크로 들어온 데이터를 다른 태스크 쪽으로 내보낸다. 동일한 물리적 장비에 할당되면 네트워크 통신이 발생하지 않는다.

- 브로드캐스트(Broadcast): 모든 레코드 연산자의 모든 병렬 태스크로 내보낸다. 데이터 복제 및 네트워크 통신이 발생해 비용이 비싸다.

- 키 기반(Key-based): 키 기준으로 같은 것 끼리 같은 태스크에 할당.

- 랜덤(Random): 태스크의 부하를 균등하게 분산

 

병렬 스트림 처리

데이터 스트림: 무한 이벤트 시퀀스

지연(latency)

이벤트를 처리하는 데 얼마나 많은 시간이 걸리는지. 요청 후 응답까지 걸리는 시간.

처리율(throughput)

단위 시간당 얼마나 많은 이벤트를 처리할 수 있는지 알려준다. 1분 동안 처리 가능한 요청 수. 시스템이 처리할 수 있는 비율보다 높게 데이터를 계속 받으면 버퍼링도 불가능하게 돼 데이터를 잃을 수도 있다. 이런 상황을 Backpressure라 한다.

Ingestion egress

데이터 인입과 싱크

Transformation

각 이벤트를 독립적으로 처리.

롤링 집계 연산

SUM, MIN, MAX와 같은 연산으로, 이벤트가 들어올 때마다 계속해서 상태를 갱신한다. 집계 연산은 상태가 있으며 새로 들어온 이벤트와 현재 상태를 결합해 집계 값을 계산.

윈도우 연산

Transformation과 롤링 집계 연산은 상태를 갱신할 때 한 번에 한 이벤트만 처리

윈도우 연산은 일정량의 이벤트를 모아 보관하고 있는다. 평균 함수, 조인 연산이 있다.

무한 이벤트 스트림에서 버킷이라 부르는 이벤트의 유한 집합을 지속적으로 생성.

- 텀블링 윈도우: 고정 길이에 서로 겹치지 않는 버킷으로 이벤트를 할당한다. 개수 기반 및 시간 기반이 존재

- 슬라이딩 윈도우: 겹치는 고정 길이의 버킷으로 이벤트를 할당한다. 두 버킷에 같은 이벤트가 있을 수 있다.

- 세션 윈도우: 현실 세계 시나리오에서 유용하다. 

시간 시멘틱

처리 시간: 이벤트를 연산자가 측정한 시간

이벤트 시간: 이벤트가 발생한 시간

워터 마크: 이벤트가 더 지연되지 않고 도착할 것이라고 확신할 수 있는 시간. 해당 값을 조절하여 지연 시간과 정확도 사이를 조절할 수 있음

결과 보장

최대 한 번: 가장 낮은 수준의 보장. 뭘 해주는 게 없음

최소 한 번: 중복 발생

정확히 한 번: 가장 엄격한 구현 방식. 구현이 어려움. 경량 스냅샷 방식으로 정확히 한 번을 구현

3. 아파치 플링크 아키텍처

플링크 컴포넌트

잡매니저: 애플리케이션을 제어하는 마스터 프로세스

태스크 매니저: 워커 프로세스

리소스 매니저: YARN, MESOS, K8S

디스패처: 애플리케이션을 제출할 수 있는 REST 인터페이스 제공

5. DataStream API

일반적으로 플링크 애플리케이션은 다음과 같은 구조로 돼 있다.

1. 실행 환경설정

- 로컬이나 원격 실행 환경을 설정

- env.setStreamTimeCharacteristic(TimeCharacteristic.EventTime)을 호출해 이벤트 시간을 사용해 시간 의미를 해석하라 설정할 수 있다.

2. 데이터 소스에서 하나 이상의 스트림 읽기

    // ingest sensor stream
    val sensorData: DataStream[SensorReading] = env
      // SensorSource generates random temperature readings
      .addSource(new SensorSource)
      // assign timestamps and watermarks which are required for event time
      .assignTimestampsAndWatermarks(new SensorTimeAssigner)

- addSource를 통해서 데이터 소스를 추가

- assignTimestampsAndWatermarks를 통해 이벤트 시간과 워터마크를 할당

3. 스트리밍 변환 연산을 적용해 애플리케이션 로직 구현

4. 하나 이상의 싱크로 결과 출력

5. 프로그램 실행

변환 연산

기본 변환 연산: 개별 이벤트를 처리(map, filter, flatMap)

KeyedStream 변환 연산: 어떤 속성을 공유하는 이벤트들을 그룹으로 처리

롤링 집계 연산: sum, min, max, minBy, maxBy. 여러 롤링 집계 메서드를 결합하는 것은 불가능하다. reduce는 롤링 집계 연산을 일반화함.

다중 스트림 변환 연산: union, connect, coMap, coFlatMap. 여러 소스로 부터 읽어 하나로 합칠 때

분산 변환 연산: 파티션 스큐 발생 시 리파티셔닝을 통한 부하 분산

8. 외부 시스템 연동

멱등적 쓰기: 해시맵. append 연산은 여러 번 레코드를 추가하면 레코드가 여러번 추가되므로 멱등적 연산이 아니다.

트랜잭션 쓰기: 단대단 정확히 한 번 일관성 보장을 위해 쓴다.

- 마지막으로 성공한 체크포인트의 계산 결과까지만 외부 싱크 시스템에 쓰는 것이다.

- 장애 복구 시에 애플리케이션 상태를 마지막 체크포인트로 재설정하고, 마지막 체크포인트 이후 레코트를 다시 계산해 내보내지 않으므로 단대단 정확히 한 번을 보장할 수 있다.

- 체크포인팅마다 한 번만 데이터를 쓰기 때문에 트랜잭션 쓰기 방식은 멱등적 쓰기 방식에서 애플리케이션이 재실행될 때 발생하는 일관성 불일치 문제를 겪지 않는다. 그러나 체크포인팅이 완료돼야만 결과를 볼 수 있어 지연 시간이 늘어나게 된다.

플링크는 트랜잭션 싱크 커넥터를 구현하고자 일반적으로 WAL 싱크와 2PC 싱크 두 가지를 제공한다.


- WAL 싱크: 모든 결과 레코드를 애플리케이션의 상태에 쓰고 체크포인트 완료 알림을 받으면 싱크 시스템에 이 결과를 내보낸다.

단 완벽하게 정확히 한 번 일관성을 보장할 수 없고, 애플리케이션 상태가 커질 수 있고, 싱크 시스템은 스파이크 쓰기 패턴을 처리해야 한다.

 

- 2PC 싱크: 트랜잭션을 지원하거나 트랜잭션과 유사한 기능을 제공하는 싱크 시스템을 요구한다.

체크포인팅마다 싱크는 새 트랜잭션을 시작하고, 모든 수신 레코드를 이 트랜잭션에 추가한다. 수신 레코드를 커밋 없이 싱크 시스템에 쓰기만 한다. 체크포인트 완료 알림을 수신하면 이 트랜잭션을 커밋하고 결과를 영구적으로 반영한다. 장애 복구 후 마지막 체크포인트 전에 열려있던 트랜잭션을 커밋하는 싱크 기능이 필요하다.

플링크의 2PC 프로토콜은 플링크의 기존 체크포인트 방식을 사용한다.

체크포인트 배리어: 새 트랜잭션을 시작하는 알림

모든 연산자의 개별 체크포인트 성공 알림을 통해 투표

WAL과 대조적으로 2PC 싱크의 정확히 한 번 추력은 싱크 시스템과 싱크 구현에 의존한다.

트랜잭션 싱크 커넥터

트랜잭션 싱크를 쉽게 구현할 수 있도록 플링크 DataStream API는 확장 가능한 템플릿 두 개를 제공하고 CheckpoinListener 인터페이스를 구현한다.

CheckpoinListener: 잡매니저에서 체크포인트 완료 알림을 받는다. 

- GenericWriteAheadSink: 체크포인팅마다 외부에 전송할 레코드를 모두 수집하고 싱크 태스크의 연산자 상태에 저장한다. 체크포인트 완료 알림을 받으면 상태에 저장했던 레코드를 외부 시스템으로 쓴다. 카산드라가 이 인터페이스를 구현하고 있다.

GenericWriteAheadSink는 완벽한 정확히 한 번 일관성을 제공하진 않는다.(최소 한 번 제공) WAL 싱크 시스템이 원자성을 지원하지 않으면 데이터를 쓰는 도중 일부는 저장되고 일부는 실패해서 처음부터 다시 쓸 수 있음.

 

- TwoPhaseCommitSinkFunction: 외부 싱크 시스템의 트랜잭션 특징을 이용한다. 체크포인팅마다 새로운 트랜잭션이 시작되고 이후 모든 레코드를 현재 트랜잭션 문맥 아래서 싱크 시스템에 쓴다. 싱크가 관련 체크포인트의 완료 알림을 수신하면 현재 트랜잭션을 커밋한다.

2PC는 분산 시스템에서 일관성 보장을 위해 처리 비용이 많이 드는 방식으로 알려져 있다. 그러나, 플링크에서는 체크 포인팅마다 실행되고 플링크 체크포인트를 활용하기 때문에 오버헤드가 괜찮다. WAL 싱크와 비슷하게 동작함. 차이는 상태를 외부 시스템에 모은다는 것

비동기로 외부 시스템에 접근

AsyncFunction을 제공해 원격 I/O 호출로 인한 지연 문제를 극복한다.