- Designing Data-Intensive Applications 를 읽고 그 중 분산시스템의 오류처리에 대한 부분 정리
- 언제나 늘 그렇듯이 새로운 개념이라기 보다는 얼마나 체계적으로 잘 정리해서 핵심을 간직하는가에 집중
<내용>
1. Faults and Partial Failures
- Single Computer에서 작업을 할 경우 same operation은 same result를 만들어낸다. (deterministic)
- 우리가 수 대의 컴퓨터에서 동작하는 소프트웨어를 개발할 때 (즉, 네트워크로 연결되어 있는 상태)는 이와는 다르다.
- 분산처리 시스템에서는 예측할 수 없니 특정 부분에서 문제가 발생하는 경우가 있는데 이를 partial failure 라고 부른다.
- partial failure의 어려운 점은 non-deterministic이라는 점이다. 여러 노드와 네트워크에 걸쳐서 무언가를 했을때 예상과는 다르게 성공하기도 하고 실패하기도 한다는 점인데 이러한 부분이 분산처리 시스템을 어렵게 만든다.
2. Building a Reliable System from Unreliable Components
- unreliable한 구성요소를 가지고 어떻게 하면 reliable한 시스템을 만들수 있을까? 몇 가지 아이디어를 소개한다.
a. error-correcting code를 가지고 어느부분이 오류가 발생했는지 체크하고 정확하게 전송할 수 있다.
b. TCP/IP : IP는 unreliable하다. 사라지거나 늦게 도착하거나 혹은 중복되거나.. 순서보장도 안된다. 여기에 TCP가 Transpory layer의 역할을 상위에서 해줌으로 사라진것은 다시 보내주고, 중복된것은 재거해주며, 발송순서에 맞게 재조립을 해준다.
물론 언제나 한계점은 존재한다. 데이터의 양이나, 혹은 네트워크 자체의 지연현상등은 커버하기 어렵다. 하지만 까다로운 일반적인 문제를 제거하고 나면 훨씬 쉬워지는것도 사실이다.
3. Unreliable Networks
- 분산처리에서는 shared-nothing구조를 유지하는 것이 일반적이다. 왜냐하면 다른 machine끼리는 네트워크로 연결이 되며 각자의 disk나 메모리등에는 접근할 수 없기 때문이다.
- 인터넷 환경에서 각 데이터 센터는 멀리 떨어져 있기 때문에 더욱 이러한 구조인데 대부분 비동기처리로 진행이 된다.(asynchronous packet networks)
- 메시지를 보내기는 하지만 언제 도착할지, 도착이 하기는 할지.. 보장할 수가 없다.
a. request가 사라질 수 있다. b. request가 늦게 도착할 수 있다. c. 원격 node가 죽을 수도 있고 d. 원격 node가 잠깐 멈출수도 있고. (gc) e. 원격 node가 request처리를 했지만 response가 사라질수 있다. f. 원격 node가 request처리를 하고 reponse를 보냈지만 늦게 도착할수도 있다. - 종합해 보면 메시지를 보낸쪽에서는 무엇이 문제인지 알수 있는 방법이 없다.
* 이러한 문제를 처리하는 방법이 일반적으로 "Timeout" 이다. 기다리는 것을 포기하는 것이다.
수신 node가 받았는지, 메시지가 사라졌는지는 여전히 알 수 없지만...
4. Network Faults in Practice
- 위에서 살펴본것처럼 reliable한 시스템을 만드는 법은 완벽한것은 없다. (왜? 네트워크는 여전히 불안하기 때문에..)
- 결국 소프트웨어에서 이를 처리할 수 있도록 해야한다. (이것을 지속적으로 테스트하도록 만든 프레임워크가 바로 Chaos Monkey)
5. Detecting Faults
- Faults를 감지해야 이후의 처리를 할수 있으니 감지하는 법을 살펴보자.
- 불확실성을 통해서 작동여부를 판단하는 것은 어려우니, 거꾸로 특정상황에서 작동하지 않는다는 것을 명시적으로 알려주는 FeedBack !
a. 대상 포트에서 수신 프로세스가 없는 경우 OS에서 RST 또는 FIN을 전송한다.
b. 노드 프로세스가 죽었지만 OS가 여전히 실행중이면 script로 다른 노드에 알릴 수 있다. (e.g Hbase)
c. 데이터 센터에서 NIC 관리 기능을 사용중이면 하드웨어 수준으로 감지 할 수 있다.
d. 라우터가 해당 IP에 연결할 수 없다고 확신하면 ICMP destination unreachable 패킷으로 응답할 수 있다.
- 이렇게 빠르게 feedback처리를 하면 매우 유용함을 알 수 있다. 하지만 이것도 역시 신뢰할 수는 없다! (네트워크이니까)
- 결국 request가 성공적으로 처리되었는지는 application레벨에서의 positive response를 받는 것이 필요하다.
- read commited 와 snapshot isolation은 주로 동시 쓰기가 있는 경우, 읽기전용 트랜잭션의 볼수 있는 내용을 보장하는 것이다.
- 이전 글에서는 두 트랜잭션이 동시에 쓰는것에 대해서는 다루지 않았고, 특정 유형의 쓰기-쓰기 에 대해서만 살펴봤다.
- 몇 가지 다른유형의 충돌을 살펴 볼텐데 그 중 가장 유명한 것이 Lost Updates 이다.
- 아래 그림을 살펴보자.
TimeLine
1
2
3
4
User #1
getCounter
42 + 1
setCounter 43
Data
42
42
43
43
User #2
getCounter
42 + 1
setCounter 43
- 두 Client간의 race condition이 발생하였다. 주로 App이 read - modify - write의 사이클을 가지고 있을때 발생한다.
- 즉 write가 발생하기 전에 그 사이에 일어난 modification을 포함하지 않기 때문이다.
Solutions
Atomic write Operations
- 많은 DB가 atomic update operations를 제공하기 때문에 App에서 해당 로직을 구현하지 않는 것이 좋다. 일반적인 Best Solution
예) UPDATE counters SET val = val + 1 WHERE key = 'foo';
- 일반적으로 DB내부에서 execlusive lock을 통해서 구현하기 때문에 (update할때 읽을 수 없다!) "Cursor Stability 유지"
- 다른 방법으로 all atomic operations를 single thread 에서 수행하는 방법도 있다. (성능 고려)
Explicit Locking
- DB에서 해당 기능을 제공하지 않는다면 App에서 명시적으로 update 될 object를 잠그는 방법이다.
- 잠금을 수행후 read - modify - write 를 수행할 수 있으며, 다른 트랜잭션이 같은 object 에 접근할 경우에 첫 번째 read - modify - write 가 완료될 때 까지 강제로 대기한다.
예 )
BEGIN TRANSACTION
SELECT * FROM ... WHERE ... FOR UPDATE; (해당쿼리로 반환된 all rows lock !) UPDATE ...
COMMIT;
Automatically detecting lost updates
- atomic operations & locks : read - modify - write 를 순차적으로 하여 lost updates 를 막는다.
- 대안 : 병렬 수행을 허락하고, Transaction Manager 가 lost update를 감지하면, 트랜잭션을 중단하고 강제로 read - modify - write 를 retry 한다!
- 장점 : DB가 이 검사를 효율적으로 수행할 수 있다는 것. with Snapshot Isolation
PostgreSQL
repeatable read
automatically detect and abort the offending transaction
Orale
serializable
SQL
snapshot isolation
MySQL, InnoDB
repetable read
X
Compare-and-Set
- 우리가 CAS연산이라고 부르는 방법이다. DB가 Transcations를 제공하지 않는 atomic compare-and-set을 찾는 것이다. (Single-object writes)
- 마지막으로 값을 읽은 후 값이 변경되지 않았을때에만 업데이트가 발생할 수 있도록 허용하는 것이다.
- 만약 변경이 일어났다면? read-modify-write연산을 재시도한다. 반드시!
주의! Conflict replication
- Locks and Compared and Set은 Single up-to-date, copy of the data를 가정한다.
- replicated DB에서는 여러 노드에 복사본이 존재하고, 데이터 수정이 다른 노드에서 발생할 수 있기 때문에 다른차원의 접근이 필요하다. 즉, 다시 말하면 multi leader 또는 leaderless replication에서는 write가 동시에 발생하고, 비동기 연산이 있다면 보장할 수 없다. (Linearizability)
- 대신 "Detecting Concurrent Writes" 챕터에서 살펴본 내용처럼 concurrent writes 가 충돌된 값의 버전들을 생성하고 (App 또는 별도의 자료구조활용), 이러한 충돌을 versions를 통해서 reslove , merge하는 방법이 가능하다.
- Atomic Operations는 영향을 받지 않는다. (특히 Commutative한 Actions이라면 !)
- 슬프게도.. 많은 replicated DB에서는 기본값으로 Last Write Wins 이다.
<정리>
- 개인적으로 매우 유익했던 챕터이다. 결국 두 개 이상의 동시쓰기가 발생한다면 해결방법은 아래와 같이 정리할 수 있다.
1) 해당 사이클을 통째로 묶는다.
2) 동시수행을 제한한다. ( Lock or Single Thread )
3) 일단 진행시켜! 에러나면 다시 시도
- 멀티 노드를 가지는 Database라면 여러 곳에서 동시다발적으로 데이터에 대한 복제 / 연산이 일어나기 때문에
1) Single Leader를 통해서 제어하던지(Hbase 같은)
2) 마지막에 Write한 값으로 저장
3) 별도의 Application이나 자료구조를 활용하여 충돌버전을 관리하고 resolve / merge
- 그래서 대부분의 분산병렬처리 오픈소스 진영에서 zookeeper를 사용하고 있는 듯 하다.
- 다음 글에서는 이 글에서 다루지 못한 Isolation Level ( Write Skew, Phantoms read )을 좀 더 자세히 살펴보고 분산환경의 Consistency 에 대해서 정리하도록 해야겠다.
- Designing Data Intensive Applications 를 읽고 그 중 Transactions에 대한 내용을 정리.
- 기존에 일반적인 내용들 (e.g RDB기준), 새로 추가되는 개념들 (e.g NoSQL)을 포함하여 정리
<내용>
1. Transactions
- read/write 가 일어날때 논리적인 하나의 단위로 묶어서 생각해본다.
- application에서 database에 접근할때 프로그래밍을 좀더 단순화하는데 목적이 있다고 본다. (추상화)
2. ACID
Atomicity, Consistency, Isolation, Durability
위와 같은 것은 일반적으로 트랜잭션의 특성으로 보고 있으며, 실제 각 DBMS에서 구현하는 방법들은 조금씩 차이가 있다.
특히, 이 책에서는 수년간 사용해왔던 이 단어들에 역시 모호한 부분들이 존재하며, 관점에 따라 차이가 있음을 소개하고 있다.
Atomicity
- 통상적으로 더이상 나눌수 없는 단위로 본다. 그러나 그 세부의 이해에는 약간의 차이가 있다.
- 예를 들어서 멀티쓰레드 환경에서는 하나의 쓰레드가 atomic operation을 수행할때 다른 쓰레드에서는 그 중간값을 참조할 수 없음을 의미한다. 그러나 ACID내에서 Atomicity는 concurrency의미가 아니다. 이 개념은 Isolation에 포한된다.
- ACID내에서 Atomicity는 write가 수행될때 (내부적으로 process, network, disk등의 여러 에러가 발생할 수 있는 요인이 있지만) 성공 혹은 실패가 하나로 묶이는 것을 의미한다. (committed / aborted)
즉, "어디까지는 성공하고 어디까지는 실패하고" 가 발생하지 않도록 하는 것이 그 의미에 가까워서 이 책에서는 abortability가 좀 더 의미적으로 가깝지 않나라고 이야기한다.
Consistency
- 역시 다양한 영역에서 혼용되어 사용하고 있다. 예를 들어서 eventual consistency, consistent hashing, CAP, 그리고 ACID내에서 같은 단어들이지만 다른의미를 가지고 있다.
- ACID내 C의 경우 invariants가 항상 참인것에 집중한다고 볼 수 있다. 그 예로 여러가지 constraint를 이야기하고 있는데 이 책에서는 이러한 영역이 database보다는 application이 보장해야하는 것으로 보고 있다.
Data에 어떤 내용을 담을 것인가는 결국 application 에서 정해지며 A,I,D는 database의 속성이지만 consistency는 그렇게 볼수 없다는 것이다.
- 나도 여기에 동의하는 것이 최근 NoSQL에서는 한 application의 기능에서 다루는 단위(set)를 기준으로 데이터를 구성하는 것이 일반적이며(JOIN X), 그에 따라서 각 데이터의 key값에 대한 정합성을 보장하는 것은 응답속도나 reliability에 따라서 타협하여 구현하는 모습을 통해서 체감할 수 있다고 생각한다.
Isolation
- 같은 record에 대해 동시에 여러 clients가 접근하여 경합이 발생했을 때 어떻게 해결할 것인가.
- 특히 우리가 일반적으로 알고 있는 Isolation level과 비슷하면서도 다른 용어, 추가적인 개념설명으로 재미있었던 부분이었다.
(e.g dirty write, read write skew 등)
- 용어에서 설명하듯이 기본적으로 여러 transactions이 동시에 수행되더라도 각각은 별도로 격리 되어야 한다는 것이 기본적인 원칙이다. 이 때 발생할 수 있는 여러가지 데이터 이상현상이 있으며 (DIRTY READ, NON REPETABLE READ, PHANTOM READ등) 각 현상을 제거할 수 있도록 Level을 조정하여 성능과 정합성을 타협하는 것이다.
- 다만 각 database가 정의하는 isolation level는 차이가 있으며, 그것을 구현하는 방법도 다르다는 내용을 소개하고 있다.
(e.g snapshot isolation, Inno DB consistent read 등)
Durability
- 결국 모든 저장소 (File, Database, Storage)는 안전하게 데이터를 저장하는 것이 목적이다.
- single-node database 에서는 이것을 보장하기 위해서 저장공간 (hdd,ssd 등)에 데이터를 기록하고, write-ahead log를 기록하고 recover event하는 형태로 접근했으며
- replicated database 에서는 복제하여 다른 노드에 저장하고, 각 transaction이 이루어질 때 write / replication을 수행하는 형태로 관리하고 있다.
특히 여기에는 개발자들이 놓치는 문제들이 간혹 있는데 간단히 살펴보면 다음과 같다.
- Disk에 기록한뒤 다운될 경우 Data는 잃어버리지 않으며 접근이 불가능한 것으로 접근 가능한 다른 시스템으로 대체할 수도 있다.
- Memory에 저장된 데이터는 휘발성이기 때문에 Disk가 필요하다.
- 비동기로 복제하는 시스템에서는 Leader의 상태에 따라서 최근 변경사항이 누락될 수 있다.
- Storage에도 버그나 문제는 존재하며 완벽은 없다. (e.g 정전, 펌웨어 버그 등)
- Disk는 생각보다 자주 망가진다. 갑자기 죽기도 하지만.. 천천히 망가지기도 한다. (bad sector!) 물리적인 매체가 망가진다는 것은 replicas, backup도 손상되는 것을 의미하고, 그래서 historical backup전략이 존재한다.
- 특히 SSD는 빠르지만 불량블록이 생각보다 자주 발생하고, HDD는 갑자기 죽기도 한다. - SSD는 전원이 공급되지 않으면 온도에 따라서도 손실이 발생한다고 한다!?
결국 절대적인 보장은 이세상에 존재 하지않기 때문에 우리는 여러 기술을 복합적으로 사용하는 것이다.
<정리>
- ACID는 과거부터 쭉 사용해온 용어이지만 Computing Area가 다양해지면서 비슷한 용어들의 모호함이 존재한다.
- Database에서 보장해야하는 개념들은 결국 각 벤더마다 다를 수도 있다.
즉 JDBC 드라이버의 실제 구현은 내가 생각하고 기대한 것과는 다를 수 있다. (과거에 update 쿼리실행 후 결과값이 변경된 건수로 나와야 하는데 0으로 나왔던 기억이 있다.)
- Consistency는 application의 영역으로 보는 것이 어떨까 라는 의견이 있다.
(유효성을 위한 제약조건 같은 기능들은 Database가 제공할 수 있지만 결국 어떤값을 기록하는가는 Application에 달려있다.)
- 우리는 물리적인 세상에서 살고 있기 때문에 100% 보장이라는 것은 있을 수 없다.
Transcation에 대해서 대략적으로 살펴보았으며 다음 글에는 Atomicity와 Isolation에 대해서 좀 더 자세히 정리하도록 하겠다.
- 당연히 잘 될것으로 예상하고 수행하였으나 다음과 같은 Exception을 만나게 되었다.
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot
construct instance of `org.springframework.data.domain.PageImpl` (no
Creators, like default construct, exist): cannot deserialize from Object
value (no delegate- or property-based Creator)
at [Source: (byte[])"
- 기본 생성자가 존재하지 않기 때문에 발생하는 오류로
A1. 기본 생성자를 가진 wrapper class를 활용하여 문제를 해결할 수 있다.
@JsonIgnoreProperties(ignoreUnknown = true, value = {"pageable"})
public class RestPage<T> extends PageImpl<T> {
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
public RestPage(@JsonProperty("content") List<T> content,
@JsonProperty("number") int page,
@JsonProperty("size") int size,
@JsonProperty("totalElements") long total) {
super(content, PageRequest.of(page, size), total);
}
public RestPage(Page<T> page) {
super(page.getContent(), page.getPageable(), page.getTotalElements());
}
}
Q2. Page클래스는 다음과 같은 형태로 해결하였으나 Java 8 부터 제공하는 LocalDateTime에 대해서도 비슷한 오류가 발생한다.
기본적으로 LocalDataTime을 저장하면 "yyyy-MM-dd'T'HH:mm:ss" 와 같은 형태로 처리되기를 원하지만 아래와 같은 형태로 저장이 된다. 그래서 json 역직렬화를 할때 문제가 발생하게 된다.
공통적인 로직을 Service Layer 에 두면 다른 REST API 나 Controller 활용할 때에도 별도로 검증로직을 추가하지 않아도 됩니다.
또한 값의 의미를 검증하는 부분은 비지니스와 연관성이 있다고 판단하였습니다.
먼저 서비스명과 서비스코드의 문자패턴을 정규표현식으로 나타냅니다.
문자패턴을 정규표현식으로 나타내면서 (시작문자,종료문자,트림,길이 등) 에 대한 처리를 합니다.
public class ValidationPattern {
public final static Pattern serviceNamePattern = Pattern.compile("(^[a-z][a-z0-9]{1,19}$)");
public final static Pattern serviceCodePattern = Pattern.compile("(^[a-z][a-z0-9-]{1,19}$)");
}
이후 해당 패턴을 이용하여 검증하는 로직을 구현합니다.
@Service
public class ServiceService {
@Autowired
private ServiceRepository serviceRepository;
@Autowired
private ModelMapper modelMapper;
public ServiceDto createService(ServiceDto serviceDto) throws DataFormatViolationException {
String serviceCode = serviceDto.getServiceCode();
checkServiceCode(serviceCode);
ServiceEntity serviceEntity =modelMapper.map(serviceDto, ServiceEntity.class);
serviceRepository.save(serviceEntity);
return modelMapper.map(serviceEntity, ServiceDto.class);
}
private void checkServiceCode(String serviceCode) throws DataFormatViolationException {
if(serviceCode == null){
throw new DataFormatViolationException("Code value should be not null");
}else{
Pattern codePattern = ValidationPattern.serviceCodePattern;
Matcher matcher = codePattern.matcher(serviceCode);
if(!matcher.matches()){
throw new DataFormatViolationException("Code value should be consist of alphabet lowercase, number and '-', (length is from 2 to 20)");
}
}
}
7. 결론
두서없이 쓰다보니 글의 요점이 모호해진 것 같습니다.
정리해보면...
- 어떠한 문제를 해결하기 위해서 바로 코딩으로 뛰어들어서는 안된다.
- 문제를 재해석하여 나만의 방식으로 표현하고 시간복잡도 / 공간복잡도 를 정리한다.
- 로직은 최대한 간결하게! 대/중/소, 중복없이, 누락없이!
- MVC 의 경우 각 Layer의 하는 일들이 나누어져 있다.
- 만약 시간복잡도가 높은 연산이 있다면 이러한 연산은 최소한으로 해주는 것이 좋고, 이러한 필터링을 각 Layer별로 해주면 효과적이다.
지원하는 프로토콜(MQTT, AMQP, JMS)에 따라서 분류되기도 하며 메모리 기반/디스크 기반으로 나누어지기도 합니다.
또한 Message를 처리하는 방식에 따라서 Push / Pull 방식으로 나누기도 합니다.
그리고 Queue 의 정상적인 동작을 돕기 위해서 Broker가 개입하는 것이 일반적입니다.
그러나 다양한 제품 가운데 공통적인 것이 있으니 결국은 Queue 라는 것입니다.
FIFO(First In First Out)의 메시지 처리가 필요하다는 전제로 각 시스템의 특징과 요구사항에 따라 맞춰서 솔루션을 선택하면 됩니다.
2. Message Queue 를 사용하는 이유는?
특히 Queue 는 비동기식 메시지 처리에서 많이 사용되는데 그 이유는 다음과 같습니다.
- 어플리케이션을 구현할 때 메시지가 어디서 오는지(Source), 어디로 보내야 하는지(Target) 신경쓸 필요가 없다.
- Producer는 메시지를 작성하는 데에만 집중하고 메시지 전달에 관련된 (목적지, 순서, QoS 등) 항목들에 대해서는 MQ에 모두 위임한다.
- Consumer 역시 해당 메시지를 어떻게 소모하여 처리하는 지에 대해서만 집중하면 된다.
- Producer 와 Consumer가 보다 각자의 역할에 집중할 수 있게 도와준다.
3. 자료구조 Queue
특정 자료구조에 대한 정리나 코드연습은 Algorithm 카테고리에서 자세히 소개할 예정이기 때문에 간단히 살펴봅니다.
Queue
이해를 돕기 위해서 array를 이용해서 작성한 Queue Sample입니다.
class Queue {
private static int front, rear, capacity;
private static int queue[];
Queue(int c)
{
front = rear = 0;
capacity = c;
queue = new int[capacity];
}
static void queueEnqueue(int data)
{
// check queue is full or not
if (capacity == rear) {
return;
}
// insert element at the rear
else {
queue[rear] = data;
rear++;
}
return;
}
static void queueDequeue()
{
// if queue is empty
if (front == rear) {
return;
}
else {
for (int i = 0; i < rear - 1; i++) {
queue[i] = queue[i + 1];
}
if (rear < capacity)
queue[rear] = 0;
rear--;
}
return;
}
}
-queueEnqueue
rear 는 현재 데이터를 넣어야하는 위치의 index 입니다.
만약에 capacity 만큼 꽉 차있다면 더 이상 데이터를 넣을 수 없습니다.
데이터를 넣는 것이 가능하다면 삽입 후 index를 변경합니다.
-queueDequeue
front는 데이터가 삭제되는 위치의 index입니다.
front == rear 라면 비어있는 상태입니다.
데이터를 제거하는 것이 가능하다면 제거 후 index를 변경합니다.
4. Queue 사용시 주의점
위의 sample소스를 통해서 Queue의 특징을 이해하셨다면 주의점도 파악하실 수 있습니다.
- Queue는 무한한 용량이 아니다. 적절히 소모가 이루어지지 않는다면 Queue의 모든 용량을 소모하게 되고 유실이 발생한다.
- Queue에는 많은 쓰레드들이 동시에 접근할 수 있다. enqueue/dequeue 동작은 thread-safe 해야 합니다.
Java에서는 이를 위해서 concurrent 패키지를 제공합니다.
- 서로 다른 Queue에서는 순서보장이 되지 않습니다. (당연한 말씀)
Computer Science의 기초인 DS & Algorithm은 반드시 알고 있는 것이 좋습니다.
5. Apache Kafka
많은 Message Queue 중에서 Apache Kafka는 대용량 실시간 처리에 특화된 아키텍처 설계를 통하여 우수한 TPS를 보장합니다.
기본적으로 pub-sub 구조로 동작하며, scale out과 high availability를 확보하는 것으로 유명합니다.
아키텍처상 다양한 특징이 있지만 이번 글에서는 자료구조 & 알고리즘 측면에서 살펴보겠습니다.
5-A. Multi Partition
Kafka가 분산처리가 가능한 것은 Multi Partition이 가능하기 때문입니다.
대용량의 데이터가 들어올 수록 병렬처리를 통해서 속도를 확보할 수 있는 구조를 가지고 있습니다.
그렇다면 단점은 없을까요?
서로 다른 Partition에 대해서는 순서를 보장하지 않습니다.(다른 큐로 보시면 됩니다.)
예를 들어서 Partition 0 내의 메시지들끼리, Partition 1 내의 메시지들끼리 순서를 보장하지만 Partition 0과 1사이에는 순서를 보장할 수 없습니다.
반드시 순서를 보장해야 하는 케이스가 존재한다면?
- Partition 을 하나만 사용하던지
- Partitioner를 Custom 하게 작성하여 순서가 보장되어야 하는 메시지들은 같은 Partition으로 보내도록 합니다.
Topic과 혼동하시면 안됩니다. 특정 Topic내에 여러 Partition이 존재하며 Broker를 통해서 각 Topic별 Parition 의 저장정보와 복제 등이 이루어집니다.
5-B. Partitioner
Kafka의 Default Partitioner는 modulo 연산을 사용하여 파티션을 나누게 되어 있습니다.
Hash 알고리즘을 잘 작성하여 데이터를 고르게 분산하면 병렬처리 성능을 더 끌어올릴 수 있습니다.
간단한 Hash Function의 예제입니다.
private int hashFunction(K key){
int val=0;
for(int i=0; i<key.toString().length();i++){
val+= (key.toString().charAt(i)) % size;
}
return val % size;
}
일반적으로 우리가 사용하는 HashMap에서도 같은 Hash값이 자주 발생할 경우 특정 row가 길어지게 되어 chaining hash table을 구현하게 되는데, 이 때 worst case로 특정 bucket에 데이터가 집중되어 메모리를 효율적으로 사용하지 못하는 경우가 발생합니다.
Kafka에서도 partition을 적절하게 분산하지 못할 경우 이러한 문제가 발생할 수 있습니다.
6. 정리
- Apach Kafka는 자료구조 Queue와 동일합니다.
- 병렬처리를 위해서 multi partiton을 지원합니다. 여러 개의 Queue 를 동시에 사용한다고 생각하면 됩니다.