지금까지 작성했던 내용을 하나씩 정리하며 옮기는 중입니다...https://velog.io/@thgks779/posts
전체 글
한 줄 요약/actuator/*events 모니터링 API에서 ConcurrentModificationException이 터지는 문제를 모니터링 레이어의 읽기 계약을 live iterator -> snapshot 읽기로 바꿔 해결했습니다.이슈 찾기GPT에게 이슈 목록을 주고 아래 기준으로 순위를 부여해 달라고 했다.1. 재현 가능성2. 메인테이너 버그/기능 등 이슈로 인정받음3. 아키텍처 변경이 필요하거나 정책적으로 합의가 필요하지 않은 순4. 코드 변경만으로 기여가능성이 높은 순 그렇게 나열된 이슈들을 직접 검토하며 아래 이슈를 선정했습니다.https://github.com/resilience4j/resilience4j/issues/2189 GitHub - resilience4j/resilience4..
이벤트 정합성문제 정의허브(Hub)를 생성하면 비즈니스 규칙에 따라 허브 간 경로(HubRoute)를 자동으로 생성합니다.허브 경로(HubRoute) 도메인은 허브 생성 이벤트를 수신받아 생성/삭제 등을 진행합니다.허브 생성 이벤트가 단순한 비동기로 발행될 경우 다음 문제가 발생할 수 있습니다.DB 트랜잭션은 롤백되었는데 이벤트만 발행되는 경우DB는 커밋됐지만 이벤트 발행이 실패하는 경우두 경우 모두 정합성이 깨집니다.해결 방안BEFORE_COMMIT 단계에서 Outbox 테이블에 Pending 상태 이벤트 저장AFTER_COMMIT 단계에서 Kafka로 발행, 실패시 Fail 상태 갱신5분동안 Pending 상태이거나 실패 상태 이벤트 대상 재시도더보기BEFORE_COMMIT: Outbox 저장이벤트는..
PG approve API에는 TPS 제한이 있었고, 초과 시 거절 대신 대기 처리로 흡수해야 했습니다.이때 고민은 “어떤 자원을 직접 보호할 것인가”였습니다.이 설계의 1차 보호 대상은 PG approve API TPS이며, 대기열 위치에 따라 TPS 보호 정확도와 UX, 확장성이 어떻게 달라지는지 비교했습니다. 주문 / 결제 흐름1. 사용자는 상품상세 화면에서 주문하기 버튼을 눌러 주문서 작성화면으로 이동합니다.2. 주문서 작성화면에서 결제 수단 선택 후 결제하기 버튼으로 결제화면으로 이동합니다.3. 결제화면에서 결제정보를 입력 후 출금을 대기합니다.4. 출금 완료 후 주문/결제 완료 화면으로 이동합니다.참고사항현재는 PG webview 방식의 결제수단만 사용하고, 결제수단이 추가되더라도 대기열은 ..
배경CI/CD 파이프라인이 길었고, 개발하면서 로컬에서 테스트를 자주 돌려보는 과정도 오래 걸렸습니다.테스트가 약 300개가 넘어가다 보니, 한 번 실행할 때마다 개발 리듬이 끊겼습니다.실제로 개선 전 기준으로 전체 테스트 실행 시간은 평균 72.19초 수준이었습니다.따라서 테스트 시간을 단축하는 것이 가장 우선순위 높은 개선 과제라고 판단하였습니다.원인Spring Test는 같은 설정으로 만든 ApplicationContext라면 Context Caching으로 재사용합니다.하지만 테스트마다 설정이 조금씩 달라지면(컨텍스트 키)가 달라지면) 캐시를 사용하지 못하고 컨텍스트를 새로 생성하게 됩니다.측정 환경 및 방법환경OS: macOSJDK: Corretto 21빌드/테스트: Gradle Wrapper(..
배경허브(Hub)를 생성하면 비즈니스 규칙에 따라 허브 간 경로(HubRoute)를 자동으로 생성합니다.특히 센터 허브는 다른 센터 허브들과, 지점 허브는 같은 센터에 소속된 지점허브들과 양방향 경로(A→B, B→A)를 생성합니다.이 과정에서 허브가 동시에 생성되면, 생성 순서나 처리 타이밍에 따라 동일한 방향의 경로가 이미 존재한 상태에서 다시 생성될 수 있어, 경로 생성 로직은 중복 생성에 안전한 쓰기(Insert)가 필요했습니다. 환경: SpringBoot3.x, PostgreSQL16문제 상황: 일부 유니크 충돌 때문에 트랜잭션 전체가 롤백됨신규 허브가 생성되면 HubRoute 서비스는 여러 경로를 한 번에 만들어 저장합니다.구현은 “여러 route를 만든 뒤, for-loop로 insert” 하..
요구사항구매자가 주문을 통해 재고를 차감할 수 있어야 합니다.공급업체가 입고를 통해 재고를 증가시킬 수 있어야 합니다.동일 재고에 대한 동시 요청에서도 재고 음수가 발생하지 않아야 하며, 재고 부족 시 일관되게 실패 처리되어야 합니다.환경SpringBoot, 단일DB (PostgreSQL), Redis동시성 설계Redis 락장점 : DB가 다중화되거나 여러 노드에서 쓰기가 동시에 발생해도 일관된 락 제어가 가능합니다.단점: Redis에 의존적 DB 락 장점: 락과 데이터 갱신이 같은 장애 도메인 안에서 원자적으로 처리되어, 장애 지점이 단순화 됩니다.단점: 쓰기 DB가 다중화되는 시점에는 분산 락으로의 전환 비용이 발생할 수 있습니다. 선택확장 관점에서 Redis 락이 유리할 수 있으나, 쓰기 DB 다중..
주문 API는 장애가 나면 매출도 같이 멈춥니다. 특히 이벤트처럼 트래픽이 순간적으로 치솟는 시기에는주문 API 자체의 병목이 큰 만큼 매출이 저하될 수 있다고 판단했습니다.모니터링을 통한 느린 지점 식별하기Tempo를 API 실행 과정에서 느린 지점을 확인했습니다. 다음으로는 어떤 지점이 개선이 가능하고 우선순위는 어떻게 되는지 판단했습니다. 계측 결과, 멱등성 검증이 35ms로 가장 큰 병목이었습니다.멱등키 검증 쿼리가 Full Table Scan 문제멱등성 검증은 “같은 주문이 중복 처리되지 않도록” 이미 처리된 idempotency_key가 있는지 확인하는 단계입니다.exists 쿼리에서 전체 테이블 스캔(Full Table Scan)이 발생하고 있었기에 idempotency_key 에 인덱스를..
배경이벤트로 인해 주문/결제 구간에 순간적인 트래픽 집중이 발생할 수 있다고 가정했습니다.애플리케이션 서버와 DB는 예상 트래픽 기준으로 충분히 스케일 아웃/업 되어 있다고 가정했습니다.주문 의사를 보인 요청 수는 재고 이상이었는데, 실제 처리된 주문은 재고 이하 라고 가정했습니다.따라서 스케일 아웃으로 해결이 불가능한 처리량이 도달했을 때 어떻게 제어할 것인지 고민이 필요했습니다.발생 현상단순 거절만으로 대응하면, 잠시 기다리면 주문 가능했던 사용자까지 이탈할 수 있습니다.거절된 사용자의 반복 재시도는 순간 부하를 더 키워 주문 서버의 장애로 이어질 수 있었습니다.따라서 목표는 시스템을 보호하면서도 사용자 이탈을 최소화하는 것 입니다.해결 방법 선택하기재시도 간격 제어는 단순하지만 이탈 방지에 약하고,..
1. 설계 배경블랙프라이데이와 같은 이벤트 트래픽 상황에서는 주문 API의 병목이 곧 매출 손실로 직결됩니다.특히 재고 차감, 쿠폰 사용, 멱등성 검증과 같이 주문 처리 경로에 반드시 포함되는 로직이 모두 DB에 집중되면서, 고트래픽 상황에서 DB 부하와 락 경합이 주요 병목으로 나타났습니다.이에 주문 처리 경로의 핵심 쓰기 트래픽을 Redis로 분리함으로써, 향후 피크 트래픽 대응 시 DB 스케일업에 의존하지 않는 구조를 만들고자 했습니다. 단순히 Redis를 도입하면 성능이 좋아질 것이라고 생각했지만, 실제로는 캐시 일관성, 장애 시 복구, 이벤트 유실 방지, 운영 복잡도까지 함께 고려해야 할 포인트가 계속 늘어난다는 것을 경험했습니다.이를 통해 성능 개선은 장애 시나리오와 운영 부담까지 포함해 매우..
주문은 사용자에게 즉시 성공/실패를 반환해야 했고, 쿠폰·재고 등은 외부 서비스 API에 의존하고 있었습니다. 이 제약 속에서 성공 흐름은 오케스트레이션으로 관리하고, 실패 보상은 Kafka 기반 비동기 이벤트로 분리해 최종적 일관성을 달성한 경험을 정리한 글입니다.요약문제: 주문은 즉시 성공 여부를 반환해야 했지만, 쿠폰·재고·배송 등은 외부 서비스 API에 의존하고 있어 실패 시 복구와 응답 지연 문제가 함께 발생할 수 있었습니다.해결: 성공 흐름은 주문 서비스를 Saga 오케스트레이터로 두어 상태 전이와 진행 상황을 중앙에서 관리하고, 실패 보상은 Kafka 이벤트 기반 비동기 방식으로 각 서비스가 처리하도록 분리했습니다.신뢰성 보완: 주문 ID를 기분으로 파티션을 구성하여 동일 주문 이벤트의 순서..
기술적으로 도전적인 사람에게 주는 상이다.. 주변 동료들의 투표로 선정되는 상인데, 좋게봐주신 분들 덕분에 선정될 수 있어서 감사했습니다. 그저 좋은 분들을 만났던 제가 운이 좋았던 것 같습니다.70일 이라는 짧았던 시간속에 수많은 우역곡절이 있었던 것 같습니다. 또 노력이 배신하지 않고 우수프로젝트에 선정되기까지..! 마지막 수료식날 모든 피로가 풀리는 듯한 좋은 소식이 몰아쳐 올 한해를 기분좋게 마무리할 수 있게 된 것 같습니다.
1. 배경재고 시스템을 개발하며, 동시 요청 환경에서 재고 정합성을 어떻게 보장할 것인지가 주요 고민이었습니다. 특히 다음 두 가지 트랜잭션 처리 방식을 비교해 실제 차이가 얼마나 발생하는지 확인하고자 했습니다.2. 두 가지 락 전략2.1 ROW-LEVEL UPDATE LOCK (DEFAULT)조회 시 락을 사용하지 않고, UPDATE 문의 WHERE 절에서 조건을 검증하는 방식입니다.public StockRes decrement(DecreaseStockCommand command) { Stock stock = getStock(command.productId()); // 일반 SELECT entityManager.detach(stock); stock.decrease(command...
JavaParser는 Java 코드 처리 라이브러리입니다. 코드 문자열을 파싱해서 AST로 만들고, 수정할 수 있으며 다시 문자열로 출력할 수 있습니다. 사이드 프로젝트에서 JavaParser를 사용하던 중, 내부 동작이 궁금해져 관련 이슈들을 살펴보게 되었습니다.이슈(#4781) 에서 int foo;가 foo;로 원시타입이 사라진 채 출력되는 문제를 확인했습니다.해당 문제를 발생시키는 코드를 JavaParser에서 지원하는 다른 동작으로 변경하여 증상이 없어지도록 수정했습니다.하지만 리뷰를 통해 “이게 정말 근본 원인 fix인가?”는 별도로 검증해야 한다는 피드백을 받았고결국 개선(refactor) 성격의 변경을 분리한 PR(#4823)이 머지됐습니다. 해당 글은 위 과정을 기록한 글입니다.사전 지식 ..
배경현재 개발 중인 서비스는 사용자의 소스코드를 분석하여, API 문서를 자동으로 생성하는 플랫폼입니다.이 플랫폼은 다음과 같은 두 가지 유형의 요청을 처리하게 됩니다.요청 유형 설명 작업량신규 사용자 요청프로젝트 전체의 API 문서를 처음부터 생성매우 높음기존 사용자 요청일부 코드 변경분에 대해서만 문서 갱신낮음신규 사용자의 경우 수백 개의 API 명세를 생성해야 하며, 기존 사용자는 일부 API에 대해서만 문서를 갱신하는 수준이므로 요청마다 필요한 리소스의 크기와 처리 시간이 크게 다릅니다.문제 인식모든 요청을 순차적으로 처리할 경우, 스레드풀이 가득 찼을 때 대기큐에서 다음과 같은 문제가 발생할 수 있습니다.처리량(Throughput) 저하먼저 들어온 무거운 요청에 의해 가벼운 요청들이 지연되면서 ..
Batch save를 통한 DB 부하 감소API문서 생성 파이프라인을 실행하는 과정에서 생성해야할 API 개수 만큼 Save 쿼리가 실행되면서 네트워크 I/O 가 늘어나게 되었습니다. 1.프로젝트에서 Controller파일을 수집합니다.2. Controller 파일에서 api를 수집합니다.3.수집된 api를 LLM을 통해 API 명세로 변환합니다. 위 시퀀스 다이어그램 처럼 하나의 API 명세를 생성하는데 2번의 save쿼리가 필요했습니다.파이프라인의 실행은 백그라운드로 진행되며, 사용자는 실시간이 아닌 전체 파이프라인 처리가 끝난 뒤 API문서를 확인할 수 있기 때문에 Batch save를 적용 할 수 있다고 판단하여 개선 작업을 진행했습니다Batch save 과정 설명: DB 부하를 줄이기 위해 모든..
요약문제: API 문서를 생성할 프로젝트에 API 개수 만큼 반복문을 통해 하나씩 처리해결: 반복문 내부에 CompletableFuture를 이용한 비동기 적용성과: 처리 시간 54% 단축 (202초 → 93초) // LLM API ratelimit은 고려되지 않은 수치입니다. 문제 상황APIDOC43은 Spring Boot 프로젝트의 소스코드를 분석해 API 문서를 생성하는 도구입니다.문서 생성 과정은 크게 다음과 같습니다.프로젝트에서 Controller 파일을 수집한다.Controller 파일에서 API 정보와 메타데이터를 추출한다.추출한 정보를 바탕으로 LLM을 호출해 API 명세를 생성한다.초기 구현은 각 API를 반복문으로 하나씩 순차 처리하는 구조였습니다.따라서 프로젝트 내 API 수가 많아질..
입력받은 사용자 개인정보를 통해 존재하는 회원인지 확인하고 화면을 분기하는 과정의 유스케이스가 있었습니다.이때, 아무런 장치가 없다면 Mysql은 Full Table Scan이 발생합니다. O(N) Full Table Scan은 테이블의 모든 데이터를 순차적으로 읽으며 테이블의 모든 행을 탐색합니다.그렇게 되면 해당 로직이 가입자 수에 비례하게 처리시간이 늘어납니다.해결 방안사용자를 조회될 때 사용되는 컬럼에 index를 생성하면 정렬된 B-tree 구조의 구조가 생성되기 때문에 O(log N)의 시간복잡도로 조회할 수 있습니다.해결과정1. 테이블 생성CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, encrypted_info CHAR(64)..
허슬풋볼에서는 축구 강사 등록 시 프로필 사진을 함께 업로드할 수 있는 기능을 제공합니다. 사용자는 사진을 업로드하고, 기타 정보를 입력한 뒤 등록 폼을 제출하면 서버에서 해당 이미지를 수신해 저장합니다.이 때 강사 프로필 등록에서 이미지 업로드가 오래걸린 다는 것을 인식하였습니다. 문제의 원인은 이미지 처리와 관련된 비효율적인 동기 처리 구조에 있었습니다.기존 구조의 문제이전에는 다음과 같은 방식으로 프로필 사진이 처리되고 있었습니다:사용자가 강사 프로필 등록 화면에서 프로필 이미지를 업로드서버가 배경제거 외부 API에 요청을 보내 배경을 제거서버가 byte로 응답 받은 이미지를 AWS S3에 업로드업로드가 완료된 후, S3 URL을 포함하여 응답을 반환이 모든 작업이 동기적으로 순차 처리되었기 때문에..
사용자에게 진행률을 제공해야 하는 요구사항이 생겼고,외부 API에 이미지를 전송하는 구간(output stream) 까지 추적을 진행하고자 했습니다.해결방안 탐색단계https://github.com/square/okhttp프로젝트에서 okhttp를 http client 이용하고 있어 커뮤니티를 찾아봤습니다. Upload progress recipe #1528 (https://github.com/square/okhttp/pull/1528)okhttp깃허브 2015년도 pull requests에서 upload progress에 관한 논의가 이루어진 게시글을 찾았습니다.해당 글에서 제시하는 방법은 다음과 같습니다.해결법 이해하기okhttp사용법 File file = new File("README.md"..