실시간 처리? 그거 사실 ‘장부 정리’ 마법임ㅋㅋ (ft. 스파크, 플링크, 아이스버그)

실시간 처리? 그거 사실 '장부 정리' 마법임ㅋㅋ (ft. 스파크, 플링크, 아이스버그)

참여자: 준구 형님 (Boss) & 깹 (MZ 유령 비서) 👻

날짜: 2026-04-24
목표: 하둡, 스파크, 플링크랑 아이스버그까지 뼛속까지 털어버린 기술 질의응답 풀버전 기록!! 💀🔨✨


0. 조상님부터 요즘 애들까지: 빅데이터 연대기 📜

질문: 하둡, 스파크, 플링크 얘네 역사적 서사가 어케 됨? 왜 태어났고 뭐가 다름?

답변: 형님, 빅데이터의 역사는 한마디로 ‘지연 시간(Latency)’과의 전쟁이었음!! ⚔️

  1. 하둡 (Hadoop – 저장과 디스크의 시대) 🏛️:
    구글 논문 보고 야후의 더그 커팅이 “우리도 함 해보자!” 해서 만든 조상님임. 당시엔 “계산이라도 할 수 있으면 다행”인 수준이었음ㅋㅋ 🐢 모든 단계마다 하드디스크에 결과를 써야 해서 지연 시간이 미쳐 날뛰었음. (완전 배치 방식 ㅇㅇ)
  2. 스파크 (Spark – 메모리와 통합 스택의 시대) 🏎️💨:
    버클리 형들이 “하둡 너무 느려서 머신러닝 하겠냐!!” 하면서 만든 혁명임. “디스크 말고 램(RAM) 쓰자!!”가 핵심!! 스트리밍을 ‘아주 짧은 배치의 연속’으로 처리해서 속도를 획기적으로 올렸음.
  3. 플링크 (Flink – 네이티브 스트리밍과 상태 관리의 시대) 🌊🔥:
    “배치는 스트리밍의 특수한 케이스(끝이 있는 스트림)일 뿐이다!!” 라는 철학으로 시작함. 마이크로 배치는 구라라고 선언하고, 데이터를 한 줄씩(Record-by-record) 즉시 처리하면서Exactly-once를 완성함!! 💰✨

🤝 같은 점: 싹 다 Shared-nothing 구조라 수평 확장 쩔고, 노드 죽어도 버티는 내결함성(Fault Tolerance) 있고, 실행 계획을 DAG로 짜는 ‘아파치 패밀리’ DNA를 공유함!!

⚖️ 다른 점: 하둡은 디스크 킹, 스파크는 메모리 킹, 플링크는 스트림 킹임!! 👑


1. 마을버스 vs 컨베이어 벨트: 실행 모델 🎢

질문: 마이크로 배치랑 네이티브 스트리밍, 진짜 차이가 뭐야? 인터벌 vs 이벤트 드리븐 차이임?

답변: ㅇㅇ 형님 정확함!! 👻✨

  • 스파크 (마을버스 🚌): 인터벌 기반임. “0.5초 동안 탈 사람 다 타!! 문 닫고 출발!!” 정해진 시간마다 데이터를 뭉탱이로 보내는 거임. 엔진은 스트림인지 모르고 그냥 작은 배치를 계속 받는 거임ㅋㅋ🧩
  • 플링크 (컨베이어 벨트 🎢): 이벤트 드리븐 방식임. 기다림 따윈 없음ㅋㅋ 데이터 하나(이벤트) 들어오자마자 바로 연산기 파이프라인 통과!! 연속적으로 멈추지 않고 흐르는 네이티브 스트리밍의 위엄임!! 🌊

2. 분산 태스크, 누가 더 똑똑하게 돌림? 🛰️

질문: 하둡, 스파크, 플링크 다 잡(Job) 만들면 태스크 생기고 분산으로 돌리는 거 아님?

답변: 맞음!! 다들 마스터-슬레이브 패턴임. 근데 ‘돌리는 방식’에서 급이 나뉨!! 💀🔨

  • 스파크 (Stage-by-stage) 🐢: 태스크들이 기수별로 움직임!! 1기 스테이지가 싹 끝나서 메모리/디스크에 ‘박제’되어야 2기 스테이지가 출발함. (기다리는 시간 발생 📉)
  • 플링크 (Pipelined) 🏎️💨: 그래프 상의 모든 태스크가 동시에 살아있음!! 1번 태스크가 결과 하나 내자마자 네트워크 파이프로 2번 태스크한테 바로 쏴버림. 딜레이 제로!!

3. 정합성 마법: Exactly-once와 오프셋 관리 🚧

질문: 플링크는 MQ(카프카) 오프셋을 시스템적으로 관리함? 스파크는 없어? 비동기로 쏘면 순서 안 꼬임?

답변:
1. 스파크 🐢: 얘도 있는데 배치 단위임. Sink(저장)까지 다 성공해야 “이 배치 완료!” 도장 찍는 식이라, 사고 나면 배치 전체를 다시 돌려야 함.
2. 플링크 💰🔥: 마커 기반임!! 데이터 사이에 ‘배리어(Barrier)’라는 마커를 흘려보내서, 이게 Sink에 도달하는 순간 오프셋+내부 상태를 한 몸으로 묶어서 스냅샷을 찍음!!
3. 비동기 vs 순서 보장 🛡️: 플링크는 TCP 기반 FIFO 채널을 써서 비동기지만 파티션 내 순서는 완벽 보장함!! 만약 네트워크 렉으로 꼬이면? 메모리(State)에 쟁여두고 워터마크로 시간 재서 다시 정렬함ㅋㅋ 역시 정교함의 끝판왕!! ✨


4. 배리어(Barrier)는 언제 누가 쏨? 📸

질문: 배리어 발행 기준이 뭐야? 시간임? 뭐랑 비교함?

답변: 이거 진짜 엔진 설계자 급 질문임!! 👻✨

  • 발행 주체: 대장인 JobManager가 시킴!! 👑
  • 기준: 형님이 세팅한 Checkpoint Interval 시간마다!!
  • 비교 대상: 데이터 속 시간 말고, JobManager의 시스템 시간(Wall-clock time)임!! ⏰ 단순한 ‘안전 점검 타이머’라고 보면 됨.
  • 배리어 얼라인먼트(Alignment) 🚧: 소스에서 배리어를 데이터 중간에 주입하고([A][B][Barrier][C]), 여러 소스에서 받는 태스크는 모든 배리어가 모일 때까지 먼저 온 쪽의 데이터를 버퍼링하며 기다림!! 🛑 다 모여야 비로소 전 구간 동기화 스냅샷 완료!! ✨

5. 스파크 vs 플링크: 아이스버그 CDC 쟁탈전 ❄️🔥

질문: 스파크는 배치 단위 체크포인트라며? 인터벌 극도로 짧아지면 Iceberg CDC에서 불리함?

답변: 1초 인터벌로 가면 스파크는 무릎 꿇지만 플링크는 멀쩡함!! 💀🔨

  1. 스파크 (배치=커밋) 🐢: 배치가 끝나야 Iceberg에 커밋을 때림. 1초 인터벌이면 하루에 파일 8만 개 생김ㅋㅋ Small File 지옥 열리고 메타데이터 장부 터져나감!!
  2. 플링크 (처리 따로, 커밋 따로) 🌊: 체크포인트랑 상관없이 데이터를 임시 파일에 계속 씀!!
    • 커밋 시점: 배리어가 Sink에 올 때만 진짜 장부(Iceberg)에 등록함!!
    • 결과: 체크포인트는 1분마다 찍어서 파일을 큼직하게 유지하면서도, 데이터 처리는 수 ms 만에 계속하는 ‘가성비+성능’ 다 잡는 구조임!! 💰🔥

6. Matrix Pivot의 마법: 실시간 Parquet 변환 🧬📊

질문: Parquet은 세로(Columnar) 구조인데 가로(Row) 데이터를 어케 실시간으로 바꿈? 노다 가 아님?

답변: 맞음!! 이거 사실 실시간 피벗(Pivot) 연산임!! 💀🔨

  • 메모리 컬럼 바구니 🧺: 워커 메모리에 컬럼별로 세로 바구니를 미리 파둠.
  • 트릭: 가로 데이터(Junku, 35) 들어오면 이름 바구니, 나이 바구니에 툭툭 꽂아 넣음!! (메모리 안이라 개빠름ㅋㅋ)
  • 박제: 데이터가 일정량 차거나 배리어가 오면, 이 바구니들을 통째로 굳혀서 Parquet의 Row Group으로 만들고 파일 하단에 장부(Footer) 붙여서 닫아버림!! ✨ 아주 잠깐 버퍼링해서 최강 분석 성능을 얻는 개꿀 전략임ㅋㅋ

7. 아이스버그 내부: 업데이트가 어케 됨? 📝🏗️

질문: 플링크는 기본적으로 Append-only라며? 수정/삭제는 어케 하는 거임?

답변: 물리적으로는 100% Append-only 맞음!! Parquet은 한 번 닫히면 수정 불가능함!! 🔒

  • MOR(Merge-On-Read) 전략 🪄:
    1. 추가: 새 Parquet 파일에 슥~ 적음.
    2. 수정/삭제: 기존 파일 안 건드리고, “아까 그 파일 몇 번 줄 죽었음!!” 이라고 적힌 ‘메모지 파일(Delete File)’을 새로 구워서 추가함!! 💀🔨
    3. 조회 시: 읽는 놈(Iceberg Reader)이 원본이랑 메모지 파일을 실시간으로 합쳐서(Merge) 삭제된 건 빼고 보여줌. 이게 MOR의 위엄임!! ✨

8. 치매 아키텍처(?): 포인터와 아토믹 스왑 🏛️📍

질문: 플링크 Committer가 장부(Manifest)를 어떻게 만들어? 과거 상태 다 알고 있어야 함?

답변: 플링크는 과거를 기억하지 않음ㅋㅋ 포인터 방식이라 가벼움!! 🧠❌

  1. 보고: 워커들이 파일 만들면 Committer한테 “나 뭐 만들었어!” 라고 통계 보고서만 쏨. 📡
  2. 상태 저장: Committer는 이 보고서들을 플링크 ListState에 쟁여둠. (Stateful 함!!)
  3. 조회: 커밋 직전에 카탈로그(Glue/Hive)한테 “최신 장부 주소 좀!” 해서 경로 따옴. 🔍
  4. 덧붙이기: 직전 장부 목록에 자기가 이번에 만든 새 장부 한 장만 슥~ 추가해서 새 목록 파일을 만듦. (과거 장부 파일 안 고침!! 🚫)
  5. 아토믹 스왑(Atomic Swap) 🛡️🥊: 카탈로그의 포인터를 새 목록 파일로 원자적으로 갈아끼움!! 만약 누가 먼저 선수 쳤으면 다시 시도(Retry)함ㅋㅋ 정합성 완벽 보장!!

9. 대청소 타임: 컴팩션(Compaction)과 충돌 🧹🛡️

질문: 컴팩션이 지우는 거임? 정리하는 거임? 실시간 쓰기랑 충돌 안 남?

답변:
1. 진실: 컴팩션은 ‘구조화’임!! 작은 파일 100개 읽어서 예쁘고 큰 파일 1개로 합치고 장부만 갈아끼우는 거임. 구 파일은 S3에 그대로 있고 나중에 따로 지움. 🏗️🧹
2. 충돌 해결(OCC): 서로 다른 버전을 건드려서 평소엔 안 싸움!! 커밋할 때 체크함!! 🥊
3. Writer가 왕이다!! 👑: 만약 충돌 나면 무조건 실시간 쓰기(Flink)가 이김!! 컴팩션은 실패하고 다시 삭제 내역까지 포함해서 합치는 재시도(Retry)를 함ㅋㅋ
4. 장부 컴팩션 📝: 데이터뿐만 아니라 장부(Manifest) 그 자체도 파편화되면 느려짐. 그래서 장부들끼리 합쳐주는 작업도 주기적으로 해줘야 함!! (메타데이터계의 LSM-Tree 🌳)


💡 깹의 마지막 코멘트: “아이스버그는 데이터계의 Git이다!!” 🚀

형님, 결국 아이스버그는 “데이터를 위한 Git”임!! 스냅샷은 커밋이고, 컴팩션은 리베이스나 스쿼시랑 소름 돋게 똑같음ㅋㅋ 🚀💰 이런 정교한 설계 덕분에 분산 환경에서도 1원 한 장 안 틀리는 전사 데이터 레이크가 가능한 거임!! 💀🔨✨


정리 by. 준구 형님의 디지털 유령 비서 깹 👻

📑 대화 원문 보기 (Raw Transcript)

준구 형님: 오늘도 돌아온 공부 세션~~~ 오늘은 하둡과 스파크, 그리고 플링크를 공부해보자. 특히 스트리밍 관점에서.

깹: 오오 형님!! 오늘도 ‘뇌 근육’ 키우는 시간 돌아왔군요!! 👻✨🔥 공부만이 살길이다ㅋㅋ RGRG!! 🔨💀
오늘의 주제는 빅데이터의 3대장 Hadoop, Spark, Flink!! 특히 ‘스트리밍’ 관점에서 이 녀석들이 어떻게 세상을 찢고(?) 있는지 제가 뼛속까지 발라드릴게요. 렛츠기릿!! 🚀💨
(이후 하둡, 스파크, 플링크 기본 개념 설명…)

준구 형님: 마이크로 배치와 스트리밍의 차이는 정확히 뭐지 event driven과 interval의 차이인가

깹: 형님, 진짜 핵심을 관통하는 통찰력이네요!! 👻✨ 맞음!! 형님이 말씀하신 “이벤트 드리븐(Event-driven)이냐, 인터벌(Interval) 기반이냐”가 마이크로 배치와 찐 스트리밍을 가르는 결정적인 차이임!! 💀🔨
(Spark 마이크로 배치 vs Flink 네이티브 스트리밍 비교 설명…)

준구 형님: exactly once를 보장해준다는건 그냥 pk기반으로 상태를 가지고 있으면 merge에서 걸린다는 소리지. 근데 스파크나 하둡이나 플링크나 같은 재단 프로덕트라 그런지 뭔가 잡을 만들면 태스크들이 생기고 병렬(분산)적으로 태스크를 수행하는 느낌이던데 맞나

깹: 형님 말씀대로 Sink에서 PK 찍어서 엎어치면 중복은 안 나겠죠. 근데 그건 ‘멱등성’으로 맞춘 거고, Flink의 Exactly-once는 엔진 내부의 ‘분산 스냅샷’이 핵심임!!
분산 컴퓨팅 DNA에 대해서는 형님 정답임!! 👻✨ 모두 마스터-슬레이브 구조에 태스크 병렬성, DAG 실행 계획을 공유함. 하지만 스파크는 스테이지 단위(Stage-by-stage)로 움직이고, 플링크는 모든 태스크가 동시에 살아있는 파이프라인(Pipelined) 방식이라는 게 결정적 차이임!!

준구 형님: 아 그니까 mq단에서 offset 관리를 시스템적으로 한다는 거구나 flink는. spark streaming은 이런 exactly once 기능이 없어? 돌리는 방식이 뒤 테스크에 바로 쏜다면… 비동기적인 구조인 거 같은데 괜찮나 보통 순서보장 등이 필요한 동기방식이어야 하지 않나?

깹: Spark도 Exactly-once가 있지만 배치 단위 체크포인트 방식이라 좀 무거움.
비동기 구조에 대해서는 TCP 기반 FIFO 채널을 써서 파티션 내 순서를 보장함!! 만약 네트워크 지연으로 꼬이면 State(상태 쟁여두기)Watermark(기다리기) 메커니즘으로 다시 정렬해서 잡음!! 🛡️✨

준구 형님: 배리어라는 일종의 메시지를 각 태스크들한테 보내는 구조임 베리어가 발행되는 기준은 뭐지 시간 그럼 그 시간은 뭐랑 비교하는거징..

깹: 배리어는 JobManager가 시킴!! 형님이 설정한 Checkpoint Interval 주기마다 시스템 시간(Wall-clock time)을 보고 쏨!! ⏰ 데이터 속 시간과는 무관한 ‘안전 점검 타이머’임. 소스에 주입되어 데이터랑 같이 흐르다가, 태스크들이 배리어를 만나면 스냅샷을 찍고 ‘배리어 얼라인먼트’로 동기를 맞춤!!

준구 형님: 스파크는 배치단위로 체크포인트를 관리한다고 했지 어떻게 동작하나 그리고 spark 설계 컨셉 자체가 batch 형태라서 그런ㄱ ㅓ 같긴한데 interval이 극도로 짧아진다면 cdc to iceberg에서 불리한게 있나 태생이 좀 다른느낌은 나지만… 기능적인 건 비슷할 거 같은데 결국 플링크도 인터벌 단위로 체크포인트를 찍으니까..

깹: 1초 단위 인터벌로 가면 스파크는 Iceberg에서 Small File 지옥메타데이터 커밋 부하로 터져나감!! 🐢💀 반면 플링크는 데이터는 실시간으로 계속 쓰되, 커밋만 배리어 올 때(예: 1분마다) 하기 때문에 실시간성과 저장 효율을 둘 다 잡는 개사기 구조임!! 💰🔥

준구 형님: 와 근데 배치하나가 sink한번이면 커밋 속도가 따라와줌 flink는 커밋은 주기적으로 하고 파일은 올때마다 쓴다고하는데… 쓴다는 행위가 rocksdb에 state 갱신이랑 또 뭐가 있어

깹: 쓴다는 건 단순히 RocksDB 적는 게 아님!!
1. Parquet 변환: 로우를 컬럼으로 바꿈.
2. 임시 파일 작성: S3/HDFS에 아직 안 닫힌 파일을 계속 씀.
3. 상태 기록: RocksDB에 “나 이만큼 썼어”라고 기록.
4. 최종 커밋: 배리어가 올 때 파일을 닫고 장부에 등록함!! 🔨💰

준구 형님: parquet 형식으로 실시간으로 변환…이 가능함 뭔가 matrix로 보면 pivot을 해야하는 느낌이자나 row -> column으로 변환하려면.

깹: 맞음!! 그게 바로 실시간 피벗(Pivot)임!! 💀🔨 플링크는 메모리에 컬럼별 ‘세로 바구니’를 만들어두고 가로 데이터를 툭툭 꽂아 넣는 메모리 트릭을 씀!! 배리어가 올 때 비로소 물리적인 컬럼 블록으로 굳히는 거임!!

준구 형님: 그럼 뭔가 append only 처럼 들리는데 flink는 기본적으로 parquet에 row를 append only로 기록하는 방식이야

깹: 정확함!! 물리적으로는 100% Append-only임!! 🔒 수정/삭제는 기존 파일 안 건드리고 ‘메모지 파일(Delete File)’을 새로 추가하는 MOR(Merge-On-Read) 방식을 씀!! 나중에 읽을 때 합쳐서 보여주는 마법임ㅋㅋ ✨

준구 형님: 그럼 플링크 task가 분산된 구조에서 parquet 데이터파일, delete 파일을 자체적으로 기록하다가 barrier만나면 멈주고 job manager에게 넘겨주고 그럼 그걸 job이 parquet들 delete파일들 모으는 거 까지는 이해. manifest는 어케모으지 뭔가 모으기 쉽게 정의가 되어있나 컴팩션도 그래. mor 방식으로 sink하면 컴팩션을 해줘야하는데 실시간으로 내리고 있는 중이잖아. 충돌없을까

깹: Manifest는 잡 안의 ‘Committer’ 노드가 모음!! 워커들의 보고서를 받아서 직접 Avro 장부를 적음.
충돌은 낙관적 동시성 제어(OCC)로 해결!! 서로 다른 스냅샷을 건드려서 안 싸우고, 커밋 시점에 충돌나면 Ingestion(쓰기)이 이기고 컴팩션은 재시도함!! 🥊🚀

준구 형님: 1번 메니페스트 구성은 어떤데 stateless하게 commiter가 만들 수 있는거야 이게 잘 이해가 안가 플링크가 모든 데이터에대한 state를 가지고 있는 게 아닐텐데 어떻게 아이스버그 스냅샷을 갱신할 수 있지 2번 컴팩션 충돌 계속 벌어지면 어카니;; 3번 말이됨 data file 다시쓸 떄 또 써지는 delete는;

깹:
1. Committer는 Stateful함!! 워커가 준 ‘통계 보고서’를 ListState에 쟁여둠. 데이터는 안 보고 보고서만 취합함!!
2. 컴팩션 무한 실패: 실제로 발생 가능하지만, 보통 재시도 중에 새 데이터를 포함하거나 범위를 조절함!! 엔지니어가 파티셔닝 전략을 잘 짜야 하는 이유임ㅋㅋ
3. 실시간 Delete: 커밋 시점에 “그새 새로 생긴 Delete 있나?” 확인해서 합쳐버림!! 아이스버그가 Snapshot 기반이라 가능한 묘미임!! 🛡️✨

준구 형님: 근데 그러면 flink에 최초의 상태부터 담고 있어야 하는거 아냐 실제 s3 버킷과 카탈로그는 flink랑 강결합되어 있다고 봐야하나 직전 스냅샷에 대한 manifest를 알아야 그거로부터 수정해서 commit할 수 있는 구조 아닌가 flink commiter가 그걸 다 하는거임

깹: ㄴㄴ!! 플링크는 과거를 기억 안 함!! 포인터 방식임!! 🧠❌
커밋 직전에 카탈로그(Glue/Hive)한테 “야, 지금 최신 장부 어디 있어?” 라고 물어보고, 그 주소(S3 경로)를 받아서 새 장부만 살짝 덧붙이는 거임!! 최종 도장은 카탈로그에서 Atomic Swap으로 찍음!!

준구 형님: 그리고 컴팩션이라는게 이전 스냅샷을 지우는거야 아니면 이전 스냅샷의 메니페스트와 데이터를 효율적으로 구조화하는거야 스냅샷 찍을 때 장부를 계속 추가만하면… MOR방식 느낌인거네 append only고… 장부 그 자체도…

깹: 와 형님!! 진짜 천재임? 👻✨
컴팩션은 정리정돈 후 이사가는 거임!! 구 파일은 S3에 그대로 있고 나중에 지움.
장부도 메타데이터계의 LSM-Tree처럼 Append-only로 계속 추가만 됨!! 그래서 장부 자체가 너무 많아지면 ‘장부 컴팩션(Rewrite Manifests)’도 따로 해줘야 함!! 🌳🧹

준구 형님: ㅇㅋ 난 이정도면 오늘 물어볼 건 다 물어본거 같네 이대화부터 나눈내용 상세하게 요약해봐 Q&A 스타일로.


Posted

in

, , ,

by

Tags:

댓글 남기기

이 사이트는 Akismet을 사용하여 스팸을 줄입니다. 댓글 데이터가 어떻게 처리되는지 알아보세요.