카프카 탄생 배경

최근에 MSA도 그렇고 많은 서비스들이 서로 데이터를 주고받아야 할때 위와 같은 상황이 일어난다. 소스가 어디로 데이터를 보내야 하는지가 굉장히 복잡하게 이어져있는데 데이터 복잡성이나 관리 효율적인 측면에서 굉장히 다루기 어려워지고 있었다.

출처 : 카프카, 어떻게 하면 더 효율적으로 사용할까? (고승범님) 유튜브 영상

이를 효율적으로 관리하기 위해서 데이터를 무조건 카프카로 보내고, Destination에서는 카프카에서 데이터를 가져오는 식으로 구성하면 다음과 같이 된다.

출처 : https://www.youtube.com/watch?v=fikIR4eUkM0

 

카프카 용어 및 설명

* 토픽과 파티션

카프카에서 메시지를 저장하는 단위 ( 파일 시스템의 폴더와 유사하다고 볼 수 있다 )

파티션은 메시지를 저장하는 물리적인 파일이며 한 개의 토픽은 한 개 이상의 파티션으로 구성된다.

그럼 어떤 파티션에 프로듀서가 값을 저장할까?

라운드로빈 혹은 키를 활용해서 파티션을 선택 - 같은 키를 활용하면 메시지의 순서를 유지시킬 수 있음을 의미한다.

 

컨슈머는 컨슈머 그룹에 속하고, 한 개의 파티션은 컨슈머 그룹의 하나의 컨슈머만 연결이 가능하다.

(그룹 내부의 다른 컨슈머가 하나의 파티션을 공유하지 못한다)

출처 : https://www.youtube.com/watch?v=geMtm17ofPY

 

 

카프카의 성능이 좋은 이유

파티션 파일은 OS의 페이지 캐시를 사용하고, 파일 IO를 메모리에서 처리한다. 그러므로 서버에서 페이지 캐시를 카프카만 사용해야 성능에 유리하다.

Zero Copy : 디스크 버퍼에서 네트워크 버퍼로 직접 데이터를 복사한다.

컨슈머 추적을 위해서 브로커가 하는일이 단순하다. 메시지 필터링 메시지 재전송등은 프로듀서나 컨슈머가 직접 해야 한다.

묶음처리(batch)를 통해 묶어서 메시지를 전송(프로듀서), 최소 크기만큼 메시지를 모아서 조회(컨슈머).

확장에 용이한 구조를 가지고 있다. 예를들면 브로커나 파티션을 추가하거나, 컨슈머를 추가하여 처리량을 증대시킬 수 있다.

 

리플리카

replication factor 만큼 파티션의 복제본을 가지고 있음.

리더와 팔로워로 구성하여, 프로듀서와 컨슈머는 리더를 통해서만 메시지를 처리한다.

장애 대응 : 리더가 속한 브로커 장애가 생기면 팔로워가 리더가 됨.

 

 

프로듀서

프로듀서의 기본 흐름, 출처 : https://www.youtube.com/watch?v=geMtm17ofPY

여기서 버퍼전까지 넣어주는 Serialize, Partioner부분까지 별도의 스레드

Sender가 별도의 스레드.

이렇게 별도의 스레드로 구성하기 때문에 동시에 동작한다. 

Sender는 linger.ms(전송대기시간) 설정에 따라 브로커로 메시지를 보내는데, 배치가 덜 차더라도 브로커로 바로 전송한다. 

 

- ACK : 0, 1, ALL

Ack=0 : 빠른 전송, 카프카가 받았는지 확인하지 않는다. (0.29ms)

메시지 손실 가능성

Ack=1 : 빠른 전송, 카프카가 받았는지 체크를 한다. (1.05ms)

메시지 손실 가능성 희박하게 있음. (Ack를 받았는데, Ack를 던져주자마자 다운되면 리더가 죽고 다른 팔로워가 리더가 되는데 레플레케이션이 미쳐 되지 않아서 손실될 수 있다)

Ack = All : 느린 전송, 메시지 손실 없음 (2.05ms)

 

출처 : https://www.youtube.com/watch?v=fikIR4eUkM0 

 

결국 buffer memory안에 메시지를 쌓고 Batch size만큼 이동하는데, linger.ms만큼 지연시간을 가지고 send하게된다.

* 기타 전송 재시도 주의 사항

중복 전송 가능, 순서 바뀜 가능 (설정값으로 컨트롤하기)

 

 

 

 

프로듀서

출처 : https://www.youtube.com/watch?v=geMtm17ofPY

 

 

출처 : https://www.youtube.com/watch?v=geMtm17ofPY

 

처음 접근이거나 커밋된 오프셋이 없는 경우

auto.offset.reset 설정을 사용한다. earliest(제일 처음 오프셋), latest(가장 마지막 오프셋), none(컨슈머 그룹에 대한 이전 커밋이 없으면 익셉션 발생)

기타 컨슈머 설정

fetch.min.bytes : 조회시 브로커가 전송할 최소 데이터 크기 (기본 1)

fetch.max.wait.ms : 데이터가 최소 크기가 될 때까지 기다릴 시간 (기본 500)

max.partition.fetch.bytes: 파티션 당 서버가 리턴할 수 있는 최대 크기 (기본 1MB)

 

출처 : https://www.youtube.com/watch?v=fikIR4eUkM0

파티션내부에서는 순서를 보장하지만, 다른 파티션과의 순서는 라운드로빈형태다.

출처 : https://www.youtube.com/watch?v=fikIR4eUkM0

 

기타 카프카 에코시스템

카프카 커넥트

출처 : https://www.youtube.com/watch?v=fikIR4eUkM0

Simple Producer, Consumer.

Producer와 Consumer를 프레임워크 형태로 제공해주는 것.

출처 : https://www.youtube.com/watch?v=fikIR4eUkM0

 

스키마 레지스트리 - 스키마를 저장하고 이를 통해서 통신할 수 있도록함. (가끔 컨슈머에서 맞지 않는 값때문에 오류가 나는 경우가 있는데 이런 것들을 방지)

 

 

기타 디테일한 옵션 부분

log.retention.hours - 어느정도 기간을 보관할 것인지

실제로 diskfull이 일어나는 경우가 많은데, 기본 옵션이 1주일이므로, 생각보다 크게 잡힌다.

delete.topic.enable - 토픽삭제 가능 옵션. diskfull이나면 삭제할 수 있도록.

allow.auto.create.topics - 자동 토픽 생성 옵션.

log.dirs - 초기 옵션은 Temp 폴더에 저장되도록 되어 있는데, Temp는 실제로 임시 저장소의 의미로 OS에 의해서 예기치 않게 삭제될 수 있다.

min.insync.replicas - 프로듀서 ack옵션이 all일 때 저장된 replica의 개수.

DevOps

일반적으로 '개발'이라는 단어가 모두에게 다르게 쓰인다. 

보통 이제야 시작하는 스타트업은 기획 - 설계 - 코딩, 개발 - 배포 - 운영 등의 일련의 긴 과정에서 기획 후의 모든 부분을 하는 것을 (슈퍼풀스택)개발자로 퉁 치고 운영한다. 사실 기획도 결국 같이 하게 된다.

규모가 있는 서비스가 되면 점점 복잡해 지기 때문에 인프라 담당자가 따로 있고 (설계)

갖춰진 인프라 위에 개발하는 개발자가 (프론트 / 백엔드 등등..으로 나뉜다)

또 여러명이서 개발을 하게 되면 협업을 위해 협업툴 활용 (git, gitlab...)을 하면서 서로의 소스를 지속적으로 통합(CI)해야 하고

여러 테스트를 거쳐야 하고

또 해당 프로젝트를 빌드해야 하고

이를 빠른 주기로 사용자들에게 배포를 해줘서 애자일하게 개발을 해야 한다.

또 서비스가 잘 운영되고 있는지, 서버는 잘 돌아가고 있는지 모니터링 해주고 적절하게 대응도 해 줘야 한다.

 

 이렇게 많은 일들을 일반적으로 개발 / 운영 으로 나눴었는데 이렇게 두개를 나누면서 생기는 문제점들이 많았다. 예를들면 개발자들은 요건사항에 맞춰서 개발만하고 이를 운영쪽으로 넘겨버리는데, 사실 요구조건은 처음부터 완벽한 것이 아니라 사용자들의 여러 피드백을 받으면서 완벽해 지기 때문이다.

 그래서 이를 나누지말고 협업을 중시하는 하나의 팀을 만들며 문화를 바꾸자! 라고 얘기하는 것이 Dev(개발)+Ops(운영) DevOps이다. (라고 이해했다 사실 추상적인 개념인 것 같다)

 

 DevOps가 추상적인 개념이라면 그래도 구체적으로 함께 따라오는 특징, 키워드들이 있다.

영국정부에서 제공하는 "Good Habit for Devops"의 내용이다.

Cross Functional Team - 개발~배포,테스트를 할 수 있는 역량을 팀내에 채워라.

Widely Shared Metrics - 서비스 현황을 파악할 수 있는 지표를 만들어라

Automating repetitive tasks - CI/CD를 이용하여 빌드배포테스팅 프로세스를 자동화해라

PostMortems - 팀원들과 장애를 함께 공유해라

Regular Release - 짧은 주기로 배포하면서 VOC를 반영해라

 

결국 DevOps란 Dev + Ops로 서비스를 개발에서 부터 운영까지 할 수 있게 노력하는 문화라고 할 수 있다.

 

CI (Continuous Integration)

여러명의 개발자가 하나의 프로젝트에 함께하면 코드 충돌이 일어나기도 하고, 서로 영향을 줄 수 있는데 이를 지속적으로 통합하고 빌드하고 테스트 하는 과정이다. (사실 CI라고 하면 이를 지속적으로 통합하는 과정이기도 하지만 그 과정을 자동화 시키는 것을 얘기한다)

 

CD(Continuous Delivery/Deployment)

여러 개발자들의 코드 변경사항을 버그 테스트를 거쳐 저장소에 업로드 되고, 이를 프로덕션 환경으로 릴리스 하는 것이다.

 

 

 

 

참고 : bcho.tistory.com/817

 

개발과 운영의 조화 - Devops #2/2

1편 글 링크 - http://bcho.tistory.com/815 Devops의 정의  이러한 개념들을 적극적으로 적용한 기업들이 Netflix, Flicker와 같은 인터넷 서비스 기업이다. 기존 개발 프로세스에 비해서 훨씬 빠르게 고객의

bcho.tistory.com

 

Index란?

- 자료 검색을 효율적으로 하기 위한 목차의 개념.

Clustered-Index / Non-Clustered Index

Clustered Index는 인덱스와 데이터가 함께(clustered) 있는 개념으로, Index를 타고 내려가서 Leaf노드에는 해당하는 값이 함께 존재한다. Non-Clustered Index는 최종 리프노드는 데이터가 저장되어 있는 저장소의 주소를 Pointer로 가리키고 있다.

Clustered Index는 primary key나 unique 값을 통해 보통 자동으로 생성되는데, index를 기준으로 정렬하여 이를 활용하기 때문에 검색 속도가 Non-Clustered-Index에 비해 빠르다. 또한 인덱스와 데이터가 함께 정렬되고, 이 정렬 할 기준을 정해야 하므로 하나의 인덱스만 생성할 수 있다. (= 이름으로 정렬하고, 주민번호로 정렬하고 할수 없으니 당연)

Non-Clustered Index는 Index만 정렬해 놓고, 최종 노드에서 참조값을 통해 데이터를 읽는것으로 볼 수 있다. 함께 정렬되지 않기 때문에 여러 인덱스를 만들 수 있다.

 

그럼 인덱스는 무조건 많이 만드는 것이 좋나?

 그렇지 않다. 인덱스 자료구조 자체를 저장할 공간도 따로 필요하고, 해당 인덱스 구조를 유지하기 위해 값이 추가되거나, 수정, 삭제될 경우 인덱스 자료구조에도 업데이트 해주는 추가 작업이 필요하게 된다. 따라서 조회 작업을 할 때 유리하다. 따라서 조회 작업이 상대적으로 많거나, 데이터가 많고 조회 하는 값이 2~4%이하일때 생성하는 것이 좋다. 또한 인덱스를 여러개 생성할 경우, 옵티마이저가 잘못된 인덱스를 선택할 수 있기 때문에 3~4개가 적당하다.

 

인덱스를 사용할 컬럼은 어떻게 정하나?

 카디널리티가 높고, 선택도가 좋고(=낮아서 좋은거), 중복도가 낮고, 활용도가 높은 것을 선택한다. 카디널리티는 중복된 열을 제외한 남은 열의 개수로 생각하면 편하고, 선택도는 해당 컬럼으로 조회했을 때 전체 행에 비해 남는 행의 비율을 생각하면 된다. 활용도는 Where나 join절에 많이 사용하는 것을 얘기한다.

 

인덱스를 생성해두면 반드시 해당 인덱스를 활용하게 되나?

 그렇지 않다. 여러 규칙이 존재한다. 우선 복합 컬럼으로 인덱스를 설정해뒀을 경우 첫 컬럼의 조건을 활용해야 해당 인덱스를 타게 된다. 예를들면 A,B,C인덱스가 있을때 B=10, C=20의 조건으로 검색하는 경우 A를 사용하지 않아 활용되지 않는다. 

 또한 범위조건이 포함된 컬럼 다음 인덱스는 활용되지 않는다. 예를들면 , A=10, B>20, C=30 의 조건을 걸어 쿼리를 보내면 B까지는 포함되겠지만 C는 포함되지 않는다.

 인덱스 값을 조작한 경우 활용되지 않는다. A+10 > 20 의 조건으로 찾는경우와 A > 10의 경우는 같아 보이지만 앞의 조건은 A+10으로 A값을 조작한 것이므로 활용되지 않는다. 

 null의 값은 is Null로 인덱스 레인지 스캔이 가능하다.

 

 

 

Reference : https://jojoldu.tistory.com/243

GC란?

Garbage Collection는 JVM의 Heap영역에서 사용하지 않는 객체들을 제거하는 프로세스를 지칭한다.

 

어떤 알고리즘으로 작동하는가?

GC는 Mark & Sweep (+Compact) 알고리즘으로 작동한다. 사용하지 않는 객체를 Marking하고, 추후에 이를 Sweeping(쓸어버림)한다. 그럼 중간 중간 빈 공간이 생기는데 몇몇 GC들은 메모리 단편화를 막기위해 이를 Compact해주기도 한다.

 

Unreachable, Reachable?

Mark할 객체의 기준을 어떻게 정하는가? 사용하지 않는 객체들을 Unreachable하다고 얘기하는데, 해당 객체를 정하는 기준은 GC Root로 부터 해당 객체를 참조하고 있는지 확인한다. GC Root는 Stack, Method Area에 있는 지역변수값등을 얘기하고, Heap의 또다른 객체로 부터 이를 참조하는지도 확인한다.

 

상세 알고리즘 동작 방식은? *(Major GC, Minor GC, Young Generation, Old Generation)

java에서는 갓 생성된 객체를 Eden이라는 메모리 영역에 올리는데 해당 영역이 꽉차면 minor GC가 실행되면서 사용하지 않는 객체(unreachable한 객체)를 제거하고, 살아남은 객체들을 Survivor영역에 올린다. Survivor 영역은 0, 1로 나뉘고 한 phase당 하나만 사용된다. Minor GC는 Eden + Survivor(0 or 1)을 살피며 살아남은 객체를 현재 사용되지 않는 Survivor영역으로 옮긴다. 이렇게 옮기면서 각 객체들의 age를 올려준다.

특정 age 임계점이 되면 이를 Old generation 영역으로 옮긴다. Old generation영역도 꽉차면 Major GC가 실행되면서 사용하지 않는 객체들을 제거한다.

 

위와 같은 알고리즘(Young/Old 분리) 동작 방식을 취하게 된 계기는? (GC 설계의 두가지 전제)

1. 새롭게 생성된 객체들의 대부분은 금방 사용하지 않게 된다. (=> Young Generation에 한해서 Minor GC를 설계하게 된 계기)

2. 오래된 객체가 새로운 객체를 참조하는 일은 드물게 일어난다.

 

Stop- The - World

GC가 실행되는 동안 다른 스레드가 모두 멈추는데, 이를 Stop-The-World라고 한다.

 

GC의 종류는?

Serial GC - 싱글 스레드로 GC를 진행한다. 그만큼 Stop-The-World시간이 길다.

Parallel GC - Young Generation 처리를 병렬로 처리한다.

Parallel Old GC - Old Generation'도' 병렬로 처리한다.

CMS GC - GC를 처리하는 스레드는 하나, 하지만 다른 스레드는 멈추지 않고 프로그램을 진행한다. Compact과정이 없다.

G1 GC - CMS를 발전시킴. Heap 영역을 Region영역으로 나누어 Garbage만 있는 Region을 우선적으로 탐색하여 수거한다. Java9의 default GC.

1. 웹서버 - 클라이언트의 요청에 의해 정적 파일을 제공하는 서버. Apache, NginX, IIS등의 종류가 있으며, 정적파일만 제공할 수 있다.

2. WAS(Web Application Server) - 웹 서버와 웹 컨테이너를 포함한 개념. 웹 서버에서는 정적 파일만 제공하므로, 동적으로 DB와 연결하여 여러 데이터를 활용해 Php, Jsp, Asp로 표현된 페이지를 해석할 수 있어야 하는데 이 역할을 웹 컨테이너가 제공한다.

 

왜 웹 서버와 WAS를 분리했을까?

- 두개를 분리하면 웹 서버를 한번 거치기에 보안상의 이점 존재. 또한 웹 서버에서 고장난 WAS로 보내지 않고, 그 시간동안 이를 복구시킨다면 사용자는 끊김없이 서버에서 서비스를 제공받을 수 있다. (=fail over, fail back). 톰캣에서는 웹서버와 WAS의 역할을 모두 하기도 한다.

 

Node.js는 Apache, 톰캣과 같은 개념과 무엇이 다른가?

 Node.js 는 정적 파일 제공과 WAS기능(=언어 해석) 모두 담당한다. Express.js를 통해 정적 파일을 제공하고 나머지가 언어해석, DB연결등을 담당하는 큰 그림으로 볼 수 있지만 나누는 것이 크게의미가 있나 싶다. 그렇다고 Node.js를 사용하면 NginX와 같은 웹 서버를 사용하지 않는 것은 아니다. 기업에서는 NginX나 Apache등의 그동안 쌓인 기술들을 활용하여 이를 리버스 프록시 서버로 활용하여 보안상의 이점과 캐싱등을 통해 속도상의 이점을 갖는다.

DFS (Depth First Search)

 

  • 깊이 우선 탐색, 트리에서 깊이를 기준으로 하나씩 정복해 나간다.

  • Stack 자료구조를 사용, 컴퓨터는 내부적으로 스택 구조를 활용하므로 재귀함수를 통해 구현 가능.

  • 순열, 조합의 개념과 연관되는 것이 많으므로 아래 링크를 통해서 개념을 이해하고 먼저 구현 해보기.

https://yabmoons.tistory.com/99

 

BFS (Breadth First Search)

 

  • 너비 우선 탐색, 트리에서 너비를 기준으로 하나씩 정복해 나간다.

  • Queue 자료구조를 사용

백트래킹이란?

 

  • 쉽게 DFS + 가지치기라 생각할 수 있다. 

  • 대표적 예제 (N-Queen). 모든 경우의 수를 생각하지 않고 미리 조건을 확인하며 제외 해 나간다.

https://www.youtube.com/watch?v=_hxFgg7TLZQ

 

위 영상 내용을 통해 깔끔하게 개념 정리 가능. 

[C++ 벡터 구조 이해해기]

https://jhnyang.tistory.com/230

 

벡터는 배열과 매우 유사하지만, 길이 변화가 비교적 자유로운 동적 배열 구조라고 생각하자.

[벡터의 단점]

중간값 삭제, 검색, 중간 추가가 어려움.

[벡터의 장점]

마지막값 추가, 삭제가 쉬움.

구현이 쉬움.

주소를 알 경우 접근이 쉬움.

사진 출처 : http://myblog.opendocs.co.kr/archives/1347 

 



[벡터는 언제 사용하나요?]

저장하려는 데이터의 갯수가 가변적이고, 마지막에서 추가 삭제가 이루어지며

데이터가 적거나, 많으면 검색이 잦지 않으며, 랜덤하게 데이터 접근을 허용할 때.

 

[벡터 사용법]

벡터는 C++의 표준(Standard) 템플릿(Template) 라이브러리(Library) 줄여서 STL!

STL에서 제공되는 컨테이너는 일반적으로 헤더 파일 이름이 컨테이너 이름과 같다.

즉, #include <vector>

 

[iterator(반복자)의 개념]

컨테이너에 저장된 원소를 순회, 접근하는 일반화된 방법을 제공하는 것은 Iterator!

Iterator가 컨테이너에 범용적으로 사용되기 때문에 종속적이지 않고, 독립적으로 운용 가능.

일반적으로 begin(), end()를 활용해 반복자를 사용한다. 해당 element를 반환하는 것이 아닌 반복자의 위치를 설정하는 개념



벡터 예제 연습 코드.

#include <iostream>

#include <vector>


using namespace std;


int main(void)

{

    

    //벡터 생성. vector<자료형> 이름.

    vector<int> v1;

    vector<char> v2;

    vector<string> v3;

    

    //개수를 넣어 생성자 활용해 디폴트(char 빈값, int 0)값 선언 초기화.

    vector<char>v4(3);

    vector<int>v5(5);


    //vector for loop

    for(char x: v4) cout<< x;

    cout<<endl;

    for(int x : v5) cout<< x;

    cout<<endl;

    

    //다른 값으로 초기화 하고 싶다면? vector<자료형>이름(갯수, 초기화값)으로.

    vector<int> v6(5, 10);

    for(int x : v6) cout<< x<<",";

    cout<<endl;

    

    //여러값을 다르게 초기화 하고 싶다면? 배열처럼{}를 이용. 단, c++ 11이상

    vector<char>v7 = {'a', 'b', 'c', 'd'};

    for(char x : v7) cout<< x << " ";

    cout<<endl;

    //벡터의 제일 뒤에 값을 추가하고 싶다면? push_back 사용.

    vector<string>v8;

    v8.push_back("a11");

    v8.push_back("b11");

    v8.push_back("c11");

    for (string x : v8 ) cout << x << " ";

    cout<<endl;

    

    //응용버전.

    int size = 10; vector<int> v9;

    for(int i=0; i<size; i++) v9.push_back(i+1);

    cout<<endl;

    for(int x: v9) cout<< x << "!";

    cout<<endl;

    

    //벡터 복사하기 v9을 v10으로 복사.

    vector<int> v10(v9);

    for(int x : v10) cout<< x << " ?";

    cout<<endl;

    

    

    //컨테이너에 저장된 원소를 순회, 접근하는 일반화된 방법을 제공하는 것은 Iterator!

    //Iterator가 컨테이너에 범용적으로 사용되기 때문에 종속적이지 않고, 독립적으로 운용 가능.

    //일반적으로 begin(), end()를 활용해 반복자를 사용한다. 해당 element를 반환하는 것이 아닌 반복자의 위치를 설정하는 개념

    vector<int> v11(v10.begin(), v10.end()); // 전체 복사.

    vector<int> v12(v10.begin(), v10.begin()+5); // 앞에서 다섯 개 복사.

    for(int x : v11) cout<< x << "@";

    cout<<endl;

    for(int x : v12) cout<< x << "#";

    cout<<endl;



}

 

+ Recent posts