https://medium.com/javarevisited/9-best-java-profilers-to-use-in-2024-cc5d21f46f00

 

9 Best Java Profilers to Use in 2024

Recently, I was working on a third-party Linux application, and I started encountering instances where I figured that one of my…

medium.com

 

위 글에서 저자는 프로파일러는 무엇인지, 어떻게 동작하는지, 어떤 사례가 있는지를 설명하고 관련 툴 9가지에 대해 설명한다. 

저자는 결론적으로 YourKit과 Digma를 결합하는 것이 최상의 최적화 결과를 얻는 데 가장 적합했다고 말한다.

Grafana는 애플리케이션 로그 시각화를 위한 것이고 YourKit은 잠재적인 병목 현상에 대한 애플리케이션 프로파일링을 위한 것이며 Digma는 잠재적인 문제가 있는 코드 조각에 대한 원활한 통찰력을 제공하는 것입니다.

날이 따뜻해지니 작년 여름의 힘들었던 라이딩이 생각나며 오픈페이스 헬멧이 사고 싶어졌다. 

오픈페이스 헬멧을 사려고 하니 쉴드보다는 고글이 멋있을것 같아 고글까지 같이 알아보는 중이다. 

 

오픈페이스 이면서 안전 인증을 받고 가능하면 소두핏으로 알아보니 세개 브랜드 헬멧으로 좁혀졌다

블레이드라이더의 바이킹과 모테로스 레트로버 그리고 쿤타치 모즈앤락커즈 .. 

블레이드 라이더는 송정동의 비티샵에서 볼수 있는것 같고 레트로버는 모테로스 강남, 마포 두군데서 볼수 있는것 같다. 

쿤타치는 어바너스에서 판매하는것 같다. 

다 다르다 헬멧이라 한번써봐야하는데 3군데 다 가보려니 너무 귀찮다. 시내를 바이크 가지고 가기도 싫은데 서울을 한바퀴 돌아야 한다. 

sol ao-1 도 좋은것 같은데 재고가 없는것 같다. 

 

모든 브랜드가 모여있는 매장이 있으면 좋겠다. 

고글은 헬멧을 정하고 헬멧에 어울리는 변색렌즈 고글로 알아봐야겠다.

셋째날

주요루트 : 산방산 -> 색달해수욕장 -> 쇠소깍 테라로사 카페 -> 표선해수욕장 -> 탐만장 -> 성산일출봉 -> 세화해변

 

이날도 오전에는 날이 좋았다가 오후부터 흐려지기 시작했다. 

산방산에서 내리막길로 가는 길이 있는데 그길을 보니 카메라가 없는것이 너무 아쉬웠다. 그 멋진길을 카메라에 담았어야 하는데.. 왜 카메라를 가져올 생각을 못했을까.. 색달해수욕장으로 가는길에 유채꽃밭이 보여서 바이크 사진 찍어 주고 쇠소깍으로 이동하여 테라로사에서 커피한잔 했다. 테라로사가 제주에도 있는지 몰랐는데 느낌도 비슷하게 꾸며놨더군. 표선해수욕장은 백사장의 규모가 어마어마 했다. 

 

친구가 탐만장 사장님을 알아서 점심을 그곳에서 먹었는데 돈까스와 떡볶이를 맛있게 먹었다. 탐만장에 들어가자 마자 비가쏟아지기 시작했는데 밥을 먹고 나오니 다행히 비는 그쳐있었다. 그리고 마침 다음날이 쉬는날이라 다음날에는 탐만장 사장님도 같이 라이딩을 하기로 했다. 

표선으로다시 가서 해안길을 타고 성산까지 가려고 했는데 깜박하고 성산으로 바로 가버려서 일주 루트에 구멍이 생겨 버렸다. 

성산에 갔다가 해안도로를 따라 세화까지 가서 커피한잔하고 숙소로 복귀했는데 복귀하는 길에 빗방울이 중간중간 조금씩 날려서 급하게 돌아갔다. 

 

넷째날

주요루트 : 1100고지 -> 5.16 도로 -> 비자림로 -> 송당 스타벅스 -> 표선 -> 성산 -> 막둥이 해녀 복순이네 -> 월정리 

제주도 투어 마지막날 이었는데 날이 매우 좋아서 더있고 싶은 마음이 간절했지만 다음을 기약하며 집으로 돌아가야 한다. 

 

이날은 탐만장 사장님이 인스타360을 이용해서 간간히 영상촬영을 해주셨다. 처음에 설정이 잘못되서 타임랩스로 촬영된 부분이 있다. 1100고지에서 탐만장 사장님과 조인 후 송당 스타벅스로 이동했다. 이후 전날 채우지 못한 표서 성산 구간을 채우기위해 표선으로 내려와 성산까지 갔고 막둥이 해녀 복순이네 서 점심을 먹었다. 

송당스타벅스

해안도로를 따라 월정리까지 이동 후 나는 제주항 근처의 숙소로 가면서 나머지 구간을 채웠다. 

3일간 제투 투어를 하며 제주를 한바퀴돌면서 내륙을 왔다갔다 했다. 

 

다섯째날

주요루트 : 제주항 -> 완도 -> 필암서원 -> 완주

집으로 돌아가기 위해 7:20 배를 타고 완도로 갔다. 완도에는 10:00 쯤 도착했다. 여수까지 오는길이 너무 힘들어서 올라갈때는 중간에 한번 쉬어 갔다. 올라가는 길에 필암서원을 들렸는데 필암서원보다 그 앞에 있던 정자에서 햇빛을 맞으며 솔솔부는 바람을 느끼며 잠시 누워 쉬었는데 그때가 아직 생각난다. 

 

여섯째날

주요루트 : 완주 -> 천안 할리우드 카페 -> 양평 하우스베이커리 -> 서울

9시쯤 출발해서 천안 할리우드 카페에서 점심을 먹었다. 할리우드를 네비에 찍고 갔는데 네비가 근처에서 끊겨서 지나치는 바람에 좀 더 올라가서 돌아오려고 했는데 웬지 산속으로 올라가고있어 당황스러웠는데 다행히 카페 주차장이 보여 들어가 돌려 나올 수 있었다. 하지만 언덕에서 돌리다가 꿍을 하고 말았다. 긴 여행의 마지막날 결국 꿍을 했지만, 다치지는 않아 다행이다. 

친한형이 양평 하우스베이커리에서 일을 한다고 해서 하우스베이커리에 가서 커피한잔하고 집으로 왔다. 

 

집에와서 트립정보를 보니 총 1417 km 를 5박 6일간 달렸다. 

기간 : 3/10 ~ 3/15

주요루트 : 서울 -> 여수 -> 제주 -> 완도 -> 완주 -> 서울

 

 

첫째날

집에서 9시쯤 출발하여 여수에 도착하니 오후 6시쯤 되었다. 트립초기화후 거리는 405 키로미터 였다. 날이 생각보다 추워서 가는 동안 덜덜 떨면서 갔다. 해지기전에 도착하기 위해 카페, 식당 도 들리지 않고 중간중간 편의점하고 주유소만 잠깐식 들려 쉬면서 갔다.

저녁을 먹고 라또아라는 카페에서 10시까지 배 시간을 기다렸다. 카페 클로징 시간쯤 가니 차를 배에 싣는 시간하고 비슷했다. 차를 싣고 다시 터미널로 와서 한시간쯤 기다리고 배에 탑승할 수 있었다. 

여수 도착 트립 거리

 

둘째날

주요경로 : 제주항 -> 은희네해장국 -> 이호테우 해변 -> 협재해수욕장 -> 신창풍차 해안도로 -> 송악산 -> 방주교회 -> 구억불사나이 

숙소 : 담모라호텔&리조트

 

0:20 배는 출발하고 나는 침대칸에서 잠을 자면서 제주에 도착하기를 기다렸다. 피곤해서 잠을 푹 잘줄 알았는데 생각보다 잠이 안와서 몇번을 깼다. 6시쯤 제주에 도착하고 제주 사는 친구가 추천해준 은희네 해장국집에서 아침을 먹고 서쪽해변부터 해안도로를 돌기위해 먼저 이호태우 해변으로 갔다. 

친구가 같이 투어를 해준다고 해서 협재해수욕장에서 만나기로 했다. 협재까지 가는 동안 애월 해안도로를 타고 가는데 길이 꼬불꼬불하니 재밌었다. 네비는 자꾸 큰길로 나가라고 했지만 꿋꿋하게 해안도로를 따라 갔다. 

협재해수욕장

이호테우 해변까지는 날씨가 좋았는데 점점 흐려지고 있다. 

신창풍차 해안도로를 타고가다가 친구가 추천해준 카페에서 커피와 빵을 좀 먹고 사진도 찍었다. 엄청 크고 조경이 잘되어 있는 한옥카페다.

 

커피와 빵을 먹고 힘을내서 다시 송악산 산방산 방주교회를 들러 구억불사나이에서 짬뽕을 먹고 나오니 비가 오기 시작 친구 농장 창고에 바이크를 세워두고 숙소로 이동했다. 새벽부터 달려서 마무리한게 두시쯤 이었지만 생각보다 많은곳을 들릴수 있었다. 

등갈비 짬뽕

숙소는 담모라호텔&리조트 라는 곳인데 산방산이 보이는 숙소이고, 트윈룸을 예약했는데 침대 두개가 다 퀸사이즈 침대였다. 2박에 78000쯤으로 예약했는데 완전 맘에든 숙소여서 담에 가족들하고도 갈수 있을가 싶어 애견동반 가능여부를 문의 했는데 아쉽게도 애견동반은 안된다고 했다. 

산방산뷰

작년 가을에 가려고 했으나 못갔던 제주도 투어를 다음주에 가려고 한다. 

제주내에서 2박 3일, 항구까지 2일 해서 4박 5일 정도의 일정을 생각중이다. 

 

바이크를 어떻게 가져갈지 고민했는데 목포항에서 배타고 들어가기로 했다. 

  • 탁송을 보낼지
  • 배를 탈지
    • 완도에서 탈지
      • 2시간 30분으로 배 소요시간이 가장 짧다. 
      • 02:30 / 05:10, 15:00 / 17:40
    • 목포에서 탈지
      • 5시간 소요
      • 01:00 / 06:00, 08:45 / 13:15
    • 여수
      • 00:20 / 06:00

완도 배 시간은 좀 애매하다. 목포에서 1시 배를 타고 가는동안 잠을 자고 도착해서 바로 투어를 시작하는게 좋을것 같다. 

하지만 월요일에는 1시 출발하는 배가 없다. 여수를 살펴보니 00:20 출발하는 배가 있어서 결국 여수에서 출발하기로 함

인천에서 출발하는 배가 있으면 좋겠는데 무슨일인지 없다. 

 

집에서 목포항까지 6시간 13분 432KM 거리이다. 

중간에 쉬고 밥먹고 차를 23시쯤 선적한다고 해도 시간이 충분하다. 

432km 는 한번에 가는 최장거리가 될것 같다. 

 

여수 엑스포 여객선터미널까지 집에서 407KM

중간에 쉬고 밥먹고 차를 22시쯤 선적한다고 해도 시간이 충분하다. 체력이 따라줄지가 문제다.

407km 는 한번에 가는 최장거리가 될것 같다. 

 

제주도에서는

첫째날 제주 한바퀴를 돌아볼까 생각중 

둘째날 1100고지, 사려니숲길 등 내륙에서 갈만한 곳들을 투어하고 

셋째날 육지로 돌아가기

 

제주도 맛집

  • 고기덕후
  • 은희네해장국
  • 명진전복
  • 평대스낵
  • 벵디
  • 금능낙원(새벽오픈)
  • 삼무국수
  • 우진해장국
  •  

카페

  • 친봉상장
  • 동광
  • 델문도
  • 보롬왓
  • 몽상드애월
  • 서연의집(건축학개론)
  • 휘닉스
  • 카페루시아

갈곳

  • 신창 풍차해안도로
  • 516도로
  • 비자림로
  • 월정리 해안도로
  • 1100 고지
  • 방주교회
  • 산방산
  • 녹산로유채꽃도로

제주도에서 돌아올때는 완도로 갈지 목포로갈지 좀 고민된다. 

완도행 : 07:20 / 10:00, 19:30 / 22:10 

목포행 : 13:40 / 18:10, 16:45 / 21:15 

 

아침 일찍 완도로 와서 좀 천천히 올라가면서 둘러보고 여유롭게 집으로 갈지 

제주도에서 최대한 늦게까지 있다가 19:30 배를 타고 완도로 와서 일박하고 다음날 집으로 빡시게 이동할지

목포로 와서 일박하고 다음날 집으로 다시 빡시게 올라갈지 

고민된다. 

 

완도로 일찍와서 올라가는 길 중간에 일박하고 가는것으로 정했다.

 

 

 

chat gpt에게 물어본 결과

 

"At-Most-Once" 처리는 메시지가 최대 한 번 처리됨을 보장합니다. 이는 메시지가 유실될 수 있지만, 중복 처리는 발생하지 않습니다. Kafka에서 "At-Most-Once" 처리를 구현하는 방법은 다음과 같습니다:

### 1. 자동 오프셋 커밋 활성화
- **자동 오프셋 커밋:** `enable.auto.commit` 설정을 `true`로 설정하여 자동 오프셋 커밋을 활성화합니다. 이렇게 하면 메시지를 폴링한 직후 자동으로 오프셋이 커밋되므로, 처리 중에 애플리케이션이 실패하더라도 이미 커밋된 오프셋 이후의 메시지는 재처리되지 않습니다.

### 2. 메시지 처리 전 오프셋 커밋
- **오프셋 미리 커밋:** 메시지 처리 전에 미리 오프셋을 커밋함으로써, 해당 메시지가 처리되기 전에 실패가 발생하더라도 중복 처리되지 않도록 합니다. 이 방식은 메시지 유실의 가능성을 수용하는 경우에 적합합니다.

### 3. 빠른 실패 메커니즘
- **예외 처리:** 메시지 처리 중에 예외가 발생하면, 즉시 처리를 중단하고 실패로 간주합니다. 이를 통해 메시지 처리를 재시도하지 않고 다음 메시지로 넘어갈 수 있으며, 이는 "At-Most-Once" 처리의 특징을 충족시킵니다.

### 4. 최소한의 재시도 로직
- **재시도 최소화:** "At-Most-Once" 처리 모델에서는 메시지 처리를 위한 재시도 로직을 최소화하거나 사용하지 않습니다. 메시지 처리 시도가 실패하면 해당 메시지는 버려지고, 시스템은 다음 메시지로 넘어갑니다.

### 5. 신속한 메시지 소비
- **효율적인 메시지 소비:** 메시지 처리 로직을 가능한 한 신속하게 수행하여, 시스템이 빠르게 다음 메시지로 이동할 수 있도록 합니다. 이는 시스템의 전반적인 처리량을 최적화하는 데 도움이 됩니다.

"At-Most-Once" 처리 전략은 메시지 중복 처리를 방지하는 데 초점을 맞추지만, 이로 인해 메시지 유실이 발생할 수 있다는 점을 명심해야 합니다. 따라서, 이 전략은 메시지 유실이 비즈니스 로직에 큰 영향을 미치지 않는 시나리오에 적합합니다. 중요한 데이터를 처리할 때는 다른 전송 보증 방식("At-Least-Once" 또는 "Exactly-Once")을 고려하는 것이 좋습니다.

오른쪽 엄지 발가락과 발바닥이 연결되는 부위에 난생 처음 티눈이 생겼다. 

처음에는 조금 걸리적 거리는 수준이었지만 조금 지나니 걷기가 힘들어 졌고 절뚝거리게 되었다.

 

티눈을 제거하기 위해 방법을 알아보니

  • 병원에 가서 레이저나 급속냉동시켜 제거하는 방법
  • 티눈액이나 밴드를 이용하여 집에서 자가로 치료하는 방법
  • 유튜브를 보니 전문가가 메스로 깊숙히 파내는 방법도있는것 같다

병원에가서 치료하는 것은 아프고 시간도 오래걸리는 것 같아 집에서 티눈밴드를 이용하여 자가 치료하기로 했다. 

 

발바닥형 티눈밴드를 사서 붙였다. 티눈 밴드 중심에 살리실산 액이 묻어있는 동그란 핵 같은 부분을 티눈위치에 붙여야 했다. 티눈밴드만 붙이니 움직일때마다 밴드가 떨어져서 일반 밴드 두개를 붙여 안움직이도록 고정했다. 

4일쯤 지나서 제거하려고 보니 티눈 부위가 하얐게 불어 있었고 손톱깍기를 이용하여 제거하려고 했는데 살짝 대기만 해도 너무 아파서 깍지는 못하고 살살 긁어봤다. 하지만 티눈을 제거하는것은 실패 했다. 

 

다시 밴드를 붙이고 이틀정도 지나 제거를 시도해봤다. 이번에는 집에있던 핀셋과 메스와 비슷하게 생긴 이름 모를 어떤 도구를 사용했다. 

여전히 아팠지만 조금씩 긁어내고 핀셋을 이용하여 뜯어 내었다. 이렇게 반복하다 보니 어느 순간 약간의 피와 함께 덩어리 같은게 뜯어졌다.

발도 편해졌고 걷는것도 문제 없었다. 티눈이 제거 되었다고 생각했다. 

 

하루 정도가 지나고 확인해보니 티눈이 어느정도 제거 되었지만 아직 뿌리가 완전히 제거 된거 같지는 않다. 

티눈 밴드를 좀더 붙이고 몇일지나 다시 제거를 시도하면 뿌리까지 제거가 가능할것 같다. 

 

처음에 손톱깍기는 소독도 안하고 사용했는데 두번째 사용한 핀셋과 메스 비스무리한 도구는 알콜솜으로 소독하고 사용했다. 

 

 

 

카프카 커넥트는 언제 사용하나?

  • 카프카를 직접 코드나 API를 작성하지 않았고, 변경도 할 수 없는 데이터 저장소에 연결시켜야 하는 경우

카프카 커넥트의 구성

  • Worker
    • 커넥터와 태스크를 실행시키는 역할
    • 커넥터 설정을 내부 토픽에 저장
    • 적절한 설정값 전달
    • 소스와 싱크 커텍터의 오프셋 커밋
    • 문제 발생시 재시도
    • REST API, 설정관리, 신뢰성, 고가용성, 규모 확장성, 부하 분산 담당
    • 워커 프로세스의 장애 또는 신규 추가
      • 커넥터 클러스트안의 다른 워커들이 감지
      • 해당 워커에서 실행중이던 커넥터와 태스크를 다른 워커에 할당
  • 커넥터 플러그인
    • 커넥터 API 구현
    • Connector
      • 커넥터에서 몇 개의 태스크가 실행되어야 하는지 결정
      • 데이터 복사 작업을 각 태스크에 어떻게 분할해 줄지 결정
      • 워커로부터 태스크 설정을 얻어와서 태스크에 전달
    • Task
      • 데이터를 실제로 카프카에 넣거나 가져오는 작업 담당
      • 워커로부터 컨텍스트를 받아서 초기화
      • 소스 태스크
        • 소스 레코드의 오프셋을 저장할 수 있게 해주는 객체 포함
        • 외부 시스템을 폴링해서 워커가 카프카 브로커로 보낼 레코드 리스트 리턴
      • 싱크태스크
        • 카프카로부터 받는 레코드를 제어할 수 있게 해주는 메소드들 포함
        • 워커를 통해 카프카 레코드를 받아서 외부 시스템에 쓰는 작업 담당
  • Data API
    • 소스 커넥터에서 데이터 객체를 어떻게 생성할지에 대한 방법을 알려줌
  •  Converter
    • 소스 커넥터에서 카프카에 데이터를 어떻게 쓸지에 대한 방법을 알려줌
    • 카프카에서 데이터를 읽어 컨버터를 통해 데이터 API 레코드로 변환하여 싱크 커넥터로 전달
    • 기본 데이터 타입, 바이트 배열, 문자열, Avro, Json, 스키마 있는 JSON, Protobuf 사용가능
  • 오프셋 관리
    • 소스커넥터
      • 커넥터가 워커에 리턴하는 레코드에 논리적인 파티션과 오프셋 포함
        • 파일 인경우, 파일이 파티션, 파일안의 줄 또는 문자 위치가 오프셋
        • JDBC의 경우, 테이블이 파티션, 테이블의 레코드 id/timestamp 가 오프셋
    • 싱크커넥터
      • 토픽, 파티션, 오프셋 식별자가 포함되어 있는 카프카 레코드를 읽은 뒤 대상 시스템에 저장
      • 성공하면 오프셋 커밋

프로듀서

  • 재시도로 인해 발생하는 중복을 방지
  • 멱등적 프로듀서 + 트랜잭션
  • 멱등적 프로듀서
    • 멱등적 프로듀서 기능 on : enable.idempotence=true, acks = all
    • 식별자 생성
      • producer Id, sequence id 생성
      • 대상 토픽, 파티션
      • max.in.flights.requests.per.connection <= 5
    • 중복시 에러가 발생하지는 않지만 메트릭에는 수집됨
      • client : record-error-rate
      • broker : RequestMetrics - ErrorsPerSec
    • 예상보다 높은 시퀀스 값을 받게 될경우 out of order sequence number 에러 발생
      • 트랜잭션 기능을 사용하지 않는 다면 무시해도 됨
      • 프로듀서와 브로커 사이에 메시지 유실
      • 브로커 설정 재점검 및 언클린 리더 선출 발생 여부 확인
    • 프로듀서 재시작
      • 초기화 과정에서 브로커로부터 프로듀서id를 생성 받음
      • 프로듀서 초기화마다 새로운 id 생성
      • 새 프로듀서가 이미 기존 프로듀서가 전송한 메시지를 다시 전송할 경우 중복될 수 있음
    • 브로커 장애
      • 컨트롤러는 장애가 난 브로커가 리더를 맡고 있었던 파티션들에 대해 새 리더를 선출
      • 새 리더는 중복메시지를 체크하기 시작함
        • 리더는 인메모리 프로듀서 상태에 저장된 최근 5개의 시퀀스 넘버를 업데이트 하고 팔로워는 새로운 메시지를 복제할 때마다 자체적인 인메모리 버퍼를 업데이트함으로써 상태를 알 수 있다. 
      • 장애난 브로커의 복구 
        • 브로커는 종료되거나 새 세그먼트가 생성될 때마다 프로듀서 상태에 대한 스냅샷을 파일 형대로 저장
        • 브로커가 시작되면 파일에서 최신 상태를 읽어옴
        • 현재 리더로부터 복제한 레코드를 사용해서 프로듀서 상태를 업데이트 함
    • 한계
      • 내부로직으로 인한 재시도가 발생할 겨우 생기는 중복만 방지
        • 동일한 메시지를 producer.send를 두번 호출하면 중복 발생

트랜잭션

  • 카프카 스트림즈를 사용해서 개발된 애플리케이션에 정확성을 보장하기 위해 도입 (읽기-처리-쓰기 패턴)
  • 다수의 파티션에 대해 원자적 쓰기(atomic multipartition write) 기능도입
  • 문제상황
    • 애플리케이션 크래시에 의한 재시도
    • 좀비 애플리케이션에 의해 발생하는 재처리
  • Transactional producer
    • transactional.id 설정 
      • 재시작시에도 값이 유지됨
    • initTransactions() 호출로 초기화
      • transactional.id와 producer.id 의 대응관계를 유지함
      • 이미 있는 transactional.id 프로듀서가 initTransactions를 호출하는 경우 이전에 쓰던 producer.id값을 할당
    • Zombie fencing
      • 좀비 인스턴스가 출력 스트림에 결과를 쓰는 것을 방지
      • epoch 사용
      • initTransactions 호출 시 transactional.id에 해당하는 epoch 값을 증가 시킴
      • epoch값이 낮은 프로듀서가 전송 후 트랜잭션 커밋/중단 요청을 보낼경우 FencedProducer 에러 발생
      • close()를 호출하여 좀비 애플리케이션 종료
  • 컨슈머 격리 수준(isolation.level)
    • read_uncommitted(default) : 진행중이거나 중단된 트랜잭션에 속한 모든 레코드 리턴
    • read_committed
      • 커밋된 트랜잭션에 속한 메시지 / 트랜잭션에 속하지 않는 메시지만 리턴
      • 트랜잭션에 속한 일부 토픽만 구독하므로 트랜잭션에 속한 모든 메시지가 리턴된다고 보장되지 않음
      • 트랜잭션이 처음으로 시작된 시점 (Last Stable Offset, LSO) 이후에 쓰여진 메시지는 리턴되지 않음
        • 트랜잭션이 커밋/중단 되는 시점 또는 transaction.timeout.ms 만큼 시간이 지날 때까지 보류
    • transaction.timeout.ms
      • default : 15분
      • 종단 지연이 길어질 수 있음
  • 한계
    • 스트림 처리내에서 외부 효과를 일으키는 작업
    • 카프카 토픽에서 읽어서 데이터 베이스에 쓰는 경우
    • DB에서 읽어서 카프카에 쓰고 다른 DB에 쓰는 경우
    • 한클러스터에서 다른 클러스터로 데이터 복제
    • 발행/구독 패턴
  • 사용법
    • 카프카 스트림즈
      • processing.guarantee= exactly_once / exactly_once_beta 로 설정
    • 트랜잭션 API 사용
      • transactional.id 설정
      • enable.auto.commit = false
      • isolation.leve = read_committed
      • producer.initTransactions()
        • transactional.id 등록 또는 epoch 값 증가
      • producer.beginTransaction()
        • 프로듀서에 현재 진행중이 트랜잭션이 있음을 알림
        • 레코드 전송 시 프로듀셔가 브로커에 AddPartitionsToTxn 요청
        • 트랜잭션 로그에 기록
      • producer.sendOffsetsToTransaction
        • 트랜잭션이 커밋 되기전 호출
        • 트랜잭션 코디네이터로 오프셋과 컨슈머 그룹 아이디가 포함된 요청 전송
        • 트랜잭션 코디네이터는 컨슈머 그룹 아이디를 이용하여 컨슈머 그룹 코디네이터를 찾고 오프셋 커밋
      • producer.commitTransaction / producer.abortTransaction
        • 트랜잭션 코디네이터로 EndTxn 요청 전송
        • 트랜잭션 로그에 기록
        • 트랜잭션에 포함된 모든 파티션에 마커 쓰기
        • 트랜잭션 로그에 기록
          • 로그에기록 되었지만 코디네이터가 종료되거나 크래쉬 되면 새로 선출된 코디네이터가 마무리 짐
      • transaction.timeout.ms 
        • 해당 시간내에 커밋/중단 되지 않으면 트랜잭션 코디네이터가 중단
  • 원리
    • 찬디-램포트 스냅샷 알고리즘 : 통신채널을 통해 카머라 불리는 컨트롤 메시지를 보내고 도착을 기준으로 일관적인 상태를 결정
    • two phase commit, 트랜잭션 로그(transaction_state 내부 토픽) 사용
      • 현재 진행중인 트랜잭션과 파티션을 함께 로그에 기록
      • 커밋/중단 시도 기록
      • 모든 파티션에 트랜잭션 마커를 씀
      • 트랜잭션 종료 로그 기록
  • 모니터링
    • LastStableOffsetLag 지표
      • 파티션의 LSO가 최신 오프셋 값에서 얼마나 떨어졌는지를 나타냄
      • 이값이 무한히 증가하는 상황 : 트랜잭션 멈춤 현상

파티션과 컨슈머

  • 컨슈머는 컨슈머그룹의 일부로서 작동
  • 하나의 파티션에는 하나의 컨슈머가 할당됨
  • 파티션수보다 컨슈머수가 더 많다면 유휴 컨슈머가 생김
  • 파티션수보다 컨슈머수가 적다면 하나의 컨슈머가 여러파티션을 컨슘하여 처리함
  • 애플리케이션에서 모든 토픽의 데이터를 컨슘하기 위해 애플리케이션 별로 컨슈머 그룹을 생성해야 함

파티션 리밸런스

  • 컨슈머가 추가되거나, 크래시되었거나 파티션이 추가되는 등의 변경이 발생하면 컨슈머를 파티션에 재할당하는 작업이 실행되고 이 과정을 리밴런스라 함
  • Eager rebalance(조급한 리밸런스)
    • 모든 컨슈머가 읽기 작업을 멈추고 모든 파티션에 대한 소유권을 포기한 후 컨슈머 모두가 그룹에 다시 참여하여야 새로운 파티션을 할당받도록 하는 방법
    • Stop the world
  • Cooperative rebalance(협력적 리밸런스) / Incremental rebalance(점진적 리밸런스)
    • 컨슈머 리더가 일부 파티션 재할당을 통지하면, 해당 컨슈머들은 작업을 중단하고 소유권을 포기한 후 이 컨슈머들을 새로운 파티션에 재할당 하는 방법
    • 안정적으로 파티션이 할당될때까지 반복될 수 있음
    • Stop the world는 발생하지 않음
  • 2.4 이후 Eager rebalance가 default 였으나 3.1 부터 Cooperative rebalance가 default가 되고 Eager rebalance는 deprecated 될 예정
  • session.timeout.ms에 설정된 값(default - 2.8 : 10초, 3.0:45초)동안 컨슈머가 heartbeat를 보내지 않으면 해당 컨슈머가 죽은것으로 판단하여 리밸런스 발생
  • max.poll.interval.ms(default 5분) 에 지정된 시간동안 컨슈머가 폴링하지 않으면 해당 컨슈머는 죽은것으로 판단
    • heartbeat는 백그라운드 스레드에서 전송하므로 메인스레드가 블록 되더라도 hearbeat는 전송가능 함
    • 이러한 경우를 판단하기 위해 max.poll.interval.ms로 컨슈머가 정상적으로 컨슘하는지 확인

폴링루프

  • while(true)를 이용한 무한 루프
  • 루프내에서 컨슈머의 poll(Duration) 메소드를 호출하여 메시지를 가져오고, 처음으로 호출되는 경우 GroupCoordinator를 찾아서 컨슈머 그룹에 참가하고 파티션을 할당 받음
  • max.poll.interval.ms에 지정된 시간 이상으로 호출되지 않는 경우 컨슈머는 죽은 것으로 판정되므로 블록되지 않도록 주의 해야함

파티션 할당 전략

  • partition.assignment.strategy 설정
  • Range(default)
    • RangeAssignor
    • 컨슈머가 구독하는 각 토픽의 파티션들을 연속된 그룹으로 나눠서 할당
    • 홀수개의 파티션을 갖는 경우 앞의 컨슈머는 뒤의 컨슈머보다 많은 파티션을 하당받게 됨
  • RoundRobin
    • RoundRobinAssignor
    • 모든 구독된 토픽의 모든 파티션을 가져다 순차적으로 하나씩 컨슈머에 할당
    • 모든 컨슈머들이 동일한 수의 파티션 할당(많아야 1개차이)
  • Sticky
    • StickyAssignor
    • 파티션들을 가능한 균등하게 할당(RoundRobin과 유사)
    • 리밸런스가 발생했을때 가능하면 많은 파티션들이 같은 컨슈머에 할당
    • 컨슈머가 변경되는 오버헤드를 최소화
  • Cooperative Sticky
    • CooperativeStickyAssignor
    • Sticky 와 동일
    • 컨슈머가 재할당되지 않은 파티션으로부터 레코드를 계속해서 읽어올 수 있도록 해주는 협력적 리밸런스기능 지원

오프셋 커밋

  • 파티션에서의 현재위치를 업데이트 하는 작업
  • poll이 리턴한 마지막 오프셋 바로 다음 오프셋을 커밋함(수동으로 오프셋을 다룰 경우 유의)
  • 레코드를 개별적으로 커밋하지 않고 파티션에서 성공적으로 처리해 낸 마지막 메시지를 커밋 함
  • 커밋된 오프셋이 클라이언트가 마지막으로 처리한 오프셋보다 작을 경우 중복 처리 됨
  • 커밋된 오프셋이 클라이언트가 마지막으로 처리한 오프셋보다 클 경우 메시지 누락처리됨
  • 자동 커밋
    • enable.auto.commit = true 로 설정
    • 컨슈머가 대신 커밋
    • auto.commit.intervel.ms(default 5초)에 한번 poll을 통해 받은 메시지 중 마지막 메시지 오프셋 커밋
    • 커밋 인터벌 내에서 리밸런싱이 발생한다면 해당 인터벌내의 메시지는 중복 처리됨
  • 현재 오프셋 커밋
    • enable.auto.commit = false 로 설정
  • commitSync
    • 가장 간단하고 신뢰성있는 commit API
    • poll에 의해 리턴된 마지막 오프셋 커밋
    • poll에서 리턴된 모든 메시지를 처리하기 전 commit 할 경우 메시지가 누락될 수 있음
    • 브로커가 커밋 요청에 응답할 때 까지 블록됨
    • 성공하거나 재시도 불가능한 실패가 발생할때까지 재시도
  • commitAsync
    • 재시도 하지 않음
    • 콜백 처리(OffsetCommitCallback)
    • 콜백에서 재시도 처리시 커밋의 순서를 유의 해야 함
      • Apache Kafka에서 컨슈머의 `commitAsync` 메소드는 비동기적으로 오프셋을 커밋합니다. 이 방법은 `commitSync`에 비해 성능상 이점이 있지만, 비동기적 특성으로 인해 오프셋 커밋 순서가 보장되지 않는 문제가 있습니다. 즉, 늦게 시작된 커밋이 먼저 완료될 수 있으며, 이로 인해 컨슈머 재시작 시 메시지 중복 처리나 누락의 가능성이 존재합니다.

        ### 커밋 순서 강제 방법

        비동기 커밋 시 순서를 강제하는 명확한 방법은 Kafka 자체적으로 제공하지 않지만, 다음과 같은 방법으로 순서 문제를 완화할 수 있습니다:

        1. **단일 콜백 사용**: 비동기 커밋을 할 때마다 새로운 콜백을 생성하는 대신, 단일 콜백 인스턴스를 재사용하면서 내부적으로 최신 오프셋을 추적하는 방법입니다. 콜백 내에서는 커밋된 오프셋이 현재 알고 있는 최신 오프셋보다 이전인지 확인하고, 이전이면 무시합니다. 이 방법은 순서를 완벽하게 보장하지는 않지만, 순서 문제로 인한 영향을 최소화할 수 있습니다.

        2. **커밋 직렬화**: 오프셋 커밋 요청을 내부적으로 관리하는 큐를 구현하여, 이전 커밋 요청의 완료를 확인한 후에 다음 커밋을 실행하는 방법입니다. 이 방법은 성능에 영향을 줄 수 있지만, 커밋 순서를 엄격하게 관리할 수 있습니다.

        3. **비동기 커밋 후 동기 커밋으로 마무리**: 정기적으로 또는 특정 조건(예: 애플리케이션 종료 시)에서 `commitSync`를 호출하여 오프셋 커밋을 확실하게 완료하는 방법입니다. 이는 비동기 커밋 과정에서 발생할 수 있는 순서 문제를 마지막에 동기 커밋으로 보정합니다.

        4. **외부 저장소 사용**: 컨슈머가 처리한 오프셋을 외부 시스템(예: 데이터베이스)에 저장하고, 컨슈머 재시작 시 외부 시스템의 오프셋 정보를 기반으로 메시지 처리 위치를 결정하는 방법입니다. 이 방법은 추가적인 외부 시스템 의존성이 필요하지만, 컨슈머의 상태 관리를 더 세밀하게 할 수 있습니다.

        비동기 오프셋 커밋을 사용할 때는 커밋 순서가 보장되지 않는 점을 고려하여 애플리케이션의 로직을 설계해야 하며, 중복 처리나 메시지 누락을 방지하기 위한 추가적인 조치가 필요할 수 있습니다.
  • commitSync, commitAsync 동시 사용
    • 정상적인 상황에서는 commitAsync를 사용함
    • 컨슈머를 닫는 상황에서는 commitSync를 사용하여 재시도하도록 함
  • 특정 오프셋 커밋
    • 배치 처리 중 오프셋을 커밋하고 싶은 경우
    • Map<TopicPartition, OffsetAndMetadata> 을 commitSync/commitAsync에 파라미터로 넘겨 처리
    • 에러 처리
  • 컨슈머가 닫힐때 오프셋 커밋
    • ConsumerRebalanceListener 인터페이스를 구현하여 onPartitionsRevoked 메소드에 커밋 로직 구현
      • Eager rebalance 인경우 컨슈머가 읽기를 멈추고 리밸런스가 시작되기 전 호출
      • Cooperative rebalance 인 경우 리밸런스가 완료될때 할당해제해야할 파티션들에 대해서 호출

Standalone consumer

  • 컨슈머가 스스로 특정 토픽의 모든 메시지를 처리해야 하는 경우
  • 토픽을 구독할 필요가 없음
  • 리밸런스 필요없음
  • List<PartitionInfo> partitions = consumer.partitionsFor("Topic") 을 호출하여 파티션 정보를 조회
  • consumer.assign(partitions)
  • 루프를 돌면서 consumer.poll 호출하여 레코드를 가져와서 처리
  • offset commit
  • 리밸런싱을 사용하지 않으므로 파티션이 추가되는 경우를 자동으로 감지할 수 없으므로 consumer.partitionsFor를 주기적으로 호출하여 감지 하거나 애플리케이션을 재시작 해야할 수 있음

읽기 지연

  • 리더 레플리카는 팔로워 레플리카가 복제한 오프셋을 관리 함
  • 컨슈머는 모든 in sync 레플리카에 복제된 메시지만 읽을 수 있음
  • 복제가 지연되는 경우 컨슈머가 읽는 메시지도 지연될 수 있음
  • replica.lag.time.max.ms 에 정의된 시간 만큼 복제지연이 발 생할 수 있고 이 시간을 지나면 해당 레플리카는 out of sync 레플리카가 됨

 

+ Recent posts