개인 프로젝트로 SNS 피드 서비스를 개발하면서 팬아웃(포스팅 전송) 기능의 성능을 높이고자 Kotlin의 Coroutine 및 Dispatcher.IO 워커 스레드를 사용하다가 성능 테스트를 통해 OutOfMemoryException을 발견하게 되었고, 이를 해결하고 처리 성능을 높인 경험을 소개하려고 한다. 문제의 발단 일단 팬아웃(포스팅 전송) 기능은 내가 SNS 게시물을 올리면, 나를 팔로우하는 사람에게 피드를 전송하는 기능이다. 그래서 팔로워들은 자신의 피드에서 나의 게시물을 볼 수 있다. 그리고 아키텍처는 다음과 같이 설계했다. Server에서 Kafka로 Feed 이벤트를 발행할 때, 아래와 같은 로직으로 구현을 했다. 우선 execute 메서드 안에서는 CoroutineScope(Dispa..
문제 Spring Batch에서 Partitoner를 통해 Step들을 병렬 처리하려고 했었다. 그런데 처음에는 정상적으로 수행이 되다가 어느 시점에 아래와 같은 에러가 발생했다. 병렬 처리를 위한 TaskExecutorPool의 Size는 50으로 설정했었고, DB Connection Pool의 Size는 10으로 설정했었다. 일반적인 상황이라면, 충분히 수행되어야 하는 상황인데도 위와 같은 에러가 발생하게 되었다. 원인 하나의 스레드에서 2개의 DB Connection을 획득하려고 하다 보니 Connection이 부족하게 되었고, 이로 인해 Timout이 발생한 상황이었다. Spring Batch에서 Chunk 지향 처리는 Reader, Processor, Writer라는 3가지 구성 요소를 기반으로..
기존에 운영되고 있던 Api 서버에서 동시성 이슈가 발생이 되었다. 포인트에서 마일리지로 전환하는 과정에서 적립과 차감내역이 정상적으로 반영되지 않던 문제였는데, 해결한 과정을 정리하려고 한다. 문제 포인트에서 마일리지로 전환할 때, 포인트는 차감되고 마일리지는 적립이 된다. 이때, 포인트가 1,000원이 있고 마일리지가 0원인 상태에서 포인트 -> 마일리지 전환에 대한 3번의 요청이 동시에 들어오면, 아래와 같은 결과가 발생하게 되었다. 포인트 차감 포인트 차감의 경우, 950원 -> 900원 -> 850원으로 반영되어야 하는데 3개의 내역이 모두 950원의 내역으로 반영되어 있다. 마일리지 적립 마일리지 적립의 경우, 50원 -> 100원 -> 150원으로 반영되어야 하는데 3개의 내역이 모두 50원..
지난 몇 개월 동안 인스타그램 API를 활용해서 사내의 콘텐츠 서비스와 연동하는 작업을 진행했었고, 약 한 달간의 QA를 끝으로 실제 서비스에 배포하게 되었다. 워낙 큰 작업을 진행했었기 때문에 대규모 단위의 배포를 진행했었다. 사전에 꼼꼼하게 점검하고, 놓친 부분이 없는지 다시 점검한 후에 실 서비스에 배포했지만, 불과 2시간 만에 ConnectionAcquireTimeoutError가 발생했다. 문제 배포를 하고 나서 직접 기능들을 사용해 봤을 때는 문제가 없었다. 하지만 2시간 후에 원인을 알 수 없는 에러로 인해 전체 서비스에 장애가 발생하게 되었다. 우선 Error로그를 파악해봤는데, 특정 시점부터 아래와 같은 Error 로그가 지속적으로 발생하게 되었다. 굉장히 당황스러웠고, 뭐부터 해야 할지..
1. 비관적 락 select ~ for update 구문을 사용한다. 한 Row의 Lock을 획득하기 하기 위해, 무한정 기다리는 이슈가 발생할 수 있고, 서비스 장애를 야기할 수 있다. (Timeout 설정이 없음) REPEATABLE READ 격리 수준을 사용하고 있다면, 갱신 분실의 문제(Lost Update)가 발생할 수도 있다. RDMBS에서 제공하는 Lock 기능을 사용한다. 언두 영역(InnoDB 스토리지 엔진에 내부에 있는) 레코드에 잠금을 걸 수 없고, 테이블 레코드에 잠금을 건다. 2. JPA에서 지원하는 @Version @Version을 명시하여, 수정이 일어나는 시점에 Version이 일치하지 않으면, 예외를 발생시킨다. REPEATABLE READ 격리 수준에서도 갱신 분실의 문제..
MongoDB를 사용하여 간단한 프로젝트를 진행하는 도중, MongoDB 인증을 설정하는 방법에 대해서 정리해봤습니다. (Docker 기반의 내용을 다루고 있습니다.) docker-compose를 통한 Mongo 컨테이너 실행 docker-compose.yml는 아래와 같은 내용들로 정의되어 있습니다. version: '3.3' services: my-mongo: image: mongo container_name: my-mongo ports: - "27017:27017" # Authentication를 설정하기 위해서, command: [--auth]를 주석 처리해야한다. 나머지 내용은 따로 정리하고 링크를 달 것! # command: [--auth] restart: always netwo..
Spring Batch Application과 JPA, JDBC를 함께 사용하여 Batch 작업을 처리해야 하는 상황이 있었습니다. 이때 MySQL Connector/J (JDBC Reference) - Configuration Properties for Connector/J 에서 제공하는 기능들을 몰랐기에, 원하는 대로 동작하지 않았던 문제가 있었습니다. 문제 상황 (예시) 데이터를 가공해서 1,000 ~ 150,000개의 데이터를 Pay 테이블에 넣어야 하는 상황이었습니다. MySQL을 사용하는 상황이었고, JPA의 IDENTITY 방식으로 Batch Insert를 활용하기에는 성능적으로 좋지 않은 부분들이 있었습니다. 이를 해결하기 위해서 JdbcTemplate의 batchUpdate를 활용해서 B..