WebClient와 CachingFilter 사용 시 InputStream 재사용 오류 해결
·
프로덕트 문제 해결 기록
문제 배경서비스에는 사용자가 이미지를 업로드하면, 해당 파일을 다른 인스턴스(배경 제거 워커) API 서버로 전달해 배경을 제거하는 기능이 있었습니다. 파일 전송은 Spring WebFlux의 WebClient를 사용하여 POST /ml/remove-background API를 호출하는 방식으로 구현되었습니다.스프링 서버는 WebClient를 사용했지만, 전체적인 비즈니스 로직 흐름에 맞춰 block()을 호출해 동기적으로 결과를 기다리는 구조였습니다.장애 발생과 증상최근 에러 모니터링 강화를 위해 요청 본문(Request Body)을 로깅하는 캐싱 필터(ContentCachingRequestWrapper 등)를 도입했습니다. 유닛 테스트는 모두 통과했지만, 실제 운영 환경에 배포한 직후부터 파일 전송이..
[ThatzFit] AOP로 로깅 일원화
·
프로덕트 문제 해결 기록
배경서비스 전반에 반복되는 로깅 코드가 많아 가독성과 유지보수성이 떨어졌습니다. 공통 관심사인 로깅을 AOP로 분리해 DRY 원칙(Don't Repeat Yourself)을 지키는 것을 목표로 했습니다. 목표서비스/컨트롤러/레포지토리 단의 공통 로깅을 AOP로 분리메서드 실행 시간 로깅으로 병목 구간 식별예외 로깅 표준화기존 서비스 코드에서 불필요한 시작/완료 로그 제거설계ServiceLoggingAspect: 서비스 레이어의 메서드 시작/완료 자동 로깅PerformanceLoggingAspect: 서비스/컨트롤러 실행 시간 로깅ExceptionLoggingAspect: 서비스/컨트롤러/레포지토리 예외 로깅핵심 포인트컷// 서비스 레이어execution(* tryonu.api.service..*Servi..
[ThatzFit] Spring Data JPA 비관적 락 리팩토링: 문제 해결 기록
·
프로덕트 문제 해결 기록
익명 사용자 초기화 엔드포인트에서 동시성 제어가 필요해(에러 발생) 비관적 락을 적용했습니다. 처음에는 서비스 레이어에 락을 걸었지만, 코드의 책임을 더 명확히 하고 성능을 해치지 않도록 리팩토링을 진행했습니다. 이 글에선 그 과정에서 만난 문제들과 해결 과정을 정리해보겠습니다!배경 사실 같은 uuid로 동시 요청이 들어오는게 굉장히 엣지 케이스라 예측하지 못했었는데, 막상 개발 서버에 머지하고 나니 다음과 같은 에러가 떴습니다. 이처럼 실제 개발 환경에서는 초기화 API는 같은 uuid로 동시에 요청이 들어올 수 있습니다. 이때 같은 사용자가 중복 생성되지 않도록, 조회 단계에서 비관적 락으로 선점해주는 전략이 필요했습니다. 그런데!! 그 전에 빠른 문제 해결을 위해 일단 전체 메서드에 락을 걸어놓은 ..
[ThatzFit] 트랜잭션 최적화: 쓰기 경계 분리
·
프로덕트 문제 해결 기록
배경가상피팅 요청은 외부 I/O(머신러닝 모델을 통한 카테고리 예측, S3 업·다운로드, 폴링)와 DB 저장이 한 흐름 안에서 섞여 수행되고 있었습니다. 트랜잭션 경계가 넓게 설정될 경우 커넥션 점유 시간이 길어지고, 읽기 전용 구간도 쓰기 트랜잭션으로 실행되어 불필요한 더티 체크가 발생할 가능성이 있었습니다. 이를 최적화하는 작업을 수행했습니다.최적화 중 나타난 문제로그에 “cannot execute INSERT in a read-only transaction” 오류가 발생하였습니다. TryOn엔티티 저장 시점에 INSERT가 읽기 전용 트랜잭션에서 실행된 것이 원인이었습니다.2025-08-30 14:36:33.879 [http-nio-8080-exec-9] ERROR t.a.c.e.GlobalExce..
[ThatzFit] API 에러 모니터링: 체계적인 슬랙(Slack) 알림 시스템 구축기
·
프로덕트 문제 해결 기록
배경API 개발이 완료되고 클라이언트 연동을 앞둔 시점에서, 발생하는 에러를 체계적으로 관리하고 알림을 받을 필요성이 대두되었습니다. 특히 4xx 클라이언트 에러와 5xx 서버 에러를 구분하여 슬랙(Slack)으로 알림을 전송하는 시스템을 구축하고자 했습니다.이를 통해 클라이언트 개발자는 4xx 에러 발생 시 원인을 빠르게 파악하여 API 사용법을 수정할 수 있고, 서버 개발자는 5xx 에러 발생 시 별도로 애플리케이션 로그를 확인하는 번거로움 없이 즉각적으로 원인을 파악하는 것을 목표로 삼았습니다. 또한, OOM(OutOfMemoryError)과 같은 심각한 이슈 발생 시에도 관련 정보(요청 내용, 스택 트레이스, 메모리 상태)를 종합하여 신속하게 대응하고자 했습니다.발생한 문제 및 해결 과정문제 1:..
[소프트웨어공학] 인터페이스 개념과 연결되는 스프링 DI
·
컴퓨터/CS
소프트웨어공학을 공부하다 보면 UML(Unified Modeling Language)을 접하게 됩니다. UML 표현 방법 중 인터페이스 표현 방식에 대해 배우면서 문득 의문이 들었습니다"인터페이스는 왜 public abstract지?""UML의 인터페이스 표현과 실제 스프링 개발은 어떻게 연결되지?" 평소에 자바를 활용한 개발을 할 때 인터페이스를 많이 사용하면서도 별 생각 없었는데 문득 걸려서 짚고 넘어가게 되었습니다.1. 소프트웨어공학에서의 인터페이스 개념객체지향 설계 원칙 중 "인터페이스에 의존하라"는 원칙이 있는데, 이는 구체적인 구현 보다 추상화된 행동에 의존해야 한다는 의미입니다.인터페이스는 본질적으로 행동의 명세서입니다. "이런 행동을 할 수 있어야 한다"라는 계약을 정의하는 것이죠. 자바에..
구글 플레이 콘솔 개발자 인증: 주민등록등본 제출
·
etc/정보
WIMI를 릴리즈하는 과정에서 필요했던 구글 플레이 콘솔 개발자 인증 과정에서 겪었던 경험을 공유하려고 합니다.구글 플레이 콘솔에서는 개발자 인증을 위해 다음과 같은 서류를 요구합니다: - 90일 이내 발행된 공공요금 청구서 - 90일 이내 발행된 신용카드 명세서 - 90일 이내 발행된 은행 명세서 - 90일 이내 발행된 임대 계약서 처음에는 현대카드 명세서를 제출했지만, 주소가 완전히 표시되지 않아 거절당했습니다. 두 번째로 가스요금 청구서를 제출했으나, 이름의 일부가 가려져 있어(민*원) 이 역시 거절되었습니다.여러 시도 끝에 주민등록등본을 제출했더니 단 하루 만에 인증이 완료되었습니다! 민원24나 카카오톡, 토스 등 편한 곳에서 주민등록등본을 PDF로 발급받아 제출하면 됩니다. 저처럼 시간 낭비하지..
[운영체제] "파이프는 공유 메모리인가?" 리눅스의 통신 방식
·
컴퓨터/CS
도입리눅스에서 `ls | less` 같은 파이프 명령어는 일상처럼 사용됩니다. 그런데 어느 날 문득 이런 생각이 들었습니다."ls 프로세스의 출력이 less 프로세스로 넘어갈 때 메모리에 있는 정보를 넘기는건데 그럼 Shared Memory 아닌가?"다 공부한 다음 다시 보면 이 질문은 어이없을 수 있지만 당시에는 사뭇 진지했습니다. 파이프도 메모리를 쓰니까 대충 Share할거고 그럼 Shared Memory..? 이런 의식의 흐름이었던 것 같습니다. 그리고 파이프하면 뭔가 파이프를 통해 바로 줄 것 같았습니다.이 의문은 pipe와 아예 다른 범주에 있는 shared memory 개념을 혼동해서 발생했고, 실제로 파이프가 어떻게 동작하는지, 그리고 리눅스가 자원을 어떻게 다루는지를 이해하게된 계기였습니다..
[네트워크] TCP 소켓 프로그래밍: read() 함수와 연결 종료 감지의 이해
·
컴퓨터/CS
러스트로 TCP 소켓 프로그래밍에서 리스닝 소켓을 통해 커넥션 소켓을 할당하고 바이트를 읽어드릴 때, 종종 다음과 같은 코드 패턴을 볼 수 있습니다.match connection_socket.read(&mut receive_buffer) { Ok(0) => { // 0바이트 수신은 클라이언트가 연결을 닫았음을 의미 println!("Client {} disconnected.", client_addr); break; // 내부 루프 종료 -> 다음 연결 기다림 } // 다른 케이스 처리...}  이 코드에서 read() 메서드가 -1을 반환할 때 "클라이언트가 연결을 닫았다"고 처리하는 부분을 봤을 때, 처음에 다음과 같은 의문이 들었습니다."그런데 클..
[네트워크] TCP와 UDP 소켓 프로그래밍의 근본적인 차이: 실제 에러를 통해 이해하기
·
컴퓨터/CS
들어가며Rust 소켓 프로그래밍을 진행하면서 TCP와 UDP의 차이점에 대해 이론적으로만 알고 있었던 것을 실제 코드 구현과 에러 메시지를 통해 생생하게 경험하게 되었습니다.특히 다음과 같은 TCP 클라이언트 에러 메시지는 두 프로토콜의 본질적인 차이를 확인할 수 있었습니다. UDP 클라이언트와 서버, TCP 클라이언트와 서버 각각을 rust로 프로그래밍 하는 것이 과제였는데, UDP 클라이언트는 서버가 실행 중이지 않아도 문제 없이 실행 됐었는데 TCP 클라이언트는 서버가 실행 중이지 않은 상태에서 실행될 경우 위와 같은 에러 메시지를 발생시켰습니다.이 에러를 통해 TCP와 UDP의 근본적인 차이점과 각 프로토콜이 실제 애플리케이션에 미치는 영향에 대해 생각해보게 되었습니다.TCP vs UDP: 연결..