2019.08.29 if kakao
if kakao 2019 day1 후기
초당옥수수의 취소를 막아라! : 수만 건의 주문을 1초내에 처리하는 기술
고희경님, 마태일님
1. 초당옥수수의 비밀
초당옥수수?
- Super Sweet Corn
- 엄청 단 옥수수
농작물은 특성상 일반 상품들과는 다르게 작동한다.
- 농작물을 수확하기 전, 구매자들에게 주문을 먼저 받는다.
- 옥수수가 다 자랄 때까지 기다린다.
- 수확과 동시에 구매자들에게 택배 배송이 시작된다.
- 위 과정에서 문제점이 있다.
1번 과정(결제)이 완료되면, 배송요청 상태가 된다. 다음날까지 송장이 등록되지 않은 경우에는 고객들이 취소하는 사례가 많다.
그렇기 때문에 농작물의 경우, 수확시기가 오기 전까지 취소를 막는 것이 중요하다. 이를 위해, 배송예정일을 선택할 수 있으며, 만약 배송이 지연될 때에는 지연사유를 빠르게 전송해 취소를 막아야한다.
2. Legacy 시스템의 문제
배송지연 안내(TMS)가 실시간이 아니라 배치작업
- 해당 문제는 배치작업의 간격을 줄이는 방식으로 해결할 수 있다.(1시간 -> 5분)
하지만, 이는 근본적인 문제 해결방법이 아니다.
복잡한 비지니스 로직
- 여러 비지니스가 스파게티 코드로 이루어져 있어, 어떤 로직을 추가하거나 수정하기에 어려움이 있다.
단일 스레드에서 반복되는 테스크 수행
- 대량 주문을 빠르게 처리하기에는 부적합하다.
- 멀티 스레드를 통해 해당 문제를 해결할 수 있지 않을까? 라는 생각도 해봤지만, 트랜잭션과 실패에 대한 처리를 해결하는 것까지 고려한다면 좋은 방법은 아니다.
3. 미션
실시간 TMS 발송
- 실시간으로 TMS를 발송하지 않는 이유는?
- 성능에 상당한 부담을 준다.
- 여기서 TMS 발송 배치 작업은 아래와 같은 한계가 존재한다.
- 병렬이어도 실시간으로 보낼 수 없다.
- 단일 머신에서 수행한다.
- 이를 해결하기 위해 비동기 워커를 구성했고, RabbitMQ를 도입했다.
복잡한 비즈니스 코드 리팩토링
- 구조를 명확한 단위로 나누고 유지보수하기 좋게 변경
- validation -> 비지니스 로직 -> TMS 발송 배치
처리 속도 개선
- 순차적으로 작업이 진행된다면, 오랜 시간이 걸리며 병목이 발생한다.
-
각 단계에서 걸리는 시간을 측정했을 때, 비지니스 로직을 처리하는 단계에서 대부분의 시간이 소요된다.
이를 해결하기 위해 비동기 워커를 생성했다. 해당 워커를 사용하면, 아래와 같은 장점이 있다.- 패치 사이즈만큼만 요청하기 때문에 DB 접근 횟수가 줄어든다.
- 대량의 데이터 중에서 원하는 만큼만 분리시켜 빠르게 처리할 수 있다.
- validation -> 비지니스 로직(비동기 워커) -> TMS 발송(비동기 워커)
4. 비동기 워커
-
간단하게 성능을 높이는 방법은 factory 갯수를 늘리는 것이다. 이는 consumer의 총 갯수가 늘어나게 된다.
이 때, atomic한 처리를 할 수 있도록 각 consumer들은 서로 공유하는 데이터가 없고 영향을 끼치지 않아야한다. 또한, 각 consumer의 결과를 합치는 것을 최소화해야한다. -
Spring의 SimpleRabbitListenerContainerFactory를 확장해 각 factory별로 consumer의 최소/최대 갯수나 속성을 커스텀하게 설정할 수 있다. 이때, default 최소/최대 갯수를 override를 통해 커스텀하게 조절할 수 있다.
자동 재처리 및 실패 감지
성공 상태
- 메세지가 발행된다.
- consumer가 메세지를 get한다.
- 작업이 성공해 ack를 반환한다.
재처리 상태
- 메세지가 발행된다.
- consumer가 메세지를 get한다.
- 작업 처리과정에서 예외가 발생한다.
- 최대 재처리횟수를 초과하지 않는지 확인 => 초과하지 않음
- nack을 반환한다.
- x-death-cnt 증가시킨다.
- queue-retriable에서 x-message-ttl이 지날 때까지 대기한다.
- x-message-ttl이 지나면, 재시도를 할 수 있도록 다시 queue에 넣는다.
실패 상태
- 메세지가 발행된다.
- consumer가 메세지를 get한다.
- 작업 처리과정에서 예외가 발생한다.
- 최대 재처리횟수를 초과하지 않는지 확인 => 초과
- queue-failed에 해당 메세지를 넣는다.
- 배치 작업이 들어가고 개발자에게 알림이 간다.
- 실패 관리툴을 통해 개발자가 실패 상태들을 관리하게 된다.
RabbitMQ를 사용한 이유?
- ack, nack에 대해 자유롭게 커스텀한 동작을 설정할 수 있다.
Actor Model
- 각 비지니스 처리 단위가 Actor가 된다. Actor는 메세지의 수신을 대기하고 있다가 메세지를 받으면 작업을 진행하고 결과를 반환한다. 이때, 여러 actor의 작업은 동시에 일어날 수 있다.
- scalable, 신뢰가능한 것을 만들기 위해 노력함
5. 배포없이 빠르게 롤백하는 방법
- ON, OFF 스위치를 셋팅한다.
watcher가 스위치의 변경을 감지해서 기존 코드와 신규 코드를 스위칭할 수 있도록 한다. - 해당 설정은 Zookeeper로 관리한다.
- 단순히 true/false로 2가지만 설정할 수 있는게 아니라 더 많은 경우를 설정할 수 있고 퍼센트를 지정할 수도 있다.
Practical Microservices in gRPC Go feat. GraphQL, Kafka(서포터즈 기사 개발기)
강태훈님, 김민영님, 문주성님, 양진선님, 이하건님
Go
파트원 프로그래밍 언어 선호도
- Java 극혐하는 사람들 <-> Java 익숙한 사람들
- 중재안을 찾다 Go 언어로 결정
- keyword가 25개로 적다.(Java의 keyword는 56개)
특징
- 배우기 쉽다.
- 자유도가 낮은 Coding Style
- 적은 메모리 사용
- JVM같은 무거운 VM이 없다.
의존성 주입
- Spring 프레임워크에서는 의존성 주입하기 쉬움
- Go언어에서는 fix를 통해 의존성 주입을 할 수 있다.
단, 어노테이션은 없다.
MicroService
사용한 이유?
- 잘게 나누어서 가급적 작은 부분만 책임지기 위해
- 독립적으로 배포가 가능하다.
- 각 부분이 단순화되고 고립되어 개발자 외 다른 분야의 사람들과 이야기하기에 편했다.
- 하나의 온전한 코드저장소를 개인이 소유할 수 있다.
단점
- 잘게 나누어져 있기 때문에 여러곳에 여러번 호출해야할 수 있다
- API Gateway를 통해 해결 가능하다.
- MicroService 간 네트워크 통신
- DC 내부 비용, 병렬처리를 염두에 두고 개발한다.
- 공통기능에 대한 중복이 생길 수 있다.
- library를 만들어 해결 가능하다.
- 테스트가 복잡하다.
gRPC
- Restful API보다 성능이 좋다.
- json에 비해 효율적으로 CPU를 사용한다.
- postman과 유사한 gRPC UI가 있다.
- gRPC-gateway를 통해 REST proxy 제공 가능하다.
현재 Go언어만 있다. - IDL Registry
테스트
-
부하 테스트
- ghz(단위 시간당 실행했던 숫자)
-
load 테스트
- nGrinder
-
통합 테스트
- docker compose로 실행된 서비스를 postman을 통해 테스트
Kafka
- kafka가 없다면?
- 각 callback 호출을 보내야한다.
- 기사님들이 출근하는 시점에 work-service는 출근 메시지를 기록한다.
해당 메시지가 필요한 각 서비스는 consumer를 통해 해당 메시지를 가져다 쓴다. - 독립적으로 각 서비스가 메시지를 처리한다.
하나의 서비스가 처리를 진행하다가 오류가 발생한 경우, 다른 서비스에 영향을 끼치지 않는다. 오류가 난 서비스가 복구되면, 해당 단계부터 시작할 수 있도록 offset을 기록한다.
이점
- TCP 프로토콜을 사용해 패킷 오버헤드 감소
- consumer 구현에 따라 자유로운 메시지 처리
- 하나의 이벤트를 여러 서비스에서 가져다 쓸 수 있다.
- 서비스 간 메시지 흐름을 단순화
- 메시지 영속성을 보장 = 유실방지
API Gateway
- 클라이언트는 기능 하나를 구현하기 위해 여러 API를 조합해야하는 문제가 발생한다.
gateway가 microService에 붙어 클라이언트에게 정보를 제공하려 했다.
graphQL
- 서버에 변경이 생겼을 때, 하위버전을 위해 버저닝을 어떻게 해야할까? 문서는 어떻게 하지?
- 클라이언트가 원하는 query를 보낸다. = 클라이언트가 원하는 정보를 직접 선택할 수 있다.
- 하나의 엔드포인트를 가진다.
- 원하는 element만 한번에 가져올 수 있는 기능을 제공한다.
Middleware
- 공통된 기능을 넣을 수 있다.
서비스 모니터링
- Peak Time에 서비스 지연이 발생했다. 기사에게 콜정보를 제공하는 API에서 timeout이 발생한 것이다. 하나의 DB에서 커넥션이 끊기는 문제가 발생했고 해당 DB를 사용하는 모든 요청에서 병목현상이 생겼다.
장애 원인?
- 상용환경을 복제해 동일하게 stage환경에서 사용했다.
- 하나의 서비스에서 stage환경과 연결되어 있는 상태로 live가 되었고 병목현상이 일어난 것이다.
Prometheus
- 이벤트 모니터링과 알림에 사용되는 오픈소스 메트릭 수집도구
- Pull 방식으로 메트릭 수집
- 추이를 보는데 유용
- 모든 로그를 추적하는 목적에는 부적합(일정 주기로 발생하는 메트릭 수집)
Grafana
- 유려하게 시각화가 가능하다.
- Prometheus가 권장하는 모니터링 시각화 도구
- Query Language를 이용해 데이터 소스로부터 정보를 조회해 시각화한다.
Zipkin
- 오픈소스
- 대부분 언어를 지원한다.
- 다양한 네트워크 프로토콜을 사용할 수 있다.
- 분산 로그 추적 시스템
Legacies
- 레거시 시스템 -> 신규 시스템으로 전환할 때, 변화를 최소화하면서 새로운 기능은 도메인에 맞는 별도의 서비스로 분리했다.
- traffic throttling 기능을 추가해 트래픽을 조절했다.
기존 배정 시스템의 영향도를 모니터링하고 신규 서비스로 넘어가도록 했다. - nginx reverse proxy를 통해 콜 관련 API만 마이크로 서비스로 분리했다.