Kafka send()가 반환되지 않는 구간 - max.block.ms 이해하기
·
Server
Kafka 프로듀서의 send()는 Future를 반환합니다. 비동기 API이므로 호출 스레드를 붙잡지 않는다고 알고 있었는데, 실제로는 수십 초 동안 반환되지 않는 경우가 있습니다.어느 구간에서 얼마나 붙잡히는지 확인하려고 테스트를 작성했고, 그 과정에서 잘못 알고 있던 것을 두 가지 확인했습니다. 실험은 개인 프로젝트에서 진행했고 브로커는 Testcontainers로 띄웠습니다. 테스트 코드는 글 마지막에 있습니다.1. send()는 예외를 던지지 않습니다2MB 레코드를 보내면 기본 한도(1MiB)를 초과하므로 예외가 발생할 것으로 보고 이렇게 작성했습니다.assertThatThrownBy(() -> producer.send(record)) .isInstanceOf(RecordTooLar..
잔액을 저장하지 마세요! 결제 시스템이 원장(Ledger)을 쓰는 이유
·
공부방
"지금 잔액이 얼마인가?"이 질문에 답하는 방법은 두 가지입니다. 하나는 잔액을 컬럼에 저장해두고 읽는 것이고, 다른 하나는 거래 기록을 전부 더하는 것입니다.개발자로서 처음 드는 생각은 당연히 전자입니다. SELECT balance FROM account가 SELECT SUM(amount) FROM entries보다 빠르고 단순하니까요. 그런데 은행도, 카드사도, 결제 게이트웨이도 후자를 씁니다. 성능을 몰라서가 아닙니다.이 글은 그 이유에 대한 이야기입니다. 그리고 제 토이 프로젝트에 원장을 넣으면서, 원장을 만들었기 때문에 비로소 보인 구조적 결함 하나를 발견한 기록이기도 합니다.1. 잔액은 "상태"고, 원장은 "사실"이다차이를 한 문장으로 줄이면 이렇습니다.잔액은 덮어써지고, 기록은 쌓인다.bal..
Saga가 멈춰도 아무도 몰랐다! Choreography를 고른 대가 갚기
·
공부방
2편에서 저는 Orchestration 대신 Choreography Saga를 골랐습니다. 그러면서 이렇게 썼습니다."안무형의 최대 단점인 '흐름 추적 어려움'이 이 규모에서는 아직 크게 문제되지 않을 거라고 판단했습니다."판단 자체는 지금도 유지합니다. 서비스 3~4개에 흐름도 선형적이니까요. 그런데 글을 쓰고 나서 이 문장이 계속 걸렸습니다. "크게 문제되지 않는다"는 건 "문제가 없다"가 아니라, 빚을 나중에 갚겠다는 말이었으니까요.이번 편은 그 빚을 갚은 기록입니다. 결론부터 말하면 순서는 이랬습니다.보이게 만들고 (멈춘 사가를 관측)되살리고 (두 가지 실패 모드를 각각 복구)그 과정에서 버그 두 개를 잡았습니다. 하나는 배선이 조용히 어긋나 있었고, 하나는 가드를 한 곳에만 걸어둔 탓이었습니다...
파일 하나 올렸을 뿐인데 서명이 세 개나 필요했다
·
Server
presigned URL, 해시, 전자서명이 각각 보장하는 것 - 그리고 서명 파일을 왜 따로 두는가들어가며파일 업로드/다운로드 기능을 만들다 보면 "서명(signature)"이라는 단어를 하루에 세 번쯤 마주친다.presigned URL을 발급하면서 서명한다파일 해시를 붙이면서 이것도 흔히 서명이라 부른다설치 파일에 딸려오는 별도 파일도 서명이다같은 단어인데 셋 다 다른 것이다. 보장하는 범위가 다르고, 깨지는 조건이 다르고, 법적 의미도 다르다. 그런데 코드에서는 전부 sign, signature, verify라는 이름으로 등장하니 헷갈리기 딱 좋다.이 글은 파일 하나가 스토리지에 올라갔다가 클라이언트로 내려가는 흐름을 따라가면서, 그 과정에 등장하는 서명들을 하나씩 뜯어본다. 목표는 각각이 무엇을 ..
Redis Lua 대신 MySQL을 선택했다. 13배 느려졌지만 얻은 것이 있었다.
·
공부방
2편에서 재고를 Redis + Lua 스크립트로 원자적으로 차감해 오버셀을 막았습니다. 글을 쓰고 나니 스스로 이런 질문이 남았습니다."그럼 RDB로는 어떻게 하지? 그리고 Redis랑 뭐가 다를까?"예전 취업 준비 때 만든 프로젝트에서는 재고와 주문이 같은 DB, 같은 트랜잭션 안에 있어서 SELECT ... FOR UPDATE 하나로 끝났습니다. 그런데 이번 MSA 프로젝트는 재고(Redis), 주문(RDB), 결제(RDB)가 서비스별로 쪼개져 있어서 그때의 방식이 그대로 통하지 않았습니다. 그래서 이번엔 재고 차감을 Redis와 RDB 두 방식으로 다 만들어 같은 워크로드로 측정해봤습니다. 결론부터 말하면 RDB 쪽이 약 13배 느렸습니다. 그런데 이 글에서 하고 싶은 이야기는 "그러니 Redis가..
MSA에는 롤백이 없다, 보상만 있다: Choreography Saga를 구현하며
·
공부방
지난 글에서 저는 이렇게 예고하며 마무리했습니다."다음 글에서는 이 두 패턴 위에 쌓아 올린 Saga 패턴과, 보상 트랜잭션을 구현하며 실제로 만들었던 데이터 정합성 버그를 다뤄보겠습니다."1편(멱등성과 Outbox 패턴)에서 두 가지 함정을 막았습니다. Outbox 패턴으로 "DB 저장은 됐는데 이벤트가 안 나가는" 상황을, 멱등성으로 "같은 이벤트가 두 번 오는" 상황을요. 그런데 이 두 패턴은 결국 하나의 서비스가 다른 하나의 서비스에게 일을 넘기는, 딱 두 단계짜리 흐름을 안전하게 만든 것이었습니다. 현실은 더 깁니다. 주문 → 재고 차감 → 결제처럼 세 개, 네 개의 서비스를 거쳐야 하나의 "주문"이 완성됩니다. 그리고 중간의 어느 한 단계가 실패하면, 이미 성공해버린 앞 단계들을 되돌려야 합니..