목록 Architecture/Application (10)
배경알림 서비스 이야기이다.카프카로부터 메시지를 수신하고 이를 사용자에게 전달하는 서비스를 운영하고 있다.네트워크 통신 비용 절감을 위해 카프카에서 메시지를 배치 수신, 그룹 단위로 처리한다.그룹 단위 처리 중 예외가 발생하면 이미 처리한 내용을 잊기에 복구 시 메시지를 중복 수신하게 된다.이에 수신한 메시지 ID를 레디스에 기록하고, 처리 전 비교하는 것으로 이전에 수신했던 메시지 여부를 검증한다.간단한 메시지 ID 저장 구조 변경으로, 레디스 메모리 사용률을 33% 개선한 경험을 소개한다. String vs Hash 기존 구조는 수신한 각 메시지 ID를 레디스 String 타입으로 저장했다.양이 많고, 짧은 보관 기간의 데이터이기에, 각 메시지 ID를 레디스 Key로 하여 TTL을 명확하게 지정했다...
요구 사항 우리 팀은 IoT 기기를 다룬다. 도어락의 문 열림 기록이나, 온습도계의 일간, 월간 온습도 변화 기록 조회 등, 기기의 상태 기록을 조회할 수 있는 기능을 제공한다. 이런 기능을 위해 ‘히스토리’ 서비스는 기기의 상태 이벤트를 수신하고, 기록해야 할 테이터를 필터링하여 DB에 적재하는 역할을 수행한다. 현재 히스토리 서비스에서 저장해야 하는 이벤트 양은 초당 초당 2600 ~ 2800건이다. 그리고 아래는 현재 히스토리 서비스 초당 처리량이다. 이벤트 유입량과 처리량이 크게 차이가 나지 않음을 알 수 있다. 지금까진 가까스로 처리되었지만, 지금보다 유입량이 조금만 더 많아지면, 유입량이 처리량보다 많아져 이벤트 유실이나 OOM이 발생하기 좋다. 우리 팀의 목표 요구치는 초당 7천 건의 저..
OOM 발생최근 서비스 하나가 말썽이었다. 대략 이벤트를 HTTP로 수신해서, DB와 API를 호출하여 처리 여부를 결정하고 RabbitMQ로 전달하는 중간 파이프라인 역할의 서비스였다. 부끄럽지만 예전에 개발되고 더 이상 건들지 않고 운영되던 히스토리가 없는 코드였다. 갑자기 메모리 사이즈 사용률이 비정상적임을 알리는 경고 메시지를 수신했고, '올게 왔구나' 했다. 가장 먼저 그라파나 대시보드를 살폈다. Heap 메모리와 GC 의 동작을 확인했다. GC 동작 이후에도 Old Gen의 최저 수위가 점점 높아지는 것을 볼 수 있다. Major GC의 처리 대상이 되지 못하는 메모리 영역이 계속 쌓이고 있고, 긴 Stop the world 시간과 함께, Major GC 수행만 반복되는 것을 확인할 수 있다..
처리량 개선이 필요하다.회사가 성장하며 기기 이벤트가 크게 늘었다. 8개월 전까지만 해도 초당 1500건이었던 기기 이벤트가, 이제는 3500건을 넘는다. 한 달 동안의 이벤트 수 변화량만 보더라도, 그 수위가 점점 높아지는 것을 볼 수 있다. 기존 인프라와 로직으로는 늘어난 유입량을 못 따라가고, 곧 지연과 유실로 이어진다. 이벤트 컨슘부터 Ack까지의 흐름 안에는 다양한 로직들이 있다. 그래서 '어떤 구간이 문제다!'라기보다는, 주변 영향을 최소화하면서도 효과적인 개선 방향을 고민한다. 물론 어떤 경우에는 단순히 인프라만 늘리면 해결되는 경우도 많다. 프로세스를 더 띄우고, 스레드 수를 늘리는 게 제일 빠를 수도 있겠지만, 최대한 주어진 자원 안에서 개선하는 방법을 고민하는 게 요즘 우리 팀의 방향..
두 번의 갱신 분실 문제 발생유저가 파일을 업로드하면 사용 가능 공간을 확인하고 현재 사용 중인 공간을 업데이트한다. 업로드 동시 요청이 발생하는 경우, 마지막에 수행되는 커밋의 결과만 반영되어 의도하지 않은 값이 저장된다. 이렇게 여러 트랜잭션에서 동일한 데이터를 동시에 수정하려할 때, 마지막으로 수행한 수정만 반영되는 경우를 두 번의 갱신 분실 문제라고 한다.# 두개의 스레드에서, 동시에 5MB 파일을 업로드할 때Thread 1 - 현재 스토리지 사용량 조회 0MBThread 2 - 현재 스토리지 사용량 조회 0MBThread 1 - 스토리지 사용량 기록 5MBThread 2 - 스토리지 사용량 기록 5MB 낙관적 락낙관적 락은 조회와 수정에 버전을 함께 확인하여 충돌 여부를 파악한다. 엔티티 버전을..
Mysql InnoDB 의 RepeatableRead Mysql 의 기본 Transaction isolate 수준은 RepeatableRead 이다. RepeatableRead 는 트랜잭션이 시작된 시점 이후로 여러 번 Select Row 를 확인해도 동일한 값을 갖는다는 것이다. Mysql 은 SnapShot을 사용해서 이를 보장한다. 트랜잭션마다 별도의 스냅샷을 기록하여 다른 트랜잭션이 값을 변경하고 Commit 해도 이 스냅샷을 이용해서 동일한 값을 읽게 되는 것이다. Phantom read 문제Repeatable Read 는 데이터의 추가, 삭제의 변경은 막지 못해 Phantom read 문제가 발생한다. 한 트랜잭션 내에서 전과 다른 조회 결과 row 수를 조회하게 된다는 것이다. InnoDB..
0. AS IS 기존에는 이 메인 / 백업 스토리지 업로드를 동기로 처리했다. 400KB의 이미지를 업로드할 때 main 33ms, backup 1680ms 정도가 필요했고, 사용자 응답은 이 둘을 더한 값 + ⍺ 가 될 것이다. 이미지 업로드 시에 각 스토리지 업로드를 비동기로 처리하되 모든 업로드가 정상일 경우에만 사용자에게 정상으로 응답하고자 한다. 그리고 동시에 비동기식 업로드 과정에서 생길 수 있는 더미 파일을 사용자 흐름에 포함하지 않고 처리하고자 한다. 이 글에선 위 요구 사항을 만족하기 위한 작업 과정을 소개한다. 목차는 다음과 같다. 1. 단순 비동기 처리 2. Future로 비동기 / 블록킹 방식으로 처리하는 경우, 그 문제점 3. CompletableFuture으로 쉽게 구현하는 다양..
배경 : 회원 가입이 실패되는 것이 옳을까?Picup 프로젝트에서 회원 가입이 요청되면 Member 서버에서 가입 내용을 기록하고, Storage 서버로 유저 타입과 함께 스토리지 생성을 요청한다. 기존에는 쉽게 가입을 실패시켰다. Storage 서버에서 처리에 실패하면 회원가입은 실패되었다. 외부 API 호출과 원자성, 서버 간 정합성이라는 키워드에만 집착해 기술로만 풀이하려고 했던 것 같다, 나라면 회원가입 폼을 열심히 작성했는데, 마지막 최종 제출에서 가입에 실패하면 그 서비스 안 쓸 것 같다. 가입 실패를 최소화하기 위해 외부 API 가 포함된 신규 가입 로직에서 필수적인 이벤트와 그렇지 않은 이벤트 분리를 고민했다. 그리고 각각의 이벤트 처리에서 발생할 수 있는 문제와 해결을 위한 고민을 정리..